How I structure my REST APIs
Lars Wächter

Lars Wächter @larswaechter

About: 25 | CS Student | Software Engineer

Location:
Germany
Joined:
May 30, 2017

How I structure my REST APIs

Publish Date: Oct 3 '18
161 19

This post was originally published on my blog.


When I started using Node.js for building REST APIs on the server side, I struggled a lot with the same question over and over again:

How should the folder structure look like?

Obviously there’s no perfect or 100% correct answer to this question but after reading some articles regarding this topic, I found a folder structure and architecture that fit my needs quite well. So today I’d like to show you how I structure and organize my Node.js REST APIs.

I also published a GitHub repository including an example application which you can use as template for your own project.

One thing to mention is that I use Express.js as web-framework and TypeORM as an ORM. It shouldn't be that hard to apply this folder structure for other frameworks too.

The architecture is mostly component based what makes it much easier to request only the data we really need. For example we have a User component that contains all information about users.

Let's start with the root directory.

Directory: root

expressjs-api
└───db
│
└───dist
│
└───logs
│
└───node_modules
│
└───src
│
│   README.md
│   ...
Enter fullscreen mode Exit fullscreen mode

This structure is nothing special and shouldn’t be new to you. It’s actually a basic Node.js setup. The interesting part here is the content of the src folder which this post is about.

So what do we have in here?

expressjs-api
└───src
   │
   └───api
   │   │
   │   └───components
   │   │
   │   └───middleware
   │   │
   │   │   routes.ts
   │   │   server.ts
   │
   └───config
   │
   └───services
   │
   └───test
   |
   │   app.ts
Enter fullscreen mode Exit fullscreen mode

From here on, we'll go top-down through the files / directories and I'll explain each one. Let’s start with the api directory, the most important part of the application.

Directory: src/api/components

expressjs-api
└───src
    │
    └───api
        │
        └───components
            │
            └───article
            │
            └───auth
            │
            └───country
            │
            └───user
            │   helper.ts
            │   index.ts
Enter fullscreen mode Exit fullscreen mode

Here we have the heart of our component based Node API. Each component has its own routes, controller, model, repository, policies, tests and templates.

Let’s step into the User component and have a look.

Directory: src/api/components/user

expressjs-api
└───src
    │
    └───api
        │
        └───components
            │
            └───user
                │
                └───services
                |   │   mail.ts
                └───templates
                |   │   confirmation.html
                |   |   invitation.html
                │   controller.ts
                │   model.ts
                │   policy.json
                │   repository.ts
                │   routes.ts
                │   user.spec.ts
Enter fullscreen mode Exit fullscreen mode

As you can see a component consists of the files I just mentioned before. Most of them represents a single class that is exported. Of course, you can add here more component specific stuff.

Since I have multiple components and their classes have the same structure most of the time, I also create interfaces that are implemented in the classes. This helps me to keep the components’ structure straight.

Moreover, we have the services directory here which includes local component services like mail for example. Those interhite from the global services.

The templates directory includes mail HTML templates for the given component. For dynamically rendering HTML code I highly recommend ejs.

controller.ts

The controller class handles incoming requests and sends the response data back to the client. It uses the repository class to interact with the database. Request validation happens via middleware few steps before

A shortened example:

export class UserController {
  private readonly repo: UserRepository = new UserRepository()

  async readUser(
    req: Request,
    res: Response,
    next: NextFunction
  ): Promise<Response | void> {
    try {
      const { userID } = req.params

      const user: User | undefined = await this.repo.read({
        where: {
          id: +userID,
        },
      })

      return res.json(user)
    } catch (err) {
      return next(err)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

model.ts

The model represents the database model for its component. In my case it’s a TypeORM class. Mostly it’s used by the repository classes.

policy.json

This json file includes the access rights for each user-role for the given component. It's part of a access control list based system.

Example:

{
  "Admin": [{ "resources": "user", "permissions": "*" }],
  "User": [{ "resources": "user", "permissions": ["read"] }]
}
Enter fullscreen mode Exit fullscreen mode

repository.ts

The repository class acts like a wrapper for the database. Here we read and write data to the database. Furthermore, we can implement caching for example.

You can import the repository class into any other file and query the data for this component from the database. Moreover, it prevents us from writing redundant code since we don't have to rewrite the SQL statements multiple times.

Since most component repositories need the same basic access methods like readAll, read, save and delete I use a generic parent class that includes all these methods. This saves a lot of code.

See AbsRepository for the implementation.

routes.ts

Here we define the API endpoints for the corresponding component and assign the controller methods to them. Moreover we can add more stuff like

  • authorization (e.g. JWT)
  • permission checking (ACL)
  • request body validation
  • component specific middleware in here.

A shortened Example:

class UserRoutes implements IComponentRoutes<UserController> {
  readonly name: string = "user"
  readonly controller: UserController = new UserController()
  readonly router: Router = Router()
  authSerivce: AuthService

  constructor(defaultStrategy?: PassportStrategy) {
    this.authSerivce = new AuthService(defaultStrategy)
    this.initRoutes()
  }

  initRoutes(): void {
    this.router.get(
      "/:userID",
      this.authSerivce.isAuthorized(),
      this.authSerivce.hasPermission(this.name, "read"),
      param("userID").isNumeric(),
      this.authSerivce.validateRequest,
      this.controller.readUser
    )
  }
}
Enter fullscreen mode Exit fullscreen mode

user.spec.ts

This is the test file for testing the component and its endpoints. You can read more about testing this architecture here.

Directory: src/api/middleware/

expressjs-api
└───src
    │
    └───api
        │
        └───middleware
            │   compression.ts
            │   loggin.ts
Enter fullscreen mode Exit fullscreen mode

This folder includes all the API’s global middlewares like compression, request logging etc.

File: src/api/routes.ts

expressjs-api
└───src
    │
    └───api
        │   routes.ts
Enter fullscreen mode Exit fullscreen mode

Here we register all component and middleware routes. Those ones are used from the server class later on.

File: src/api/server.ts

expressjs-api
└───src
    │
    └───api
       │   server.ts
Enter fullscreen mode Exit fullscreen mode

Here we declare everything required for the Express.js server:

  • import middlware
  • import routes
  • error handling

Later on, we can import the server class for unit tests as well.

Directory: src/config

expressjs-api
└───src
    │
    └───config
       │   globals.ts
       │   logger.ts
       │   permissions.ts
Enter fullscreen mode Exit fullscreen mode

This directory includes the API's configuration files. This could be for example:

  • global variables
  • logger config
  • ACL permission
  • SMTP config

Feel free to put any config-related files in here.

Directory: src/services/

This directory contains global services we might need for authorization, sending mails, caching, or helper methods for example.

expressjs-api
└───src
    │
    └───services
        │   auth.ts
        │   helper.ts
        │   mail.ts
        |   redis.ts
Enter fullscreen mode Exit fullscreen mode

auth.ts

Here we setup things like our app's passport strategies and define authorization methods.

helper.ts

The helper class contains helper methods for hashing, UUIDs and so on.

mail.ts

This service is used for sending mails and rendering the templates of the components. Again, I recommend the renderFile function of ejs.

Directory: src/test/

This directory includes a test factory for running the component tests.
You can read more about it here.

File: src/app.ts

This is the startup file of our application. It initializes the database connection and starts the express server.

expressjs-api
└───src
    │   app.ts
Enter fullscreen mode Exit fullscreen mode

All together

Last but not least a complete overview of the project structure:

expressjs-api
└───src
    │
    └───config
    │   │   globals.ts
    │   │   logger.ts
    │   │   permissions.ts
    │
    └───api
    │   │
    │   └───components
    │   │   │
    │   │   └───article
    │   │   │
    │   │   └───user
    |   │   │   │
    |   │   │   └───templates
    |   │   │   |   │   confirmation.html
    |   │   │   |   │   invitation.html
    │   │   |   │   controller.ts
    │   │   |   │   model.ts
    │   │   |   │   policy.json
    │   │   |   │   repository.ts
    │   │   |   │   routes.ts
    │   │   |   │   user.spec.ts
    │   │
    │   └───middleware
    │   │   │   compression.ts
    │   │   │   logging.ts
    │   │
    │   │   routes.ts
    │   │   server.ts
    │
    └───services
    │
    └───test
    |
    │   app.ts
Enter fullscreen mode Exit fullscreen mode

That’s it! I hope this is a little help for people who don't know how to structure their Node.js application or didn’t know how to start. I think there are still many things you can do better or in a more efficient way.

If you're interested in writing unit tests for Node.js REST APIs have a look at this article which covers the same architecture.

I also published a GitHub repository including an example application. Have a look.

Comments 19 total

  • Mathieu PATUREL
    Mathieu PATURELOct 4, 2018

    Interesting post :)

    I'm messing with Go at the moment, and the structure is definitely one of the key factor.

    Especially testing, what I didn't cover here.

    Yep, that's the dodgy one. I'd be very interested in knowing how you handle it though.

  • Massimo Artizzu
    Massimo ArtizzuOct 4, 2018

    I think you should mention earlier that you're using Express. 🙃
    And maybe if your setup is valid for alternatives, e.g. Fastify or Hapi.

    • Lars Wächter
      Lars WächterOct 4, 2018

      I added Express and TypeORM at the beginning :)

  • Sdu
    SduOct 4, 2018

    How did tdo the file structure thingie, is it markup or an image?

  • Dávid Szabó
    Dávid SzabóOct 5, 2018

    What's the reason of repeating the name of the module in the modules directory? For example you got modules/user/user.controller.ts, how about just modules/user/controller? I don't see the point of duplicating it. When you import it, it looks weird import UserController from './modules/user/user.controller'

    Same question applies to the services. I don't like the idea of adding ".service" to the file names. I already know that I am in the services folder.

    Anyway, I like to modular approach!

    • Lars Wächter
      Lars WächterOct 5, 2018

      Yeah, that's actually a good point. Thanks!

    • Lars Wächter
      Lars WächterMay 30, 2020

      One advantage might be: It's easier to differentiate the files by the tab titles in your editor :)

  • Matt Miller
    Matt MillerOct 5, 2018

    I recently started on a little TypeScript Express starter project and I'm enjoying it so far. I'm also looking to add TypeORM to my setup! I'm trying to create a super lean setup to replace Adonis.js as the maintainer has refused to adopt TS.

    I was hoping you could answer some questions though.

    I was trying to find a convenient way that to add all the routes and I struggled with that (though I just started learning TS). Looking at your setup, does each module router create a new Express router? And those are then exported and added to the Express instance via the app.use('route group', module_router) ?? I never knew it accepted instances of a router.

  • Lars Wächter
    Lars WächterFeb 12, 2019

    Note: I just refactored the folder structure a little bit :)

  • Vladislav Guleaev
    Vladislav GuleaevMay 23, 2019

    Pretty nice explanation. So sad your web app is not accessible because of HSTS!

    • Lars Wächter
      Lars WächterMay 25, 2019

      Thanks :) The web app is just a landing page for one of my projects. I'll fix it soon!

  • robinsjp
    robinsjpNov 8, 2019

    Do you have some example code for a simple (one or two component) API? I'm wondering whether each model contains a database connection with SQL operations etc, object or if it is just a model representing each table.

    This is helpful though, thanks.

  • Utkal
    UtkalMay 29, 2020

    Thank you for the nice post. I am beginner to Expressjs and have some beginner doubts :-
    1) Why should we have "services" seperately (why not write the logic in controllers themselves?)
    2) If controllers are to be kept seperate from services for some reason, how are routes and controllers different (since the controllers and routes are accepting the same parameters and the controller expects results from service, why can't a route directly call service instead and respond to the request?)?

    I hope that I am missing some case where each of them is to be seperate, but can't get it.

    • Lars Wächter
      Lars WächterMay 30, 2020

      1)
      If we have the services (logic) separated from the controllers, we can query the required data from anywhere else in our codebase. Otherwise you have to write the same code every time again just to get the same data. (or you send a request to the API itself -> bad)

      2)
      A route does not directly call a service, because before calling the service the controller takes care of the incoming HTTP request, validation and the outgoing response. In a controller we might send different HTTP status codes or prepare the data before sending it back to the client. In a service we just load the data and handle the business logic. It does not care about where it gets called from.

      Let's say you call a service, that expects parameters, directly from a route. Now the router sends the incoming HTTP request as argument to your service. But what if you want to call the service from within your codebase and not from an incoming HTTP request? You can't really pass a HTTP request as argument.

      • Utkal
        UtkalMay 30, 2020

        Thank you. Everything is clarified now.

Add comment