Skip to main content
0
Back-End Developer

มาลองทำโปรเจกต์ NestJS + Docker + PostgreSQL

สวัสดีครับ สำหรับวันนี้เอาใจสาย 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

หลังจากนั้นสามารใช้คำสั่ง docker ps –filter “ancestor=postgres” เพื่อหา Container ที่กำลังรันอยู่ด้วย Image ชื่อว่า postgres

ถ้าได้ตามภาพด้านบนแล้ว เราก็ลองเปิดโปรแกรม pgAdmin หรือโปรแกรมอื่นที่ใช้ในการดูฐานข้อมูลได้ แล้วเชื่อมต่อตาม connection ที่เราได้ทำการเขียนไว้ในไฟล์ Docker Compose


Host name : localhost

Port : 5432

Username : yourusername

Password: yourpassword

โอเคครับ ตอนนี้เราก็ได้ทำการเซ็ต Database เตรียมพร้อมสำหรับใช้งานเรียบร้อยแล้ว ต่อไปเราไปสร้างโปรเจกต์ NestJS กันครับ โดยการเริ่มจากติดตั้ง NestJS CLI ด้วยคำสั่ง

npm install -g @nestjs/cli

สร้างโปรเจกต์ของ NestJS ใหม่โดยใช้ชื่อโปรเจกต์ว่า example-crud-product

npx nest new example-crud-product

เลือก package manager ที่ต้องการใช้งานเช่น npm, yarn หรือ pnpm โดยตัวอย่างนี้เราจะใช้ npm

เสร็จแล้วเราก็จะได้โปรเจกต์ Default ของ NestJS มาเป็นที่เรียบร้อย

เสร็จแล้วเราก็ cd เข้าไปที่โปรเจกต์และรันด้วยคำสั่ง npm run start ได้เลย

เชื่อมต่อ NestJS กับ PostgreSQL ด้วย Prisma

ติดตั้ง Prisma ด้วยคำสั่ง

npm install @prisma/client prisma

โดย Prisma เป็นเครื่องมือที่ช่วยให้สามารถทำงานกับฐานข้อมูลได้โดยไม่ต้องเขียน SQL โดยตรง ซึ่งช่วยให้สามารถทำงานกับข้อมูลโดยใช้ภาษาที่เราเขียนได้เลย เช่น TypeScript, JavaScript

โครงสร้างของโปรเจกต์ NestJS

โดยทั่วไปแล้วหากเราสร้างโปรเจกต์ NestJS จะได้โฟลเดอร์และไฟล์ดังนี้มา 👇

📂 โฟลเดอร์:

  • 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

แล้วทำการลบ AppController และ AppService ซะ เพราะเราไม่จำเป็นต้องใช้ร่วมถึงที่ import และไฟล์ app.controller.ts, app.controller.spec.ts และ app.service.ts ด้วย

ต่อมาใช้คำสั่ง npx prisma init เพื่อสร้างไฟล์ schema.prisma ในโฟลเดอร์ prisma สำหรับกำหนด data model ขึ้นมา คล้ายกับกำหนดตารางในฐานข้อมูลและกำหนดค่า Prisma ในการต่อกับฐานข้อมูล

หลังจากจะได้ไฟล์ schema.prisma ในโฟลเดอร์ prisma

ต่อมาก็เอาค่าของฐานข้อมูลเราไปใส่ให้ตรงใน .env

กำหนด data model ในฐานข้อมูลสำหรับตาราง “Product”

ต่อมาเราสามารถสั่งให้สร้างตาราง “Product” ในฐานข้อมูลให้ตรงกับ data model ในไฟล์ schema.prisma โดยการใช้คำสั่ง npx prisma migrate dev –name init

เราจะเห็นได้ว่าในฐานข้อมูลจะมีตารางตามที่เรากำหนดไว้แล้ว

ต่อมาให้เรากลับไปที่โฟลเดอร์ src สร้างไฟล์ชื่อว่า prisma.service.ts ขึ้นมา

PrismaService นี้เป็นสร้างการเชื่อมต่อกับฐานข้อมูล PostgreSQL โดยใช้ Prisma ตอนที่ NestJS สตาร์ทแอปขึ้นมา โดย PrismaService จะเป็นตัวจัดการทุกๆ การสื่อสารกับฐานข้อมูล

ต่อมาเราจะมาสร้าง CRUD ของโปรเจกต์ NestJS กัน

ต่อมาในโฟลเดอร์ 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 ได้เลย

จากนั้นเราก็สามารถยิง Postman ทดสอบได้เลย

เมื่อเราเปิด pgAdmin มาก็จะเห็นว่ามีข้อมูลถูกเพิ่มไปแล้ว

การแพ็ค NestJS เป็น Docker Image

ทำการสร้าง Dockerfile สำหรับแพ็คแอปพลิเคชัน NestJS ให้เป็น Docker Image

หลังจากนั้นเพิ่มส่วนในการ build และรัน Dockerfile นี้ไปใน docker-compose.yml เดียวกับฐานข้อมูล PostgreSQL โดยจะมีการต่อ network ตัวเดียวกันให้ nest-app กับ postgreSQL สามารถคุยกันได้

ต่อมาเราก็ใช้คำสั่ง docker compose up -d –build เพื่อให้สิ่งที่เราเขียนในไฟล์ docker compose ทำงานแล้ว build image ใหม่ด้วย

เมื่อลองรันดูแล้วเราจะเห็นว่าตัวแอปไม่ติด เพราะไม่สามารถเชื่อมต่อฐานข้อมูลได้ ให้เราไปเปลี่ยน database connection จาก localhost เป็นชื่อ container ของฐานข้อมูลแทน

เดิม

แก้ใหม่

แล้วลองทำการ docker compose up -d –build ใหม่ และทำการ docker ps ออกมาจะเห็นว่าตอนนี้ทั้งแอปและฐานข้อมูลถูกรันอยู่แล้ว

แล้วถ้าเกิดเรายิง Postman ไปใหม่จะเห็นว่ามันใช้ได้แล้วนั่นเองงง

Sirasit Boonklang

Author Sirasit Boonklang

More posts by Sirasit Boonklang

เราใช้คุกกี้เพื่อพัฒนาประสิทธิภาพ และประสบการณ์ที่ดีในการใช้เว็บไซต์ของคุณ คุณสามารถศึกษารายละเอียดได้ที่ นโยบายความเป็นส่วนตัว และสามารถจัดการความเป็นส่วนตัวเองได้ของคุณได้เองโดยคลิกที่ ตั้งค่า

ตั้งค่าความเป็นส่วนตัว

คุณสามารถเลือกการตั้งค่าคุกกี้โดยเปิด/ปิด คุกกี้ในแต่ละประเภทได้ตามความต้องการ ยกเว้น คุกกี้ที่จำเป็น

ยอมรับทั้งหมด
จัดการความเป็นส่วนตัว
  • คุกกี้ที่จำเป็น
    เปิดใช้งานตลอด

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

  • คุกกี้สำหรับการติดตามทางการตลาด

    ประเภทของคุกกี้ที่มีความจำเป็นในการใช้งานเพื่อการวิเคราะห์ และ นำเสนอโปรโมชัน สินค้า รวมถึงหลักสูตรฟรี และ สิทธิพิเศษต่าง ๆ คุณสามารถเลือกปิดคุกกี้ประเภทนี้ได้โดยไม่ส่งผลต่อการทำงานหลัก เว้นแต่การนำเสนอโปรโมชันที่อาจไม่ตรงกับความต้องการ
    รายละเอียดคุกกี้

บันทึกการตั้งค่า