Most Node.js developers have crossed paths with express at one point or another. Most modern frameworks even have express working underneath the hood.
The objective of this post is to share some tools, design patterns, and techniques I've used to supercharge my express applications to make them more robust, efficient and maintainable.
Decorators
Decorator is a design pattern that makes it possible to easily wrap our code with extra functionalities. NestJS makes use of decorators a lot, making it super easy to abstract redundant or repeated codes. In practice, decorators can be used to abstract routers, middleware, request validation, and anything else in an express application. What's even more awesome is that you don't need to learn so much to get started using decorators. With @decorators/express you can easily setup and use basic decorators in your express application.
import { Controller, Post, Request, Response } from '@decorators/express';
import { Request as Req, Response as Res } from 'express';
@Controller('/auth')
export class AuthController {
private readonly services: Services;
constructor() {
this.services = Container.get<Services>(Services);
}
@Post('/signup')
async SignUp(@Request() req: Req, @Response() res: Res) {
const data = await this.services.signUp(req.body);
res.status(201).json({
message: 'SignUp successful',
data,
});
}
}
The code snippet above shows an example of how we use @decorators/express to create a signup route without even setting up the router.
Dependency injection
A classical programming principle says that a class should receive its dependencies instead of instantiating them. In its simplest form, we want to avoid writing const classInstance = new Class()
whenever we need to use a class. Abstracting object initialisation and destruction minimises the stress of designing and using classes. The module responsible for maintaining class instances is called an IoC container (IoC means Inversion of control).
IoC is only a methodology, not a concrete implementation of DI. Again, you don't have to build your own IoC container to get started with using DI, some frequently used DI frameworks are;
- Inversify
- TypeDI for TypeScript
- @decorators/di
- Nest.Js
My personal favourite is @decorators/di. The code snippet below shows how I use DI to manage my auth service class in an Express application;
import { Injectable } from '@decorators/di';
import * as bcrypt from 'bcrypt';
import * as jwt from 'jsonwebtoken';
import { AppError } from '../../Exceptions';
import { Errors } from '../../Exceptions/errors';
import { vars } from '../../utils/vars';
import { authModel } from './schemas/schema';
@Injectable()
export class Services {
private async genToken(id: string, type: string, exp: string): Promise<string> {
const token = jwt.sign({ id, type }, vars.jwtSecret, { expiresIn: exp });
return token;
}
async signUp(data: SignUpRequest) {
const { email, password, firstname, lastname } = data;
const alreadyExistingUser = await authModel.findOne({ email });
if (alreadyExistingUser) throw new AppError(Errors.EmailAlreadyExits);
const hashedPassword = await bcrypt.hash(password, 10);
let user: any = await new authModel({ email, firstname, lastname, password: hashedPassword }).save();
user = user.toObject();
delete user.password;
const token = await this.genToken(user._id.toString(), 'auth', '100d');
return { user, token };
}
}
In my auth controller, I don't have to create an instance of the auth service myself since I used @Injectable()
decorator to make my class injectable. In the code below, notice how I get the instance of the auth service from the IoC container;
import { Container } from '@decorators/di';
import { Services as AuthService } from './service';
@Controller('/auth')
export class AuthController {
private readonly services: AuthService;
constructor() {
this.services = Container.get<AuthService>(AuthService);
}
...
Just like that, we're writing clean codes 😁.
Request validation using decorators
Another classic technique used by Nest.js is the use of decorators in validating request body. When using this methodology, you can use same class based types for type checking and also for validation. I wrote an article on how we could archive this using class validation here. I also wrote an npm package called pipe-validation which means you don't have to implement the entire validation flow yourself.
Top comments (0)