Getting Started with NestJS - Part 1

If you have been programming in Node JS, you might have noticed it becomes unmaintainable over time. Barebones Node JS is almost never the choice for a production app. Even using libraries like Express, it becomes out of control in no time.

That's where NestJS comes into play. NestJS combines Node JS with modern features such as TypeScript, Modules, Dependency Injections, Queuing, Logging, Microservices etc. out of the box. It also uses Express or Fastify internally, but also exposes their APIs in case you want.

In this series, we'll build a small RESTful app using NestJS. This is the first post of the series, and in this post, we'll go over the first steps with NestJS

Prerequisites

A local development environment with Node JS and npm installed. If you do not have Node installed, follow this guide

Become a Patron!

Installation

Once you have npm ready to go, you can install NestJS with this command -

npm i -g @nestjs/cli

First steps

The first step is to create a NestJS project. We can use the nest cli to scaffold a new project using the new command and passing it the name of a project-

nest new my-nest-project

This command will create a directory called my-nest-project and clone a starter template there and installed the node modules. If you have both npm and yarn installed, it will also ask you which one to use. For this tutorial, I'll use npm

After a while, when the installation is done, you should see a screen similar to below

Now we can move into the directory and start the server-

nest new my-nest-project
npm run start

You should see some log lines, where the last line should say "Nest application successfully started"

Our server is now up and running! Open up a browser and visit localhost:3000 and you should see "Hello, World!" printed

Press ctrl+c in the terminal to stop the server

File structure

Let's take a look at the file structure. Apart from the Node JS specific stuff (`node_modules`, package.json and package-lock.json) there are 3 directories and 5 files.

  1. Since we will write the server in TypeScript, it needs to be compiled to JavaScript. The dist directory is where the compiled and bundled javascript code will stay.
  2. The src is the heart of the server. This is where all our codes will be. We will take a look at the contents in details.
  3. The test folder is where the e2e tests will reside. Currently it has two files - app.e2e-spec.ts and jest-e2e.json. We will not bother about the tests in this tutorial.
  4. The nest-cli.json is the configuration file for the nest cli. Most of the times you will not need to edit this file. But there are a few cases where you will need to edit this file.
  5. The .prettierrc and .eslintrc.js are the configuration files for prettier and eslint respectively. You already know them if you have used them before.
  6. The tsconfig.json and tsconfig.build.json files are for the TypeScript compiler. They tell the compiler where to find our code, which directory to output the compiled files in, and sets some useful flags. You'll probably not need to edit this file.

Code structure

Inside the src directory, our entire source code resides. Currently there are 4 files. We'll look at them now.

The main.ts file is the entry point of the server

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

As you can see, it creates a server variable named app and starts it at port 3000. Think of this app as a wrapper around the express server. This is the place where we can modify server wide settings (e.g. enabling CORS, adding interceptors, or exception filters etc.)

The other 3 files are related to the app module. So, let's clear our terminologies -

Modules

You might have noticed this line in the main.ts file -

import { AppModule } from './app.module';

And then -

const app = await NestFactory.create(AppModule);

This AppModule is the root module of our server.

What are modules? Think of a module as a small "section" of your server consisting of closely related codes that work on a particular feature.

Generally one module is responsible for some closely related feature. For example, if you have a bookstore server, you might have

  1. a user module whose job is to find users based on username, verify a user's username and password and create a user.
  2. an auth module which handles the authorization. It sets up the routes to register and login.
  3. a book module which can be used to find all books, upload new books etc.

You get the idea. Each module "encapsulates" codes that do some closely related job.

One module can also depend on one or more modules. For example, the auth module might use the user module to perform authentication. Then the book module can use the auth module to check if the user is authenticated to upload a book or not.

Essentially, the depenedency structure follows a graph - we have a root module - AppModule, which depends on some modules, which in turn depend on other modules and so on.

Let's look at the app.module.ts file -

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

So the class AppModule is defined here and decorated with @Module() decorator. This tells Nest that AppModule is a module. No surprise there!

The decorator takes additional configuration, which are used to find out about dependencies. The imports array tells what are the other modules that are dependencies of AppModule. Since it's empty, Nest knows AppModule does not depend on any other module. If we have other modules, we will add them here. In our example, we might add the UserModule, BookModule and AuthModule to the imports array.

The controllers array tells Nest what are the controllers. More on that later. Our AppModule has only one controller - AppController.

And finally, the providers array list the providers. Again, more on that later. Currently, our AppModule has one provider AppService.

The @Module() decorator has one more optional configuration option - exports. This tells which providers this module exports to other modules.

Controllers

A controller's purpose is to receive specific requests for the application. When you visit a route on the server e. g localhost:3000 or localhost:3000/users/register, some controller is responsible to receive the request and act on it. A controller can have multiple actions, and one route maps to one action.

Now let's look at the app.controller.ts -

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

The class AppController is decorated with @Controller which tells Nest that this is a controller.

The class has a method called getHello() which is decorated with @Get(). The @Get() decorator tells Nest that this is GET request. The argument to @Get() sets the route. Since it has no argument passed, this corresponds to the root url i. e. localhost:3000. So, when you visit that page, this getHello method is run and the return value is returned.

Controller is definitely one of the most feature rich and complicated part of NestJS, and we will not go too deep. But for now, let's explore a little bit.

The @Controller() takes an argument, which is the prefix for the controller. Change it to this -


@Controller("app")
export class AppController {
...

Now run the server again -

npm run start

Now if you visit localhost:3000 you should get a 404 error

Now visit localhost:3000/app and you should see the familliar page -

By using @Controller("app") we have changed the prefix of AppController to app. Now instead of handling requests under the root url, our controller handles requests under /app. So, if we had an action that responded to /xyz, it now responds to /app/xyz.

Let's take a look at how to add new actions. Let's try adding a POST method to /app/mypost. Add this code to the AppController class

@Post("mypost")
myPost(): string {
  return "POST successful"
}

You need to add Post to the list of imports from @nestjs/common.

Run the server again. This time, you can't make the request from browser since it's a POST. So either use Postman, Insomnia, or cURL to make a POST request to localhost:3000/app/mypost. You should see POST successful returned.

Of course, POST request without a body is pretty useless. So how can we access the body? It's easy. You just have to add an argument with the @Body() decorator.

 @Post("mypost")
 myPost(@Body() body): string {
 	return `You sent ${JSON.stringify(body)}`;
}

Remember to add Body to the import list from @nestjs/common. The @Body() decorator tells Nest that the argument body is a request body. Here it has any type, but if you have a specific request format, you can create a class or interface and make body of that type. So that you'll have type checking and even validation!

At this point, let me share a little trick. So far every time we changed the code, we had to restart the server. This time, instead of npm run start, we will run npm run start:dev This starts the server in watch mode - which means every time you change the code, the server restarts automatically!

Now that the server is up and running, make a post request, but add a body. You should see the same body returned.

Now let's talk about query parameters. Suppose we want to make a GET request to localhost:3000/app/users?age=21&location=kolkata. We can do this easily by adding the @Query decorator with the name of the query parameter.

@Get("users")
myUsers(@Query('age') age: string, @Query('location') location: string): string {
	return `You sent age: ${age}, location: ${location}`
}

You can easily guess, @Query('age') tells Nest that the age variable corresponds to the age query parameter and similarly for the location

Dealing with path parameters is also easy. Suppose we want to make a request like localhost:3000/books/:id/name where the :id stands for id of the book and is a path parameter. For example, we might send a request localhost:3000/books/10/name and get the name of the book with id 10.

This can be achieved with the @Param() decorator

@Get("books/:id/name")
bookName(@Param('id') id: string): string {
	return `You want the name of book with id ${id}`
}

First thing to note is the path - books/:id/name. The : in front of id tells Nest that id is a parameter. Then we capture the value using the @Param('id) decorator.

Make the request and see for yourself.

Unfortunately, we have barely scratched the surface of Controller. For a beginner however this is enough. We will cover controllers in depth in later parts.

Providers

This is a more advanced topic. Put easily, providers in Nest JS are objects which can inject dependencies. It means providers are instantiated and automatically made available by Nest itself.

If you remember app.module.ts, there was a providers array containing AppService. That's the provider here.

If you look at the constructor of AppController, you'll notice AppService in play -

 constructor(private readonly appService: AppService) {}

We are getting an instance of the appService class, but nowhere have we created an instance. That's where dependency injection comes into play. The AppService instance is created and "injected" automatically and we do not need to worry about it.

If you open up app.service.ts, you notice the code -

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

The @Injectable() decorator tells Nest that it is a provider. Generally it's a good idea to create a provider for data storage and retrieval and use this provider in the controller. This is a good practice as controllers should not be responsible for accessing data. This not only makes controllers simple, but also helps the programmer in case they want to change how data is stored and accessed later (e. g. switching from PostgreSQL to MongoDB) without changing the controller code.

Providers are very complex topic, and really advanced for a beginner guide. So, we'll not go into too much details and leave it at this.

Conclusion

That's it for this part of the series. In the next part, we'll look at how we can create our own modules, controllers and providers.

Be sure to subscribe to the blog so that you can be notified of when a new installment comes out. Follow me on Twitter for more updates.

Aniket Bhattacharyea

Aniket Bhattacharyea