Setup JWT using NestJS , Prisma & MySQL Database

JWT atau Json Web Token adalah slah satu metode authentikasi yang saat ini banyak digunakan untuk bermacam skema seperti Mobile, Website, Desktop karena kemudahan dan keamananya.

pada tutorial kali ini, saya akan membagikan cara mudah membuat JWT menggunakan Framework NestJS dan Prisma sebagai databasenya. kita juga menggunakan OpenAPI untuk dokumentasi API nya.

Install NestJS

Pertama kita Install NestJS terlebih dahulu menggunakan Nest CLI untuk menginstall Nest CLI bisa menggunakan NPM dengan perintah

npm i -g @nestjs/cli

setelah Nest CLI terinstall, selanjutnya kita buat project NestJS dengan perintah

nest new example_nest_jwt

example_nest_jwt adalah nama project yang kita buat, untuk penamaan bisa dengan nama apa saja atau bebas.

Nest Install

pilih npm atau yarn jika kalian biasa menggunakan yarn lalu enter dan tunggu sampai proses instalasi selesai

kemudian masuk ke dalam project

cd example_nest_jwt/

Buka project menggunakan Text Editor seperti Visual Studio Code

Siapkan Database

buat database dengan nama sama persis dengan project contohnya “example_nest_jwt”

Setup Prisma

Install prisma dengan perintah

yarn add prisma -D
yarn add @prisma/client

atau

npm install prisma -D 
npm install @prisma/client

kemudian jalankan perintah

npx prisma
npx prisma init

setelah melakukan perintah diatas maka akan terbentuk folder prisma dan file .env

edit file .env dan sesuaikan dengan Database MySQL yang sudah kita persiapkan sebelumnya

# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema

# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings

DATABASE_URL="mysql://root:[email protected]:3307/example_nest_jwt"

edit koneksi file “prisma/schema.prisma” ubah file provider menjadi “mysql”

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

kemudian buat skema table User di dalam file “prisma/shcema.prisma”

model users {
  id         Int       @id @default(autoincrement())
  name       String    @db.VarChar(45)
  email      String    @unique(map: "email_UNIQUE") @db.VarChar(45)
  password   String    @db.VarChar(200)
  created_at DateTime? @db.DateTime(0)
  updated_at DateTime? @db.DateTime(0)
}

lebih lengkapnya seperti

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model users {
  id         Int       @id @default(autoincrement())
  name       String    @db.VarChar(45)
  email      String    @unique(map: "email_UNIQUE") @db.VarChar(45)
  password   String    @db.VarChar(200)
  created_at DateTime? @db.DateTime(0)
  updated_at DateTime? @db.DateTime(0)
}

kemudian jalankan perintah

yarn prisma db push

atau

npx prisma db push

perintah di atas adalah untuk menjalankan migration dari prisma membuat table dengan nama “users”. kalian bisa cek di dalam database “example_nest_jwt” yang sudah kita buat sebelumnya

kemudian jalankan perintah

yarn prisma generate

atau

npx prisma generate

kemudian test jalankan NestJS dengan perintah

yarn start:dev

atau

npm run start:dev

jika tidak ada error berarti kita sudah berhasil menginstall NestJS dan Prisma dengan benar.

kemudian kita kan membuat module dan service Prisma

masukan perintah

yarn nest g mo prisma
yarn nest g s prisma

atau

nest g mo prisma
nest g s prisma

setelah perintah di atas, akan terbentuk module baru dengan nama prisma

kemudian edit file “src/prisma/prisma.module.ts” mewnjadi seperti berikut

import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';

@Global()
@Module({
  providers: [PrismaService],
  exports:[PrismaService]
})
export class PrismaModule {}

kemudian edit file “src/prisma/prisma.service.ts” dengan code seperti berikut

import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient
    implements OnModuleInit {

    async onModuleInit() {
        await this.$connect();
    }

    async enableShutdownHooks(app: INestApplication) {
        this.$on('beforeExit', async () => {
            await app.close();
        });
    }
}

Setup JWT

selanjutnya adalah setup JWT

Install JWT dengan perintah

yarn add @nestjs/passport passport passport-jwt
yarn add @types/passport-jwt -D
yarn add @nestjs/jwt

atau

npm install @nestjs/passport passport passport-local
npm install @types/passport-jwt -D 
npm install @nestjs/jwt

buat file “src/jwt.config.ts” yang akan menyimpan informasi JWT seperti Token dan Expired time

export const JwtConfig = {
  user_secret: "7Myh9rN0y9PCrFYMVeuZCiGDLsISWkezBMI7adli877=",
  user_expired: '1000s',
};

Buat Module Authentication

setelah menginstall JWT selanjutanya kita akan buat module authentikasi dengan struktur “src/auth”

masukan perintah

yarn nest g mo auth

atau

nest g mo auth

buat file di dalam “src/auth/dto/login.dto”

export class LoginDto {

  email: string;
  password: string;

}

buat file di dalam “src/auth/dto/register.dto”

import { Prisma } from '@prisma/client';

export class RegisterDto implements Prisma.usersCreateInput {

  name: string;

  email: string;

  password: string;
  
}

buat auth service, controller dengan perintah

yarn nest g s auth
yarn nest g co auth

atau

nest g s auth
nest g co auth

buat JWT Strategy “src/auth/jwt.strategy.ts”

import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { JwtConfig } from 'src/jwt.config';


@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy,'jwt-user') {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: JwtConfig.user_secret
    });
  }

  async validate(payload: any) {
    return {
      user_id: payload.sub,
      email: payload.email,
      expired: payload.exp
    };
  }
}

kemudian buat guard JWT “src/auth/jwt-auth.guard.ts” atau dengan perintah

yarn nest g gu auth/jwt-auth

atau

nest g gu auth/jwt-auth

edit file “src/auth/jwt-auth.guard.ts” menjadi seperti berikut

import {
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';

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

export class JwtAuthGuard extends AuthGuard('jwt-user') {
  canActivate(context: ExecutionContext) {
    // Add your custom authentication logic here
    // for example, call super.logIn(request) to establish a session.
    return super.canActivate(context);
  }

  handleRequest(err, user, info) {
    // You can throw an exception based on either "info" or "err" arguments
    if (err || !user) {
      throw err || new UnauthorizedException();
    }
    return user;
  }
}

kemudian edit file “src/auth/auth.module.ts” menjadi

import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './jwt.strategy';
import { JwtConfig } from 'src/jwt.config';

@Module({
  imports: [
    PassportModule.register({
      defaultStrategy: 'jwt',
      property: 'user',
      session: false,
    }),
    JwtModule.register({
      secret: JwtConfig.user_secret,
      signOptions: {
        expiresIn: JwtConfig.user_expired,
      },
    }),
  ],
  providers: [AuthService, JwtStrategy],
  controllers: [AuthController]
})
export class AuthModule { }

Setup Auth Service, Controller

install bycript untuk mengenkripsi password kita

yarn add bcrypt
yarn add @types/bcrypt -D

atau

npm install bcrypt
npm install @types/bcrypt -D

buat Pipe untuk merubah password menjadi hash dengan bycript

buat file “src/auth/tranform-password.pipe.ts”

import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';
import { hash } from 'bcrypt';

@Injectable()
export class TransformPasswordPipe implements PipeTransform {
  async transform(value: any, metadata: ArgumentMetadata) {
    value.password = await hash(value.password, 12);
    return value;
  }
}

edit file “src/auth/auth.service.ts” menjadi seperti berikut

import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { PrismaService } from 'src/prisma/prisma.service';
import { LoginDto } from './dto/login.dto';
import { omit } from 'lodash';
import { compare } from 'bcrypt';
import { JwtConfig } from 'src/jwt.config';

@Injectable()
export class AuthService {
    constructor(private jwtService: JwtService, private dbService: PrismaService) { }


    /**
     * Register Service
     * @param dto 
     * @returns 
     */
    async register(dto: any) {
        let user = await this.dbService.users.findFirst({
            where: {
                email: dto.email
            }
        });
        if (user) {
            throw new HttpException('User Exists', HttpStatus.BAD_REQUEST);
        }
        let createUser = await this.dbService.users.create({
            data: dto
        })
        if (createUser) {
            return {
                statusCode: 200,
                message: 'Register success',
            };
        }
        throw new HttpException('Bad request', HttpStatus.BAD_REQUEST);
    }


    /**
     * Login Service
     * @param dto 
     * @returns 
     */
    async login(dto: LoginDto) {
        let user = await this.dbService.users.findFirst({
            where: { email: dto.email }
        });

        if (!user) {
            throw new HttpException('User not found', HttpStatus.NOT_FOUND);
        }

        let checkPassword = await compare(dto.password, user.password);
        if (!checkPassword) {
            throw new HttpException('Credential Incorrect', HttpStatus.UNAUTHORIZED);
        }
        return await this.generateJwt(user.id, user.email, user, JwtConfig.user_secret, JwtConfig.user_expired);
    }

    /**
     * Generate JWT
     * @param userId 
     * @param email 
     * @param user 
     * @param secret 
     * @param expired 
     * @returns 
     */
    async generateJwt(userId: any, email: string, user: any, secret: any, expired = JwtConfig.user_expired) {
        let accessToken = await this.jwtService.sign({
            sub: userId,
            email,
            name: user.first_name + ' ' + user.last_name
        }, {
            expiresIn: expired,
            secret
        });
        return {
            statusCode: 200,
            accessToken: accessToken,
            user: omit(user, ['password','created_at','updated_at'])
        };
    }

}

install class validator

yarn add class-validator
yarn add class-transformer

atau

npm install class-validator
npm install class-transformer

kemudian edit file controller “src/auth/auth.controller.ts”

import { Body, Controller, Get, HttpCode, Post, UseGuards, UsePipes, ValidationPipe } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';
import { RegisterDto } from './dto/register.dto';
import { JwtAuthGuard } from './jwt-auth.guard';
import { TransformPasswordPipe } from './transform-password.pipe';


@Controller('auth')
export class AuthController {

    /**
     * Constructor
     * @param authService 
     */
    constructor(private authService: AuthService) {

    }

    /**
     * Register controller
     * @param dto 
     * @returns 
     */
    @UsePipes(ValidationPipe, TransformPasswordPipe)
    @HttpCode(200)
    @Post('register')
    async register(@Body() dto: RegisterDto) {
        return await this.authService.register(dto);
    }

    /**
     * Login Controller
     * @param dto 
     * @returns 
     */
    @HttpCode(200)
    @Post('login')
    async login(@Body() dto: LoginDto) {
        return await this.authService.login(dto);
    }

    /**
     * Get detail User
     */
    @UseGuards(JwtAuthGuard)
    @Get('profile')
    async profile() {
        return {
            message: "Profile"
        }
    }
}

Selamat kita sudah bisa melakukan register, login dengan Respon JWT

Setup OpenAPI

Untuk mengetes API kita akan menggunakan OpenAPI

pertama adalah install OpenAPI

yarn add @nestjs/swagger swagger-ui-express

atau

npm install --save @nestjs/swagger swagger-ui-express

kemudian edit file “src/main.ts”

import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle('Open API Test Dansmultipro')
    .setDescription('API Test Dansmultipro')
    .setVersion('1.0')
    .addTag('dansmultipro')
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('openapi', app, document);

  const cors = {
    origin: [
      'http://localhost:3000',
      'http://localhost',
      '*',
    ],
    methods: 'GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS',
    preflightContinue: false,
    optionsSuccessStatus: 204,
    credentials: true,
    allowedHeaders: ['*'],
  };

  app.enableCors(cors);

  await app.listen(3000);
}
bootstrap();

edit file “src/auth/dto/login.dto.ts”

import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty } from 'class-validator';

export class LoginDto {

  @ApiProperty()
  @IsNotEmpty()
  email: string;

  @ApiProperty()
  @IsNotEmpty()
  password: string;

}

edit file “src/auth/register.dto.ts”

import { ApiProperty } from '@nestjs/swagger';
import { Prisma } from '@prisma/client';
import { IsNotEmpty } from 'class-validator';

export class RegisterDto implements Prisma.usersCreateInput {

  @ApiProperty()
  @IsNotEmpty()
  name: string;

  @ApiProperty()
  @IsNotEmpty()
  email: string;

  @ApiProperty()
  @IsNotEmpty()
  password: string;
  
}

lalu edit file “src/auth/auth.controller.ts”

import { Body, Controller, Get, HttpCode, Post, UseGuards, UsePipes, ValidationPipe } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';
import { RegisterDto } from './dto/register.dto';
import { JwtAuthGuard } from './jwt-auth.guard';
import { TransformPasswordPipe } from './transform-password.pipe';

@ApiTags('Auth')
@Controller('auth')
export class AuthController {

    /**
     * Constructor
     * @param authService 
     */
    constructor(private authService: AuthService) {

    }

    /**
     * Register controller
     * @param dto 
     * @returns 
     */
    @UsePipes(ValidationPipe, TransformPasswordPipe)
    @HttpCode(200)
    @Post('register')
    async register(@Body() dto: RegisterDto) {
        return await this.authService.register(dto);
    }

    /**
     * Login Controller
     * @param dto 
     * @returns 
     */
    @HttpCode(200)
    @Post('login')
    async login(@Body() dto: LoginDto) {
        return await this.authService.login(dto);
    }

    /**
     * Get detail User
     */
    @UseGuards(JwtAuthGuard)
    @Get('profile')
    async profile() {
        return {
            message: "Profile"
        }
    }
}

Open API berhasil di install dan di tambahkan ke auth.controller, selanjutanya kita akan test OpenAPI dengan membuka browser http://localhost:3000/openapi

selanjutnya kita bisa mencoba register menggunakan try out yang ada di openapi

Response sukses jika berhasil

Selajutnya kita akan coba login

Selamat!! kita sudah berhasil membuat API Register,Login dengan JWT.\

Untuk menggunakan Token JWT kalian bisa menambahkan di dalam Header request

contoh:

Authtehntication: “Bearer TOKEN_JWT”

Repository:

https://github.com/agik-setiawan/example_nest_jwt