loading...

How I structure my REST APIs

larswaechter profile image Lars Wächter Updated on ・4 min read

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 not a perfect or 100% correct answer to this question but after reading some articles regarding this topic, I found a folder structure that fits my needs quite good. So today I’d like to show you how I structure my REST APIs.

The APIs are 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.

One thing to mention is that I use express.js as web-framework and TypeORM as ORM. Let’s have a look at the structure.

Directory: root

nodejs-api-structure
└───dist
│
└───logs
│
└───node_modules
│
└───src
│
│   README.md
│   ...

This structure is nothing special and shouldn’t be new to you. It’s actually a basic Node.js setup. The interesting part is the src folder on which our focus lies.

So what do we have in here?

nodejs-api-structure
└───src
   │
   └───config
   │
   └───api
   │   │
   │   └───components
   │   │
   │   └───middleware
   │   │
   │   │   routes.ts
   │   │   server.ts
   │
   └───services
   │   index.ts

From here on, we’ll always work from the top of the directory down and I explain each one. Let’s start with the config directory.

Directory: src/config

nodejs-api-structure
└───src
    │
    └───config
       │   globals.ts
       │   logger.ts
       │   permissions.ts

This directory includes configuration files. This could be for example:

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

Directory: src/api/components

nodejs-api-structure
└───src
    │
    └───api
        │
        └───components
            │
            └───article
            │
            └───auth
            │
            └───country
            │
            └───user
            │   index.ts

Here we have the heart of our component based API. Each component consists of its own routes, controller, model and service.

Let’s deep into the user component and take it as example.

Directory: src/api/components/user

nodejs-api-structure
└───src
    │
    └───api
        │
        └───components
            │
            └───user
                │   controller.ts
                │   model.ts
                │   routes.ts
                │   service.ts

As you can see a component consists of the files I mentioned before. Each file represents one class that is exported. Of course, you can add here more component specific stuff like config or test files.

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.

controller.ts

The controller class handles incoming requests, validates them and sends the response data back to the client. It uses the service class to interact with the database.

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 service class.

routes.ts

Here we define our API endpoints for the corresponding component and assign the controller methods to them. Moreover we can do things like authorization (e.g. JWT), permission validation (e.g. ACL) or add component specific middleware.

service.ts

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

Directory: src/api/middleware/

nodejs-api-structure
└───src
    │
    └───api
        │
        └───middleware
            │   auth.ts
            │   compression.ts

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

File: src/api/routes.ts

nodejs-api-structure
└───src
    │
    └───api
        │   routes.ts

Here we register all component and middleware routes.

File: src/api/server.ts

nodejs-api-structure
└───src
    │
    └───api
       │   server.ts

Here we declare everything required for our express server:

  • import middlware / component routes
  • error handling

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

Directory: src/services/

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

nodejs-api-structure
└───src
    │
    └───services
        │   auth.ts
        │   helper.ts
        │   mail.ts

auth.ts

Here we setup things like our 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 their templates.

File: src/index.ts

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

nodejs-api-structure
└───src
    │   index.ts

All together

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

nodejs-api-structure
└───src
    │
    └───config
    │   │   globals.ts
    │   │   logger.ts
    │   │   permissions.ts
    │
    └───api 
    │   │
    │   └───components
    │   │   │
    │   │   └───article
    │   │   │
    │   │   └───user
    │   │       │   controller.ts
    │   │       │   model.ts
    │   │       │   routes.ts
    │   │       │   service.ts
    │   │
    │   └───middleware
    │   │   │   auth.ts
    │   │   │   compression.ts
    │   │
    │   │   routes.ts
    │   │   server.ts
    │
    └───services
    │   index.ts

That’s it! I hope this is a little help for people who struggled with the same question and didn’t know where or how to start. I think there are still many things you can do better or in a more efficient way.

One thing I didn’t not cover here is testing but I’ll write a new article for that.

I’m currently working on a side project, where you can see this folder structure (in a small modified way) in action. Have a look.

I also wrote an article about unit tests with such a project structure. Check it out

Discussion

pic
Editor guide
Collapse
davidszabo97 profile image
Dávid Szabó

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!

Collapse
larswaechter profile image
Lars Wächter Author

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

Collapse
larswaechter profile image
Lars Wächter Author

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

Collapse
matt123miller profile image
Matt Miller (he/him)

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.

Collapse
larswaechter profile image
Lars Wächter Author

Yeah, each router in the /modules/ folder creates a new instance of the express router and those instances are exported to server.ts

This might be interesting for you: expressjs.com/en/4x/api.html#router

Collapse
utkal97 profile image
Utkal

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.

Collapse
larswaechter profile image
Lars Wächter Author

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.

Collapse
utkal97 profile image
Utkal

Thank you. Everything is clarified now.

Collapse
math2001 profile image
Mathieu PATUREL

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.

Collapse
larswaechter profile image
Collapse
robinsjp profile image
robinsjp

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.

Collapse
sduduzog profile image
Beautus S Gumede

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

Collapse
larswaechter profile image
Collapse
sduduzog profile image
Beautus S Gumede

Thanks! I couldn't select the text on my mobile browser and I thought...Anyway I installed tree to generate it for me

Collapse
vguleaev profile image
Vladislav Guleaev

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

Collapse
larswaechter profile image
Lars Wächter Author

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

Collapse
maxart2501 profile image
Massimo Artizzu

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.

Collapse
larswaechter profile image
Lars Wächter Author

I added Express and TypeORM at the beginning :)

Collapse
larswaechter profile image
Lars Wächter Author

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