TL;DR: In this series, you will learn how to build a modern web application using React and Nest.js. In the end, you will also learn how to secure the application with Auth0. Rather than building a React application with JavaScript, we will deviate a bit and use TypeScript instead. Structuring the application this way comes with a lot of benefits such as type checking, enforcing, and associating every variable within the application with a datatype, cool, right? In this first part of the series, we will focus on building the backend API with Nest.js. The second part is about building the user interface and handling all frontend logic using React. You can find the second part here. The complete source code developed throughout this series can be found in this GitHub repository if you will prefer to head straight into the code.
Prerequisites
Basic knowledge and previous experience with building web applications with React will help you to get the best out of this series. Although not mandatory, you should know a few things about TypeScript. And to make it easy for everyone to follow along, I will endeavor to break down any complex implementation. Furthermore, you will also need to ensure that you have Node.js and Yarn package manager installed in your development machine. If you are yet to get them installed, kindly follow the instructions here to learn how to install Node.js properly and here for Yarn package manager.
Also, you need to have MongoDB installed on your machine. Follow the instructions here to download and install it for your choice of the operating system. To successfully install MongoDB, you can either install it by using Homebrew on Mac or by downloading it from the MongoDB website.
This tutorial uses a macOS machine for development. If you’re using another operating system, you may need to use
sudo
for npm
commands in the first steps.Introduction
Nest.js is a progressive Node.js framework with a modular architecture for building efficient, reliable, and scalable server-side applications. It is fully built with TypeScript, yet it still preserves compatibility with JavaScript and takes advantage of the latest JavaScript features. It brings design patterns and mature solutions to the Node.js development world. If you are conversant with the structure of Angular applications, you are going to feel more comfortable working with Nest.js. If you are new to Nest.js, check out this article on Nest.js Brings TypeScript to Node.js and Express to get yourself familiarized with some of the key concepts of Nest.js.
React is an open-source JavaScript frontend framework for building an intuitive and interactive user interface. It is widely adopted and a top choice among developers because of its great performance and simplicity in the rapid development of Single-Page Applications. Learn how React works and equip yourself with its key concepts in this React tutorial; Building and Securing Your First App.
MongoDB is a schema-less NoSQL database that can receive and store data in JSON-like documents. It takes away the idea of thinking and visualizing a database table in rows and columns. It allows you to be more productive by building JavaScript applications in a JSON format, making it not strange to any JavaScript developer. It supports arrays and nested objects values and allows for flexible and dynamic schemas. It is often used with Mongoose, an Object Data Modeling (ODM) library, that helps to manage relationships between data and provides schema validations.
“MongoDB allows you to be more productive by building JavaScript applications in a JSON format”
Tweet This
TypeScript, as described on its official website, is a superset of JavaScript that compiles down to plain JavaScript. It was designed and developed to help improve the productivity of developers when building large and complex programs by adding extra features that ensure the successful development of awesome applications with fewer bugs. You can easily focus on implementing new features without getting too worried over breaking an existing application in production, as TypeScript can allow you to easily spot errors in the code at a very early stage.
As pointed out earlier, in this tutorial, we will combine these awesome contemporary web tools and build an application with it. At the end of the article, you would have gathered enough knowledge for you to explore the benefits of building applications by combining React and TypeScript in either a new project or to enhance your existing projects.
Why Build React Applications with TypeScript
As you may be aware, React is a component-based frontend framework and make use of props and state objects here and there. Building React applications with TypeScript allows you to have strongly-typed components that have well defined and identifiable props and state objects. This will ensure that the usages of your components are type-checked by the compiler, and you can spot errors on time.
This will, in a nutshell, make you more productive as a developer and make your code easier to read and understand.
“React is a component-based frontend framework.”
Tweet This
What You Will Build
You are going to build a blog application with which users can:
- Create and save a new post.
- View the newly saved post and all other created posts on the homepage.
- Carry out processes such as editing and deleting posts.
Also, to persist data into the database, you will make use of MongoDB. Here is a preview of what to expect at the end of this tutorial:
This application will allow users to create a blog post only if they have been authenticated and authorized. Otherwise, they will only be able to view the created posts by authenticated users. Authentication and authorization of users will be handled by Auth0.
You will start gradually and take things one step at a time, starting by building the complete backend API and allow any user to have access and make successful API calls to create, retrieve, and edit data in the database. Then you will proceed to secure the API by managing user authentication via Auth0. To test all the implementation in this part of the series, you will use Postman.
Building Backend APIs with Nest.js
As mentioned, the backend API will be built using Nest.js. Here, you will start by installing and configuring Nest.js and then proceed to flesh out the structure necessary for the API.
Installing and configuring Nest.js
You can easily install a new Nest.js project using the command-line interface built specifically for scaffolding Nest applications called Nest CLI.
You will start by using the command-line interface built specifically for scaffolding a new Nest.js application to create yours. Alternatively, you can clone the starter project for Nest.js here on GitHub. Even though both approaches will produce the same outcome, for the sake of this tutorial and as it recommended for a first time user by the Nest.js team, you will use the Nest CLI to create your project.
Install the CLI by running the following command:
npm install -g @nestjs/cli
Once the process is complete, confirm if Nest CLI has been installed by running the following command:
nest --version
You will see an output similar to the following indicating the version installed on your machine:
6.6.4
💡 Please note that this version might be different from yours.
Craft a new project for this tutorial by using the
nest
command as shown here:nest new blog-backend
Immediately after running the preceding command,
nest
will prompt you to choose a package manager you would like to use. Select npm
and hit ENTER
on your computer to start installing Nest.js:This will create a new Nest.js project in a
blog-backend
directory within your local development folder. Now, move into the newly created directory and run a command to install other required server dependencies:// change directory cd blog-backend // install dependency npm install --save @nestjs/mongoose mongoose
Because Nest.js supports integrating with MongoDB database by using Mongoose, what you have done here is to install
mongoose
and the dedicated package created by Nest.js team for the integration named @nestjs/mongoose
.Once the installation process is complete, you can then easily import the
MongooseModule
into your application. More about this later in the tutorial.Next, before you start the application, open the project using your code editor, and edit the default port as shown below:
// /blog-backend/src/main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(5000);// edit this } bootstrap();
What you have done here is to change the default port of Nest.js to
5000
to avoid port conflict with the React application, which you will build in the next part of this series. That will run on port 3000
by default. With that done, start the application using the following command:
npm run start:dev
This will run the application on port
5000
on your local machine. Navigate to http://localhost:5000 from your browser of choice, and you will see your application running.In this section, you have successfully installed the Nest CLI and leveraged it to generate the project for this tutorial. You then proceeded to run the application on the default port
5000
. Next, you will work on ensuring a successful connection between your app and its database that will be created. Configuring a database connection
In this section, you will start by setting up the configuration necessary for database connection, which includes configuring and integrating MongoDB into your application. As instructed in the prerequisites section of this tutorial, you should have MongoDB installed by now. If this is your first time installing it, you should have it running automatically at the moment and might not be necessary to start it. To check if MongoDB is currently running, open a different terminal window to keep the backend application running and run the following command :
ps aux | grep -v grep | grep mongod
If an output similar to the one below is displayed in your terminal, then MongoDB is running:
root 4190 0.0 0.1 4349676 7476 s001 S+ 9:58AM 0:00.04 sudo mongod root 4191 0.0 0.5 5560120 43160 s001 S+ 9:58AM 0:00.86 mongod
Otherwise, execute the following command to start MongoDB:
sudo mongod
Note: MongoDB usually stores data in
within the root directory of your computer’s operating system, but If your operating system is macOS Catalina or ran into an error while running the command above, the root folder is not writable. To handle this, you will have to create a different directory in another location and reference that path when running the command, as shown here:/data/db
sudo mongod --dbpath=/Users/<user>/data/db
This will start the MongoDB service and run the database in the background while waiting for connections from your application.
Next, open this file
./src/app.module.ts
and update its content, as shown here:// blog-backend/src/app.module.ts import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { MongooseModule } from '@nestjs/mongoose'; // add this @Module({ imports: [ MongooseModule.forRoot('mongodb://localhost/nest-blog-project', { useNewUrlParser: true }), ], controllers: [AppController], providers: [AppService], }) export class AppModule {}
Here, you imported the
MongooseModule
into the root AppModule
and then used the forRoot()
method to supply the connection to the database. Once the editing of the above file is completed, you have now successfully set up a database connection for your application by using the Mongoose module for MongoDB.Setting up a database schema and interface
Here, you will define the structure and datatype of the data in your application by creating a TypeScript interface, which will be used for type-checking and to define the types of data that should be passed into the application. Also, you will create a database schema, as Mongoose tends to derive everything from a Schema defined within a particular application.
To begin, go back to the terminal where the application is currently running and stop the process with
CTRL + C
, then navigate back into the ./src/
directory and create a directory named blog
within it. Now create a sub-directory named schemas
within the initially created blog
directory. This new directory will house all the database schema that will be required by your application. Now, create a schema file and called it
blog.schema.ts
and save it within the schemas
folder. Open this file and add the following content within it:// blog-backend/src/blog/schemas/blog.schema.ts import * as mongoose from 'mongoose'; export const BlogSchema = new mongoose.Schema({ title: String, description: String, body: String, author: String, date_posted: String, });
This definition specifies that all fields will store and only accept string values. With this in place, the datatype of data that will be stored in the database will be properly controlled.
Next, you will create the interface majorly for type-checking. To begin, create a new directory named
interfaces
within the /blog-backend/src/blog
folder. And within it, create a file and name it post.interface.ts
and add the following code to it:// /blog-backend/src/blog/interfaces/post.interface.ts import { Document } from 'mongoose'; export interface Post extends Document { readonly title: string; readonly description: string; readonly body: string; readonly author: string; readonly date_posted: string; }
Here you have successfully defined the types of data for a
Post
type as string values.Creating a Data Transfer Object (DTO)
A data transfer object will help define how data will be sent over the network and control how data will be posted from the application to the database. To achieve this, create a directory
dto
inside the ./src/blog
folder. Within the newly created folder, create a new file and name it create-post.dto.ts
. Paste the following code into it:// /blog-backend/src/blog/dto/create-post.dto.ts export class CreatePostDTO { readonly title: string; readonly description: string; readonly body: string; readonly author: string; readonly date_posted: string; }
From the preceding code snippet, you have marked each of the individual properties in the
CreatePostDTO
class to have a data type of string
and as read-only
to avoid unnecessary mutation.Creating the blog module
Module in Nest.js is a class annotated with a
@Module()
decorator. It helps to keep the application structure organized. It is recommended by Nest.js that each application should have at least one module, mostly the root module. In view of that, you will use the nest
command to generate a module for your application. To do that, ensure that you are still within the blog-backend
directory and execute the following command:nest generate module blog
The command above will generate a new module named
blog.module.ts
for the application and update the root module for it by automatically importing the newly created BlogModule
. With this in place, Nest.js will be aware of another module within the application besides the root module. The generated blog module file will look like this:// /blog-backend/src/blog/blog.module.ts import { Module } from '@nestjs/common'; @Module({}) export class BlogModule {}
You will update this
BlogModule
with the required contents later in the tutorial.Creating Nest.js services and controllers
Here you will generate a service, also known as a provider, and afterward, you will create a controller to handle all HTTP requests from the application. Services in Nest.js are meant only to handle any complex business logic for a specific purpose and return the appropriate response to the controller.
Create service
Run the following command while you are still within the project directory to generate a new service file:
nest generate service blog
The thing to note here is that the
nest
command above will create a blog.service.spec.ts
file, which you can use for testing. It has also created a new blog.service.ts
file, which will hold all the logic for this application and then communicate with the MongoDB database by adding and retrieving data from it. Lastly, it has also automatically imported the newly created service and added it to the blog.module.ts
.Open the newly created
blog.service.ts
and replace its default contents with the following code with methods for creating a post, retrieving all created posts, and fetching the details of a single post from the database:// /blog-backend/src/blog/blog.service.ts import { Injectable } from '@nestjs/common'; import { Model } from 'mongoose'; import { InjectModel } from '@nestjs/mongoose'; import { Post } from './interfaces/post.interface'; import { CreatePostDTO } from './dto/create-post.dto'; @Injectable() export class BlogService { constructor(@InjectModel('Post') private readonly postModel: Model<Post>) { } async addPost(createPostDTO: CreatePostDTO): Promise<Post> { const newPost = await this.postModel(createPostDTO); return newPost.save(); } async getPost(postID): Promise<Post> { const post = await this.postModel .findById(postID) .exec(); return post; } async getPosts(): Promise<Post[]> { const posts = await this.postModel.find().exec(); return posts; } }
In this file, you first imported the required module from
@nestjs/common
, mongoose
, and @nestjs/mongoose
. You also imported an interface named Post
and a data transfer object CreatePostDTO
. In the constructor
, you added @InjectModel('Post')
, which will inject the Post
model into this BlogService
class. With that, you will now be able to use this injected model to retrieve all posts, fetch a single post, and carry out other database-related activities.Next, you created
addPost()
, getPost()
and getPosts()
methods to add a new post, retrieve a single post and fetch all posts from the database respectively.Lastly, within this file, to enable editing and deleting any created post, you need to add the following immediately after the
getPosts()
method, as shown here:// /blog-backend/src/blog/blog.service.ts ... @Injectable() export class BlogService { ... async editPost(postID, createPostDTO: CreatePostDTO): Promise<Post> { const editedPost = await this.postModel .findByIdAndUpdate(postID, createPostDTO, { new: true }); return editedPost; } async deletePost(postID): Promise<any> { const deletedPost = await this.postModel .findByIdAndRemove(postID); return deletedPost; } }
All the methods created above will help facilitate proper interaction with the MongoDB database from the backend API. You can now proceed to create the required routes that will handle HTTP calls from a front-end client.
Create a new controller
Controllers in Nest.js are meant to receive incoming HTTP requests from an application frontend and return an appropriate response. This will ensure that the controller is not bloated as most of the business logic has been abstracted to a service.
You have created the service earlier, so here, you will leverage the
nest
command to generate a new controller file by running the following command, while you are still within the blog-backend
project directory:nest generate controller blog
The preceding command created two new files within the
src/blog
directory, blog.controller.spec.ts
, and blog.controller.ts
. You can ignore the former file for now, as you won’t be writing any tests in this tutorial. The latter file is the controller itself and it is a TypeScript file decorated with @Controller
metadata, as it is obtainable for every controller created in Nest.js. Now, open the blog.controller.ts
file with your text editor and update its content with the following:// /blog-backend/src/blog/blog.controller.ts import { Controller, Get, Res, HttpStatus, Param, NotFoundException, Post, Body, Put, Query, Delete } from '@nestjs/common'; import { BlogService } from './blog.service'; import { CreatePostDTO } from './dto/create-post.dto'; import { ValidateObjectId } from './shared/pipes/validate-object-id.pipes'; @Controller('blog') export class BlogController { constructor(private blogService: BlogService) { } // Submit a post @Post('/post') async addPost(@Res() res, @Body() createPostDTO: CreatePostDTO) { const newPost = await this.blogService.addPost(createPostDTO); return res.status(HttpStatus.OK).json({ message: 'Post has been submitted successfully!', post: newPost, }); } // Fetch a particular post using ID @Get('post/:postID') async getPost(@Res() res, @Param('postID', new ValidateObjectId()) postID) { const post = await this.blogService.getPost(postID); if (!post) { throw new NotFoundException('Post does not exist!'); } return res.status(HttpStatus.OK).json(post); } // Fetch all posts @Get('posts') async getPosts(@Res() res) { const posts = await this.blogService.getPosts(); return res.status(HttpStatus.OK).json(posts); } }
From the code snippet above, you imported all the necessary modules to handle HTTP requests from
@nestjs/common
module. You then proceeded to import three new modules, which are: BlogService
, CreatePostDTO
, and ValidateObjectId
. You will create the ValidateObjectId
module in the next section.To have access to all the functions declared within the
BlogService
earlier, you injected it into the controller via a constructor. This is a pattern regarded as dependency injection used in Nest.js to increase efficiency and enhance the modularity of the application. Finally, you created the following asynchronous methods:
: This method will carry out the functionality of receiving an HTTP GET request from the client to fetch all posts from the database and then return the appropriate response. It is decorated with agetPosts()
.@Get('posts')
: This takes agetPost()
as a parameter and fetches a single post from the database. In addition to thepostID
parameter passed to this method, you realized the addition of an extra method namedpostID
. This method implements theValidateObjectId()
interface from Nest.js. Its purpose is to validate and ensure that thePipeTransform
parameter can be found in the database. You will define this method in the next section.postID
: This method will handle a POST HTTP request to add a new post to the database.addPost()
For editing and deleting a particular post, add two more methods to the
blog.controller.ts
file. To accomplish that, paste the editPost()
and deletePost()
methods directly after the addPost()
method that you previously added in the same file:// /blog-backend/src/blog/blog.controller.ts ... @Controller('blog') export class BlogController { ... // Edit a particular post using ID @Put('/edit') async editPost( @Res() res, @Query('postID', new ValidateObjectId()) postID, @Body() createPostDTO: CreatePostDTO, ) { const editedPost = await this.blogService.editPost(postID, createPostDTO); if (!editedPost) { throw new NotFoundException('Post does not exist!'); } return res.status(HttpStatus.OK).json({ message: 'Post has been successfully updated', post: editedPost, }); } // Delete a post using ID @Delete('/delete') async deletePost(@Res() res, @Query('postID', new ValidateObjectId()) postID) { const deletedPost = await this.blogService.deletePost(postID); if (!deletedPost) { throw new NotFoundException('Post does not exist!'); } return res.status(HttpStatus.OK).json({ message: 'Post has been deleted!', post: deletedPost, }); } }
Here you have added:
: This method accepts a query parameter ofeditPost()
and will carry out the functionality of updating a single post. It also made use of thepostID
method to provide proper validation for the post that you need to edit.ValidateObjectId
: This method will accept a query parameter ofdeletePost()
and will delete a particular post from the database.postID
Similarly to the
BlogController
, each of the asynchronous methods you have defined here has a metadata decorator and takes in a prefix that Nest.js uses as a routing mechanism. It controls which controller receives which requests and points to the methods that should process the request and return a response, respectively.For example, the
BlogController
that you have created in this section has a prefix of blog
and a method named getPosts()
that takes in a prefix of posts
. This means that any GET request sent to an endpoint of blog/posts
(http:localhost:3000/blog/posts
) will be handled by the getPosts()
method. This example is similar to how other methods will handle HTTP requests.Validation for Mongoose with Pipes
Navigate to the
blog
directory and create a new folder named shared
. Now create another folder within the newly created folder and name it pipes
. Then finally, create a new file within the newly created folder and name it validate-object-id.pipes.ts
. Open this file and add the following content to define the accepted postID
data:// /blog-backend/src/blog/shared/pipes/validate-object-id.pipes.ts import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common'; import * as mongoose from 'mongoose'; @Injectable() export class ValidateObjectId implements PipeTransform<string> { async transform(value: string, metadata: ArgumentMetadata) { const isValid = mongoose.Types.ObjectId.isValid(value); if (!isValid) { throw new BadRequestException('Invalid ID!'); } return value; } }
The
ValidateObjectId()
class implements the PipeTransform
method from the @nestjs/common
module. It has a single method named transform()
that takes in value as a parameter — postID
in this case. With the method above, any HTTP request from the frontend of this application with a postID
that can’t be found in the database will be regarded as invalid.After creating both the service and controller, you need to set up the
Post
model that is based on the BlogSchema
. This configuration could be set up within the root ApplicationModule
, but in this instance building, the model in BlogModule
will maintain your application’s organization. Open the ./src/blog/blog.module.ts
and update it as shown here:// /blog-backend/src/blog/blog.module.ts import { Module } from '@nestjs/common'; import { BlogService } from './blog.service'; import { BlogController } from './blog.controller'; import { MongooseModule } from '@nestjs/mongoose'; // add this import { BlogSchema } from './schemas/blog.schema'; // and this @Module({ imports: [ MongooseModule.forFeature([{ name: 'Post', schema: BlogSchema }]), ], // add providers: [BlogService], controllers: [BlogController] }) export class BlogModule {}
This module uses the
MongooseModule.forFeature()
method to define which models should be registered in the module. Without this, injecting the PostModel
within the BlogService
using @injectModel()
decorator wouldn’t work.Test without authentication
Go back to the terminal and test the application by running the following command from the terminal while you are still within the project’s directory:
npm run start:dev
The preceding command will run the application in watch mode to track any new changes while the application is still running.
Note: Ensure that the MongoDB instance is still running from the other terminal as mentioned earlier. Otherwise, open another terminal and run
to start the MongoDB process in the background. You can use Postman to test the API. Postman is a testing tool to confirm and check the behavior of your web service before deploying to production.sudo mongod
Create a post using the application
As shown here, a POST HTTP call was made to http://localhost:5000/blog/post endpoint with the details of a blog post. After a successful process, the created post was returned as a response with a message indicating that it was created successfully.
View post
From the screenshots above, it is obvious that all the implemented logic for your backend API is working properly, but this raises another concern. Any random user can easily make an API call to retrieve or create a new post without authentication and succeed. This is not acceptable as the applications need to be smart enough to manage the identity of users.
To make the API protected, first, you will need an Auth0 account, create a new account if you don’t have one already.
Try out the most powerful authentication platform for free.
Get started →Securing Nest.js with Auth0 and managing the identity of users
After creating an account, log in to it and head over to the API section of your Auth0 management dashboard and select APIs from the side menu. This will show you your list of APIs if you have created any for your account, but for this tutorial, go ahead and click on the CREATE API button and set up a new one. Next, provide a friendly name as you deem fit for your API. Set the identifier as
http://localhost:5000/api
. This will be used as the audience later when configuring the access token. Once you are done, leave the signing algorithm as RS256
, as it is the best option from the security point of view, and click on the CREATE button to proceed:Immediately when you created this API, Auth0 also automatically created a test application for you to use. This is a machine-to-machine application that you will only use here for testing purposes for this part of the tutorial. You will create a new single-page application that will represent the front-end React app in the next part of the tutorial.
Next, click on "Applications" from the side menu to view the list of applications for your account and select the test application that was generated. You will then click on the "Settings" tab.
Now locate the Allowed Callback URLs fields and add
http://localhost:3000
as the value. This will be useful in the next part of this series. You can now save the changes to proceed. Don’t forget to copy the Domain
, Client ID
, and Client Secret
as you will need it soon to configure your API.Defining a Nest.js authentication middleware
To protect your routes and ensure that every user gets authenticated before accessing some of the resources provided by your API, you will make use of Auth0.
How this will work is that once a user is authenticated by Auth0, an access token will be generated for such user to make API calls subsequently. To verify the generated JWT token issued by Auth0, you will make use of Express middleware provided by
express-jwt
.Stop the application if running and install the following libraries:
npm install express-jwt jwks-rsa dotenv
Included in the libraries above is a
dotenv
module to load variables from a .env
file.To easily access and reference credentials such as the
Domain
for your application on Auth0, create a .env
within the root of your application and paste the following content:AUTH0_DOMAIN=YOUR_AUTH0_DOMAIN AUTH0_AUDIENCE=http://localhost:5000/api
Replace the
YOUR_AUTH0_DOMAIN
with the appropriate credential as obtained from your Auth0 dashboard.To configure the authentication middleware that will ensure that every call to your protected endpoint is identified by an access token, create a folder named
common
within the src
directory. Then create a new file named authentication.middleware.ts
in it. Paste the following in the newly created file:// blog-backend/src/common/authentication.middleware.ts import { Injectable, NestMiddleware } from '@nestjs/common'; import * as jwt from 'express-jwt'; import { expressJwtSecret } from 'jwks-rsa'; import { Request, Response } from 'express'; import * as dotenv from 'dotenv'; dotenv.config(); @Injectable() export class AuthenticationMiddleware implements NestMiddleware { use(req: Request, res: Response, next: Function) { jwt({ secret: expressJwtSecret({ cache: true, rateLimit: true, jwksRequestsPerMinute: 5, jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`, }), issuer: `https://${process.env.AUTH0_DOMAIN}/`, algorithms: ['RS256'], })(req, res, (err) => { if (err) { const status = err.status || 500; const message = err.message || 'Sorry we were unable to process your request.'; return res.status(status).send({ message, }); } next(); }); } }
This code will check if the Access Token included in a request is valid. If the token is not valid, the user will get a message indicating that the authorization token is not valid.
After this, navigate back to the
blog.module.ts
file and update the file as shown here:import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common'; import { BlogService } from './blog.service'; import { BlogController } from './blog.controller'; import { MongooseModule } from '@nestjs/mongoose'; import { BlogSchema } from './schemas/blog.schema'; import { AuthenticationMiddleware } from 'src/common/authentication.middleware'; @Module({ imports: [ MongooseModule.forFeature([{ name: 'Post', schema: BlogSchema }]), ], // add this providers: [BlogService], controllers: [BlogController] }) export class BlogModule implements NestModule { configure(consumer: MiddlewareConsumer): MiddlewareConsumer | void { consumer.apply(AuthenticationMiddleware).forRoutes( { method: RequestMethod.POST, path: '/blog/post' }, { method: RequestMethod.PUT, path: '/blog/edit' }, { method: RequestMethod.DELETE, path: '/blog/delete' } ) } }
With this implementation, any subsequent requests without an Access Token to the following routes will not be allowed by the application:
GET /blog/post
PUT /blog/edit
DELETE /blog/delete
To test this out, start the application and the MongoDB instance again using
npm run start:dev
and sudo mongod
respectively. Then try to send a POST
request to http://localhost:5000/blog/post
from Postman. You will see the response as depicted by the image below:In the next part of this series, you will have the opportunity to generate an access token on the fly once authenticated by Auth0. But as a proof of concept, for now, you can obtain a test access token for your API from the Auth0 account management dashboard. Access the API section in your Auth0 dashboard and click on the API that was created earlier.
Now click on the
Test
tab and scroll down to copy the Access Token shown under the Response subtitle.Next, open up Postman and from the Authorization tab, select
Bearer Token
and paste your Access Token: Test all endpoints using Postman
Now, you can try to access all the protected endpoints:
Conclusion
In this tutorial, you were able to successfully build an API using Nest.js and then persist data into your database using MongoDB. You went further to protect some of the endpoints with the application by ensuring and verifying the JWT (JSON Web Token) issued by Auth0.
This is the first part of this series, and from the second part, you will set up a user interface and create frontend logic using React and TypeScript.
About Auth0
Auth0 by Okta takes a modern approach to customer identity and enables organizations to provide secure access to any application, for any user. Auth0 is a highly customizable platform that is as simple as development teams want, and as flexible as they need. Safeguarding billions of login transactions each month, Auth0 delivers convenience, privacy, and security so customers can focus on innovation. For more information, visit https://auth0.com.
About the author
Olususi Oluyemi
Software Engineer / Technical Content Creator