Introduction to NestJS, Dependency Injections in Typescript
By: Jack Yeh
Tags: Dependency Injection, Nodejs, Express, Backend, Nestjs

NestJS is pretty sweet, I recommend prototyping api with this framework for all of my clients.

tl;dr: NestJS’s constructor based dependency injection can help with ordering initialization in the background. You can also read the original document here: https://docs.nestjs.com/providers.

What’s Covered in this article:

What is NestJS (vs Express)?

How classes are wired together in traditional Express applications.

How does dependency injection change things.

Real-world example on the NestJS approach.

What is NestJS

From the official documentation:

Nest (NestJS) is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with and fully supports TypeScript (yet still enables developers to code in pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).

NestJS is created by Kamil Mysliwiec, and it helps bring in familiar concepts from the Java Spring world. Nest sits on top of Express, and handles all kinds of responsibilities:

Routing

Controller input/output serialization

Middleware (also called interceptors)

Validators/Transforms

With NestJS, your code can be structured into finer separation of concerns. This makes reasoning/relearning what each class does simpler. Let’s look at some examples:

How classes are wired together in a traditional Express application

At its simplest, an express application is made of:

The express application itself

Route handler (also known as Controller Methods)

Router/Sub-Routers

Middleware

Here is the hello world from expressjs.com:

const express = require('express')

const app = express()

const port = 3000 app.get('/', (req, res) => res.send('Hello World!')) app.listen(port, () => console.log(`Example app listening on port ${port}!`))

The above example shows the express App itself, and it puts a static route of / and a simple route handler which sends the string Hello World! to the response for each request that comes in.

As the application gets more complex in an Express App, it will introduce the following:

Database Access Layer — Also known as repositories, they handle database connection and crud operations (Create/Read/Update/Delete database records)

Configuration Manager — Either load application specific variables from the environment or from a file. E.G. AWS clientId, AWS clientSecret, Database paths, etc. Configuration Manager can be categorized as a Service when needed to be.

Request middleware — These functions will intercept the request and perform validation/logging/error handling/more. They help in reducing the amount of duplicate logic in route handlers too.

Services —These are either functions or classes that perform business logic, make calls against 3rd party API, or handle side effects (sending notification/emails).

And More.

These classes/functions will often need to reference each other. If you store database/s3 credentials in the configuration manager, you cannot actually instantiate repository until those are ready!

This is usually solved with several different approaches:

Make the configuration manager into a singleton, then statistically import the credentials as needed.

Run a bootstrap method in the program entry point, initialize the configuration manager, then pass them in as constructor arguments into the database/service classes that need them. And make the express app ready after that.

Use a dependency injection framework. Classes will receive the dependency it needs in the constructor.

How does Dependency Injection change things

In your entry file, instead of:

async bootstrap(app) {

// Setup configurationManager

// Initialize db connection

// Send valid repository classes to all service/controllers // Setup all router and controller // app now listens on a port

} const app = express()

bootstap(app)

.then(() => logger.info(`Application is running on port 8080`))

.catch((err) => {

// log the last err, perform cleanup, exit the process

})

With Nest.JS, you do:

async bootstrap() {

let app: INestApplication try {

app = await NestFactory.create(AppModule)

// setup global pipe for validation

await app.listen(port, () => { logger.info(`Application is running on port 8080`) })

} catch (err) {

// log the last err, perform cleanup, exit the process

if (app) {

await app.close();

}

}

} bootstrap();

And AppModule is responsible for setting up its own dependency toward other modules and hooking up controller to routes!

Real-world example on the NestJS approach

Our project has the following responsibility:

Let user register for email notification.

Batch send out emails when applicable on a daily schedule.

And we start with the following class:

ConfigService — Singleton to load in the daily schedule CRON string. Also has Database/SendGrid credential. Reads all of them from environment variables.

UserRepo — Singleton to find users (and in turn, emails).

EmailService — Singleton to provide method for batch sending emails, requires the SendGrid credential.

CronService — Singleton that uses node-cron package to trigger its own class method to look for user emails, then send emails out.

Can you figure out who depends on what?

ConfigService — Depends on no one.

UserRepo — ConfigService

EmailService — ConfigService

CronService — ConfigService, UserRepo, EmailService

We can put all of the wiring logic in the bootstrap method, if we are using express.

Or, we can put them in NestJS modules.

@Injectable()

export class ConfigService {

constructor() {

// load from environment variable

}

} // Put ConfigService in a Common Module

@Global()

@Module({

imports: [],

providers: [ConfigService],

exports: [ConfigService],

})

export class CommonModule // NOTE: UserRepo is omitted, I recommend using typeorm, which will handle the class generation and injection for you @Injectable()

export class EmailService {

constructor(private readonly configService: ConfigService) {} async sendBatchEmail(emails){

// we can access sendGrid credentials here

// with this.configService.sendGrid

}

} @Module({

imports: [CommonModule],

providers: [EmailService],

exports: [EmailService],

})

export class NotificationModule // cron service @Injectable()

export class CronService {

constructor(

private readonly configService: ConfigService,

private readonly userRepo: UserRepo,

private readonly emailService: EmailService) {

// Setup cron scheduling here with this.configService.cronTimeString

} async sendDailyEmails(){

// Use this.userRepo to find user emails

// Use this.emailService to send out emails

}

} @Module({

imports: [CommonModule, UserModule, NotificationModule],

providers: [CronService],

exports: [CronService],

})

export class CronModule // And AppModule

@Module({

imports: [CommonModule, UserModule, NotificationModule, CronModule],

controllers: [.... ], // Pipe in controllers here

providers: [],

exports: [],

})

export class AppModule

So, instead of having the wiring logic being inside the program entry-point, we have moved the them into respective modules, which makes the code simpler to read.

Now, suppose your boss or tech lead comes over to ask you to load parts of the configuration from database or a s3 bucket, so that we can deploy dynamically without touching deployment (Environment variables are considered as deployment configurations).

With NestJS, we make a small change to the ConfigService/Common module like this:

@Injectable()

export class ConfigService {

constructor(private readonly s3Service:S3Service) {

// load from environment variable

}



async loadFromS3() {

// look at bucket content and fill in the rest

}

} // Put ConfigService in a Common Module

@Global()

@Module({

imports: [],

providers: [

S3Service, {

provide: ConfigService,

inject: [S3Service],

useFactory: async (s3Service:S3Service) => {

const configService = new ConfigService(s3Service);

await configService.loadFromS3();

return configService;

}

}

],

exports: [S3Service, ConfigService],

})

export class CommonModule

And, everywhere else where we need to use ConfigService will continue to function as before! NestJS will figure out when the ConfigService instance is ready, and initialize the rest of the dependencies for us.

Do you have a complex application bootstrapping process? Try NestJS today! Got a question? Feel free to reach out to info@teamzerolabs.com too.