Another ExpressJS API tutorial for 2021, part 06 — Middleware

We are finally reaching the half of the series of articles about how to develop an API using ExpressJS and Typescript. Today we are going to talk about Middleware
Hey there, if you are completely lost here, then I recommend to go back to the first article about how to create an API using ExpressJS and Typescript.
Just as a quick overview, here is the series of articles that we are writing:
- Create your hello world ExpressJS API (Yes… we always need to start with a hello world)
- Configure our hello world application to use Typescript (TS is cool, believe me ;) )
- Create our first CRUD API endpoints for an user (Everyone wants a registered user in their applications, no?)
- Create and configure your controllers (We will talk about it after)
- Create and configure your services (What the h. is that?)
- Middleware’s! (Yes, we need and use it everyday) We are here now ;)
- End to end testings!
- Configuring a secure way to manage user permissions via API
- Putting all together into a Docker container
- Configuring a simple MongoDB to connect to our application
- Configuring permission level to the application
- Add logs with Winston!
So… let’s review what we have so far:
- A configured environment for Typescript
- Routes configuration
- Controllers
- Services
- A temporary database
Today we are going to add and introduce Middleware to our API, using the users routes as an example. Make sure to grab the last code article before going further. The code is HERE.
Ok, ok, but what are middleware? By ExpressJS documentation is basically:
Middleware functions are functions that have access to the request object (
req), the response object (res), and the next middleware function in the application’s request-response cycle. The next middleware function is commonly denoted by a variable namednext.
Remember that when we configured our routes we used an Array and we were using only the Controller on it. The code was like the following:
this.app.get(`/users`, [
usersController.listUsers
]);What we want to do now is to create some steps before the final destination that is the controller in order to maintain short functions at our code and to have a good organisation. We will be also being able to re-use middleware during our coding.
So, with all said, should we start?
Let’s create a folder called middleware inside the users folder and a file called users.middleware.ts with the following code:
import express from 'express';
import {UsersService} from '../services/user.services';export class UsersMiddleware {
private static instance: UsersMiddleware;static getInstance() {
if (!UsersMiddleware.instance) {
UsersMiddleware.instance = new UsersMiddleware();
}
return UsersMiddleware.instance;
}validateRequiredCreateUserBodyFields(req: express.Request, res: express.Response, next: express.NextFunction) {
if (req.body && req.body.email && req.body.password) {
next();
} else {
res.status(400).send({error: `Missing required fields email and password`});
}
}async validateSameEmailDoesntExist(req: express.Request, res: express.Response, next: express.NextFunction) {
const userService = UsersService.getInstance();
const user = await userService.getByEmail(req.body.email);
if (user) {
res.status(400).send({error: `User email already exists`});
} else {
next();
}
}async validateUserExists(req: express.Request, res: express.Response, next: express.NextFunction) {
const userService = UsersService.getInstance();
const user = await userService.readById(req.params.userId);
if (user) {
next();
} else {
res.status(404).send({error: `User ${req.params.userId} not found`});
}
}async extractUserId(req: express.Request, res: express.Response, next: express.NextFunction) {
req.body.id = req.params.userId;
next();
}
}We created some functions that contains some business logic to our application such as to control if an user exists or not, if we are not duplicating any email and on. Those rules are generic and just to make us to have an example on how to use middleware
In a real world scenario, you should map the business rules with your client and create validations that will make sense to each case of use.
Now we are ready to add the middleware to our routes file. Let’s open again the user.routes.config.ts file and update it with the following:
import {CommonRoutesConfig, configureRoutes} from '../common/common.routes.config';
import {UsersController} from './controllers/users.controller';
import {UsersMiddleware} from './middlewares/users.middleware';
import express from 'express';export class UsersRoutes extends CommonRoutesConfig implements configureRoutes{
constructor(app: express.Application) {
super(app, 'UsersRoute');
this.configureRoutes();
}configureRoutes() {
const usersController = new UsersController();
const usersMiddleware = UsersMiddleware.getInstance();
this.app.get(`/users`, [
usersController.listUsers
]);this.app.post(`/users`, [
usersMiddleware.validateRequiredCreateUserBodyFields,
usersMiddleware.validateSameEmailDoesntExist,
usersController.createUser
]);this.app.put(`/users/:userId`, [
usersMiddleware.validateUserExists,
usersMiddleware.extractUserId,
usersController.put
]);this.app.patch(`/users/:userId`, [
usersMiddleware.validateUserExists,
usersMiddleware.extractUserId,
usersController.patch
]);this.app.delete(`/users/:userId`, [
usersMiddleware.validateUserExists,
usersMiddleware.extractUserId,
usersController.removeUser
]);
this.app.get(`/users/:userId`, [
usersMiddleware.validateUserExists,
usersMiddleware.extractUserId,
usersController.getUserById
]);
}}import {CommonRoutesConfig, configureRoutes} from '../common/common.routes.config';
import {UsersController} from './controllers/users.controller';
import {UsersMiddleware} from './middlewares/users.middleware';
import express from 'express';export class UsersRoutes extends CommonRoutesConfig implements configureRoutes{
constructor(app: express.Application) {
super(app, 'UsersRoute');
this.configureRoutes();
}configureRoutes() {
const usersController = new UsersController();
const usersMiddleware = UsersMiddleware.getInstance(); this.app.get(`/users`, [
usersController.listUsers
]);this.app.post(`/users`, [
usersMiddleware.validateRequiredCreateUserBodyFields,
usersMiddleware.validateSameEmailDoesntExist,
usersController.createUser
]);this.app.put(`/users/:userId`, [
usersMiddleware.validateUserExists,
usersMiddleware.extractUserId,
usersController.put
]);this.app.patch(`/users/:userId`, [
usersMiddleware.validateUserExists,
usersMiddleware.extractUserId,
usersController.patch
]);this.app.delete(`/users/:userId`, [
usersMiddleware.validateUserExists,
usersMiddleware.extractUserId,
usersController.removeUser
]); this.app.get(`/users/:userId`, [
usersMiddleware.validateUserExists,
usersMiddleware.extractUserId,
usersController.getUserById
]);
}}
Now we can check that before calling the usersController, we are passing more functions that will handle specific rules that we want.
Remember that our middleware always call the next function when everything is ok, which will make ExpressJS to call the next function or our end function that we are using as Controller. If anything went wrong we are using the response object already to return an error to the client.
And that’s it all we need to make things work. We can test now running npm start and send the requests but now considering what we’ve added as a middleware. A simple test to do is to create twice the same user with same email.
Create a new user:
curl --location --request POST 'localhost:3000/users' \
--header 'Content-Type: application/json' \
--data-raw '{
"email":"[email protected]",
"password":"amazingMediumArticle"
}'Feel free to test your brand new middleware. With the next article we are going to put e2e tests and we will be able to start testing and coding without much need of Postman or Insomnia clients since we can do it using our own project.
Thanks for reading and see you at the next article!
tips:
- the full code of this article is HERE
- don’t forget to use a nice app to test your API such as Postman or Insomnia
- I know you still didn’t… Read the ExpressJS documentation!
- Book suggestion: Clean Code and again Design Patterns
- Brazilian? PT-BR Book suggestion: Clean Code and Design Patterns
- Complete project is here! (Includes even the non existing articles yet)
