A quick guide for NestJS | February 2023
You can install the NestJS CLI globally with the command: $ npm i -g @nestjs/cli
. This will make the development with Nest much easier and quicker.
Although you can find all the features with the command: $ nest
, the main ones used in this guide are:
- Create a new project:
$ nest n MyApp
- Generate a module:
$ nest g module user
- Generate a controller:
$ nest g controller user
- Generate a service:
$ nest g service user
You can add validation to the requests as follows:
-
$ yarn add class-validator class-transformer
// main.ts app.useGlobalPipes( new ValidationPipe({ whitelist: true, // This will filter out properties not included in the expected DTO }), );
-
Update DTO accordingly:
// create-user.dto.ts @IsEmail() email: string;
-
Bind the corresponding pipes:
// user.controller.ts @Param('id', ParseIntPipe) id: number
Nest provides the @nestjs/typeorm
package (Object Relational Mapper (ORM)) to integrate with any DB:
-
Using Postgres:
yarn add @nestjs/typeorm typeorm pg
-
Create the required entities using the
@Entity()
decorator fromtypeorm
-
Inject the repository in the service:
// user.service.ts constructor( @InjectRepository(User) private userRepository: Repository<User>, ) {}
-
Add the required dependencies in modules:
// user.module.ts imports: [TypeOrmModule.forFeature([User])], // app.module.ts imports: [ UserModule, TypeOrmModule.forRoot({ type: 'postgres', host: 'localhost', port: 25432, username: 'postgres', password: 'pass', database: 'my-app', entities: [User], synchronize: process.env.NODE_ENV !== 'production', }), ],
-
Update the tests (docs):
// *.spec.ts providers: [ UserService, { provide: getRepositoryToken(User), useValue: Repository<User>, }, ],
NOTE: To be able to use crossed-services you need to export it from the original module and import it in the desired one, i.e.:
// user.module.ts
exports: [UserService],
// auth.module.ts
imports: [UserModule], // UserModule just makes the UserService available, nothing else
Passport
is a straightforward library that has a rich ecosystem of strategies that implement various authentication mechanisms.
-
$ yarn add @nestjs/passport passport passport-local
-
$ yarn add -D @types/passport-local
-
Create a
strategy
:// user.module.ts @Injectable() export class LocalStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { super({ usernameField: 'email' }); } async validate(username: string, password: string): Promise<any> { const user = await this.authService.validateUser(username, password); if (!user) { throw new UnauthorizedException(); } return user; } }
NOTE:
Passport-local
strategy by default expectsusername
andpassword
in the request body. Pass an options object to specify different property names, for example:super({ usernameField: 'email' })
-
Create an
AuthService
(that includes the controller business logic), and update module and controller:// auth.module.ts imports: [UserModule, PassportModule], providers: [AuthService, LocalStrategy], // auth.controller.ts constructor(private authService: AuthService) {} @UseGuards(AuthGuard('local')) @Post('/login') async login(@Body() { email, password }: LoginDTO) { return this.authService.validateUser(email, password); }
IMPORTANT: The
AuthController
can be simplified sincePassport
handles the request, and now theuser
will be included in it.export class AuthController { @UseGuards(AuthGuard('local')) @Post('/login') async login(@Req() { user }: Request) { return user; } }
JWT functionality
can be achieved with:
-
$ yarn add @nestjs/jwt passport-jwt
-
$ yarn add -D @types/passport-jwt
-
Update module, controller and service:
// auth.module.ts imports: [ ... ConfigModule, JwtModule.registerAsync({ inject: [ConfigService], useFactory: async (configService: ConfigService) => configService.get('jwt'), }), ], // auth.service.ts constructor( private userService: UserService, private jwtService: JwtService, ) {} ... async login(user: any) { const payload = { email: user.email, sub: user.id }; return { access_token: this.jwtService.sign(payload), }; } // auth.controller.ts constructor(private authService: AuthService) {} ... async login(@Req() { user }: Request) { return this.authService.login(user); }
-
Finally you can protect the endpoints by requiring a valid JWT be present on the request by adding a
JWT Strategy
:// auth.module.ts providers: [AuthService, LocalStrategy, JwtStrategy], // jwt.strategy.ts @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor(private configService: ConfigService) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: configService.get<string>('jwt.secret'), }); } async validate(payload: any) { return { id: payload.sub, email: payload.email }; } }
NOTE: An example of this implementation can be found under
src/profile