Nest JS — Open API Just saves so much time — Part 2
In the previous article, we discussed how OpenAPI helps us save time by generating API documentation based on our code.
Let’s quickly review a few properties we talked about in the last lecture:
The @ApiProperty decorator is used to add metadata to a property in a NestJS class that represents an API endpoint.
The @ApiResponseProperty decorator is used to add metadata to the response of an API endpoint.
NestJS uses the @ApiProperty decorator to describe the properties of the request or response DTO class, and the @ApiResponseProperty decorator to describe the response of the API endpoint.
In this article, we will take a deep dive into API error responses on Swagger and NestJS, and how we can create custom decorators.
Now, lets talk about Custom Decorators — Custom decorators are a powerful feature of NestJS that allow developers to extend or modify the behavior of built-in or third-party functionality. By creating custom decorators, developers can encapsulate complex logic and make it reusable across different parts of their application. Custom decorators also allow developers to add metadata and annotations to their code, which can be used by other modules and tools, such as Swagger, to generate documentation and provide insights into the structure and behavior of the application.
Usually, every API error caught should return the same type/schema for every request in the system. For example:
{
status_code:404,
message:'Not Found',
date:''
}
The object contains status_code , message and the date.
First, let’s get to know the @ApiResponse decorator. It is provided by the @nestjs/swagger package and allows developers to define and document the expected responses of an API endpoint. By specifying the HTTP status code and the response payload schema, developers can provide clear and concise documentation about the expected behavior of the API.
It’s now time to build our new ErrorDTO file located at src/common/dto/error.dto.ts:
// src/common/dto/error.dto.ts
import { HttpStatus } from '@nestjs/common';
import { ApiProperty } from '@nestjs/swagger';
export class ErrorDTO {
@ApiProperty({ default: 'Internal Server Error' })
message: string;
@ApiProperty({ enum: HttpStatus, default: HttpStatus.INTERNAL_SERVER_ERROR })
status_code: HttpStatus;
@ApiProperty({ default: new Date().toISOString() })
date: Date;
}
In our system, Error Scheme is a crucial object. It contains a message, status_code, and date with default values. This object is significant because it helps users quickly identify and understand errors that occur in the system. The error message is a brief description of the problem that occurred, while the status_code is a numerical code that indicates the type of error. The date indicates when the error occurred.
Now lets doc our first 2 Errors.
// src/employee/employee.controller.ts
import { ErrorDTO } from 'src/common/dto/error.dto';
import { EmployeeDTO } from './dto';
import { EmployeeService } from './employee.service';
@Controller('employee')
@ApiTags('employee')
export class EmployeeController {
constructor(private readonly employeeService: EmployeeService) {}
@Post()
@ApiOperation({
summary: 'Create new Employee',
description: 'Lorem ipsum ....',
})
@ApiResponse({
status: 201,
type: EmployeeDTO,
})
@ApiResponse({
status: 500,
type: ErrorDTO,
description: 'Internal Server Error',
})
@ApiResponse({
status: 404,
type: ErrorDTO,
description: 'Not Found',
})
createEmployee(@Body() employeeDto: EmployeeDTO) {
return { ...employeeDto, id: Date.now() };
}
}
Now our Swagger Doc should look like:
As we can see, two error responses have been added to our CreateEmployee API request. “Not found” returns a 404 error, and “Internal server error” returns a 500 error. However, there is still one thing that is not working correctly: the values of the ErrorDTO are not changing. Does this mean that we need to make a class for each individual ErrorDTO? No. We can resolve this issue by using our first custom decorator: the ApiErrorDecorator. This decorator will help us add the response metadata and keep our language consistent, while also saving time by using one common language.
Lets create a new file at src/common/decorator/error/error.decorator.ts
// src/common/decorator/error/error.decorator.ts
import { applyDecorators, HttpStatus } from '@nestjs/common';
import {
ApiResponse,
ApiResponseOptions,
getSchemaPath,
} from '@nestjs/swagger';
import { ErrorDTO } from 'src/common/dto/error.dto';
export function ApiErrorDecorator(
statusCode: HttpStatus,
message: string,
description?: string,
options?: ApiResponseOptions,
) {
return applyDecorators(
ApiResponse({
...options,
status: statusCode,
description: description,
schema: {
default: {
message: message,
status_code: statusCode,
date: new Date().toISOString(),
},
type: getSchemaPath(ErrorDTO),
},
}),
);
}
ApiErrorDecorator is a function that receives the status, message, description, and options parameters, and passes them to a new custom decorator based on the API response.
The magic happens when using the Schema with getSchemaPath to build the Schema DTO and inject the message and status_code through the custom decorator.
Now, let’s replace the ApiResponses with our new decorator on the EmployeeController!
// src/employee/employee.controller.ts
import { Body, Controller, HttpStatus, Post } from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { ApiErrorDecorator } from 'src/common/decorators/error/error.decorator';
import { EmployeeDTO } from './dto';
import { EmployeeService } from './employee.service';
@Controller('employee')
@ApiTags('employee')
export class EmployeeController {
constructor(private readonly employeeService: EmployeeService) {}
@Post()
@ApiOperation({
summary: 'Create new Employee',
description: 'Lorem ipsum ....',
})
@ApiResponse({
status: 201,
type: EmployeeDTO,
})
@ApiErrorDecorator(HttpStatus.BAD_REQUEST, 'Bad Request')
@ApiErrorDecorator(HttpStatus.INTERNAL_SERVER_ERROR, 'Internal Server')
createEmployee(@Body() employeeDto: EmployeeDTO) {
return { ...employeeDto, id: Date.now() };
}
}
Our Api Docs should look like:
And as we can see, it works. We are able to inject our message and status code directly from our controller.
Another tip that I can suggest is to create additional decorators based on the ApiErrorDecorator. For example, you could create a BadRequest Decorator to handle cases where a requested resource is bad requested.
Lets Add a new file at src/decorators/error/index.ts to add Few Errors.
// src/decorators/error/index.ts
import { HttpStatus } from '@nestjs/common';
import { ApiResponseOptions } from '@nestjs/swagger';
import { ApiErrorDecorator } from './error.decorator';
export function BadRequest(
message: string,
description?: string,
options?: ApiResponseOptions,
) {
return ApiErrorDecorator(
HttpStatus.BAD_REQUEST,
message,
description,
options,
);
}
export function InternalError(
message: string,
description?: string,
options?: ApiResponseOptions,
) {
return ApiErrorDecorator(
HttpStatus.INTERNAL_SERVER_ERROR,
message,
description,
options,
);
}
Now let’s update our controller with our new custom decorators.
// src/employee/employee.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { BadRequest, InternalError } from 'src/common/decorators/error';
import { EmployeeDTO } from './dto';
import { EmployeeService } from './employee.service';
@Controller('employee')
@ApiTags('employee')
export class EmployeeController {
constructor(private readonly employeeService: EmployeeService) {}
@Post()
@ApiOperation({
summary: 'Create new Employee',
description: 'Lorem ipsum ....',
})
@ApiResponse({
status: 201,
type: EmployeeDTO,
})
@InternalError('Internal Server Error', 'Internal Server Error Description')
@BadRequest('Bad Request Working', 'Bad Request Description')
createEmployee(@Body() employeeDto: EmployeeDTO) {
return { ...employeeDto, id: Date.now() };
}
}
And Now it looks way way better :)
For Conclusion, in case of error API documentation, custom decorators can help by providing a consistent and standardized way of defining and documenting error responses across the application. By encapsulating the logic for generating error responses and adding metadata such as status codes and messages, custom decorators can ensure that error responses are uniform and well-documented, making it easier for developers and consumers to understand and handle errors in a consistent and predictable way. Additionally, custom decorators can be designed to be flexible and extensible, allowing developers to customize error responses for specific use cases or modules, while still maintaining consistency and coherence across the application.
Thanks for reading.
Nest JS — Open API Just saves so much time — Part 1: https://medium.com/@amitgal45/nest-js-open-api-just-saves-so-much-time-part-1-551c9549f01f