Free Resources

Getting Started with Eleventy
Recommended Articles

Testing a Fastify app with the NodeJS test runner
Introduction Node.js has shipped a built-in test runner for a couple of major versions. Since its release I haven’t heard much about it so I decided to try it out on a simple Fastify API server application that I was working on. It turns out, it’s pretty good! It’s also really nice to start testing a node application without dealing with the hassle of installing some additional dependencies and managing more configurations. Since it’s got my stamp of approval, why not write a post about it? In this post, we will hit the highlights of the testing API and write some basic but real-life tests for an API server. This server will be built with Fastify, a plugin-centric API framework. They have some good documentation on testing that should make this pretty easy. We’ll also add a SQL driver for the plugin we will test. Setup Let's set up our simple API server by creating a new project, adding our dependencies, and creating some files. Ensure you’re running node v20 or greater (Test runner is a stable API as of the 20 major releases) Overview `index.js` - node entry that initializes our Fastify app and listens for incoming http requests on port 3001 `app.js` - this file exports a function that creates and returns our Fastify application instance `sql-plugin.js` - a Fastify plugin that sets up and connects to a SQL driver and makes it available on our app instance Application Code A simple first test For our first test we will just test our servers index route. If you recall from the app.js` code above, our index route returns a 501 response for “not implemented”. In this test, we're using the createApp` function to create a new instance of our Fastify app, and then using the `inject` method from the Fastify API to make a request to the `/` route. We import our test utilities directly from the node. Notice we can pass async functions to our test to use async/await. Node’s assert API has been around for a long time, this is what we are using to make our test assertions. To run this test, we can use the following command: By default the Node.js test runner uses the TAP reporter. You can configure it using other reporters or even create your own custom reporters for it to use. Testing our SQL plugin Next, let's take a look at how to test our Fastify Postgres plugin. This one is a bit more involved and gives us an opportunity to use more of the test runner features. In this example, we are using a feature called Subtests. This simply means when nested tests inside of a top-level test. In our top-level test call, we get a test parameter t` that we call methods on in our nested test structure. In this example, we use `t.beforeEach` to create a new Fastify app instance for each test, and call the `test` method to register our nested tests. Along with `beforeEach` the other methods you might expect are also available: `afterEach`, `before`, `after`. Since we don’t want to connect to our Postgres database in our tests, we are using the available Mocking API to mock out the client. This was the API that I was most excited to see included in the Node Test Runner. After the basics, you almost always need to mock some functions, methods, or libraries in your tests. After trying this feature, it works easily and as expected, I was confident that I could get pretty far testing with the new Node.js core API’s. Since my plugin only uses the end method of the Postgres driver, it’s the only method I provide a mock function for. Our second test confirms that it gets called when our Fastify server is shutting down. Additional features A lot of other features that are common in other popular testing frameworks are also available. Test styles and methods Along with our basic test` based tests we used for our Fastify plugins - `test` also includes `skip`, `todo`, and `only` methods. They are for what you would expect based on the names, skipping or only running certain tests, and work-in-progress tests. If you prefer, you also have the option of using the describe` → `it` test syntax. They both come with the same methods as `test` and I think it really comes down to a matter of personal preference. Test coverage This might be the deal breaker for some since this feature is still experimental. As popular as test coverage reporting is, I expect this API to be finalized and become stable in an upcoming version. Since this isn’t something that’s being shipped for the end user though, I say go for it. What’s the worst that could happen really? Other CLI flags —watch` - https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--watch —test-name-pattern` - https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--test-name-pattern TypeScript support You can use a loader like you would for a regular node application to execute TypeScript files. Some popular examples are tsx` and `ts-node`. In practice, I found that this currently doesn’t work well since the test runner only looks for JS file types. After digging in I found that they added support to locate your test files via a glob string but it won’t be available until the next major version release. Conclusion The built-in test runner is a lot more comprehensive than I expected it to be. I was able to easily write some real-world tests for my application. If you don’t mind some of the features like coverage reporting being experimental, you can get pretty far without installing any additional dependencies. The biggest deal breaker on many projects at this point, in my opinion, is the lack of straightforward TypeScript support. This is the test command that I ended up with in my application: I’ll be honest, I stole this from a GitHub issue thread and I don’t know exactly how it works (but it does). If TypeScript is a requirement, maybe stick with Jest or Vitest for now 🙂...
Nov 29, 2023
5 mins

Introduction to RESTful APIs with NestJS
Welcome to an introduction to RESTful APIs with NestJS. Understanding JavaScript and TypeScript will make it easier to follow the directions in this article, but you don't necessarily need to be proficient. NestJS is one of the most rapidly growing frameworks for building Node.js server-side applications. Companies such as Roche*, *Adidas*, and *Autodesk*, all trust NestJS when it comes to building efficient and scalable server-side applications. NestJS is based heavily on Angular*, and uses Angular-like modules, services, controllers, pipes, and decorators. This allows NestJS to help developers create scalable, testable, loosely coupled, and easily maintainable applications. NestJS was built with TypeScript, but it can also support pure JavaScript development. NestJS offers a level of abstraction above two very popular Node.js frameworks, either Express.js* or *Fastify*. This means that all of the great middleware that are available for Express.js and Fastify can also be used with NestJS. The best way to get familiar with NestJS is to build a basic RESTful API** with **CRUD** (Create, Read, Update, and Delete) functionality. This is exactly what we'll be doing together in this article. We'll be building a simple RESTful API for a Blog, with endpoints to handle CRUD operations on blog posts. Getting Started Code editor Using Visual Studio Code as a code editor can help speed up NestJS development because of its smart IntelliSense and great TypeScript support. In Visual Studio Code**, make sure that you have the following user setting by going to `File` / `Preferences` / `Settings`, and searching for the user setting named `typescript.preferences.importModuleSpecifier`. Make sure to set it to `relative` as seen below. `json "typescript.preferences.importModuleSpecifier": "relative" ` This will allow Visual Studio Code** to use relative paths rather than absolute paths when auto-importing. Using absolute path imports in our application can lead to problems if and when our code ends up in a different directory. Insomnia Insomnia is a useful API testing tool that we will use to test the NestJS API that we will be building. The NestJS CLI To get started with NestJS, let's install the Nest CLI**. The Nest CLI is a command-line interface tool that makes it easy to develop, and maintain NestJS applications. It allows us to run our application in development mode, and to build and bundle it for a production-ready release. `bash npm i -g @nestjs/cli ` Creating a new NestJS project With the Nest CLI now installed, we can use it to create a new project. `bash nest new rest-api ` This command will create a new project directory called rest-api`. It will create a base structure for our project, and add in the following core Nest files: - app.controller.ts**: A controller with a single route. - app.controller.spec.ts**: The controller's unit tests. - app.module.ts**: The root module of our application. - app.service.ts**: A service for the AppModule's business logic. - main.ts**: The entry file of our application. The initial project structure created by the Nest CLI encourages us to follow the common convention of keeping each module in its own directory. Testing the sample endpoint The installation of NestJS comes with a sample API endpoint that we can test by making a request to it. If we open app.controller.ts`, we can see that there is a `GET` endpoint that was created for us with the `@Get()` decorator. It returns a `'Hello World!'` string. `bash @Get() getHello(): string { return this.appService.getHello(); } ` Let's run npm run start:dev` from our project folder. This will run our NestJS app in watch mode, which provides live-reload support when application files are changed. Once NestJS is running, let's open http://localhost:3000` in our web browser. We should see a blank page with the `Hello World!` greeting. We can also use API testing tools such as Insomnia to make a GET` request to `http://localhost:3000`. We should get the same `Hello World!` greeting as our result. Let's remove this endpoint since it was only added by the Nest CLI for demo purposes. Go ahead and delete app.controller.ts`, `app.service.ts`, and `app.controller.spec.ts`. Also, delete all references to `AppController` and `AppService` in `app.module.ts`. Creating a feature module The architectural design of NestJS encourages feature modules. This feature-based design groups the functionality of a single feature in one folder, registered in one module. This design simplifies the codebase and makes code-splitting very easy. Module We create modules in NestJS by decorating a class with the @Module` decorator. Modules are needed to register controllers, services, and any other imported sub-modules. Imported sub-modules can have their own controllers, and services, registered. Let's use the Nest CLI to create the module` for our blog posts. `bash nest generate module posts ` This gives us an empty PostsModule` class in the `posts.module.ts` file. Interface We will use a TypeScript interface` to define the structure of our JSON object that will represent a blog post. An interface` is a virtual or abstract structure that only exists in TypeScript. Interfaces are used only for type-checking purposes by the TypeScript compiler. TypeScript interfaces do not produce any JavaScript code during the transpilation of TypeScript to JavaScript. Let's use the Nest CLI to create our interface`. `bash cd src/posts nest generate interface posts ` These commands allow us to create a posts.interface.ts` file in the feature-based folder for our blog posts, which is `/src/posts`. The TypeScript interface` keyword is used to define our interface. Let's make sure to prefix it with `export` to allow this interface to be used throughout our application `ts export interface PostModel { id?: number; date: Date; title: string; body: string; category: string; } ` From the command-line prompt, let's reset our current working directory back to the root folder of our project by using the following command. `bash cd ../.. ` Service Services are classes that handle business logic. The PostsService` that we will be creating will handle the business logic needed to manage our blog posts. Let's use the Nest CLI to create the service for our blog posts. `bash nest generate service posts ` This will give us an empty PostsService` class in the `posts.service.ts` file. The @Injectable()` decorator marks the `PostsService` class as a provider that we can register in the `providers` array of our `PostsModule`, and then inject into our controller class. More on this later. Controller Controllers are classes that handle incoming requests and return responses to the client. A controller can have more than one route or endpoint. Each route or endpoint can implement its own set of actions. The NestJS routing mechanism handles the routing of requests to the right controller. Let's use the Nest CLI to create the controller for our blog posts. `bash nest generate controller posts ` This will give us an empty PostsController` class in the `posts.controller.ts` file. Let's inject our PostsService` into the constructor of the `PostsController` class. `ts import { Controller } from '@nestjs/common'; import { PostsService } from './posts.service'; @Controller('posts') export class PostsController { constructor(private readonly postsService: PostsService) {} } ` NestJS uses dependency injection to set up a reference to PostsService` from within our controller. We can now use the methods provided by `PostsService` by referencing `this.postsService` from within our controller class. Registering our controller and service Let's make sure that our PostsModule` registers our `PostsController` and `PostsService`. The `nest generate service post` and `nest generate controller post` commands that we ran earlier have automatically registered the `PostsService` and `PostsController` classes in the PostsModule for us. `ts @Module({ controllers: [PostsController], providers: [PostsService], }) export class PostsModule {} ` NestJS uses the term providers` to refer to service classes, middleware, guards, and more. If the PostsService` class needs to be made available to other modules within our application, we can export it from `PostsModule` by using the `exports` array. This way, any module importing the `PostsModule` will be able to use the `PostsService`. `ts @Module({ controllers: [PostsController], providers: [PostsService], exports: [PostsService], }) export class PostsModule {} ` Importing PostsModule into AppModule We now have one cohesive and well-organized module for all the functionality related to our blog posts. However, all the functionality that will be provided by our PostsModule` is not available to our application unless the `AppModule` imports it. If we revisit the AppModule`, we can see that the `PostsModule` has been automatically added to the `imports` array by the `nest generate module post` command that we ran earlier. `ts @Module({ imports: [PostsModule], controllers: [], providers: [], }) export class AppModule {} ` Adding Service and Controller logic Our PostsService` and `PostsController` have no functionality implemented at the moment. Let's now implement our *CRUD* endpoints and their corresponding logic while adhering to RESTful standards. NestJS makes it easy to use MongoDB** (using the `@nestjs/mongoose` package), or **PostgreSQL** (using *Prisma* or *TypeORM*) to persist and manage our application's data. However, for the sake of simplicity, let's create a local array in our service to mock a database. `ts import { Injectable } from '@nestjs/common'; import { PostModel } from './posts.interface'; @Injectable() export class PostsService { private posts: Array = []; } ` A service, or provider, as NestJS refers to them, can have its scope configured. By default, a service has a singleton* scope. This means that a single instance of a service is shared across our entire application. The initialization of our service will occur only once during the startup of our application. The default singleton scope of services means that any class that injects the `PostsService` will share the same `posts` array data in memory. Get all posts Let's add a method to our PostsService` that will return all of our blog posts. `ts public findAll(): Array { return this.posts; } ` Let's add a method to our PostsController` that will make the logic of the service's `findAll()` method available to client requests. `ts @Get() public findAll(): Array { return this.postsService.findAll(); } ` The @Get` decorator is used to create a `GET /posts` endpoint. The `/posts` path of the request comes from the `@Controller('posts')` decorator that was used in order to define our controller. Get one post Let's add a method to our PostsService` that will return a specific blog post that a client may be looking for. If the `id` of the requested post is not found in our list of posts, let's return an appropriate `404 NOT FOUND` HTTP error. `ts public findOne(id: number): PostModel { const post: PostModel = this.posts.find(post => post.id === id); if (!post) { throw new NotFoundException('Post not found.'); } return post; } ` Let's add a method to our PostsController` that will make the logic of the service's `findOne()` method available to client requests. `ts @Get(':id') public findOne(@Param('id', ParseIntPipe) id: number): PostModel { return this.postsService.findOne(id); } ` The @Get` decorator is used with a parameter as `@Get(':id')` here to create a `GET /post/[id]` endpoint, where `[id]` is an identification number for a blog post. @Param`, from the `@nestjs/common` package, is a decorator that makes route parameters available to us as properties in our method. @Param` values are always of type `string`. Since we defined `id` to be of type `number` in TypeScript, we need to do a `string` to `number` conversion. NestJS provides a number of pipes that allow us to transform request parameters. Let's use the NestJS `ParseIntPipe` to convert the `id` to a `number`. Create a post Let's add a method to our PostsService` that will create a new blog post, assign the next sequential `id` to it, and return it. If the `title` is already being used by an existing blog post, we will throw a `422 UNPROCESSABLE ENTITY` HTTP error. `ts public create(post: PostModel): PostModel { // if the title is already in use by another post const titleExists: boolean = this.posts.some( (item) => item.title === post.title, ); if (titleExists) { throw new UnprocessableEntityException('Post title already exists.'); } // find the next id for a new blog post const maxId: number = Math.max(...this.posts.map((post) => post.id), 0); const id: number = maxId + 1; const blogPost: PostModel = { ...post, id, }; this.posts.push(blogPost); return blogPost; } ` Let's add a method to our PostsController` that will make the logic of the service's `create` method available to client requests. `ts @Post() public create(@Body() post: PostModel): PostModel { return this.postsService.create(post); } ` The @Post` decorator is used to create a `POST /post` endpoint. When we use the NestJS decorators for the POST`, `PUT`, and `PATCH` HTTP verbs, the *HTTP body* is used to transfer data to the API, typically in `JSON` format. We can use the @Body` decorator to parse the *HTTP body*. When this decorator is used, NestJS will run `JSON.parse()` on the *HTTP body* and provide us with a `JSON` object for our controller method. Within this decorator, we declare `post` to be of type `Post` because this is the data structure that we are expecting the client to provide for this request. Delete a post Let's add a method to our PostsService` that will delete a blog post from our in-memory list of posts using the JavaScript `splice()` function. A `404 NOT FOUND` HTTP error will be returned if the `id` of the requested post is not found in our list of posts. `ts public delete(id: number): void { const index: number = this.posts.findIndex(post => post.id === id); // -1 is returned when no findIndex() match is found if (index === -1) { throw new NotFoundException('Post not found.'); } this.posts.splice(index, 1); } ` Let's add a method to our PostsController` that will make the logic of the service's `delete` method available to client requests. `ts @Delete(':id') public delete(@Param('id', ParseIntPipe) id: number): void { this.postsService.delete(id); } ` Update a post Let's add a method to our PostsService` that will find a blog post by `id`, update it with newly submitted data, and return the updated post. A `404 NOT FOUND` HTTP error will be returned if the `id` of the requested post is not found in our list of posts. A `422 UNPROCESSABLE ENTITY` HTTP error will be returned if the `title` is being used for another blog post. `ts public update(id: number, post: PostModel): PostModel { this.logger.log(Updating post with id: ${id}`); const index: number = this.posts.findIndex((post) => post.id === id); // -1 is returned when no findIndex() match is found if (index === -1) { throw new NotFoundException('Post not found.'); } // if the title is already in use by another post const titleExists: boolean = this.posts.some( (item) => item.title === post.title && item.id !== id, ); if (titleExists) { throw new UnprocessableEntityException('Post title already exists.'); } const blogPost: PostModel = { ...post, id, }; this.posts[index] = blogPost; return blogPost; } ` Let's add a method to our PostsController` that will make the logic of the service's `update` method available to client requests. `ts @Put(':id') public update(@Param('id', ParseIntPipe) id: number, @Body() post: PostModel): PostModel { return this.postsService.update(id, post); } ` We use the @Put` decorator to make use of the HTTP `PUT` request method. `PUT` can be used to both create and update the state of a resource on the server. If we know that a resource already exists, `PUT` will replace the state of that resource on the server. Testing our feature module Let's start our development server using npm run start:dev`. Then, let's open the **Insomnia** application to test the API endpoints that we created for the `PostsModule`. Get all posts Let's make a GET` request to `http://localhost:3000/posts`. The result should be a `200 OK` success code with an empty array. Create a post Let's make a POST` request to `http://localhost:3000/posts` using the following as our JSON body. `json { "date": "2021-08-16", "title": "Intro to NestJS", "body": "This blog post is about NestJS", "category": "NestJS" } ` We should get a successful 201 Created` response code, along with a `JSON` representation of the post that was created, including the `id` field that has been automatically generated. Get a post Let's make a GET` request to `http://localhost:3000/posts/1`. The result should be a successful `200 OK` response code, along with the data for the post with an `id` of `1`. Update a post Let's make a PUT` request to `http://localhost:3000/posts/1` using the following as our JSON body. `json { "date": "2021-08-16", "title": "Intro to TypeScript", "body": "An intro to TypeScript", "category": "TypeScript" } ` The result should be a successful 200 OK` response code, along with a `JSON` representation of the post that was updated. Delete a post Let's make a DELETE` request to `http://localhost:3000/posts/1`. The result should be a successful `200 OK` response code with no JSON object returned. Logging NestJS makes it easy to add logging to our application. Rather than using console.log()` statements, we should use the `Logger` class provided by NestJS. This will provide us with nicely formatted log messages in the Terminal. Let's add logging to our API. The first step is to define the logger` in our service class. `ts import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { PostModel } from './posts.interface'; @Injectable() export class PostsService { private posts: Array = []; private readonly logger = new Logger(PostsService.name); // ... } ` With the logger now defined, we can add log statements in our service class. Here is an example of a log statement that we can add as the first line of the findAll()` method in the `PostsService` class. `ts this.logger.log('Returning all posts'); ` Such statements provide us with a convenient log message in the Terminal every time a service method is called during a client's request to our API. When a GET /posts` request is sent, we should see the following message in the Terminal. `bash [PostsService] Returning all posts. ` Swagger NestJS makes it easy to add the OpenAPI* specification to our API using the NestJS *Swagger* package. The OpenAPI specification is used to describe RESTful APIs for documentation and reference purposes. Swagger setup Let's install Swagger for NestJS. `bash npm install --save @nestjs/swagger swagger-ui-express ` Swagger configuration Let's update the main.ts` file that bootstraps our NestJS application by adding Swagger configuration to it. `ts import { NestFactory } from '@nestjs/core'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); const config = new DocumentBuilder() .setTitle('Blog API') .setDescription('Blog API') .setVersion('1.0') .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api', app, document); await app.listen(3000); } bootstrap(); ` With our NestJS app running, we can now go to http://localhost:3000/api` to view the Swagger documentation for our API. Notice that `default` is displayed above the routes for our posts. Let's change that by adding @ApiTags('posts')` right below `@Controller('posts')` in our `PostsController` class. This will replace `default` with `posts` to indicate that this set of endpoints belongs to the `posts` feature model. `ts @Controller('posts') @ApiTags('posts') ` ApiProperty Let's make the properties of our PostModel` interface visible to Swagger. We do so by annotating fields with the `ApiProperty()` decorator, or the `ApiPropertyOptional()` decorator for optional fields. To use these decorators, we must first change our interface to a class in the posts.interface.ts` file. `ts export class PostModel { @ApiPropertyOptional({ type: Number }) id?: number; @ApiProperty({ type: String, format: 'date-time' }) date: Date; @ApiProperty({ type: String }) title: string; @ApiProperty({ type: String }) body: string; @ApiProperty({ type: String }) category: string; } ` Within the ApiProperty()` decorator applied to each field, we describe the field type. The `id` field is optional since we don't know of a new blog post's `id` when creating it. A `date-time` string format is used for the `date` field. These changes allow the PostModel` structure to be documented within Swagger. With our NestJS app running, we can now go to `http://localhost:3000/api` to view the documentation of the `PostModel`. ApiResponse Let's use the @ApiResponse()` decorator for Swagger to document all the possible responses from our API's endpoints. This will be helpful for informing users of our API what responses they can expect to receive by calling a given endpoint. We will be making these changes in the `PostsController` class. For the findAll` method, let's use the `@ApiOkResponse()` decorator to document a `200 OK` success response. `ts @Get() @ApiOkResponse({ description: 'Posts retrieved successfully.'}) public findAll(): Array { return this.postsService.findAll(); } ` For the findOne` method, let's use the `@ApiOkResponse()` decorator to document a `200 OK` response when the post is found. Let's use the `@ApiNotFoundResponse()` decorator to document a `404 NOT FOUND` HTTP error when a post is not found. `ts @Get(':id') @ApiOkResponse({ description: 'Post retrieved successfully.'}) @ApiNotFoundResponse({ description: 'Post not found.' }) public findOne(@Param('id', ParseIntPipe) id: number): PostModel { return this.postsService.findOne(id); } ` For the create` method, let's use the `@ApiCreatedResponse()` decorator to document a `201 CREATED` response when a post is created. Let's use the `@ApiUnprocessableEntityResponse()` decorator to document a `422 UNPROCESSABLE ENTITY` HTTP error when a duplicate post title is found. `ts @Post() @ApiCreatedResponse({ description: 'Post created successfully.' }) @ApiUnprocessableEntityResponse({ description: 'Post title already exists.' }) public create(@Body() post: PostModel): void { return this.postsService.create(post); } ` For the delete` method, let's use the `@ApiOkResponse()` decorator to document a `200 OK` response when a post is deleted. Let's use the `@ApiNotFoundResponse()` decorator to document a `404 NOT FOUND` HTTP error when the post to be deleted is not found. `ts @Delete(':id') @ApiOkResponse({ description: 'Post deleted successfully.'}) @ApiNotFoundResponse({ description: 'Post not found.' }) public delete(@Param('id', ParseIntPipe) id: number): void { return this.postsService.delete(id); } ` For the update` method, let's use the `@ApiOkResponse()` decorator to document a `200 OK` response when a post is updated. Let's use the `@ApiNotFoundResponse()` decorator to document a `404 NOT FOUND` HTTP error when the post to be updated is not found. Let's use the `@ApiUnprocessableEntityResponse()` decorator to document a `422 UNPROCESSABLE ENTITY` HTTP error when a duplicate post title is found. `ts @Put(':id') @ApiOkResponse({ description: 'Post updated successfully.'}) @ApiNotFoundResponse({ description: 'Post not found.' }) @ApiUnprocessableEntityResponse({ description: 'Post title already exists.' }) public update(@Param('id', ParseIntPipe) id: number, @Body() post: PostModel): void { return this.postsService.update(id, post); } ` After saving these changes, all response codes and their related descriptions should now be listed for each endpoint on the Swagger web page at http://localhost:3000/api`. We could also use Swagger instead of Insomnia to test our API. By clicking on the "Try it out" button that appears under each endpoint on the Swagger web page, we can see if they are working as expected. Exception filter Exception filters give us full control over the exceptions layer of NestJS. We can use an exception filter to add custom fields to a HTTP exception response body or to print out logs of every HTTP exception that occurs to the Terminal. Let's create a new filters` folder in the `/src` folder, as well as a new file called `http-exception.filter.ts` within it. Let's add the following class in this file. `ts import { ExceptionFilter, Catch, ArgumentsHost, HttpException, Logger } from '@nestjs/common'; import { Request, Response } from 'express'; @Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter { private readonly logger = new Logger(HttpExceptionFilter.name); catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const request = ctx.getRequest(); const statusCode = exception.getStatus(); const message = exception.message || null; const body = { statusCode, message, timestamp: new Date().toISOString(), endpoint: request.url, }; this.logger.warn(${statusCode} ${message}`); response .status(statusCode) .json(body); } } ` This class makes use of the NestJS logger to print a warning message to the Terminal whenever a HTTP exception occurs. It also returns two custom fields within the response body whenever a HTTP exception occurs. The timestamp` field reports when the exception occurred, and the `endpoint` field reports which route triggered the exception. In order to apply this filter, we need to decorate the PostsController with @UseFilters(new HttpExceptionFilter())`. `ts @Controller('posts') @UseFilters(new HttpExceptionFilter()) export class PostsController { constructor(private readonly postsService: PostsService) {} } ` After saving these changes, NestJS will reload our application for us. If we use Insomnia to send a PUT /posts/1` request to our API, it should trigger a `404 NOT FOUND` HTTP error, since no blog posts exist for updating in our application when it starts. The HTTP exception response body that is returned to Insomnia should now contain the `timestamp` and `endpoint` fields. `json { "statusCode": 404, "message": "Post not found.", "timestamp": "2021-08-23T21:05:29.497Z", "endpoint": "/posts/1" } ` We should also see the following line printed out to the Terminal. `bash WARN [HttpExceptionFilter] 404 Post not found. ` Summary In this article, we saw how NestJS makes back-end API development fast, simple, and effective. The NestJS application structure has helped us build a very well-organized project. We covered a lot, so let's recap what we learned: - How to use the NestJS CLI. - How to build feature modules in NestJS. - How to use services and controllers. - How to test an API with Insomnia. - How to add logging to NestJS apps. - How to use Swagger for documenting and previewing NestJS APIs. - How to get full control of HTTP exceptions in NestJS. Happy developing with NestJS! Code Visit the following GitHub repository in order to access the code featured in this article....
Sep 15, 2021
13 mins