A beginner’s guide to building web API’s with NestJs, Postgres, and Sequelize fundamentals.

onwuzor victor
17 min readMay 28, 2020

Overview of NestJs:

Nest (NestJS) is a framework for building efficient, scalable Node.js server-side applications. It 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). Nest is an MVC framework.

One of the key benefits of Nest is that it provides an out-of-the-box application architecture that allows developers and teams to create highly testable, scalable, loosely coupled, and easily maintainable applications.

Prerequisite:

Knowledge of TypeScript and JavaScript is very important. Experience with Angular is a plus, but no worries this post will explain every concept you need to know about Nest. Install Postman, we will use it to test our API endpoints.
Ensure Node.js (>= 8.9.0) is installed on your machine. Link to the final project GitHub repo is here.

Building blocks:

These sets of abstractions/concepts helps you know where to put specific business logic from project to project. Nest has a very similar concept with Angular and If you are familiar with Angular concepts it will be straight forward to you. Nonetheless, this post will assume that you have no knowledge of these concepts and explain them to you.

Controller: The controller is responsible for listening to request that comes into your application and then formulates the responses that goes out. For instance, when you make an API call to /posts the controller will handle this request and return the appropriate response you specified in your Controller.

This is just a basic Class declaration in TypeScript/JavaScript with a @Controller decorator. All Nest Controllers must have the decorator which is REQUIRED to define a basic Controller in Nest. Nest allows you to specify your routes as a parameter in the @Controller() decorator, this helps to group a set of related routes and minimize code repetition. Any request to /posts will be handled by this controller. At the class methods level, you can specify which method should handle the GET, POST, DELETE, PUT/PATCH HTTP request. In our example, the findAll() method with the @Get() decorator handles all GET HTTP requests to get all blog posts. While thefindOne() method with @Get(': id')decorator will handle aGET /posts/1 request.

Providers: Providers were designed to abstract any form of complexity and logic to a separate class. A provider can be a service, repositories, factories, or helpers. Providers are plain TypeScript/JavaScript classes with an @Injectable() decorator preceding their class declaration. Just like services in Angular, you can create and inject providers into other controllers or other providers as well. A good use case for a service provider is to create a PostService that abstracts all communication to the database into this service thereby keeping the PostsController nice and clean.

This is just a plain TypeScript class with a @Injectable() decorator (this is how Nest knows it is a provider). Post is just an interface for type checking. Here, we are using a simple data structure to store the data. In a real project, this service will be communicating with the database.

Modules: A module is a JavaScript/TypeScript class with @Module()decorator. The @Module() decorator provides metadata that Nest uses to organize the application structure. Modules are a very important aspect of Nest and each application must provide at least one Module which is the application root module. The root module is the starting point Nest uses to build the application graph. The Post service, controller, post entity, and everything related to post should be grouped into a module (PostsModule). Below, we defined the PostsModule.

Then, we import this module into the root module AppModule

The @Module() decorator takes a single object whose properties describes the module:
imports: Other modules that are needed by this module.
exports: By default, modules encapsulate providers. It’s impossible to inject providers that are neither directly part of the current module nor exported from the imported modules. To make the current module providers available to other modules in the application, they have to be exported here. We can also export modules we imported too.
controllers: The set of controllers defined in this module which have to be instantiated.
providers: in simple terms, all our services and providers within the module will be here.

Interceptor: It’s a specialized set of middleware that lets you peek into the request that goes into the application. You can peek into the request before it reaches the controller and after the controller is done with the request before it gets to the client-side as a response. You can manipulate the data on their way out in the interceptor.

Guard: Guard is also a special kind of middleware that is used mainly for authentication and authorization. They only return a boolean value of true or false. Guards have a single responsibility. They determine whether a given request will be handled by the route handler or not, depending on certain conditions (like permissions, roles, ACLs, etc.) present at run-time. A Guard should also implement the CanActivate interface.

Pipes: Pipe is also a special kind of middleware that sits between the client and the controller. They are mostly used for validation and transforming data before they get to the controller.

DTO(Data Transfer Object): Data transfer object is an object that defines how data will be sent over the network. They are also used for validation and type checking.

Interfaces: TypeScript interfaces are only used for type-checking and they do not compile down into JavaScript code.

What to learn:

In this post, I will take you through the journey of getting started with Nest. We will build a Mini Blog Web RESTful API Application. We will be covering authentication, login and sign up, Routes protection, CRUD operations for a blog post, validations, best practices, etc. We will be using Postgres and Sequelize as our ORM.

Setup
Install NestJs CLI. Nest comes with an awesome CLI that makes it easy to scaffold a Nest application with ease. In your terminal or cmd run

npm i -g @nestjs/cli

Now, you have Nest installed globally in your machine.

On your terminal or cmd, cd into the directory where you want to create your application and run

nest new nest-blog-apicd nest-blog-apinpm run start:dev

Navigate to http://localhost:3000 on any of your browsers. You should see Hello World. Bravo! you have created your first Nest app. Let’s continue.

NOTE: As of this writing, If running npm run start:dev throws error, change your typescript:3.4.2 in your package.json file to typescript:3.7.2 and then delete the node_modules and package-lock.json re-run npm i

Your folder structure should look like this

Nest Folder structure

Let’s set up our database:
We’ll start by installing the following dependencies. Ensure your terminal or cmd is currently on your project root directory. Then run

npm install -g sequelize
npm install --save sequelize sequelize-typescript pg-hstore pg
npm install --save-dev @types/sequelize
npm install dotenv --save

Now, create a database module. Run
nest generate module /core/database

Database Interface
Inside the database folder, create a interfaces folder, then create a dbConfig.interface.ts file inside it. This is for the database configuration interface. Each of the database environments should optionally have the following properties. Copy and paste the following code.

src/core/database/interfaces/dbConfig.interface.ts

Database Configuration
Now, let’s create a database environment configuration. Inside the database folder, create database.config.ts file. Copy and paste the below code.

src/core/database/database.config.ts

The environment will determine which configuration to be used.

.env file
On our project root folder, create .env and .env.sample files. Copy and paste the following code in both files.

Fill the values with the correct information, only on the .env file and ensure it’s added to the .gitignore file to avoid pushing it online. The .env.sample is for those who will like to download your project and use it so, you can push it online.

HINTS: Your username, password, and database name should be what you use to set up your Postgres. Create a Postgres database with your database name.

Nest provides a@nestjs/config package out-of-the-box to help load our .env file. To use it, we first install the required dependency.
Run
npm i --save @nestjs/config

Import the @nestjs/config into our app root module

src/app.module.ts

Setting the ConfigModule.forRoot({ isGlobal: true }) toisGlobal: true will make the .env properties available throughout the application.

Database Provider
Let’s create a database provider. Inside the database folder, create a file database.providers.ts

Core directory will contain all our core setups, configuration, shared modules, pipes, guards, and middlewares.

In database.providers.ts file, copy and paste this

src/core/database/database.providers.ts

Here, the application decides what environment we are currently running on and then chooses the environment configuration. All our models will be added to the sequelize.addModels([User, Post]) function. Currently, there are no models.
BEST PRACTICE: It is a good practice to keep all string values in a constant file and export it to avoid misspelling those values and a single place to change it. Inside the core folder, create a constants folder and inside it create a index.ts file. Paste the following code.

src/core/constants/index.ts

Let’s add the database provider to our database module. Copy and paste this

src/core/database/database.module.ts

We exported the database provider exports: [...databaseProviders] to make it accessible to the rest of the application that needs it.

Now, Let’s import the database module into our app root module to make it available to all our services.

src/app.module.ts

Setting Global endpoint prefix
We might want all our API endpoints to start with api/v1 for different versioning. It won’t be nice if we have to add this prefix to all our controllers. Fortunately, Nest provides a way to set a global prefix.

In main.ts file, add app.setGlobalPrefix('api/v1');

src/main.ts

User Module
Let’s add a User module to handle all user-related operations and to keep tabs on who is creating what Post.

Run
nest generate module /modules/users
This will automatically add this module to our root module AppModule

Generate User Service
Run
nest generate service /modules/users
This will automatically add this service to the Users module

Set Up User Database Schema Model
Inside modules/users, create a file user.entity.ts then copy and paste this.

src/modules/users/user.entity.ts

Here, we are specifying what our User table will contain. The @column() decorator provides information about each column in the table. The User table will have name email password and gender as columns. We imported all the Sequelize decorators from sequelize-typescript. To read more about Sequelize TypeScript

User DTO
Let’s create our User DTO (Data Transfer Object) schema. Inside the users folder, create a dto folder, then create a user.dto.ts file inside it. Paste the following code

src/modules/users/dto/user.dto.ts

User Repository provider
Now, create a User Repository provider. Inside the user's folder, create a users.providers.ts file. This provider is used to communicate with the database.

src/modules/users/users.providers.ts

Add this export const USER_REPOSITORY = 'USER_REPOSITORY'; to the constants index.ts file.

Also, add the user provider to the User module. Notice, we added the UserService to our exports array. That is because we’ll need it outside of the User Module.

src/modules/users/users.module.ts

Let’s encapsulate user operations inside the UsersService. Copy and paste the following code.

src/modules/users/users.service.ts

Here, we injected the user repository to communicate with the DB.
create(user: UserDto) This method creates a new user into the user table and returns the newly created user object.
findOneByEmail(email: string) This method is used to look up a user from the user table by email and return the user.
findOneById(id: number) This method is used to look up a user from the user table by the user Id and return the user.
We will use these methods later.

Lastly, let’s add the User model to the database.providers.ts file sequelize.addModels([User]);

src/core/database/database.providers.ts

Generate Auth Module
This module will handle user authentication (Login and Sign up).
Run
nest generate module /modules/auth
This will automatically add this module to our root module AppModule

Generate Auth Service
Run
nest generate service /modules/auth
This will automatically add this service to the Auth module.

Generate Auth Controller
Run
nest g co /modules/auth
This will automatically add this controller to the Auth module.
Note: g is an alias for generate and co is for controller

We will be using Passport to handle our authentication. It is straightforward to integrate this library with a Nest application using the @nestjs/passport module.

We will implement two auth strategies for this application
- Local Passport Strategy: This strategy will be used for logging in users. It will verify if the email/username and password provided by the user is valid or not. If user credentials are valid, it will return a token and user object, if not, it will throw an exception.
- JWT Passport Strategy: This strategy will be used to protect protected resources. Only authenticated users with a valid token will be able to access these resources or endpoints.

Local Passport Strategy
Run
npm install --save @nestjs/passport passport passport-local
npm install --save-dev @types/passport-local
npm install bcrypt --save

Inside the auth folder create a local.strategy.ts file and add the following code:

src/modules/auth/local.strategy.ts

Here, we are importing Strategy, PassportStrategy and AuthService. We extend the PassportStrategy to create the LocalStrategy. In our use case with passport-local, there are no configuration options, so our constructor simply calls super(), without any options object.

We must implement the validate() method. For the local-strategy, Passport expects a validate() method with the following signature: validate(username: string, password:string): any.

Most of the validation work is done in our AuthService (with the help of our UserService), so this method is quite straightforward. We call the validateUser() method in the AuthService (we are yet to write this method), which checks if the user exists and if the password is correct. authService.validateUser() returns null if not valid or the user object if valid. If a user is found and the credentials are valid, the user is returned so Passport can complete its tasks (e.g., creating the user property on the Request object), and the request handling pipeline can continue. If it's not found, we throw an exception and let our exceptions layer handle it.

Now, add the PassportModule, UserModuleand LocalStrategy to our AuthModule.

src/modules/auth/auth.module.ts

AuthService
Let’s implement the validateUser() method.

src/modules/auth/auth.service.ts

Here, we check if the user exists with the email provided, then check if the password in the DB matched what the User provided. If any of these checks fail, we return null, if not, we return the user object.

comparePassword(enteredPassword, dbPassword): This private method compares the user-entered password and user DB password and returns a boolean. If the password matches it returns true if not, it returns false.

JWT Passport Strategy:
Run
npm install @nestjs/jwt passport-jwt
npm install @types/passport-jwt --save-dev

Inside the auth folder create a jwt.strategy.ts file and add the following code:

src/modules/auth/jwt.strategy.ts

Here, we are extending PassportStrategy. Inside the super() we added some options object. In our case, these options are:
jwtFromRequest: supplies the method by which the JWT will be extracted from the Request. We will use the standard approach of supplying a bearer token in the Authorization header of our API requests.

ignoreExpiration: just to be explicit, we choose the default false setting, which delegates the responsibility of ensuring that a JWT has not expired to the Passport module. This means that if our route is supplied with an expired JWT, the request will be denied and a 401 Unauthorized response sent. Passport conveniently handles this automatically for us.

secretOrKey: This is our secret key for the token. This will use the secret key in our .env file.

The validate(payload: any) For the jwt-strategy, Passport first verifies the JWT’s signature and decodes the JSON. It then invokes our validate() method passing the decoded JSON as its single parameter. Based on the way JWT signing works, we're guaranteed that we're receiving a valid token that we have previously signed and issued to a valid user. We confirm if the user exists with the user payload id. If the user exists, we return the user object, and Passport will attach it as a property on the Request object. If the user doesn’t exist, we throw an Exception.

Now, add the JwtStrategy and JwtModule to the AuthModule.

src/modules/auth/auth.module.ts

We configure the JwtModule using register(), passing in a configuration object.

Let’s add other methods we will need to login and create a new user in AuthService

src/modules/auth/auth.service.ts

Import and inject JwtService.
login(user): This method is used to login the user. This takes the user information, generates a token with it, and then returns the token and user object.
create(user): This method is used to create a new user. This takes the user information, hash the user password, save the user to the DB, removes the password from the newly returned user, generates a token with the user object, and then returns the token and user object.
generateToken(user): This private method generates a token and then returns it.
hashPassword(password): This private method hashes the user password and returns the hashed password.

We will be using all these functions later.

AuthController
Now, let’s create our signup and login methods.

src/modules/auth/auth.controller.ts

When we hit this endpoint POSTapi/v1/auth/login it will call @UseGuards(AuthGuard('local')) This will take the user email/username and password, then run the validate method on our local strategy class. The login(@Request() req) will generate a jwt token and return it.

The POSTapi/v1/auth/signup endpoint will call the this.authService.create(user) method, create the user, and return a jwt token.

Let’s try it out…
Open your Postman application. Ensure your application is running. Send a POST request to http://localhost:3000/api/v1/auth/signup and input your body data to create a user. You should get a token and the user object returned.

Now that we have a user, let’s log the user in. Send a POST request to http://localhost:3000/api/v1/auth/login and input just your username and password. You should get a token and the user object returned

Validation
Notice how we are not validating any of the user's input. Now, let’s add validation to our application.
Run
npm i class-validator class-transformer --save

Inside the core folder, create a pipes folder and then create validate.pipe.ts file. Copy and paste the following code.

src/core/pipes/validate.pipe.ts

Let’s auto-validate all our endpoints with dto by binding ValidateInputPipe at the application level. Inside the main.ts file, add this:

src/main.ts

Now, let’s update our users dto file.

src/modules/users/dto/user.dto.ts

Here, we are importing these decorators from class-validator.
@IsNotEmpty(): ensures the field isn’t empty.
@IsEmail(): this checks if the email entered is a valid email address.
@MinLength(6): ensures the password character is not less than six.
@IsEnum: ensures only the specified value is allowed. In our case, male and female.
class-validator has tons of validation decorators, check them out.

Let’s try our validation out…

Without passing any value, I got the following validation error. Our validation is working now. This validation is automatic to all endpoints with a dto (data transfer object).

Unique User account
Let’s add a guard that prevents users from signing up with the same email twice since email is unique at the schema level.
Inside the core folder, create a guards folder, then create a doesUserExist.guard.ts file. Copy and paste the following code:

src/core/guards/doesUserExist.guard.ts

Now, let’s add this guard to our signup method in AuthController.

src/modules/auth/auth.controller.ts

Let’s try to create a user with an email that already exists in our database.

Post Module
nest g module /modules/posts
This will automatically add this module to our root module AppModule

Generate Post Service
Run
nest g service /modules/posts
This will automatically add this service to the Post module.

Generate Post Controller
Run
nest g co /modules/posts
This will automatically add this controller to the Post module.

Post Entity
Create a post.entity.ts file inside the posts folder. Copy and paste the following code:

src/modules/posts/post.entity.ts

The only new thing here is the @ForeignKey(() => User) specifying that the userId column is the id of the User table and @BelongsTo(() => User) specifying the relationship between the Post table and User table.

Post DTO(Data Transfer Object)
Inside the posts folder, create a dto folder then create post.dto.ts file inside it. Copy and paste the following code:

src/modules/posts/dto/post.dto.ts

Here, our post body object must have a title and body and title length must not be less than 4.

Post Provider
Create a posts.providers.ts file inside the posts folder. Copy and paste the following code:

src/modules/posts/posts.providers.ts

Add this export const POST_REPOSITORY = 'POST_REPOSITORY'; to the constants index.ts file.

Add our Post provider to our Post Module file.

src/modules/posts/posts.module.ts

Now, add our Post entity to our database provider. Import the Post entity inside the database.providers.ts file, add the Post to this methodsequelize.addModels([User, Post]);

Post Service Methods
Copy and paste the following inside the Post service file:

src/modules/posts/posts.service.ts

Here, we are injecting our Post repository to communicate with our database.
create(post: PostDto, userId): This accepts post object and the id of the user creating the post. It adds the post to the database and returns the newly created Post. The PostDto is for validation.
findAll(): This gets all the posts from the database and also includes/eager load the user who created it while excluding the user password.
findOne(id): This finds and returns the post with the id. It also includes/eager load the user who created it while excluding the user password.
delete(id, userId): This deletes the post from the database with the id and userId. Only the user who created the post can delete it. This returns the number of rows that were affected.
update(id, data, userId): This updates an existing post where id is the id of the post, data is the data to update, userId is the id of the original creator. This returns the number of rows that were updated and the newly updated object.

Post Controller Methods
Copy and paste the following inside the Post controller file:

src/modules/posts/posts.controller.ts

Most of the CRUD operation functionality is done in our PostService.
findAll():
This handles GET request to api/v1/posts endpoint. It returns all the posts in our database.
findOne(@Param(‘id’) id: number): This handles GET request to api/v1/posts/1 endpoint to get a single post, where 1 is the id of the post. This throws a 404 error if it doesn’t find the post and returns the post object if it does find the post.
create(@Body() post: PostDto, @Request() req): This handles POST request to api/v1/posts endpoint to create a new post. @UseGuards(AuthGuard(‘jwt’)) is used to protect the route (remember our JWT strategy). Only logged in users can create a post.
update(@Param(‘id’) id: number, @Body() post: PostDto, @Request() req): This handles the PUT request to api/v1/posts endpoint to update an existing post. It is also a protected route. If the numberOfAffectedRows is zero that means no post with the params id was found.
remove(@Param(‘id’) id: number, @Request() req): This handles the DELETE request to delete an existing post.

Let’s try our CRUD operation out…
Create a Post

Log in and add your token since creating a post route is a protected route.

Creating a post.

Read a single Post
This route isn’t protected, it can be accessed without the token.

Fetching a Single Post

Reading all Post
This route isn’t protected, it can be accessed without the token too.

fetching all posts

Updating a Single Post
This route is protected hence, we need a token and only the creator can update it.

updating a single post

Deleting a Post
This route is protected hence, we need a token and only the creator can delete it.

Deleting a post

Conclusion

Nestjs gives you a more structured way of building your server-side application with Node. For more information, check out the official NestJS website here. Finally, I hope this article is useful to you! Link to the final project GitHub repo is here.

You can connect with me on LinkedIn and Twitter.

--

--

onwuzor victor

Full Stack Software Engineer | Minimalist | Nerd | Technical Writer