- 1. API with NestJS #1. Controllers, routing and the module structure
- 2. API with NestJS #2. Setting up a PostgreSQL database with TypeORM
- 3. API with NestJS #3. Authenticating users with bcrypt, Passport, JWT, and cookies
- 4. API with NestJS #4. Error handling and data validation
- 5. API with NestJS #5. Serializing the response with interceptors
- 6. API with NestJS #6. Looking into dependency injection and modules
- 7. API with NestJS #7. Creating relationships with Postgres and TypeORM
- 8. API with NestJS #8. Writing unit tests
- 9. API with NestJS #9. Testing services and controllers with integration tests
- 10. API with NestJS #10. Uploading public files to Amazon S3
- 11. API with NestJS #11. Managing private files with Amazon S3
- 12. API with NestJS #12. Introduction to Elasticsearch
- 13. API with NestJS #13. Implementing refresh tokens using JWT
- 14. API with NestJS #14. Improving performance of our Postgres database with indexes
- 15. API with NestJS #15. Defining transactions with PostgreSQL and TypeORM
- 16. API with NestJS #16. Using the array data type with PostgreSQL and TypeORM
- 17. API with NestJS #17. Offset and keyset pagination with PostgreSQL and TypeORM
- 18. API with NestJS #18. Exploring the idea of microservices
- 19. API with NestJS #19. Using RabbitMQ to communicate with microservices
- 20. API with NestJS #20. Communicating with microservices using the gRPC framework
- 21. API with NestJS #21. An introduction to CQRS
- 22. API with NestJS #22. Storing JSON with PostgreSQL and TypeORM
- 23. API with NestJS #23. Implementing in-memory cache to increase the performance
- 24. API with NestJS #24. Cache with Redis. Running the app in a Node.js cluster
- 25. API with NestJS #25. Sending scheduled emails with cron and Nodemailer
- 26. API with NestJS #26. Real-time chat with WebSockets
- 27. API with NestJS #27. Introduction to GraphQL. Queries, mutations, and authentication
- 28. API with NestJS #28. Dealing in the N + 1 problem in GraphQL
- 29. API with NestJS #29. Real-time updates with GraphQL subscriptions
- 30. API with NestJS #30. Scalar types in GraphQL
- 31. API with NestJS #31. Two-factor authentication
- 32. API with NestJS #32. Introduction to Prisma with PostgreSQL
- 33. API with NestJS #33. Managing PostgreSQL relationships with Prisma
- 34. API with NestJS #34. Handling CPU-intensive tasks with queues
- 35. API with NestJS #35. Using server-side sessions instead of JSON Web Tokens
- 36. API with NestJS #36. Introduction to Stripe with React
- 37. API with NestJS #37. Using Stripe to save credit cards for future use
- 38. API with NestJS #38. Setting up recurring payments via subscriptions with Stripe
- 39. API with NestJS #39. Reacting to Stripe events with webhooks
- 40. API with NestJS #40. Confirming the email address
- 41. API with NestJS #41. Verifying phone numbers and sending SMS messages with Twilio
- 42. API with NestJS #42. Authenticating users with Google
- 43. API with NestJS #43. Introduction to MongoDB
- 44. API with NestJS #44. Implementing relationships with MongoDB
- 45. API with NestJS #45. Virtual properties with MongoDB and Mongoose
- 46. API with NestJS #46. Managing transactions with MongoDB and Mongoose
- 47. API with NestJS #47. Implementing pagination with MongoDB and Mongoose
- 48. API with NestJS #48. Definining indexes with MongoDB and Mongoose
- 49. API with NestJS #49. Updating with PUT and PATCH with MongoDB and Mongoose
- 50. API with NestJS #50. Introduction to logging with the built-in logger and TypeORM
- 51. API with NestJS #51. Health checks with Terminus and Datadog
- 52. API with NestJS #52. Generating documentation with Compodoc and JSDoc
- 53. API with NestJS #53. Implementing soft deletes with PostgreSQL and TypeORM
- 54. API with NestJS #54. Storing files inside a PostgreSQL database
- 55. API with NestJS #55. Uploading files to the server
- 56. API with NestJS #56. Authorization with roles and claims
- 57. API with NestJS #57. Composing classes with the mixin pattern
- 58. API with NestJS #58. Using ETag to implement cache and save bandwidth
- 59. API with NestJS #59. Introduction to a monorepo with Lerna and Yarn workspaces
- 60. API with NestJS #60. The OpenAPI specification and Swagger
- 61. API with NestJS #61. Dealing with circular dependencies
- 62. API with NestJS #62. Introduction to MikroORM with PostgreSQL
- 63. API with NestJS #63. Relationships with PostgreSQL and MikroORM
- 64. API with NestJS #64. Transactions with PostgreSQL and MikroORM
- 65. API with NestJS #65. Implementing soft deletes using MikroORM and filters
- 66. API with NestJS #66. Improving PostgreSQL performance with indexes using MikroORM
- 67. API with NestJS #67. Migrating to TypeORM 0.3
- 68. API with NestJS #68. Interacting with the application through REPL
- 69. API with NestJS #69. Database migrations with TypeORM
- 70. API with NestJS #70. Defining dynamic modules
- 71. API with NestJS #71. Introduction to feature flags
- 72. API with NestJS #72. Working with PostgreSQL using raw SQL queries
- 73. API with NestJS #73. One-to-one relationships with raw SQL queries
- 74. API with NestJS #74. Designing many-to-one relationships using raw SQL queries
- 75. API with NestJS #75. Many-to-many relationships using raw SQL queries
- 76. API with NestJS #76. Working with transactions using raw SQL queries
- 77. API with NestJS #77. Offset and keyset pagination with raw SQL queries
- 78. API with NestJS #78. Generating statistics using aggregate functions in raw SQL
- 79. API with NestJS #79. Implementing searching with pattern matching and raw SQL
- 80. API with NestJS #80. Updating entities with PUT and PATCH using raw SQL queries
- 81. API with NestJS #81. Soft deletes with raw SQL queries
- 82. API with NestJS #82. Introduction to indexes with raw SQL queries
- 83. API with NestJS #83. Text search with tsvector and raw SQL
- 84. API with NestJS #84. Implementing filtering using subqueries with raw SQL
- 85. API with NestJS #85. Defining constraints with raw SQL
- 86. API with NestJS #86. Logging with the built-in logger when using raw SQL
- 87. API with NestJS #87. Writing unit tests in a project with raw SQL
- 88. API with NestJS #88. Testing a project with raw SQL using integration tests
- 89. API with NestJS #89. Replacing Express with Fastify
- 90. API with NestJS #90. Using various types of SQL joins
- 91. API with NestJS #91. Dockerizing a NestJS API with Docker Compose
- 92. API with NestJS #92. Increasing the developer experience with Docker Compose
- 93. API with NestJS #93. Deploying a NestJS app with Amazon ECS and RDS
- 94. API with NestJS #94. Deploying multiple instances on AWS with a load balancer
- 95. API with NestJS #95. CI/CD with Amazon ECS and GitHub Actions
- 96. API with NestJS #96. Running unit tests with CI/CD and GitHub Actions
- 97. API with NestJS #97. Introduction to managing logs with Amazon CloudWatch
- 98. API with NestJS #98. Health checks with Terminus and Amazon ECS
- 99. API with NestJS #99. Scaling the number of application instances with Amazon ECS
- 100. API with NestJS #100. The HTTPS protocol with Route 53 and AWS Certificate Manager
- 101. API with NestJS #101. Managing sensitive data using the AWS Secrets Manager
- 102. API with NestJS #102. Writing unit tests with Prisma
- 103. API with NestJS #103. Integration tests with Prisma
- 104. API with NestJS #104. Writing transactions with Prisma
- 105. API with NestJS #105. Implementing soft deletes with Prisma and middleware
- 106. API with NestJS #106. Improving performance through indexes with Prisma
- 107. API with NestJS #107. Offset and keyset pagination with Prisma
- 108. API with NestJS #108. Date and time with Prisma and PostgreSQL
- 109. API with NestJS #109. Arrays with PostgreSQL and Prisma
- 110. API with NestJS #110. Managing JSON data with PostgreSQL and Prisma
- 111. API with NestJS #111. Constraints with PostgreSQL and Prisma
- 112. API with NestJS #112. Serializing the response with Prisma
- 113. API with NestJS #113. Logging with Prisma
- 114. API with NestJS #114. Modifying data using PUT and PATCH methods with Prisma
- 115. API with NestJS #115. Database migrations with Prisma
- 116. API with NestJS #116. REST API versioning
- 117. API with NestJS #117. CORS – Cross-Origin Resource Sharing
- 118. API with NestJS #118. Uploading and streaming videos
- 119. API with NestJS #119. Type-safe SQL queries with Kysely and PostgreSQL
- 120. API with NestJS #120. One-to-one relationships with the Kysely query builder
- 121. API with NestJS #121. Many-to-one relationships with PostgreSQL and Kysely
NestJS is a framework for building Node.js applications. It is somewhat opinionated and forces us to follow its vision of how an application should look like to some extent. That might be viewed as a good thing that helps us to keep consistency across our application and forces us to follow good practices.
NestJS uses Express.js under the hood by default. If you’re familiar with my TypeScript Express series and you’ve enjoyed it, there is a great chance you will like NestJS too. Also, knowledge of the Express framework will come in handy.
An important note is that the documentation of NestJS is comprehensive, and you would benefit from looking it up. Here, we attempt to put the knowledge in order, but we also sometimes link to the official docs. We also refer to the Express framework to highlight the advantages of using NestJS. To benefit from this article more, some experience with Express might be useful, but not necessary.
If you want to look into the core of Node.js, I recommend checking out the Node.js TypeScript series. It covers topics such as streams, event loop, multiple processes and multithreading with worker threads. Also, knowing how to create an API without any frameworks such as Express and NestJS makes us apprieciate them even more.
Getting started with NestJS
The most straightforward way of getting started is to clone the official TypeScript starter repository. Nest is built with TypeScript and fully supports it. You could use JavaScript instead, but here we focus on TypeScript.
1 |
git clone git@github.com:nestjs/typescript-starter.git |
A thing worth looking into in the above repository is the tsconfig.json file. I highly recommend adding the alwaysStrict and noImplicitAny options
The above repository contains the most basic packages. We also get the fundamental types of files to get us started, so let’s review them.
All of the code from this series can be found in this repository. Hopefully, it can later serve as a NestJS boilerplate with some built-in features. It is a fork of an official typescript-starter. Feel free to give both of them a star.
Controllers
Controllers handle incoming requests and return responses to the client. The typescript-starter repository contains our first controller. Let’s create a more robust one:
posts.controller.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common'; import PostsService from './posts.service'; import CreatePostDto from './dto/createPost.dto'; import UpdatePostDto from './dto/updatePost.dto'; @Controller('posts') export default class PostsController { constructor( private readonly postsService: PostsService ) {} @Get() getAllPosts() { return this.postsService.getAllPosts(); } @Get(':id') getPostById(@Param('id') id: string) { return this.postsService.getPostById(Number(id)); } @Post() async createPost(@Body() post: CreatePostDto) { return this.postsService.createPost(post); } @Put(':id') async replacePost(@Param('id') id: string, @Body() post: UpdatePostDto) { return this.postsService.replacePost(Number(id), post); } @Delete(':id') async deletePost(@Param('id') id: string) { this.postsService.deletePost(Number(id)); } } |
post.interface.ts
1 2 3 4 5 |
export interface Post { id: number; content: string; title: string; } |
The first thing that we can notice is that NestJS uses decorators a lot. To mark a class to be a controller, we use the @Controller() decorator. We pass an optional argument to it. It acts as a path prefix to all of the routes within the controller.
Routing
The next set of decorators connected to routing in the above controller are @Get(), @Post(), Delete(), and @Put(). They tell Nest to create a handler for a specific endpoint for HTTP requests. The above controller creates a following set of endpoints:
1 |
GET /posts |
Returns all posts
1 |
GET /posts/{id} |
Returns a post with a given id
1 |
POST /posts |
Creates a new post
1 |
PUT /posts/{id} |
Replaces a post with a given id
1 |
DELETE /posts/{id} |
Removes a post with a given id
By default, NestJS responds with a 200 OK status code with the exception of 201 Created for the POST. We can easily change that with the @HttpCode() decorator.
When we implement an API, we often need to refer to a specific element. We can do so with route parameters. They are special URL segments used to capture values specified at their position. To create a route parameter, we need to prefix its name with the : sign.
The way to extract the value of a route parameter is to use the @Param() decorator. Thanks to it, we have access to it in the arguments of our route handler.
We can use an optional argument to refer to a specific parameter, for example @Param('id'). Otherwise, we get access to the params object with all parameters.
Since route parameters are strings and our ids are number, we need to convert the params first.
We can also use pipes to transform the route params. Pipes are built-in feature in NestJS and we cover them later.
Accessing the body of a request
When we handle POST and PUT in the controller above, we also need to access the body of a request. By doing so, we can use it to populate our database.
NestJS provides a @Body() decorator that gives us easy access to the body. Just as in the TypeScript Express series, we introduce the concept of a Data Transfer Object (DTO). It defines the format of the data sent in a request. It can be either an interface or a class, but using the latter gives us more possibilities and we explore them later on.
createPost.dto.ts
1 2 3 4 |
class CreatePostDto { content: string; title: string; } |
updatePost.dto.ts
1 2 3 4 5 |
class UpdatePostDto { id: number; content: string; title: string; } |
The arguments of a handler function
Let’s look into the arguments of the handler function a bit more.
1 2 3 |
async replacePost(@Body() post: UpdatePostDto, @Param('id') id: string) { return this.postsService.replacePost(Number(id), post); } |
By using the method argument decorators, we tell Nest to inject particular arguments into our methods. NestJS is built around the concept of Dependency Injection and Inversion of Control. We elaborate on in a lot as we go through various features.
Dependency Injection is one of the techniques that implement Inversion of Control. If you want read a bit more about IoC, check out Applying SOLID principles to your TypeScript code
An important note is that reversing their order yields the same result, which might seem counter-intuitive at first.
1 2 3 |
async replacePost(@Param('id') id: string, @Body() post: UpdatePostDto) { return this.postsService.replacePost(Number(id), post); } |
Advantages of NestJS over Express
NestJS gives us a lot of things out of the box and expects us to design our API using controllers. Express.js, on the other hand, leaves us more flexibility but does not equip us with such tools to maintain the readability of our code.
We are free to implement controllers on our own with Express. We do it in the TypeScript Express series.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
import { Request, Response, Router } from 'express'; import Controller from '../../interfaces/controller.interface'; import PostsService from './posts.service'; import CreatePostDto from './dto/createPost.dto'; import UpdatePostDto from './dto/updatePost.dto'; export default class PostsController implements Controller { private path = '/posts'; public router = Router(); private postsService = new PostsService(); constructor() { this.intializeRoutes(); } intializeRoutes() { this.router.get(this.path, this.getAllPosts); this.router.get(`${this.path}/:id`, this.getPostById); this.router.post(this.path, this.createPost); this.router.put(`${this.path}/:id`, this.replacePost); } private getAllPosts = (request: Request, response: Response) => { const posts = this.postsService.getAllPosts(); response.send(posts); } private getPostById = (request: Request, response: Response) => { const id = request.params.id; const post = this.postsService.getPostById(Number(id)); response.send(post); } private createPost = (request: Request, response: Response) => { const post: CreatePostDto = request.body; const createdPost = this.postsService.createPost(post); response.send(createdPost); } private replacePost = (request: Request, response: Response) => { const id = request.params.id; const post: UpdatePostDto = request.body; const replacedPost = this.postsService.replacePost(Number(id), post); response.send(replacedPost); } private deletePost = (request: Request, response: Response) => { const id = request.params.id; this.postsService.deletePost(Number(id)); response.sendStatus(200); } } |
Above, we can see a similar controller created in pure Express. There are quite a few notable differences.
First, we need to handle the routing of the controller ourselves. We don’t have such convenient decorators that we can depend on to do it for us. The way NestJS works here resembles a bit the Spring Framework written for Java.
In the TypeScript Express series, we use an Application class that attaches the routing to the app.
1 2 3 4 5 6 7 8 9 |
class Application { // ... private initializeControllers(controllers: Controller[]) { controllers.forEach((controller) => { this.app.use('/', controller.router); }); } // ... } |
Another big advantage of NextJS is that it provides us with an elegant way of handling the Request and Response objects. Decorators such as @Body() and @Params() help to improve the readability of our code.
One of the most useful things Nest has to offer is how it handles responses. Our route handlers can return primitive types (for example, strings), promises, or even RxJS observable streams. We don’t need to handle it manually every time and use the response.send function. NestJS also makes it easy to handle errors in our application, and we explore it in the upcoming parts of this series.
When using NestJS, we can also manipulate the Request and Response objects directly. Handling responses ourselves strips us from some of the advantages of NestJS though.
There is also a difference in how we can handle dependencies in pure Express and NestJS.
In the above Express controller, we create a new PostsService directly in the PostsController. Unfortunately, it breaks the Dependency inversion principle from the SOLID principles. One of the issues that can cause is some trouble with writing tests.
NestJS, on the other hand, cares about compliance with the Dependency inversion principle a lot by implementing Dependency Injection.
Services
The typescript-starter repository also contains our first service. A job of a service is to separate the business logic from controllers, making it cleaner and more comfortable to test. Let’s create a simple service for our posts.
posts.service.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import CreatePostDto from './dto/createPost.dto'; import Post from './post.interface'; import UpdatePostDto from './dto/updatePost.dto'; @Injectable() export default class PostsService { private lastPostId = 0; private posts: Post[] = []; getAllPosts() { return this.posts; } getPostById(id: number) { const post = this.posts.find(post => post.id === id); if (post) { return post; } throw new HttpException('Post not found', HttpStatus.NOT_FOUND); } replacePost(id: number, post: UpdatePostDto) { const postIndex = this.posts.findIndex(post => post.id === id); if (postIndex > -1) { this.posts[postIndex] = post; return post; } throw new HttpException('Post not found', HttpStatus.NOT_FOUND); } createPost(post: CreatePostDto) { const newPost = { id: ++this.lastPostId, ...post } this.posts.push(newPost); return newPost; } deletePost(id: number) { const postIndex = this.posts.findIndex(post => post.id === id); if (postIndex > -1) { this.posts.splice(postIndex, 1); } else { throw new HttpException('Post not found', HttpStatus.NOT_FOUND); } } } |
While the above logic is straightforward, there are a few noteworthy lines there.
We use the built-in HttpException class to throw errors that NestJS can understand. When we throw HttpException('Post not found', HttpStatus.NOT_FOUND), it gets propagated to the global exception filter, and a proper response is sent to the client. We explore this topic more in the upcoming parts of this series.
The @Injectable() decorator tells Nest that this class is a provider. Thanks to that, we can add it to a module.
Modules
We use modules to organize our application. Our PostsController and PostsService are closely related and belong to the same application domain. Therefore, it is appropriate to put them in a module together.
By doing so, we organize our code per feature. This is especially useful as our application grows.
posts.module.ts
1 2 3 4 5 6 7 8 9 10 |
import { Module } from '@nestjs/common'; import PostsController from './posts.controller'; import PostsService from './posts.service'; @Module({ imports: [], controllers: [PostsController], providers: [PostsService], }) export class PostsModule {} |
Also, every application needs a root module. It is a starting point for Nest when building the application.
app.module.ts
1 2 3 4 5 6 7 8 9 |
import { Module } from '@nestjs/common'; import { PostsModule } from './posts/posts.module'; @Module({ imports: [PostsModule], controllers: [], providers: [], }) export class AppModule {} |
The module contains:
- imports
imported modules – NestJS uses the PostsModule thanks to importing it in our AppModule - controllers
controllers to instantiate - providers
providers to instantiate – they may be used at least across this module - exports
a subset of providers that are available in other modules
Summary
By doing all of the above, our src directory ends up like that:
1 2 3 4 5 6 7 8 9 10 11 |
├── src │ ├── app.module.ts │ ├── main.ts │ └── posts │ ├── dto │ │ ├── createPost.dto.ts │ │ └── updatePost.dto.ts │ ├── post.interface.ts │ ├── posts.controller.ts │ ├── posts.module.ts │ └── posts.service.ts |
In this article, we’ve just got started with Nest. We’ve figured out what is a Controller and how to handle elementary routing in our application. We’ve also briefly touched the topic of Services and Modules. In the upcoming parts of this series, we will spend quite some time discussing the application structure in NestJS.
All of the above knowledge is just the tip of the NestJS iceberg. Hopefully, it convinces you that it is worth looking into this framework as it provides lots of value. There is a lot to say about features that Nest delivers, such as neat error handling and dependency injection. We will also look into the PostgreSQL database and how to use it both through ORM and SQL statements.
You can expect it in this series, and more, so stay tuned!
Good stuff! Where’s the definition for post.interface.ts?
I strive to avoid pasting too much code. Although, this might be one of the important ones, good catch! I’ll add it to the article.
In this repository you can find The code from this series. Here you can see the interface you’ve mentioned.
Why aren’t you using a return type for controllers? I am trying to find if it’s better to return the whole object from the service to controller or a promise using async/await
Greetings Robert, it is not necessary to explictly name the return type of a function as Typescript will infer it automatically.
It’s not necessary, unless you want to do any operation on the resolved data, to return a resolved promise to the controller. This simply means that you can return a promise from the service to the controller, and immediately return this promise from the controller.
NestJS will handle this automatically: it will wait for the promise resolution and return its data outwards with as the response.
Of course, in a production application you will want to serialize this data with pagination and some other metadata. But as as POC, try this:
Return a promise from the controller immediately without await, and see how the data from it is returned in the response, even though you didn’t specifically awaited for it. You can even set a setTimeout with say 10 seconds within a fake Promise.resolve() object and see from postman how your request takes 10 seconds to be handled 🙂
Try it like this:
Hello, thank you a lot for this article, it allowed me to understand a lot at the initial stage. A very valuable resource!
Nice work, thanks for that.
Why do you use the injected service as read-only?
Thank you very much for this series. If im not mistaken there is a small issue in the PostService replacePost method, you update the content but override the id (the updated object will have no id).