Giới thiệu
Trong môi trường phát triển phần mềm hiện đại, việc tự động hoá toàn bộ vòng đời từ viết code, kiểm thử, xây dựng image cho tới triển khai lên môi trường production là yếu tố quyết định tốc độ và độ ổn định của dự án. Đối với các ứng dụng NestJS – một framework Node.js dựa trên TypeScript – việc thiết lập một pipeline CI/CD chuẩn, có thể tái sử dụng và mở rộng, không chỉ giúp giảm thiểu lỗi con người mà còn tạo điều kiện cho các team phát triển microservice hoặc monorepo làm việc hiệu quả hơn.
Bài viết sẽ đi sâu vào cách xây dựng quy trình CI/CD cho dự án NestJS bằng cách kết hợp Docker để container hoá ứng dụng, GitHub Actions làm công cụ tự động hoá, và một số công cụ hỗ trợ như Prisma (ORM) và Jest để kiểm thử. Tất cả các ví dụ đều dựa trên một dự án mẫu có cấu trúc chuẩn của NestJS, bao gồm các module, service, controller và một database PostgreSQL.
Kiến trúc CI/CD cho NestJS
1. Docker hoá ứng dụng
Docker cho phép chúng ta đóng gói toàn bộ môi trường chạy ứng dụng – Node.js runtime, các phụ thuộc npm, và cả mã nguồn – vào một container duy nhất. Điều này giúp môi trường local, CI và production luôn đồng nhất, tránh các lỗi “works on my machine”.
Dưới đây là Dockerfile tối ưu cho NestJS. Chúng ta sử dụng multi‑stage build để giảm kích thước image cuối cùng.
# Stage 1: Build FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . RUN npm run build # Stage 2: Run FROM node:18-alpine AS runtime WORKDIR /app COPY --from=builder /app/package*.json ./ COPY --from=builder /app/dist ./dist RUN npm ci --only=production EXPOSE 3000 CMD ["node", "dist/main.js"]
Giải thích:
- Stage 1 cài đặt toàn bộ phụ thuộc, biên dịch TypeScript sang JavaScript và tạo thư mục
dist. - Stage 2 chỉ sao chép các file cần thiết (package.json và dist) vào image runtime, giảm đáng kể kích thước.
- Chúng ta sử dụng
node:18-alpine– một image nhẹ, phù hợp cho production.
2. Quản lý môi trường với docker‑compose
Trong quá trình phát triển và test, chúng ta thường cần chạy đồng thời nhiều service: API NestJS, database PostgreSQL, và có thể là một Redis cache. docker‑compose giúp chúng ta định nghĩa và khởi động toàn bộ stack chỉ bằng một lệnh.
version: "3.9"
services:
api:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://postgres:postgres@db:5432/app_db
depends_on:
- db
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: app_db
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Với file này, khi chạy docker compose up -d, Docker sẽ tự động xây dựng image cho service api dựa trên Dockerfile ở trên, đồng thời khởi tạo một container PostgreSQL và kết nối chúng lại với nhau thông qua mạng nội bộ.
3. Kiểm thử tự động
Kiểm thử là một trong những bước không thể thiếu trong CI. NestJS tích hợp sẵn Jest, cho phép chúng ta viết unit test và integration test cho các module, service và controller. Dưới đây là một ví dụ về file cấu hình jest.config.js và một test đơn giản cho service.
module.exports = {
moduleFileExtensions: ['js', 'json', 'ts'],
rootDir: 'src',
testRegex: '.*\.spec\.ts$',
transform: {
'^.+\\.(t|j)s$': 'ts-jest',
},
collectCoverageFrom: ['**/*.(t|j)s'],
coverageDirectory: '../coverage',
};
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
describe('UsersService', () => {
let service: UsersService;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService],
}).compile();
service = module.get(UsersService);
});
it('should return an array of users', async () => {
const users = await service.findAll();
expect(Array.isArray(users)).toBe(true);
});
});
Khi CI chạy, chúng ta sẽ thực thi lệnh npm run test để chạy toàn bộ test suite, và nếu có bất kỳ test nào thất bại, pipeline sẽ dừng lại và báo lỗi.
GitHub Actions workflow
1. Cấu trúc file workflow
GitHub Actions sử dụng các file YAML đặt trong thư mục .github/workflows. Dưới đây là một workflow mẫu tên ci-cd.yml thực hiện các bước: checkout code, cài đặt Node.js, cache npm, chạy lint, test, build Docker image và đẩy image lên Amazon ECR.
name: CI/CD for NestJS
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint code
run: npm run lint
- name: Run tests
run: npm run test:ci
- name: Build Docker image
run: |
docker build -t ${{ secrets.AWS_ECR_REPO }}:${{ github.sha }} .
- name: Login to Amazon ECR
uses: aws-actions/amazon-ecr-login@v1
with:
region: ${{ secrets.AWS_REGION }}
- name: Push Docker image to ECR
run: |
docker tag ${{ secrets.AWS_ECR_REPO }}:${{ github.sha }} ${{ secrets.AWS_ECR_REPO }}:latest
docker push ${{ secrets.AWS_ECR_REPO }}:${{ github.sha }}
docker push ${{ secrets.AWS_ECR_REPO }}:latest
Giải thích các job:
- checkout: Lấy mã nguồn từ repository.
- setup-node: Cài đặt Node.js 18 và bật cache npm để giảm thời gian cài đặt phụ thuộc.
- install dependencies: Chạy
npm ciđể cài đặt các package theopackage-lock.json. - lint: Đảm bảo code tuân thủ quy tắc ESLint.
- test: Thực thi toàn bộ test suite; nếu có lỗi, pipeline sẽ dừng.
- build Docker image: Dùng Dockerfile để tạo image, tag bằng SHA của commit.
- login to Amazon ECR và push Docker image: Đẩy image lên registry riêng của AWS để các môi trường triển khai (ECS, EKS, hoặc EC2) có thể kéo về.
2. Các job phụ trợ
Trong thực tế, chúng ta thường muốn bổ sung các job như:
- Static code analysis bằng SonarCloud để đo chất lượng code.
- Security scanning cho Docker image bằng Trivy hoặc Snyk.
- Deploy tự động tới môi trường staging sau khi image được đẩy lên ECR.
Các job này có thể được tách ra thành các workflow riêng hoặc được thêm vào workflow hiện tại bằng cách sử dụng needs để xác định thứ tự thực thi.
Triển khai lên môi trường thực tế
1. Đẩy Docker image lên Amazon ECR
Để có thể triển khai trên AWS, chúng ta cần một repository ECR. Các lệnh sau sẽ tạo repository (nếu chưa tồn tại) và đăng nhập.
aws ecr create-repository --repository-name nestjs-app --region us-east-1 aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin <account-id>.dkr.ecr.us-east-1.amazonaws.com
Sau khi image đã được build và tag, chúng ta push lên ECR như trong workflow ở trên.
2. Cập nhật service trên Amazon ECS
Giả sử chúng ta đã có một cluster ECS và một service chạy image nestjs-app:latest. Để cập nhật service với phiên bản mới, chúng ta thực hiện:
aws ecs update-service \ --cluster my-cluster \ --service nestjs-service \ --force-new-deployment
Lệnh này buộc ECS tạo một task definition mới dựa trên image mới, sau đó thay thế các container đang chạy. Quá trình rollout sẽ được thực hiện tuần tự, giúp giảm downtime.
Best practices và lưu ý
- Cache Docker layers: Khi Dockerfile thay đổi ít, các layer phía trên sẽ được cache, giảm thời gian build trong CI.
- Separate test and build stages: Đừng chạy test trong stage runtime; thay vào đó, thực hiện test trong stage build để tránh đưa các công cụ test vào image production.
- Use environment variables cho cấu hình (database URL, JWT secret) và lưu trữ chúng trong GitHub Secrets hoặc AWS Parameter Store.
- Version tag image bằng commit SHA và đồng thời giữ một tag
latestđể dễ dàng rollback. - Zero‑downtime deployment: Khi sử dụng ECS hoặc Kubernetes, cấu hình health check và rolling update để tránh ngắt kết nối người dùng.
- Security scanning: Tích hợp Trivy trong CI để phát hiện lỗ hổng trong image trước khi đẩy lên registry.
Kết luận
Việc thiết lập một pipeline CI/CD hoàn chỉnh cho NestJS không chỉ giúp tăng tốc độ phát triển mà còn nâng cao độ tin cậy của hệ thống. Từ việc container hoá ứng dụng, viết test tự động, tới việc tự động build và deploy trên AWS, mỗi bước đều có thể được tối ưu hoá bằng các best practice đã trình bày. Khi áp dụng đúng, đội ngũ sẽ giảm thiểu lỗi môi trường, rút ngắn thời gian đưa tính năng mới lên production và duy trì được chất lượng code ổn định.
Tham khảo khóa học "RESTful API với NestJS & TypeORM" tại đây






