Skip to content

Instantly share code, notes, and snippets.

@josemasri
Last active June 2, 2022 17:02
Show Gist options
  • Save josemasri/e5deab13f306fa3cee48de0804914dcd to your computer and use it in GitHub Desktop.
Save josemasri/e5deab13f306fa3cee48de0804914dcd to your computer and use it in GitHub Desktop.
Basic configuration instructions for typeORM with NestJS

Data transfer object configuration for nest js with pipe validation

Installation

npm i class-validator class-transformer

Create a dto class with validation decorators

import { IsOptional, IsIn, IsNotEmpty } from 'class-validator';
import { TaskStatus } from '../example-status.enum';

export class GetExamplesFilterDto {
  @IsOptional()
  @IsIn(Object.keys(ExampleStatus).map(t => ExampleStatus[t]))
  status: ExampleStatus;
  @IsOptional()
  @IsNotEmpty()
  search: string;
}

Controller usage

@Controller('example')
export class ExamplesController {
  @Get()
  async getExamples(
    @Query(ValidationPipe) filterDto: GetExamplesFilterDto,
  ): Promise<Task[]> {
    // ...
  }
}

Creating a custom Pipe

Usefull for custom type validation

import { PipeTransform, BadRequestException } from '@nestjs/common';
import { ExampleStatus } from '../example-status.enum';

export class ExampleStatusValidationPipe implements PipeTransform {
  transform(value: any) {
    if (!Object.values(ExampleStatus).includes(value)) {
      throw new BadRequestException(`Invalid status ${value}`);
    }
    return value;
  }
}

Morgan Configuration

Installation

npm i nest-morgan morgan @types/morgan

In app.module.ts

import { MorganModule, MorganInterceptor } from 'nest-morgan';

@Module({
  imports: [
    ...,
    MorganModule.forRoot(),
    ...
  ],
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: MorganInterceptor('combined'),
    },
  ],
})
export class AppModule {}

Auth NestJs JWT Passport

Installation

npm i @nestjs/jwt @nestjs/passport passport passport-jwt @types/passport-jwt @types/passport

Auth Dto

Usually more than one

import { MinLength, IsString, MaxLength, Matches } from 'class-validator';

export class AuthCredentailsDto {
  @IsString()
  @MinLength(4)
  @MaxLength(20)
  username: string;
  @IsString()
  @MinLength(8)
  @MaxLength(25)
  @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, {
    message: 'Password is too weak',
  })
  password: string;
}

User Entity

import {
  BaseEntity,
  Entity,
  PrimaryGeneratedColumn,
  Column,
  Unique,
} from 'typeorm';
// Bcrypt for password encryption
import * as bcrypt from 'bcrypt';

@Entity()
// Unique keys
@Unique(['username'])
export class User extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;
  @Column()
  username: string;
  @Column()
  password: string;
  @Column()
  salt: string;

  // Entity function
  async validatePassword(password: string): Promise<boolean> {
    const hash = await bcrypt.hash(password, this.salt);
    return hash === this.password;
  }
}

User Repository

import { EntityRepository, Repository } from 'typeorm';
import * as bcrypt from 'bcrypt';

import { User } from './user.entity';
import { AuthCredentailsDto } from './dto/auth-credentials.dto';
import {
  ConflictException,
  InternalServerErrorException,
} from '@nestjs/common';

@EntityRepository(User)
export class UserRepository extends Repository<User> {
  async signUp(authCredentailsDto: AuthCredentailsDto): Promise<void> {
    const { username, password } = authCredentailsDto;

    const user = new User();

    user.username = username;
    user.salt = await bcrypt.genSalt();
    user.password = await this.hashPassword(password, user.salt);
    try {
      await user.save();
    } catch (error) {
      if (error.code === '23505') {
        // Duplicate username
        throw new ConflictException('Username already exists');
      } else {
        throw new InternalServerErrorException();
      }
    }
  }

  async validateUserPassword(
    authCredentailsDto: AuthCredentailsDto,
  ): Promise<string> {
    const { username, password } = authCredentailsDto;
    const user = await this.findOne({ username });
    if (user && (await user.validatePassword(password))) {
      return user.username;
    }
    return null;
  }

  private async hashPassword(password: string, salt: string): Promise<string> {
    return await bcrypt.hash(password, salt);
  }
}

Auth module

import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';

import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserRepository } from './user.repository';
import { JwtStrategy } from './jwt.strategy';

@Module({
  imports: [
    JwtModule.register({
      secret: 'topSecret51',
      signOptions: {
        expiresIn: 3600,
      },
    }),
    PassportModule.register({
      defaultStrategy: 'jwt',
    }),
    TypeOrmModule.forFeature([UserRepository]),
  ],
  controllers: [AuthController],
  providers: [AuthService, JwtStrategy],
  exports: [JwtStrategy, PassportModule],
})
export class AuthModule {}

Jwt Strategy

import { PassportStrategy } from '@nestjs/passport';
import { Strategy, ExtractJwt } from 'passport-jwt';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtPayload } from './jwt-payload.interface';
import { InjectRepository } from '@nestjs/typeorm';
import { UserRepository } from './user.repository';
import { User } from './user.entity';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(
    @InjectRepository(UserRepository)
    private userRepository: UserRepository,
  ) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: 'topSecret51',
    });
  }

  async validate(payload: JwtPayload): Promise<User> {
    const { username } = payload;
    const user = await this.userRepository.findOne({ username });

    if (!username) {
      throw new UnauthorizedException();
    }
    return user;
  }
}

Creating token in the service

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { UserRepository } from './user.repository';
import { InjectRepository } from '@nestjs/typeorm';
import { AuthCredentailsDto } from './dto/auth-credentials.dto';
import { JwtService } from '@nestjs/jwt';
import { JwtPayload } from './jwt-payload.interface';

@Injectable()
export class AuthService {
  constructor(
    @InjectRepository(UserRepository)
    private userRepository: UserRepository,
    private jwtService: JwtService,
  ) {}

  async signUp(authCredentailsDto: AuthCredentailsDto): Promise<void> {
    await this.userRepository.signUp(authCredentailsDto);
  }

  async signIn(
    authCredentailsDto: AuthCredentailsDto,
  ): Promise<{ accessToken: string }> {
    const username = await this.userRepository.validateUserPassword(
      authCredentailsDto,
    );
    if (!username) {
      throw new UnauthorizedException('Invalid credentials');
    }
    const payload: JwtPayload = { username };
    const accessToken = await this.jwtService.sign(payload);

    return { accessToken };
  }
}

Guarding the route and extracting payload from request

import { UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Controller('auth')
export class AuthController {
  @Post('test')
  // Guarding the route
  @UseGuards(AuthGuard())
  test(@Req() req) {
    console.log(req.user);
  }
}

Creating a custom decorator to extract user from request

import { createParamDecorator } from '@nestjs/common';
import { User } from './user.entity';

export const GetUser = createParamDecorator(
  (data, req): User => {
    return req.user;
  },
);

Using the custom decorator

import { UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Controller('auth')
export class AuthController {
  @Post('test')
  // Guarding the route
  @UseGuards(AuthGuard())
  test(@GetUser() user: User) {
    console.log(user);
  }
}

TypeORM Configuration for NestJS

Installation

npm i @nestjs/typeorm typeorm db-manager(mysql, pg)

Configuration

import { TypeOrmModuleOptions } from '@nestjs/typeorm';

export const typeOrmConfig: TypeOrmModuleOptions = {
  type: 'postgres',
  host: 'localhost',
  port: 5432,
  username: 'postgres',
  password: 'password',
  database: 'dbname',
  entities: [__dirname + '/../**/*.entity.js'],
  // Just for development
  synchronize: true,
};

In app.module.ts

@Module({
  ...,
  imports: [
    ...,
    TypeOrmModule.forRoot(typeOrmConfig),
    ...
  ],
  ...
})

Entity example

import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { ExampleStatus } from './example-status.enum';

@Entity()
export class Example extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;
  @Column()
  title: string;
  @Column()
  description: string;
  @Column()
  // Can add custom types
  status: ExampleStatus;
}

Repository example

import { Repository, EntityRepository } from 'typeorm';
import { Example } from './example.entity';

@EntityRepository(Example)
export class ExampleRepository extends Repository<Example> {
  // Methods related to db operations
}

Using repository in service

import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm';

@Injectable()
export class ExampleService {
  constructor(
    @InjectRepository(ExampleRepository)
    private exampleRepository: ExampleRepository,
  ) {}
}

In the desired module

@Module({
  ...,
  imports: [
    ...,
    imports: [TypeOrmModule.forFeature([Example, ExampleRepository])],
    ...
  ],
  ...
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment