Designing Microservices with ExpressJS
By: Berkay Yıldız
Tags: JavaScript, Docker, Expressjs, Nodejs, Microservices

Before start, I will not explain all details related to ExpressJS, because of that you can download the project’s files from following Github link.

https://github.com/yildizberkay/microservices-with-expressjs

Scenario

In the above diagram, Nginx has two duties. First one is managing all of the traffic and the second one checking user’s authentication. I will not dive into authorisation in this post. It might be a topic of another post.

We have auth and ticket services as well, and they have only one model. Auth service handles registration and auth verification while Ticket service manages tickets belongs to the users.

We have two exposed endpoint without any authentication to sign-up and sign-in. While a middleware manages auth service’s authentication, Nginx manages authentication of the rest of services. If any request comes to ticket service, Nginx will ask validity of the token to auth service, and according to result, it will reject or accept them. If the request is accepted, Nginx will also pass active user’s id to ticket service with the header.

Micro Service Design

A Micro Service’s Folder Structure

- tests

- src

- db

- config

- models

- enums

- exceptions

- middlewares

- services

- utils

- index.js

- routes.js

- server.js

tests folder is a reflection of the src folder. Tests are held with the same path and names under it. Names are suffixed with .test.js extension.

src/db/config holds database profiles. These profiles can be modified with environment parameters. In this implementation, we have 3 profile as development, test and production. Database models are held under src/db/models folder. We use Sequelize as ORM, but I won’t dive into details about it, just define Models and use it. If you wanna learn details about this ORM, it has good documentation.

src/exceptions holds micro-service specific exception classes. An exception class contains a name and httpStatusCode. globalErrorHandler uses these two parameters to generate an error response.

All business logic is located under src/services folder. Business logic files can be separated by model names.

I didn’t create a routes folder because they are simple services consist of only one model, and have one main endpoint. If you are planning to decompose your micro-services by another strategy, you can create a routes folder and store your routes under it.

Handling Authentication with Middleware

Auth service uses JWT standard in the user authentication. authHandler is our key middleware. It takes authorization key and parses it. In line 5 JWT is verified. If it is not valid, it returns 401 status code.

Communication Between Nginx and Auth Service

/check-token endpoint returns 200 status code and adds User-Id property to the header. This property will be processed by Nginx later.

Getting User-Id from Ticket Service

Nginx passes User-Id in the header to all other services implemented with auth.

Gateway Design

Nginx as Gateway

Nginx is a unique web server, you can configure it as load balancer or gateway with just a few lines of code. While Nginx is working on a Docker container, it can see other services in the same network.

In the following code, it handles all requests coming from 8080. location /auth catches endpoints starting with /auth and passes coming request to http://service-auth:3000 with proxy_pass. You may ask how Nginx will recognise this host name. Docker will set this host name for all of the containers located in the same network.

server {

listen 8080;

location /auth {

rewrite ^/auth/(.*) /$1 break;



proxy_pass http://service-auth:3000;

proxy_redirect off;

proxy_set_header Host $host;

...

}

}

This is a simple demonstration. If your services become more complicated and crowded, you can also use a service discovery solutions like Consul.

Authentication

auth_request module handles all authentication stuff before the request goes to service. After auth request completes, auth_request_set sets any value coming from auth result to a local variable like $user_id so we can use it later to pass.

So, where is the /check-token definition? We defined an internal location and it is passed to directly auth service’s related endpoint.

location /check-token {

internal;

proxy_pass http://service-auth:3000/check-token;

...

}

Load Balancing

You may have a group of auth service and put an internal load-balancer. To do this, I can suggest 2 strategies.

Strategy 1: Nginx upstream

upstream comes to our help if you want to use Nginx. It is used to combine a group of server and it has also load-balancing ability.

upstream service-auth {

server service-auth-1:3000;

server service-auth-2:3000;

server service-auth-3:3000;

}



server {

listen 8080;

location /auth {

rewrite ^/auth/(.*) /$1 break;



proxy_pass http://service-auth;

proxy_redirect off;

proxy_set_header Host $host;

}

}

Strategy 2: Process Managers like PM2

The server can be run with a process manager like PM2 or StrongLoop. They provide cluster mode and any number of the app can work in one container.

Conclusion

Thank you for reading this post. I tried to explain simply, It has much more details of course but these details are shaped by your needs. I created deployment scripts as well, but I will explain details in another post. You can find all running codes of the post in the following Github link.

Finally, keep in mind there is no a single correct way. Especially in JavaScript :)

Github Repository: https://github.com/yildizberkay/microservices-with-expressjs