When fetching data from the database, we often don’t want to present it to the user in its raw form. To prevent that, we need to serialize the response in NestJS before sending it. The most popular way to achieve that in NestJS is using the class-transformer library. In this article, we explain how to set it up with the Drizzle ORM and provide practical examples.
The database we built in the previous parts of this series contains a table to store our users.
1import { serial, text, pgTable } from 'drizzle-orm/pg-core';
2
3export const users = pgTable('users', {
4 id: serial('id').primaryKey(),
5 email: text('email').unique().notNull(),
6 name: text('name').notNull(),
7 password: text('password').notNull(),
8 phoneNumber: text('phone_number'),
9});
10
11// ...
12
13export const databaseSchema = {
14 users,
15 // ...
16};If you want to learn the basics of how to use the Drizzle ORM with NestJS, take a look at
Among other things, it contains the hashed password of each user. We don’t want to share this hash with anyone for security reasons. We can also cover a part of the phone number for privacy and display only the last few digits. To ensure that, we need to implement serialization.
A basic solution for serialization
The most straightforward way to implement serialization with NestJS is to define a simple class with a constructor.
1import { InferSelectModel } from 'drizzle-orm';
2import { databaseSchema } from '../../database/database-schema';
3
4export class AuthenticationResponseDto {
5 id: number;
6 email: string;
7 name: string;
8 phoneNumber?: string;
9
10 constructor(user: InferSelectModel<typeof databaseSchema.users>) {
11 this.id = user.id;
12 this.email = user.email;
13 this.name = user.name;
14
15 // showing only the last 3 digits of the phone number
16 if (user.phoneNumber) {
17 const numberLength = user.phoneNumber.length;
18 const visiblePart = user.phoneNumber.substring(
19 numberLength - 3,
20 numberLength,
21 );
22 this.phoneNumber = `${'*'.repeat(numberLength - 3)}${visiblePart}`;
23 }
24 }
25}Please notice that we don’t include the password property above.
Thanks to using InferSelectModel<typeof databaseSchema.users>, we expect the exact data fetched from the database to be provided when the instance of the AuthenticationResponseDto class is created.
1import { Body, Controller, Post } from '@nestjs/common';
2import { AuthenticationService } from './authentication.service';
3import { SignUpDto } from './dto/sign-up.dto';
4import { AuthenticationResponseDto } from './dto/authentication-response.dto';
5
6@Controller('authentication')
7export class AuthenticationController {
8 constructor(private readonly authenticationService: AuthenticationService) {}
9
10 @Post('sign-up')
11 async signUp(@Body() signUpData: SignUpDto) {
12 const newUser = await this.authenticationService.signUp(signUpData);
13 return new AuthenticationResponseDto(newUser);
14 }
15
16 // ...
17}If you want to know more about authenticating check out API with NestJS #3. Authenticating users with bcrypt, Passport, JWT, and cookies
Thanks to creating an instance of the AuthenticationResponseDto, we serialize our response and don’t return the raw data fetched from the database.
While the above approach works, it requires us to work with our data manually in the constructor. Also, if our controller returns an array, we need to map the array to the instances of our class manually.
Using Drizzle ORM with the class-transformer
We can use the class-transformer library to avoid creating the constructor manually. It allows us to transform regular objects into instances of a particular class and transform the data in the process.
1import { InferSelectModel } from 'drizzle-orm';
2import { databaseSchema } from '../../database/database-schema';
3import { Exclude, Transform } from 'class-transformer';
4
5type User = InferSelectModel<typeof databaseSchema.users>;
6
7export class AuthenticationResponseDto implements User {
8 id: number;
9 email: string;
10 name: string;
11
12 @Transform(({ value: phoneNumber }) => {
13 if (!phoneNumber) {
14 return null;
15 }
16 const numberLength = phoneNumber.length;
17 const visiblePart = phoneNumber.substring(numberLength - 3, numberLength);
18 return `${'*'.repeat(numberLength - 3)}${visiblePart}`;
19 })
20 phoneNumber: string | null;
21
22 @Exclude()
23 password: string;
24}It’s important to note that if we don’t want to include a particular property in our response when using the class-transformer library, we still need to add it to our class. However, we have to mark it with the @Exclude() decorator.
To ensure that we haven’t omitted any properties from the database, we use the implements keyword so that the AuthenticationResponseDto follows the correct structure.
Creating the instances of our class
Since our class does not have a constructor, we need another way of creating its instances. We can use the plainToInstance function from the class-transformer library to do that.
1import { Body, Controller, Post } from '@nestjs/common';
2import { AuthenticationService } from './authentication.service';
3import { SignUpDto } from './dto/sign-up.dto';
4import { UsersService } from '../users/users.service';
5import { plainToInstance } from 'class-transformer';
6import { AuthenticationResponseDto } from './dto/authentication-response.dto';
7
8@Controller('authentication')
9export class AuthenticationController {
10 constructor(private readonly authenticationService: AuthenticationService) {}
11
12 @Post('sign-up')
13 async signUp(@Body() signUpData: SignUpDto) {
14 const newUser = await this.authenticationService.signUp(signUpData);
15 return plainToInstance(AuthenticationResponseDto, newUser);
16 }
17
18 // ...
19}When we use the plainToInstance method, the class-transformer library applies the transformations we specified using the decorators.
Instead of manually calling the plainToInstance method, we can use the @<span class="ql-token hljs-meta">TransformPlainToInstance() decorator. It calls the plainToInstance method under the hood.
1import { Body, Controller, Post } from '@nestjs/common';
2import { AuthenticationService } from './authentication.service';
3import { SignUpDto } from './dto/sign-up.dto';
4import { AuthenticationResponseDto } from './dto/authentication-response.dto';
5import { TransformPlainToInstance } from 'class-transformer';
6
7@Controller('authentication')
8export class AuthenticationController {
9 constructor(private readonly authenticationService: AuthenticationService) {}
10
11 @Post('sign-up')
12 @TransformPlainToInstance(AuthenticationResponseDto)
13 signUp(@Body() signUpData: SignUpDto) {
14 return this.authenticationService.signUp(signUpData);
15 }
16}The TransformPlainToInstance() works as expected when our controller returns arrays.
Summary
In this article, we explored how to implement serialization in a project with NestJS and the Drizzle ORM. We implemented examples that include removing a field we don’t want to expose and modifying a property for privacy reasons. First, we implemented a simple approach where we manually implemented all of the transformations. We also learned how to simplify the process using the class-transformer library. This way, we can leverage multiple different decorators to do the work for us. Thanks to the above, we can now serialize the responses in our API in a straightforward way.