สวัสดีครับ สำหรับวันนี้เอาใจสาย Backend ที่ทำโปรเจกต์โดยใช้ NestJS แล้วอยากรู้ว่าหากเรามีโปรเจกต์ NestJS ที่ต่อกับฐานข้อมูลแล้วอยากแพ็คใส่ Docker ด้วยมันจะมีขั้นตอนการทำยังไงบ้าง เดี๋ยวเรามาดูไปพร้อมกันในบทความนี้ได้เลยครับ
พื้นฐานที่ต้องมีก่อนจะทำตามบทความนี้
1. การเขียนโปรแกรมด้วยภาษา TypeScript เพราะ NestJS ใช้ TypeScript เป็นหลัก
3. Docker ควรเข้าใจการทำงาน เช่น Dockerfile > Docker Image > Docker Container เบื้องต้นมาก่อน
3. PostgreSQL
เริ่มต้นสร้างฐานข้อมูลจาก Docker
โดยผมจะทำการสร้างฐานข้อมูล PostgreSQL ด้วย Docker Compose ไฟล์ชื่อว่า docker-compose.yaml จากโค้ดด้านล่างนี้
version: '3.8'
services:
db:
image: postgres
restart: always
environment:
POSTGRES_USER: yourusername
POSTGRES_PASSWORD: yourpassword
POSTGRES_DB: yourdatabase
ports:
- "5432:5432"
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
หลังจากนั้นทำการรันโดยการไปที่ Terminal ของ path ที่มีไฟล์โค้ดด้านบนแล้วใช้คำสั่ง
docker compose up -d
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-77-907x1024.png)
หลังจากนั้นสามารใช้คำสั่ง docker ps –filter “ancestor=postgres” เพื่อหา Container ที่กำลังรันอยู่ด้วย Image ชื่อว่า postgres
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-78-1024x114.png)
ถ้าได้ตามภาพด้านบนแล้ว เราก็ลองเปิดโปรแกรม pgAdmin หรือโปรแกรมอื่นที่ใช้ในการดูฐานข้อมูลได้ แล้วเชื่อมต่อตาม connection ที่เราได้ทำการเขียนไว้ในไฟล์ Docker Compose
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-79-1024x542.png)
Host name : localhost
Port : 5432
Username : yourusername
Password: yourpassword
โอเคครับ ตอนนี้เราก็ได้ทำการเซ็ต Database เตรียมพร้อมสำหรับใช้งานเรียบร้อยแล้ว ต่อไปเราไปสร้างโปรเจกต์ NestJS กันครับ โดยการเริ่มจากติดตั้ง NestJS CLI ด้วยคำสั่ง
npm install -g @nestjs/cli
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-87-1024x205.png)
สร้างโปรเจกต์ของ NestJS ใหม่โดยใช้ชื่อโปรเจกต์ว่า example-crud-product
npx nest new example-crud-product
เลือก package manager ที่ต้องการใช้งานเช่น npm, yarn หรือ pnpm โดยตัวอย่างนี้เราจะใช้ npm
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-88.png)
เสร็จแล้วเราก็จะได้โปรเจกต์ Default ของ NestJS มาเป็นที่เรียบร้อย
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-89-1024x485.png)
เสร็จแล้วเราก็ cd เข้าไปที่โปรเจกต์และรันด้วยคำสั่ง npm run start ได้เลย
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-90-1024x383.png)
เชื่อมต่อ NestJS กับ PostgreSQL ด้วย Prisma
ติดตั้ง Prisma ด้วยคำสั่ง
npm install @prisma/client prisma
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-91-1024x257.png)
โดย Prisma เป็นเครื่องมือที่ช่วยให้สามารถทำงานกับฐานข้อมูลได้โดยไม่ต้องเขียน SQL โดยตรง ซึ่งช่วยให้สามารถทำงานกับข้อมูลโดยใช้ภาษาที่เราเขียนได้เลย เช่น TypeScript, JavaScript
โครงสร้างของโปรเจกต์ NestJS
โดยทั่วไปแล้วหากเราสร้างโปรเจกต์ NestJS จะได้โฟลเดอร์และไฟล์ดังนี้มา 👇
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-93.png)
📂 โฟลเดอร์:
- node_modules: อันนี้สาย Node.js น่าจะรู้จักดี เอาไว้เก็บ dependacy ต่างๆ
- src: โฟลเดอร์ที่เก็บโค้ด TypeScript ของ NestJS application ของเรา
- tests: โฟลเดอร์สำหรับเก็บโค้ดเทส
- dist: ไฟล์หรือของที่ build แล้ว
🗃️ ไฟล์ใน src (สำหรับตอนนี้ให้โฟกัสที่ src ก่อนนะครับ):
app.controller.spec.ts: ไฟล์เทสสำหรับ AppController
app.controller.ts: ไฟล์ AppController (ตัวควบคุมหลัก) ของแอป
app.module.ts: ไฟล์สำหรับโมดูลของแอป
app.service.ts: ไฟล์สำหรับ AppService (บริการหลัก) ของแอป
main.ts: ไฟล์ที่เป็นจุดเริ่มต้นของแอป
เริ่มแรกให้เราไปที่ไฟล์ app.module.ts
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-92-1024x534.png)
แล้วทำการลบ AppController และ AppService ซะ เพราะเราไม่จำเป็นต้องใช้ร่วมถึงที่ import และไฟล์ app.controller.ts, app.controller.spec.ts และ app.service.ts ด้วย
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-94-1024x331.png)
ต่อมาใช้คำสั่ง npx prisma init เพื่อสร้างไฟล์ schema.prisma ในโฟลเดอร์ prisma สำหรับกำหนด data model ขึ้นมา คล้ายกับกำหนดตารางในฐานข้อมูลและกำหนดค่า Prisma ในการต่อกับฐานข้อมูล
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-95-1024x413.png)
หลังจากจะได้ไฟล์ schema.prisma ในโฟลเดอร์ prisma
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-96-1024x486.png)
ต่อมาก็เอาค่าของฐานข้อมูลเราไปใส่ให้ตรงใน .env
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-98-1024x245.png)
กำหนด data model ในฐานข้อมูลสำหรับตาราง “Product”
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-99.png)
ต่อมาเราสามารถสั่งให้สร้างตาราง “Product” ในฐานข้อมูลให้ตรงกับ data model ในไฟล์ schema.prisma โดยการใช้คำสั่ง npx prisma migrate dev –name init
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-100.png)
เราจะเห็นได้ว่าในฐานข้อมูลจะมีตารางตามที่เรากำหนดไว้แล้ว
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-101-1024x585.png)
ต่อมาให้เรากลับไปที่โฟลเดอร์ src สร้างไฟล์ชื่อว่า prisma.service.ts ขึ้นมา
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-102-1024x470.png)
PrismaService นี้เป็นสร้างการเชื่อมต่อกับฐานข้อมูล PostgreSQL โดยใช้ Prisma ตอนที่ NestJS สตาร์ทแอปขึ้นมา โดย PrismaService จะเป็นตัวจัดการทุกๆ การสื่อสารกับฐานข้อมูล
ต่อมาเราจะมาสร้าง CRUD ของโปรเจกต์ NestJS กัน
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-103.png)
ต่อมาในโฟลเดอร์ src ให้เราสร้างโฟลเดอร์ชื่อว่า product ขึ้นมาแล้วสร้างไฟล์ตามภาพด้านบนเมื่อเราสร้างไฟล์ โดยโค้ดของแอปแต่ละส่วยของ NestJS จะมีดังนี้
product.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from 'src/prisma.service';
import { Product } from './product.model';
@Injectable()
export class ProductService {
constructor(private prisma: PrismaService) {}
async getAllProducts(): Promise<Product[]> {
return this.prisma.product.findMany();
}
async getProduct(id: number): Promise<Product | null> {
return this.prisma.product.findUnique({ where: { id } });
}
async createProduct(data: Product): Promise<Product> {
return this.prisma.product.create({ data });
}
async updateProduct(id: number, data: Product): Promise<Product> {
return this.prisma.product.update({ where: { id: Number(id) }, data });
}
async deleteProduct(id: number): Promise<Product> {
return this.prisma.product.delete({ where: { id: Number(id) } });
}
}
product.model.ts
import { Prisma } from '@prisma/client';
export class Product implements Prisma.ProductCreateInput {
id: number;
name: string;
description: string;
price: number;
quantity: number;
}
product.controller.ts
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Put,
} from '@nestjs/common';
import { ProductService } from './product.service';
import { Product } from './product.model';
@Controller('product')
export class ProductController {
constructor(private readonly productService: ProductService) {}
@Get()
async getAllProducts(): Promise<Product[]> {
return this.productService.getAllProducts();
}
@Post()
async postProduct(@Body() postData: Product): Promise<Product> {
return this.productService.createProduct(postData);
}
@Get(':id')
async getProduct(@Param('id') id: number): Promise<Product> {
return this.productService.getProduct(Number(id));
}
@Delete(':id')
async deleteProduct(@Param('id') id: number): Promise<Product> {
return this.productService.deleteProduct(id);
}
@Put(':id')
async updateProduct(
@Param('id') id: number,
@Body() postData: Product,
): Promise<Product> {
return this.productService.updateProduct(id, postData);
}
}
product.module.ts
import { Module } from '@nestjs/common';
import { ProductService } from './product.service';
import { ProductController } from './product.controller';
import { PrismaService } from 'src/prisma.service';
@Module({
providers: [ProductService, PrismaService],
controllers: [ProductController],
})
export class ProductModule {}
เสร็จแล้วเราก็แวะไป import และเรียกใช้ ProductModule ในไฟล์ app.module.ts แล้วรันด้วยคำสั่ง npm run start:dev ได้เลย
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-104-1024x507.png)
จากนั้นเราก็สามารถยิง Postman ทดสอบได้เลย
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-105-1024x388.png)
เมื่อเราเปิด pgAdmin มาก็จะเห็นว่ามีข้อมูลถูกเพิ่มไปแล้ว
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-106-1024x215.png)
การแพ็ค NestJS เป็น Docker Image
ทำการสร้าง Dockerfile สำหรับแพ็คแอปพลิเคชัน NestJS ให้เป็น Docker Image
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-107-853x1024.png)
หลังจากนั้นเพิ่มส่วนในการ build และรัน Dockerfile นี้ไปใน docker-compose.yml เดียวกับฐานข้อมูล PostgreSQL โดยจะมีการต่อ network ตัวเดียวกันให้ nest-app กับ postgreSQL สามารถคุยกันได้
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-108-808x1024.png)
ต่อมาเราก็ใช้คำสั่ง docker compose up -d –build เพื่อให้สิ่งที่เราเขียนในไฟล์ docker compose ทำงานแล้ว build image ใหม่ด้วย
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-109-1024x482.png)
เมื่อลองรันดูแล้วเราจะเห็นว่าตัวแอปไม่ติด เพราะไม่สามารถเชื่อมต่อฐานข้อมูลได้ ให้เราไปเปลี่ยน database connection จาก localhost เป็นชื่อ container ของฐานข้อมูลแทน
เดิม
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-110-1024x110.png)
แก้ใหม่
แล้วลองทำการ docker compose up -d –build ใหม่ และทำการ docker ps ออกมาจะเห็นว่าตอนนี้ทั้งแอปและฐานข้อมูลถูกรันอยู่แล้ว
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-111-1024x509.png)
แล้วถ้าเกิดเรายิง Postman ไปใหม่จะเห็นว่ามันใช้ได้แล้วนั่นเองงง
![](https://b2dmain-ruk.cdn.jelastic.net/wp-content/uploads/2024/04/image-112.png)