Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save programmerShinobi/003a042e2032e150fb3af5e570d392b6 to your computer and use it in GitHub Desktop.
Save programmerShinobi/003a042e2032e150fb3af5e570d392b6 to your computer and use it in GitHub Desktop.
Validation and controller setup in NestJS (Series 04)

Validate and Set Up Controllers in NestJS

  1. Create a Serializer Interceptor
    Create serializer.interceptor.ts to handle response serialization.

    // src/utils/serializer.interceptor.ts
    import {
      CallHandler,
      ExecutionContext,
      Injectable,
      NestInterceptor,
    } from '@nestjs/common';
    import { Observable } from 'rxjs';
    
    @Injectable()
    export class SerializerInterceptor implements NestInterceptor {
      intercept(
        context: ExecutionContext,
        next: CallHandler,
      ): Observable<any> | Promise<Observable<unknown>> {
        return next.handle().pipe();
      }
    }
  2. Define Validation Options
    Create validation-options.util.ts for global validation settings.

    // src/utils/validation-options.util.ts
    import {
      HttpException,
      HttpStatus,
      ValidationError,
      ValidationPipeOptions,
    } from '@nestjs/common';
    
    const validationOptionsUtil: ValidationPipeOptions = {
      transform: true,
      whitelist: true,
      errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY,
      exceptionFactory: (errors: ValidationError[]) =>
        new HttpException(
          {
            status: HttpStatus.UNPROCESSABLE_ENTITY,
            errors: errors.reduce(
              (accumulator, currentValue) => ({
                ...accumulator,
                [currentValue.property]: Object.values(
                  currentValue.constraints,
                ).join(', '),
              }),
              {},
            ),
          },
          HttpStatus.UNPROCESSABLE_ENTITY,
        ),
    };
    
    export default validationOptionsUtil;
  3. Set Up Bootstrap Functionality
    Create bootstrap.util.ts to initialize the application.

    // src/utils/bootstrap.util.ts
    import {
      INestApplication,
      ValidationPipe,
      VersioningType,
    } from '@nestjs/common';
    import { ConfigService } from '@nestjs/config';
    import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
    import { SerializerInterceptor } from './serializer.interceptor';
    import validationOptionsUtil from './validation-options.util';
    
    export const documentationBuilder = (
      app: INestApplication,
      configService: ConfigService,
    ) => {
      const config = new DocumentBuilder()
        .addBearerAuth()
        .setTitle(configService.get('app.name'))
        .setDescription('The Shinobi API description')
        .setVersion('1')
        .build();
    
      const document = SwaggerModule.createDocument(app, config);
      SwaggerModule.setup('docs', app, document);
    };
    
    export const createApplication = (app: INestApplication) => {
      app.enableShutdownHooks();
      app.enableVersioning({
        type: VersioningType.URI,
      });
      app.useGlobalInterceptors(new SerializerInterceptor());
      app.useGlobalPipes(new ValidationPipe(validationOptionsUtil));
    
      return app;
    };
  4. Integrate Bootstrap in Main File
    Use the bootstrap functions in main.ts.

    // src/main.ts
    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './modules/app/app.module';
    import { ConfigService } from '@nestjs/config';
    import { Logger } from '@nestjs/common';
    import {
      createApplication,
      documentationBuilder,
    } from './utils/bootstrap.util';
    
    async function bootstrap() {
      const log = new Logger(bootstrap.name);
      const app = await NestFactory.create(AppModule);
      const configService = app.get(ConfigService);
    
      createApplication(app);
      documentationBuilder(app, configService);
    
      const PORT = configService.get('app.port') || 8000;
      await app.listen(PORT);
    
      log.log(
        `NestJS Series (${process.env.npm_package_version}) start on port ${PORT}`,
      );
    }
    bootstrap();
  5. Define Strong Password Config
    Create strong-password.config.ts for password strength settings.

    // src/modules/auth/strong-password.config.ts
    export const strongPasswordConfig = {
      minLength: 8,
      minLowercase: 1,
      minNumbers: 1,
      minSymbols: 1,
      minUppercase: 1,
    };
  6. Create DTOs for Authentication
    Define various data transfer objects (DTOs) for the login and registration processes.

    • Login DTO
    // src/modules/auth/dtos/login.dto.ts
    import { ApiProperty } from '@nestjs/swagger';
    import { Transform } from 'class-transformer';
    import { IsEmail, IsString, IsStrongPassword } from 'class-validator';
    import { strongPasswordConfig } from 'src/modules/auth/strong-password.config';
    
    export class LoginDto {
      @ApiProperty({ example: '[email protected]' })
      @IsEmail()
      @Transform(({ value }) =>
        typeof value === 'string' ? value.toLowerCase() : value,
      )
      email: string;
    
      @ApiProperty({ example: 'StrongP@ssword123' })
      @IsString()
      @IsStrongPassword(strongPasswordConfig)
      password: string;
    }
    • Register DTO
    // src/modules/auth/dtos/register.dto.ts
    import { ApiProperty } from '@nestjs/swagger';
    import { Transform } from 'class-transformer';
    import {
      IsEmail,
      IsNotEmpty,
      IsString,
      IsStrongPassword,
    } from 'class-validator';
    import { strongPasswordConfig } from 'src/modules/auth/strong-password.config';
    
    export class RegisterDto {
      @ApiProperty({ example: '[email protected]' })
      @IsEmail()
      @Transform(({ value }) =>
        typeof value === 'string' ? value.toLowerCase() : value,
      )
      email: string;
    
      @ApiProperty({ example: 'StrongP@ssword123' })
      @IsString()
      @IsStrongPassword(strongPasswordConfig)
      password: string;
    
      @ApiProperty({ example: 'Faqih' })
      @IsString()
      @IsNotEmpty()
      first_name: string;
    
      @ApiProperty({ example: 'Pratama Muhti' })
      @IsString()
      @IsNotEmpty()
      last_name: string;
    }
    • Email Verify DTO
    // src/modules/auth/dtos/email-verify.dto.ts
    import { ApiProperty } from '@nestjs/swagger';
    import { IsString } from 'class-validator';
    
    export class EmailVerifyDto {
      @ApiProperty({ example: 'vhsbdjsdfsd-dfmsdfjsd-sdfnsdk' })
      @IsString()
      verify_token: string;
    }
    • Reset Password DTO
    // src/modules/auth/dtos/reset-password.dto.ts
    import { ApiProperty } from '@nestjs/swagger';
    import { IsString, IsStrongPassword } from 'class-validator';
    import { strongPasswordConfig } from 'src/modules/auth/strong-password.config';
    
    export class ResetPasswordDto {
      @ApiProperty({ example: 'StrongP@ssword123' })
      @IsString()
      @IsStrongPassword(strongPasswordConfig)
      password: string;
    
      @ApiProperty({ example: 'vhsbdjsdfsd-dfmsdfjsd-sdfnsdk' })
      @IsString()
      reset_token: string;
    }
    • Send Verification Email DTO
    // src/modules/auth/dtos/send-verify-mail.dto.ts
    import { ApiProperty } from '@nestjs/swagger';
    import { Transform } from 'class-transformer';
    import { IsEmail } from 'class-validator';
    
    export class SendVerifyMailDto {
      @ApiProperty({ example: '[email protected]' })
      @IsEmail()
      @Transform(({ value }) =>
        typeof value === 'string' ? value.toLowerCase() : value,
      )
      email: string;
    }
  7. Create Email Service
    Define the service to handle email operations.

    // src/modules/auth/email.service.ts
    import { Injectable } from '@nestjs/common';
    
    @Injectable()
    export class EmailService {
      constructor() {}
    }
  8. Create Email Controller
    Define the controller to manage authentication-related routes.

    // src/modules/auth/email.controller.ts
    import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common';
    import { EmailService } from './email.service';
    import { RegisterDto } from './dtos/register.dto';
    import { EmailVerifyDto } from './dtos/email-verify.dto';
    import { LoginDto } from './dtos/login.dto';
    import { SendVerifyMailDto } from './dtos/send-verify-mail.dto';
    import {
      ApiAcceptedResponse,
      ApiCreatedResponse,
      ApiForbiddenResponse,
      ApiNoContentResponse,
      ApiNotFoundResponse,
      ApiOperation,
      ApiTags,
    } from '@nestjs/swagger';
    
    @ApiTags('Auth Email')
    @Controller({
      path: 'auth/email',
      version: '1',
    })
    export class EmailController {
      constructor(private emailService: EmailService) {}
    
      @Post('/register')
      @ApiOperation({ summary: 'Register by email' })
      @ApiCreatedResponse({
        description
    
     : 'Registered successfully',
      })
      @ApiForbiddenResponse({
        description: 'Forbidden',
      })
      async register(@Body() registerDto: RegisterDto) {
        return await this.emailService.register(registerDto);
      }
    
      @Post('/login')
      @ApiOperation({ summary: 'Login by email' })
      @ApiAcceptedResponse({
        description: 'Logged in successfully',
      })
      @ApiNotFoundResponse({
        description: 'Email or password not found',
      })
      async login(@Body() loginDto: LoginDto) {
        return await this.emailService.login(loginDto);
      }
    
      @Post('/verify')
      @HttpCode(HttpStatus.NO_CONTENT)
      @ApiOperation({ summary: 'Verify email' })
      @ApiNoContentResponse({
        description: 'Email verified successfully',
      })
      @ApiForbiddenResponse({
        description: 'Email already verified',
      })
      async verifyEmail(@Body() emailVerifyDto: EmailVerifyDto) {
        return await this.emailService.verifyEmail(emailVerifyDto);
      }
    
      @Post('/send-verify-email')
      @HttpCode(HttpStatus.NO_CONTENT)
      @ApiOperation({ summary: 'Send verification email' })
      @ApiNoContentResponse({
        description: 'Verification email sent',
      })
      async sendVerifyEmail(
        @Body() sendVerifyMailDto: SendVerifyMailDto,
      ) {
        return await this.emailService.sendVerifyEmail(sendVerifyMailDto);
      }
    }

Summary

This summary provides a structured approach to setting up validation and controllers in a NestJS application. Each code snippet is categorized and annotated, making it easy to integrate into your existing project. If you have any further questions or need additional modifications, feel free to ask!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment