- Published on
เรียนรู้ 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
จะได้ข้อมูลเป็นแบบนี้ครับ
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 จะพบหน้าจอ ดังรูปด้านล่างครับ
ถ้าสังเกตจะพบว่า 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 ต่าง ๆ เพิ่มขึ้นมาดังรูป
จะเห็นว่า Path จะเรียงต่อกันลงไป ถ้าเราอยากแบ่ง Group มันเพื่อให้รู้เรื่อง เราสามารถเพิ่ม Group ให้มันได้โดยเข้าไปแก้ไขที่ไฟล์ src/gratitudes/gratitudes.controller.ts
โดยเราจะเพิ่ม
import { ApiTags } from '@nestjs/swagger';
@ApiTags('Gratitudes')
เข้าไปครับ ซึ่งโค้ดในส่วนของ 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 ดังรูปด้านล่าง
สร้าง PostgreSQL ใน docker-compose.yaml
ขั้นตอนต่อมาเราจะมาสร้าง postgreSQL กันต่อครับ โดยเดี๋ยวเราจะสร้างใน 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
}
ซึ่งจะเป็น
// 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 เข้ามา จะได้โค้ดดังนี้ครับ
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 เข้ามา
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 เข้ามา จะปรับได้เป็นแบบนี้ครับ
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
โดยจะเพิ่มโค้ด
app.useGlobalPipes(new ValidationPipe());
ซึ่งจะปรับได้เป็นแบบนี้ครับ
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 ครับ ^^