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
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.
- 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. - 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. - The
test
folder is where the e2e tests will reside. Currently it has two files -app.e2e-spec.ts
andjest-e2e.json
. We will not bother about the tests in this tutorial. - 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. - The
.prettierrc
and.eslintrc.js
are the configuration files forprettier
andeslint
respectively. You already know them if you have used them before. - The
tsconfig.json
andtsconfig.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
- a
user
module whose job is to find users based on username, verify a user's username and password and create a user. - an
auth
module which handles the authorization. It sets up the routes to register and login. - 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.