Build a REST API from scratch using NestJS
What is NestJS?
As mentioned at NestJS 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).
Under the hood, Nest makes use of robust HTTP Server frameworks like Express (the default) and optionally can be configured to use Fastify as well!
Nest provides a level of abstraction above these common Node.js frameworks (Express/Fastify), but also exposes their APIs directly to the developer. This gives developers the freedom to use the myriad of third-party modules which are available for the underlying platform.
Installing NestJS
To install NestJS we simple use npm and run:
npm i -g @nestjs/cli
This will install Nest in a global way on our machine.
Create a new project
To start a new project we simply run:
nest new project-name
It is also possible to install the TypeScript starter project with Git:
git clone https://github.com/nestjs/typescript-starter.git project
cd project
npm install
You can also manually create a new project from scratch by installing the core and supporting files with npm (or yarn). In this case, of course, you’ll be responsible for creating the project boilerplate files yourself.
npm i --save @nestjs/core @nestjs/common rxjs reflect-metadata
Getting Started
The project
directory will be created, node modules and a few other boilerplate files will be installed, and a src/
directory will be created and populated with several core files.
App Controller
Basic controller sample with a single route
App Module
The root of the application
Main
The entry file of the application which uses the core function NestFactory
to create a Nest application instance.
Let’s Code
In our example we will start by deleting app.controller.ts
and generate a new controller using Nest Cli:
nest g controller items
This command will generate src/items/items.controller.ts
, our items controller. In order to create endpoints on our controller we use decorators such as @Get, @Post, @Put, @Delete.
Find all items
Let’s start by creating a simple findAll()
method and findById()
:
We will be back to this methods later, we want this endpoint to return real data using an database and not an hardcoded string. Now if test our endpoint we will be able to check the hardcoded return.
There are several ways to run our application:
npm start
npm run start:dev
Throughout this article we will use the second option since the server listens for new changes and automatically restarts our application without any interference on our part.
Create Items
The POST request is a bit more complex, to add an item we must create a DTO (Data Transfer Object) class with the fields we want. Let’s create our DTO file into scr/items/dto/item.dto.ts
Now let’s import out DTO into our Controller and start write our POST endpoint:
For now we will just return our input from json body, later we will be back to insert data on MongoDB.
Create Providers
As mentioned in NestJS Documentation, “providers are a fundamental concept in Nest. Many of the basic Nest classes may be treated as a provider — services, repositories, factories, helpers, and so on. The main idea of a provider is that it can inject dependencies; this means objects can create various relationships with each other, and the function of “wiring up” instances of objects can largely be delegated to the Nest runtime system. A provider is simply a class annotated with an @Injectable()
decorator.”
Let’s start by creting a simple Items Service using NestJS scaffold mechanism by typing on the console:
nest g service items
Our ItemsService
is a basic class with one property and two methods. The only new feature is that it uses the @Injectable()
decorator. The @Injectable()
decorator attaches metadata, which tells Nest that this class is a Nest provider.
Our service uses a Item interface, we can define our interface in a very simple way:
Wrap everything in a Module
Before we go any further this is the ideal time to create a module where we will wrap all the code related to the items. This is very simple to do, we just need to create a file called src/items/items.module.ts
Don’t forget to add the ItemsModule to src/app.module.ts
on @Module({imports:[ItemsModule], controllers: [], providers:[]})
.
Deploy MongoDB Instance
To deploy our MongoDB we will use Docker, although other tools like Mongo can be used.
docker pull mongo
docker run -p 27017:27017 --name mongodb -d mongo
This instance is not password protected and can be accessed directly through localhost.
Setup Mongoose
Nest supports two methods for integrating with the MongoDB database. You can either use the built-in TypeORM module described here, which has a connector for MongoDB, or use Mongoose, the most popular MongoDB object modeling tool. In this chapter we’ll describe the latter, using the dedicated @nestjs/mongoose
package.
Start by installing the required dependencies:
npm install --save @nestjs/mongoose mongoose
npm install --save-dev @types/mongoose
npm install --save @nestjs/config
Once the installation process is complete, we can import the MongooseModule
into the root AppModule
:
Since we don’t want to put our hardcoded connection string, we also have to add our Config Service to imports. After that we need to create a .env
file on our root. The .env
should look like this:
MONGOURI=mongodb://localhost:27017
PORT=3000
Let’s load a configuration file that loads our environment variables (src/config/configuration.ts
):
So we have our connection to the Mongo database configured.
Load Port Configuration
Once we have created a configuration service, we will then load the port we want our application to be listening on. To achieve this we just have to change our src/main.ts
file:
Models
With Mongoose, everything is derived from a Schema. Each schema maps to a MongoDB collection and defines the shape of the documents within that collection. Schemas are used to define Models. Models are responsible for creating and reading documents from the underlying MongoDB database.
Schemas can be created with NestJS decorators, or with Mongoose itself manually. Using decorators to create schemas greatly reduces boilerplate and improves overall code readability.
Let’s define ItemSchema
:
The @Schema()
decorator marks a class as a schema definition. It maps our Item
class to a MongoDB collection of the same name, but with an additional “s” at the end - so the final mongo collection name will be items
.
The @Prop()
decorator defines a property in the document. For example, in the schema definition above, we defined three properties: name
, description
, and quantity
. The schema types for these properties are automatically inferred thanks to TypeScript metadata (and reflection) capabilities.
Now let’s add our Schema to Items Module(src/items/items.module.ts
):
Inject Models
Since we are using Dependency Injection, our service has to inject our models. The injection is done in the constructor as follows:
Using Models
Now we have to replace everything we have hardcoded with our models using mongoose. In our service once the mongoose returns a promise our type of return will be a promise of items.
We also have to update our controller by calling our service now and returning the correct promises. Don’t forget to make the controller asynchronous.
Now we can test our API and check that everything is working:
We can also create methods for deleting and changing information, for that we have to add to our controller:
In our Items Service we have to add the methods corresponding to these actions:
We can now test our new endpoints:
If you are not sure that the item has been deleted we can always invoke the endpoint that returns us the list of all items:
Logger Service
Nest comes with a built-in text-based logger which is used during application bootstrapping and several other circumstances such as displaying caught exceptions (i.e., system logging). This functionality is provided via the Logger
class in the @nestjs/common
package. You can find more information in the official documentation.
To implement the Logger we will start by creating a service with NestCLI:
nest g service logger
After that we just have to add the scope to our logger service and we are finished with this component.
Transient providers are not shared across consumers. Each consumer that injects a transient provider will receive a new, dedicated instance.
We wrap everything in a module like we did with our item module:
So we have finished configuring the logger, we now have to add the LoggerModule to our imports in src/app.module.ts
, something like @Module({imports:[LoggerModule, ...], controllers:[...], providers:[...]})
. Finally, we just have to add to the modules where we belong to use our logger, in the case of our example we just want to use in the endpoints related to the items:
In order to be able to use the logger, we have to use dependency injection as in our Item Service:
We can now test and verify that everything is working:
And so we arrived at the basic functioning of a REST API using NestJS.
Conclusions
NestJS is a good working tool that allows us to use concepts like Dependency Injection on ExpressJS without having to install directly from other frameworks like InversifyJS.
The development using this framework is simple and intuitive and, in addition, its documentation is very well defined and allows easy learning.
Source Code
The code for the entire project is available on my github.