Published on

เรียนรู้ NestJS - สร้าง Boilerplate มี Prisma + PostgreSQL + Swagger

เรียนรู้ NestJS - สร้าง Boilerplate มี Prisma + PostgreSQL + Swagger
เรียนรู้ NestJS - สร้าง Boilerplate มี Prisma + PostgreSQL + Swagger

อันยองงงงง พอดีช่วงที่ผ่านมาเจมส์วุ่น ๆ แล้วก็รับงานนอกด้วย เลยไม่ได้เขียนบทความมาสักพักใหญ่ ๆ เลย

วันนี้ ฤกษ์งามยามดี (อีกแล้ว) และได้เรียนรู้เรื่อง NestJS มา พอดีงานนอกได้ใช้ NestJS + Prisma + PostgreSQL + Swagger ลองแล้วชอบมากกกกกกกก เลยอยากจะเอาความรู้ที่ได้จากการหาข้อมูล และลงมือทำมาแบ่งปันกันครับ

มาเริ่มกันเลย

Install NestJS Cli และ สร้าง NestJS Project

ขั้นแรก เปิด Terminal ขึ้นมา และ Install NestJS Cli เป็น Global ก่อนเน้อครับ

npm i -g @nestjs/cli

หลังจากติดตั้งแล้ว ลอง Check version สักเล็กน้อย (เพื่อความชัวร์ว่าใช้คำสั่ง nest ได้)

nest --version

เดี๋ยวเจมส์จะมาลองสร้าง Project ชื่อ nest-boilerplate ขึ้นมาครับ โดยใช้คำสั่ง

nest new nest-boilerplate

จากนั้นมันจะมีให้เลือก

? Which package manager would you ❤️  to use?

ซึ่งในที่นี้ของเจมส์จะเลือก npm เน้อครับ

ถ้าหาก install เรียบร้อยแล้ว จะพบว่า Terminal มีข้อความประมาณนี้

Thanks for installing Nest 🙏

ลองเข้ามาใน directory project ที่เราสร้าง ในที่นี้ของเจมส์คือ nest-boilerplate เจมส์จะใช้คำสั่ง

cd nest-boilerplate

จากนั้นใช้คำสั่งสำหรับ run nestjs แบบ dev ขึ้นมา

npm run start:dev

จากนั้นให้เข้าไปที่ http://localhost:3000 จะพบว่ามีข้อความ "Hello world!"

ตอนนี้เรามีโปรเจคที่ใช้ NestJS เรียบร้อยแล้วครับ เดี๋ยวเราเอา Swagger มาต่อใส่กันต่อเลยดีกว่าครับ

ติดตั้ง Swagger

เราจะ install @nestjs/swagger โดยใช้คำสั่ง

npm install --save @nestjs/swagger

จากนั้นให้เราแก้ไขไฟล์ main.ts

โดยเพิ่ม

const config = new DocumentBuilder()
  .setTitle('NestJS Boilerplate')
  .setDescription('NestJS Boilerplate API description')
  .setVersion('1.0.0')
  .build()

const document = SwaggerModule.createDocument(app, config)
SwaggerModule.setup('api', app, document)

ซึ่งใน main.ts จะได้ข้อมูลเป็นแบบนี้ครับ

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const config = new DocumentBuilder()
    .setTitle('NestJS Boilerplate')
    .setDescription('NestJS Boilerplate API description')
    .setVersion('1.0.0')
    .build();

  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);

  await app.listen(3000);
}

bootstrap();

จากนั้นลองเข้าไปที่ http://localhost:3000/api จะพบหน้าจอ ดังรูปด้านล่างครับ

หน้าจอแสดง Swagger
หน้าจอแสดง Swagger

ถ้าสังเกตจะพบว่า Title, Description และ Version จะแสดงตามที่เรา Config ใน main.ts

ลองสร้าง CRUD ใน NestJS

ใน NestJS มีคำสั่งที่ช่วย generate resource ให้เรา คือสร้าง API Create, Read, Update, Delete ให้เรา เดี๋ยวเราจะลองมาสร้างกันครับ เราจะใช้คำสั่ง

nest generate resource [ชื่อ module ที่เราต้องการสร้าง]

เช่น

nest generate resource gratitudes

ซึ่งจะมีให้เราเลือก

? What transport layer do you use? (Use arrow keys)
❯ REST API
  GraphQL (code first)
  GraphQL (schema first)
  Microservice (non-HTTP)
  WebSockets

ในที่นี้เราจะเลือก REST API ครับ จากนั้นจะมีให้เลือก

? Would you like to generate CRUD entry points? (Y/n)

ให้เราพิมพ์ Y หรือ enter ไปได้เลยครับ

ในตอนนี้ระบบจะ generate file ต่าง ๆ ขึ้นมาให้เรา ซึ่งเมื่อเราสั่ง

npm run start:dev

และเข้าไปที่ http://localhost:3000/api จะพบว่ามี path ต่าง ๆ เพิ่มขึ้นมาดังรูป

หน้าจอ Swagger แสดง path ต่างๆ
หน้าจอ Swagger แสดง path ต่างๆ

จะเห็นว่า Path จะเรียงต่อกันลงไป ถ้าเราอยากแบ่ง Group มันเพื่อให้รู้เรื่อง เราสามารถเพิ่ม Group ให้มันได้โดยเข้าไปแก้ไขที่ไฟล์ src/gratitudes/gratitudes.controller.ts โดยเราจะเพิ่ม

import { ApiTags } from '@nestjs/swagger';

@ApiTags('Gratitudes')

เข้าไปครับ ซึ่งโค้ดในส่วนของ src/gratitudes/gratitudes.controller.ts ที่เราปรับจะเป็นดังนี้ครับ

src/gratitudes/gratitudes.controller.ts
import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
} from '@nestjs/common';
import { GratitudesService } from './gratitudes.service';
import { CreateGratitudeDto } from './dto/create-gratitude.dto';
import { UpdateGratitudeDto } from './dto/update-gratitude.dto';
import { ApiTags } from '@nestjs/swagger';

@ApiTags('Gratitudes')
@Controller('gratitudes')
export class GratitudesController {
  constructor(private readonly gratitudesService: GratitudesService) {}

  @Post()
  create(@Body() createGratitudeDto: CreateGratitudeDto) {
    return this.gratitudesService.create(createGratitudeDto);
  }

  @Get()
  findAll() {
    return this.gratitudesService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.gratitudesService.findOne(+id);
  }

  @Patch(':id')
  update(
    @Param('id') id: string,
    @Body() updateGratitudeDto: UpdateGratitudeDto,
  ) {
    return this.gratitudesService.update(+id, updateGratitudeDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.gratitudesService.remove(+id);
  }
}

และเมื่อลองเข้า http://localhost:3000/api อีกครั้ง จะพบว่า จะมีเหมือนหัวข้อ 2 หัวข้อคือ default และ Gratitudes ดังรูปด้านล่าง

หน้าจอแสดง swagger ที่มี default และ Gratitudes
หน้าจอแสดง swagger ที่มี default และ Gratitudes

สร้าง PostgreSQL ใน docker-compose.yaml

ขั้นตอนต่อมาเราจะมาสร้าง postgreSQL กันต่อครับ โดยเดี๋ยวเราจะสร้างใน docker-compose.yaml กันครับ

ขั้นแรกให้สร้างไฟล์ docker-compose.yaml จากนั้นให้ใส่ข้อมูลลงไปดังนี้ครับ

docker-compose.yaml
version: '3.8'
services:
  postgres:
    image: postgres
    container_name: db.nest-boilerplate
    restart: always
    environment:
      - POSTGRES_USER=root
      - POSTGRES_PASSWORD=123456
    volumes:
      - ./postgres:/var/lib/postgresql/data
    ports:
      - '5432:5432'

ในที่นี้เจมส์จะกำหนด user เป็น root และ password เป็น 123456 ใส่ไปเลยเน้อครับ หากคุณผู้อ่านอยากเปลี่ยนก็สามารถเปลี่ยนได้ตามที่ต้องกรได้เลยครับ

จากนั้นให้เปิด Terminal ออกมา แล้วเข้ามาอยู่ที่ root project และลองสั่ง

docker-compose up

ติดตั้ง Prisma

ในขั้นตอนนี้เราจะติดตั้ง Prisma ต่อมา

npm install -D prisma

จากนั้นจะใช้คำสั่ง

npx prisma init

เมื่อใช้คำสั่งเรียบร้อยแล้ว จะพบว่ามีไฟล์ prisma/schema.prisma ถูกเพิ่มเข้ามา

และมีไฟล์ .env ถูกเพิ่มเข้ามาด้วยเช่นกัน เดี๋ยวเราจะปรับข้อมูลใน .env สักเล็กน้อยครับ โดยจะแก้ข้อมูลของ postgresql ให้เป็น username และ password ให้ตรงกับ postgresql ที่เราสร้าง และ database เดี๋ยวเราจะให้ใช้ชื่อ example ครับ

DATABASE_URL="postgresql://root:123456@localhost:5432/example"

จากนั้นเราจะเพิ่ม Gradtitude model

model Gradtitude {
  id        Int      @id @default(autoincrement())
  message   String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

ซึ่งจะเป็น

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

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

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

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

model Gradtitude {
  id        Int      @id @default(autoincrement())
  message   String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

จากนั้นจะใช้คำสั่งเพื่อ migrate โดยใช้คำสั่ง

npx prisma migrate dev --name "init"

จะพบว่าหลังจากใช้คำสั่ง จะมี directory ชื่อ migrations เพิ่มเข้ามา และมีไฟล์ migration.sql เพิ่มเข้ามา และถ้าลองใช้พวก DBeaver ลอง connect เข้าไปดูใน db example จะพบว่ามี Table Gratitude และ _prisma_migrations เพิ่มเข้ามา

สร้าง Prisma service

ขั้นตอนนี้เราจะมาสร้าง Prisma module และ Prisma service กันครับโดยใช้คำสั่ง

สำหรับสร้าง prisma module

nest generate module prisma

คำสั่งสำหรับสร้าง prisma service

nest generate service prisma

จากนั้นให้เราปรับไฟล์ src/prisma/prisma.service.ts โดยจะเพิ่มให้ extends PrismaClient เข้ามา จะได้โค้ดดังนี้ครับ

src/prisma/prisma.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient {}

ติดตั้ง class-validator และ class-transformer

ขั้นต่อมาเราจะมาติดตั้ง class-validator และ class-transformer เพื่อใช้จัดการกับ dto กันครับ

ติดตั้งโดยใช้คำสั่ง

npm install --save class-validator class-transformer

ลองทำ API create gradtitude

ขั้นแรกให้ปรับในส่วนของ src/gradtitude/gratitudes.module.ts โดย providers ให้ เพิ่ม PrismaService เข้ามา

src/gradtitude/gratitudes.module.ts
import { Module } from '@nestjs/common';
import { GratitudesService } from './gratitudes.service';
import { GratitudesController } from './gratitudes.controller';
import { PrismaService } from 'src/prisma/prisma.service';

@Module({
  controllers: [GratitudesController],
  providers: [GratitudesService, PrismaService],
})

export class GratitudesModule {}

จากนั้นเราจะมาแก้ไขไฟล์ src/gradtitudes/dto/create-gratitude.dto.ts โดยจะให้รับ message ที่เป็น string เข้ามา จะปรับได้เป็นแบบนี้ครับ

src/gradtitudes/dto/create-gratitude.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty } from 'class-validator';

export class CreateGratitudeDto {
  @ApiProperty({
    description: 'Describe your gradtitue',
    example:
      'Thanks for coffee in the morning that my girlfriend buy it for me',
  })
  @IsNotEmpty()
  message: string;
}

จากนั้นลองมาแก้ไขไฟล์ src/gradtitudes/gratitudes.service.ts โดยจะปรับเป็นแบบนี้ครับ

import { Injectable } from '@nestjs/common'
import { CreateGratitudeDto } from './dto/create-gratitude.dto'
import { UpdateGratitudeDto } from './dto/update-gratitude.dto'
import { PrismaService } from 'src/prisma/prisma.service'

@Injectable()
export class GratitudesService {
  constructor(private prisma: PrismaService) {}

  async create(createGratitudeDto: CreateGratitudeDto) {
    const gradtitude = await this.prisma.gradtitude.create({
      data: createGratitudeDto,
    })

    return gradtitude
  }

  findAll() {
    return `This action returns all gratitudes`
  }

  findOne(id: number) {
    return `This action returns a #${id} gratitude`
  }

  update(id: number, updateGratitudeDto: UpdateGratitudeDto) {
    return `This action updates a #${id} gratitude`
  }

  remove(id: number) {
    return `This action removes a #${id} gratitude`
  }
}

คือเราจะเรียกใช้งาน prisma ให้ create ข้อมูล gradtitude โดยเอาข้อมูลที่ส่งเข้ามาไปเพิ่ม

ในส่วนนี้เราสามารถลองเพิ่มใน swagger ได้เลยครับ ลองเข้าไปที่ http://localhost:3000/api และกดไปที่ POST /gratitudes จะพบปุ่ม Try it out ลองกด และลบ json ที่เป็น message ออกไปก่อน

เพื่อที่จะลองเช็คว่าถ้าหากไม่ได้ส่ง message เข้ามา ควรจะแสดง Error ให้

เมื่อลองกดแล้วจะพบว่า Error 500 ซึ่งไม่ถูกต้อง จริง ๆ ควรจะเป็น 400 จะพบว่าเหมือนคำสั่ง @IsNotEmpty() ที่เรากำหนดไว้ใน typescript:src/gradtitudes/dto/create-gratitude.dto.ts จะไม่ได้ทำงาน

สิ่งที่เราต้องปรับต่อมาคือไฟล์ src/main.ts โดยจะเพิ่มโค้ด

src/main.ts
app.useGlobalPipes(new ValidationPipe());

ซึ่งจะปรับได้เป็นแบบนี้ครับ

src/main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  const config = new DocumentBuilder()
    .setTitle('NestJS Boilerplate')
    .setDescription('NestJS Boilerplate API description')
    .setVersion('1.0.0')
    .build();

  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);

  await app.listen(3000);
}

bootstrap();

และเมื่อลองใช้ swagger ยิงแบบเดิมอีกรอบจะพบว่า error 400 bad request แล้ว

{
  "message": ["message should not be empty"],
  "error": "Bad Request",
  "statusCode": 400
}

และเมื่อลองเรายิงแบบมี message จะพบว่าได้ status 201 ซึ่งถูกต้องแล้ว

{
  "id": 1,
  "message": "Thanks for coffee in the morning that my girlfriend buy it for me",
  "createdAt": "2024-08-25T12:38:19.654Z",
  "updatedAt": "2024-08-25T12:38:19.654Z"
}

เรียบร้อยแล้วครับ ตอนนี้เราสร้าง Boilerplate ที่มี Prisma + PostgreSQL + Swagger เรียบร้อยแล้ว และลองยิง POST ผ่าน swagger ก็สามารถสร้างข้อมูลได้เรียบร้อยแล้ว

หากคุณผู้อ่านมีส่วนไหนสงสัย สามารถพิมพ์สอบถามไว้ได้เน้อครับ และหากบทความนี้มีส่วนไหนผิดพลาดประการใด ก็ขออภัยมา ณ ที่นี้ด้วยเน้อครับ

Happy Coding ครับ ^^

แหล่งความรู้