☰ 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ữ
CLI & triển khai

Libraries

Bản dịch từ docs.nestjs.com/cli/libraries

Nhiều ứng dụng cần giải quyết cùng một nhóm vấn đề chung, hoặc tái sử dụng một component dạng module trong nhiều context khác nhau. Nest có vài cách để giải quyết việc này, nhưng mỗi cách hoạt động ở một cấp độ khác nhau nhằm giải quyết vấn đề theo hướng phù hợp với các mục tiêu kiến trúc và tổ chức khác nhau.

Các module của Nest hữu ích cho việc cung cấp một execution context cho phép các component được chia sẻ trong cùng một ứng dụng. Module cũng có thể được đóng gói bằng npm để tạo thành một library có thể tái sử dụng, cài đặt trong nhiều dự án khác nhau. Đây có thể là một cách hiệu quả để phân phối các library có thể cấu hình, tái sử dụng được, dùng bởi các tổ chức khác nhau, ít liên kết hoặc không liên quan với nhau (ví dụ, bằng cách phân phối/cài đặt các library bên thứ ba).

Đối với việc chia sẻ code trong các nhóm được tổ chức chặt chẽ (ví dụ, trong phạm vi công ty/dự án), sẽ hữu ích nếu có một cách tiếp cận gọn nhẹ hơn để chia sẻ component. Monorepo ra đời như một cấu trúc để đáp ứng điều đó, và trong một monorepo, một library cung cấp một cách để chia sẻ code một cách dễ dàng, gọn nhẹ. Trong một monorepo Nest, việc dùng library giúp dễ dàng lắp ráp các ứng dụng có chia sẻ component. Trên thực tế, điều này khuyến khích việc phân rã các ứng dụng nguyên khối (monolithic) và quy trình phát triển tập trung vào việc xây dựng và ghép nối các component dạng module.

Ghi chú người dịch Nếu bạn từng làm với PHP/Laravel, một Nest library trong monorepo hơi giống một package Composer local (path repository) mà bạn require trực tiếp từ source thay vì qua Packagist — code luôn “tươi” theo commit mới nhất, không cần publish/version riêng. Còn “library” đóng gói qua npm như một package thông thường thì giống hệt việc publish một package Composer lên Packagist để dùng ở nhiều project độc lập.

Nest libraries

Một Nest library là một dự án Nest, khác với ứng dụng ở chỗ nó không thể tự chạy độc lập. Một library phải được import vào một ứng dụng chứa nó (containing application) thì code của nó mới thực thi được. Phần hỗ trợ tích hợp sẵn cho library được mô tả trong phần này chỉ khả dụng cho monorepo (các dự án standard mode có thể đạt được chức năng tương tự bằng cách dùng npm package).

Ví dụ, một tổ chức có thể phát triển một AuthModule quản lý việc xác thực bằng cách triển khai các chính sách của công ty áp dụng cho mọi ứng dụng nội bộ. Thay vì build module đó riêng cho từng ứng dụng, hoặc đóng gói vật lý code bằng npm và yêu cầu từng dự án cài đặt nó, một monorepo có thể định nghĩa module này như một library. Khi được tổ chức theo cách này, mọi bên tiêu thụ (consumer) của library module đều thấy phiên bản cập nhật nhất của AuthModule ngay khi nó được commit. Điều này có thể mang lại lợi ích đáng kể cho việc điều phối phát triển component và lắp ráp, cũng như đơn giản hóa việc test end-to-end.

Tạo library (Creating libraries)

Bất kỳ chức năng nào phù hợp để tái sử dụng đều là ứng viên để quản lý dưới dạng library. Quyết định thứ gì nên là library, và thứ gì nên là một phần của ứng dụng, là một quyết định thiết kế kiến trúc. Việc tạo library không chỉ đơn giản là copy code từ một ứng dụng có sẵn sang một library mới. Khi được đóng gói thành library, code của library phải được tách rời (decoupled) khỏi ứng dụng. Điều này có thể đòi hỏi nhiều thời gian hơn ngay từ đầu và buộc bạn phải đưa ra một số quyết định thiết kế mà bạn có thể không gặp phải với code liên kết chặt chẽ hơn. Nhưng công sức bỏ thêm này có thể được đền đáp khi library có thể được dùng để lắp ráp ứng dụng nhanh hơn trên nhiều ứng dụng khác nhau.

Để bắt đầu tạo một library, chạy lệnh sau:

$ nest g library my-library

Khi bạn chạy lệnh này, schematic library sẽ hỏi bạn về một prefix (còn gọi là alias) cho library:

What prefix would you like to use for the library (default: @app)?

Lệnh này tạo một dự án mới trong workspace của bạn tên là my-library. Một dự án kiểu library, giống như dự án kiểu ứng dụng, được sinh vào một folder có tên bằng một schematic. Library được quản lý dưới folder libs ở root của monorepo. Nest tạo folder libs vào lần đầu tiên một library được tạo.

Các file được sinh ra cho một library hơi khác so với các file sinh ra cho một ứng dụng. Đây là nội dung folder libs sau khi chạy lệnh trên:

libs
my-library
src
index.ts
my-library.module.ts
my-library.service.ts
tsconfig.lib.json

File nest-cli.json sẽ có thêm một entry mới cho library dưới key "projects":

...
{
    "my-library": {
      "type": "library",
      "root": "libs/my-library",
      "entryFile": "index",
      "sourceRoot": "libs/my-library/src",
      "compilerOptions": {
        "tsConfigPath": "libs/my-library/tsconfig.lib.json"
      }
}
...

Có hai điểm khác biệt trong metadata nest-cli.json giữa library và ứng dụng:

  • thuộc tính "type" được đặt là "library" thay vì "application"
  • thuộc tính "entryFile" được đặt là "index" thay vì "main"

Những khác biệt này giúp quá trình build xử lý library đúng cách. Ví dụ, một library export các hàm của nó thông qua file index.js.

Giống như dự án kiểu ứng dụng, mỗi library có file tsconfig.lib.json riêng, kế thừa từ file tsconfig.json gốc (toàn monorepo). Bạn có thể chỉnh sửa file này nếu cần, để cung cấp các option compiler riêng cho library.

Bạn có thể build library bằng lệnh CLI:

$ nest build my-library

Dùng library (Using libraries)

Với các file cấu hình được sinh tự động sẵn có, việc dùng library khá đơn giản. Làm sao để chúng ta import MyLibraryService từ library my-library vào ứng dụng my-project?

Trước tiên, lưu ý rằng dùng module của library cũng giống như dùng bất kỳ module Nest nào khác. Điều mà monorepo làm là quản lý đường dẫn (path) theo cách khiến việc import library và sinh build trở nên trong suốt. Để dùng MyLibraryService, ta cần import module khai báo nó. Ta có thể chỉnh sửa my-project/src/app.module.ts như sau để import MyLibraryModule.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller.js';
import { AppService } from './app.service.js';
import { MyLibraryModule } from '@app/my-library';

@Module({
  imports: [MyLibraryModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Lưu ý ở trên, chúng ta đã dùng một path alias là @app trong dòng import của ES module, đây chính là prefix chúng ta đã cung cấp cùng lệnh nest g library ở trên. Bên dưới, Nest xử lý việc này thông qua tsconfig path mapping. Khi thêm một library, Nest cập nhật key "paths" trong file tsconfig.json (toàn monorepo) như sau:

"paths": {
    "@app/my-library": [
        "libs/my-library/src"
    ],
    "@app/my-library/*": [
        "libs/my-library/src/*"
    ]
}

Cảnh báo Việc mapping paths chỉ được TypeScript compiler hiểu. Các test runner tự phân giải module theo cách riêng — nếu bạn chạy e2e test bằng Jest, hãy phản chiếu (mirror) từng alias trong option moduleNameMapper của cấu hình Jest cho e2e, ví dụ: "moduleNameMapper": { "^@app/my-library(|/.*)$": "<rootDir>/../../libs/my-library/src/$1" }. Nếu không, các import qua alias sẽ lỗi khi chạy test dù dự án vẫn compile bình thường.

Ghi chú người dịch Đây chính là cạm bẫy hay gặp nhất khi mới dùng Nest monorepo: TypeScript compile qua được (vì nó đọc tsconfig.json), nhưng Jest lại crash với lỗi “Cannot find module ‘@app/my-library’” vì Jest không biết gì về paths của TypeScript — nó cần cấu hình moduleNameMapper riêng. Tương tự như khi bạn cấu hình alias import trong Webpack/Vite nhưng quên đồng bộ với Jest ở phía frontend.

Vậy, tóm lại, sự kết hợp giữa tính năng monorepo và library đã giúp việc đưa các library module vào ứng dụng trở nên dễ dàng và trực quan.

Cơ chế tương tự cho phép bạn build và deploy các ứng dụng có ghép library. Sau khi bạn đã import MyLibraryModule, chạy nest build sẽ tự động xử lý toàn bộ việc phân giải module và đóng gói ứng dụng cùng với mọi library dependency, sẵn sàng cho việc deploy. Compiler mặc định cho monorepo là Rspack, nên file phân phối kết quả là một file duy nhất gộp tất cả các file JavaScript đã transpile lại thành một file. Bạn cũng có thể chuyển sang tsc như mô tả tại đâ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.