☰ Mục lục series
Tài liệu NestJS tiếng Việt
  1. Nhập môn (Overview)
    1. Giới thiệu
    2. First steps
    3. Controllers
    4. Providers
    5. Modules
    6. Middleware
    7. Exception filters
    8. Pipes
    9. Guards
    10. Interceptors
    11. Custom decorators
  2. Nền tảng (Fundamentals)
    1. Custom providers
    2. Asynchronous providers
    3. Dynamic modules
    4. Injection scopes
    5. Circular dependency
    6. Module reference
    7. Lazy-loading modules
    8. Execution context
    9. Lifecycle events
    10. Discovery service
    11. Platform agnosticism
    12. Testing
  3. Kỹ thuật (Techniques)
    1. Configuration
    2. Database (SQL)
    3. MongoDB
    4. Validation
    5. Caching
    6. Serialization
    7. Versioning
    8. Task scheduling
    9. Queues
    10. Logging
    11. Cookies
    12. Events
    13. Compression
    14. File upload
    15. Streaming files
    16. HTTP module
    17. Session
    18. MVC
    19. Performance (Fastify)
    20. Server-Sent Events
  4. Bảo mật (Security)
    1. Authentication
    2. Authorization
    3. Encryption & Hashing
    4. Helmet
    5. CORS
    6. CSRF
    7. Rate limiting
    8. Vòng đời request
  5. OpenAPI (Swagger)
    1. Introduction
    2. Types and parameters
    3. Operations
    4. Security
    5. Mapped types
    6. Decorators
    7. CLI plugin
    8. Other features
  6. Recipes
    1. REPL
    2. CRUD generator
    3. SWC (fast compiler)
    4. Passport (auth)
    5. Hot reload
    6. MikroORM
    7. Router module
    8. Health checks (Terminus)
    9. CQRS
    10. Prisma
    11. Serve static
    12. Commander
    13. Async local storage
  7. WebSockets
    1. Gateways
    2. Exception filters
    3. Pipes
    4. Guards
    5. Interceptors
    6. Adapters
  8. Microservices
    1. Overview
    2. Redis
    3. MQTT
    4. NATS
    5. RabbitMQ
    6. Kafka
    7. gRPC
    8. Custom transporters
    9. Exception filters
    10. Pipes
    11. Pre-request hooks
    12. Guards
    13. Interceptors
  9. GraphQL
    1. Quick start
    2. Resolvers
    3. Mutations
    4. Subscriptions
    5. Scalars
    6. Directives
    7. Interfaces
    8. Unions and enums
    9. Field middleware
    10. Mapped types
    11. Plugins
    12. Complexity
    13. Extensions
    14. CLI plugin
    15. Generating SDL
    16. Sharing models
    17. Other features
    18. Federation
  10. CLI & triển khai
    1. CLI overview
    2. Workspaces
    3. Libraries
    4. Usage
    5. Scripts
    6. Deployment
    7. Standalone apps
  11. FAQ
    1. Serverless
    2. HTTP adapter
    3. Keep-Alive connections
    4. Global path prefix
    5. Raw body
    6. Hybrid application
    7. HTTPS & multiple servers
    8. Common errors
  12. Phụ lục
    1. Bảng thuật ngữ
Recipes

SWC

Bản dịch từ docs.nestjs.com/recipes/swc

SWC (Speedy Web Compiler) là một platform mở rộng được viết bằng Rust, dùng được cho cả việc compile và bundle. Dùng SWC với Nest CLI là cách đơn giản và hiệu quả để tăng tốc đáng kể quá trình phát triển của bạn.

Gợi ý SWC nhanh hơn TypeScript compiler mặc định khoảng 20 lần.

Cài đặt

Để bắt đầu, trước tiên cài một vài package:

$ npm i --save-dev @swc/cli @swc/core

Bắt đầu (Getting started)

Sau khi cài đặt xong, bạn có thể dùng builder swc với Nest CLI như sau:

$ nest start -b swc
# OR nest start --builder swc

Gợi ý Nếu repo của bạn là monorepo, xem phần này.

Thay vì truyền flag -b, bạn cũng có thể đặt property compilerOptions.builder thành "swc" trong file nest-cli.json, như sau:

{
  "compilerOptions": {
    "builder": "swc"
  }
}

Để tùy biến hành vi của builder, bạn có thể truyền một object gồm hai thuộc tính, type ("swc") và options, như sau:

{
  "compilerOptions": {
    "builder": {
      "type": "swc",
      "options": {
        "swcrcPath": "infrastructure/.swcrc",
      }
    }
  }
}

Ví dụ, để swc compile cả file .jsx và .tsx, làm như sau:

{
  "compilerOptions": {
    "builder": {
      "type": "swc",
      "options": { "extensions": [".ts", ".tsx", ".js", ".jsx"] }
    },
  }
}

Để chạy ứng dụng ở watch mode, dùng lệnh sau:

$ nest start -b swc -w
# OR nest start --builder swc --watch

Kiểm tra kiểu dữ liệu (Type checking)

SWC không tự thực hiện việc kiểm tra kiểu dữ liệu (type checking) (khác với TypeScript compiler mặc định), nên để bật tính năng này, bạn cần dùng flag --type-check:

$ nest start -b swc --type-check

Lệnh này yêu cầu Nest CLI chạy tsc ở chế độ noEmit song song với SWC, để thực hiện type checking một cách bất đồng bộ (asynchronously). Cũng giống như trên, thay vì truyền flag --type-check, bạn có thể đặt property compilerOptions.typeCheck thành true trong file nest-cli.json, như sau:

{
  "compilerOptions": {
    "builder": "swc",
    "typeCheck": true
  }
}

Ghi chú người dịch Đây là lý do SWC nhanh hơn tsc nhiều lần: nó chỉ strip type annotation và biên dịch cú pháp (giống Babel), không xây dựng cây kiểu dữ liệu đầy đủ để kiểm tra type như tsc. Vì vậy nếu chỉ dùng -b swc mà không thêm --type-check, code có lỗi type vẫn build và chạy được — lỗi chỉ lộ ra lúc runtime, khác hẳn với việc tsc chặn build ngay khi biên dịch.

CLI Plugin (SWC)

Flag --type-check sẽ tự động chạy các plugin của NestJS CLI và sinh ra một file metadata đã serialize, để ứng dụng có thể load file đó lúc runtime.

Cấu hình SWC

Builder SWC đã được cấu hình sẵn để phù hợp với yêu cầu của ứng dụng NestJS. Tuy nhiên, bạn có thể tùy biến cấu hình này bằng cách tạo file .swcrc ở thư mục gốc và chỉnh các option theo ý muốn.

{
  "$schema": "https://swc.rs/schema.json",
  "sourceMaps": true,
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "decorators": true,
      "dynamicImport": true
    },
    "baseUrl": "./"
  },
  "minify": false
}

Monorepo

Nếu repo của bạn là monorepo, thay vì dùng builder swc, bạn phải cấu hình webpack để dùng swc-loader.

Đầu tiên, cài package cần thiết:

$ npm i --save-dev swc-loader

Sau khi cài xong, tạo file webpack.config.js ở thư mục gốc của ứng dụng với nội dung sau:

const swcDefaultConfig = require('@nestjs/cli/lib/compiler/defaults/swc-defaults').swcDefaultsFactory().swcOptions;

module.exports = {
  module: {
    rules: [
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        use: {
          loader: 'swc-loader',
          options: swcDefaultConfig,
        },
      },
    ],
  },
};

Monorepo và CLI plugin

Nếu bạn dùng CLI plugin, swc-loader sẽ không tự load chúng. Thay vào đó, bạn phải tạo một file riêng để load chúng bằng tay. Để làm điều này, khai báo file generate-metadata.ts cạnh file main.ts với nội dung sau:

import { PluginMetadataGenerator } from '@nestjs/cli/lib/compiler/plugins/plugin-metadata-generator';
import { ReadonlyVisitor } from '@nestjs/swagger/dist/plugin';

const generator = new PluginMetadataGenerator();
generator.generate({
  visitors: [new ReadonlyVisitor({ introspectComments: true, pathToSource: import.meta.dirname })],
  outputDir: import.meta.dirname,
  watch: true,
  tsconfigPath: 'apps/<name>/tsconfig.app.json',
});

Gợi ý Trong ví dụ này chúng ta dùng plugin @nestjs/swagger, nhưng bạn có thể dùng bất kỳ plugin nào bạn muốn.

Phương thức generate() nhận các option sau:

watch Có theo dõi (watch) project để bắt thay đổi hay không.
tsconfigPath Đường dẫn tới file tsconfig.json. Tương đối so với working directory hiện tại (process.cwd()).
outputDir Đường dẫn tới thư mục sẽ lưu file metadata.
visitors Một mảng các visitor được dùng để sinh metadata.
filename Tên của file metadata. Mặc định là metadata.ts.
printDiagnostics Có in diagnostic ra console hay không. Mặc định là true.

Cuối cùng, bạn có thể chạy script generate-metadata trong một terminal riêng bằng lệnh sau:

$ npx ts-node src/generate-metadata.ts
# OR npx ts-node apps/{YOUR_APP}/src/generate-metadata.ts

Các cạm bẫy thường gặp

Nếu bạn dùng TypeORM/MikroORM hoặc ORM khác trong ứng dụng, bạn có thể gặp vấn đề về circular import. SWC không xử lý tốt circular import, vì vậy bạn nên dùng cách khắc phục sau:

@Entity()
export class User {
  @OneToOne(() => Profile, (profile) => profile.user)
  profile: Relation<Profile>; // <--- xem type "Relation<>" ở đây, thay vì chỉ "Profile"
}

Gợi ý Type Relation được export từ package typeorm.

Việc này giúp tránh lưu type của property vào code đã transpile trong property metadata, từ đó tránh được vấn đề circular dependency.

Nếu ORM bạn dùng không có cách khắc phục tương tự, bạn có thể tự định nghĩa wrapper type:

/**
 * Wrapper type dùng để né vấn đề circular dependency của ESM module
 * gây ra bởi việc reflection metadata lưu lại type của property.
 */
export type WrapperType<T> = T; // WrapperType === Relation

Với tất cả circular dependency injection trong project, bạn cũng cần dùng custom wrapper type như mô tả trên:

@Injectable()
export class UsersService {
  constructor(
    @Inject(forwardRef(() => ProfileService))
    private readonly profileService: WrapperType<ProfileService>,
  ) {};
}

Ghi chú người dịch Vấn đề circular import này quen thuộc nếu bạn từng gặp circular dependency trong Go: Go compiler chặn cứng circular import giữa package ngay từ lúc build, còn ở đây SWC chỉ “quên” giữ type của property khi transpile (vì SWC transpile từng file độc lập, không phân tích toàn bộ project như tsc), khiến decorator metadata bị rỗng và NestJS resolve provider sai. Dùng Relation<T> hay WrapperType<T> chỉ là một type alias vô hại về runtime — nó đánh lừa reflection để không lưu type gốc, chứ không thực sự thay đổi logic.

Jest + SWC

Để dùng SWC với Jest, bạn cần cài các package sau:

$ npm i --save-dev jest @swc/core @swc/jest

Sau khi cài xong, cập nhật file package.json/jest.config.js (tùy theo cấu hình của bạn) với nội dung sau:

{
  "jest": {
    "transform": {
      "^.+\\.(t|j)s?$": ["@swc/jest"]
    }
  }
}

Ngoài ra bạn cần thêm các property transform sau vào file .swcrc: legacyDecorator, decoratorMetadata:

{
  "$schema": "https://swc.rs/schema.json",
  "sourceMaps": true,
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "decorators": true,
      "dynamicImport": true
    },
    "transform": {
      "legacyDecorator": true,
      "decoratorMetadata": true
    },
    "baseUrl": "./"
  },
  "minify": false
}

Nếu bạn dùng NestJS CLI Plugin trong project, bạn sẽ phải chạy PluginMetadataGenerator bằng tay. Xem phần này để biết thêm.

Vitest

Vitest là một test runner nhanh và nhẹ, được thiết kế để hoạt động với Vite. Nó cung cấp một giải pháp test hiện đại, nhanh và dễ dùng, có thể tích hợp với các project NestJS.

Cài đặt

Để bắt đầu, trước tiên cài các package cần thiết:

$ npm i --save-dev vitest unplugin-swc @swc/core @vitest/coverage-v8

Cấu hình

Tạo file vitest.config.ts ở thư mục gốc của ứng dụng với nội dung sau:

import swc from 'unplugin-swc';
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    root: './',
  },
  plugins: [
    // Cần thiết để build các file test bằng SWC
    swc.vite({
      // Đặt rõ module type để tránh kế thừa giá trị này từ file cấu hình `.swcrc`
      module: { type: 'es6' },
    }),
  ],
  resolve: {
    alias: {
      // Đảm bảo Vitest resolve đúng TypeScript path alias
      'src': resolve(import.meta.dirname, './src'),
    },
  },
});

File cấu hình này thiết lập môi trường Vitest, thư mục gốc, và plugin SWC. Bạn cũng nên tạo một file cấu hình riêng cho e2e test, với thêm field include để chỉ định regex đường dẫn test:

import swc from 'unplugin-swc';
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    include: ['**/*.e2e-spec.ts'],
    globals: true,
    root: './',
  },
  plugins: [swc.vite()],
});

Ngoài ra, bạn có thể đặt option alias để hỗ trợ TypeScript path trong test:

import swc from 'unplugin-swc';
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    include: ['**/*.e2e-spec.ts'],
    globals: true,
    alias: {
      '@src': './src',
      '@test': './test',
    },
    root: './',
  },
  resolve: {
    alias: {
      '@src': './src',
      '@test': './test',
    },
  },
  plugins: [swc.vite()],
});

Path alias

Khác với Jest, Vitest không tự resolve TypeScript path alias như src/. Điều này có thể gây lỗi resolve dependency khi test. Để khắc phục, thêm cấu hình resolve.alias sau vào file vitest.config.ts:

import { resolve } from 'node:path';

export default defineConfig({
  resolve: {
    alias: {
      'src': resolve(import.meta.dirname, './src'),
    },
  },
});

Điều này đảm bảo Vitest resolve đúng các module import, tránh lỗi liên quan đến dependency bị thiếu.

Cập nhật import trong E2E test

Đổi các import trong E2E test dùng import * as request from 'supertest' thành import request from 'supertest'. Đây là cần thiết vì Vitest, khi được bundle bằng Vite, yêu cầu default import cho supertest. Dùng namespace import có thể gây lỗi trong setup cụ thể này.

Cuối cùng, cập nhật các test script trong file package.json thành:

{
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest",
    "test:cov": "vitest run --coverage",
    "test:debug": "vitest --inspect-brk --inspect --logHeapUsage --threads=false",
    "test:e2e": "vitest run --config ./vitest.config.e2e.ts"
  }
}

Các script này cấu hình Vitest để chạy test, theo dõi thay đổi (watch), sinh báo cáo code coverage, và debug. Script test:e2e dùng riêng để chạy E2E test với file cấu hình tùy chỉnh.

Với setup này, bạn đã có thể tận hưởng lợi ích của việc dùng Vitest trong project NestJS, bao gồm chạy test nhanh hơn và trải nghiệm test hiện đại hơn.

Gợi ý Bạn có thể xem một ví dụ chạy được trong repo này

Nội dung gốc: NestJS Documentation © Kamil Myśliwiec, giấy phép MIT. Bản dịch và ghi chú: Lê Văn Dũng.