Nest có hai mode để tổ chức code:
- standard mode: hữu ích để xây dựng các ứng dụng tập trung vào một dự án riêng lẻ, có dependency và cấu hình riêng, không cần tối ưu cho việc share module hay tối ưu các build phức tạp. Đây là mode mặc định.
- monorepo mode: mode này xem các code artifact như một phần của một monorepo gọn nhẹ, và có thể phù hợp hơn cho các nhóm nhiều developer và/hoặc môi trường nhiều dự án. Nó tự động hóa một phần quá trình build để dễ dàng tạo và ghép các component dạng module, thúc đẩy việc tái sử dụng code, giúp integration testing dễ hơn, giúp dễ share các artifact toàn dự án như lint rule và các chính sách cấu hình khác, và dễ dùng hơn các giải pháp thay thế như Git submodule. Monorepo mode sử dụng khái niệm workspace, được biểu diễn trong file
nest-cli.json, để điều phối mối quan hệ giữa các thành phần của monorepo.
Ghi chú người dịch Nếu bạn từng dùng Lerna/Nx/Turborepo hay Go workspaces, ý tưởng ở đây tương tự: nhiều “dự án” độc lập (app hoặc library) sống chung một chỗ, chia sẻ node_modules, config lint, tsconfig gốc — nhưng vẫn build/run tách biệt theo từng project. Điểm khác biệt là Nest CLI tự quản lý việc này qua nest-cli.json mà không cần cài thêm công cụ monorepo bên ngoài.
Cần lưu ý rằng hầu như toàn bộ tính năng của Nest đều độc lập với mode tổ chức code bạn chọn. Tác động duy nhất của lựa chọn này là cách các dự án của bạn được cấu thành và cách build artifact được sinh ra. Mọi chức năng khác, từ CLI đến các core module hay add-on module, đều hoạt động như nhau ở cả hai mode.
Ngoài ra, bạn có thể dễ dàng chuyển từ standard mode sang monorepo mode bất cứ lúc nào, nên bạn có thể trì hoãn quyết định này cho đến khi lợi ích của cách này hay cách kia trở nên rõ ràng hơn.
Standard mode
Khi bạn chạy nest new, một dự án (project) mới được tạo ra cho bạn bằng một schematic tích hợp sẵn. Nest thực hiện các bước sau:
- Tạo một folder mới, tương ứng với đối số
namebạn cung cấp chonest new - Điền vào folder đó các file mặc định tương ứng với một ứng dụng Nest tối giản ở mức cơ bản. Bạn có thể xem các file này tại repository typescript-starter.
- Cung cấp thêm các file như
nest-cli.json,package.jsonvàtsconfig.jsonđể cấu hình và kích hoạt các công cụ khác nhau phục vụ việc biên dịch, test và chạy ứng dụng của bạn.
Từ đó, bạn có thể chỉnh sửa các file khởi tạo, thêm component mới, thêm dependency (ví dụ, npm install), và tiếp tục phát triển ứng dụng như được đề cập trong phần còn lại của tài liệu này.
Monorepo mode
Để bật monorepo mode, bạn bắt đầu từ một cấu trúc standard mode, rồi thêm dự án (project) vào. Một dự án có thể là một ứng dụng (application) đầy đủ (thêm vào workspace bằng lệnh nest generate app) hoặc một library (thêm vào workspace bằng lệnh nest generate library). Chúng ta sẽ bàn chi tiết về các loại project component này ở phần dưới. Điểm mấu chốt cần lưu ý ngay là chính hành động thêm một dự án vào một cấu trúc standard mode hiện có sẽ chuyển đổi nó thành monorepo mode. Hãy xem một ví dụ.
Nếu ta chạy:
$ nest new my-project
Chúng ta đã tạo ra một cấu trúc standard mode, với cấu trúc folder trông như sau:
Ta có thể chuyển đổi cấu trúc này sang monorepo mode như sau:
$ cd my-project
$ nest generate app my-app
Lúc này, nest sẽ chuyển đổi cấu trúc hiện có thành cấu trúc monorepo mode. Việc này dẫn đến một số thay đổi quan trọng. Cấu trúc folder giờ trông như sau:
Schematic generate app đã tổ chức lại code — chuyển mỗi dự án ứng dụng (application) vào folder apps, và thêm một file tsconfig.app.json riêng cho từng dự án vào folder gốc của dự án đó. Ứng dụng my-project ban đầu của chúng ta trở thành dự án mặc định (default project) của monorepo, và giờ ngang hàng với my-app vừa được thêm vào, nằm dưới folder apps. Chúng ta sẽ nói về default project ở phần dưới.
Cảnh báo Việc chuyển đổi một cấu trúc standard mode sang monorepo chỉ hoạt động đúng với các dự án tuân theo đúng cấu trúc chuẩn của Nest. Cụ thể, trong quá trình chuyển đổi, schematic sẽ cố di chuyển các folder src và test trong folder dự án vào bên dưới folder apps ở root. Nếu dự án không dùng cấu trúc này, việc chuyển đổi sẽ thất bại hoặc cho kết quả không đáng tin cậy.
Các dự án trong workspace (Workspace projects)
Một monorepo dùng khái niệm workspace để quản lý các thực thể thành viên của nó. Workspace được cấu thành từ các dự án (project). Một dự án có thể là:
- một ứng dụng (application): một ứng dụng Nest đầy đủ, bao gồm file
main.tsđể bootstrap ứng dụng. Ngoại trừ các cân nhắc về compile và build, một dự án kiểu ứng dụng trong workspace về mặt chức năng giống hệt một ứng dụng trong cấu trúc standard mode. - một library: library là cách đóng gói một tập tính năng dùng chung (module, provider, controller, v.v.) có thể được dùng lại trong các dự án khác. Một library không thể tự chạy độc lập, và không có file
main.ts. Đọc thêm về library tại đây.
Mọi workspace đều có một dự án mặc định (default project) (nên là một dự án kiểu ứng dụng). Dự án này được xác định bởi thuộc tính "root" ở cấp cao nhất trong file nest-cli.json, trỏ đến root của default project (xem CLI properties bên dưới để biết thêm chi tiết). Thông thường, đây là ứng dụng standard mode bạn đã bắt đầu, và sau đó chuyển đổi thành monorepo bằng nest generate app. Khi bạn làm theo các bước này, thuộc tính này sẽ được điền tự động.
Default project được các lệnh nest như nest build và nest start sử dụng khi không có tên dự án nào được cung cấp.
Ghi chú người dịch Default project ở đây tương tự khái niệm “default target” trong một Makefile hay docker-compose.yml — khi bạn không chỉ định target/service cụ thể, công cụ sẽ tự chọn một cái mặc định thay vì báo lỗi hay chạy tất cả. Điều này giúp trải nghiệm CLI mượt hơn cho project chính mà bạn làm việc hàng ngày trong monorepo.
Ví dụ, với cấu trúc monorepo ở trên, chạy
$ nest start
sẽ khởi động ứng dụng my-project. Để khởi động my-app, ta dùng:
$ nest start my-app
Applications (Ứng dụng)
Các dự án kiểu ứng dụng, hay còn gọi thân mật là “application”, là các ứng dụng Nest hoàn chỉnh mà bạn có thể chạy và deploy. Bạn tạo một dự án kiểu ứng dụng bằng nest generate app.
Lệnh này tự động sinh ra một bộ khung dự án, bao gồm các folder src và test chuẩn từ typescript starter. Khác với standard mode, một dự án ứng dụng trong monorepo không có bất kỳ dependency package nào (package.json) hay các artifact cấu hình dự án khác như .prettierrc và file cấu hình lint của workspace. Thay vào đó, các dependency và file cấu hình dùng chung toàn monorepo sẽ được sử dụng.
Tuy nhiên, schematic vẫn sinh ra một file tsconfig.app.json riêng cho dự án, đặt ở folder gốc của dự án. File cấu hình này tự động thiết lập các option build phù hợp, bao gồm cả việc đặt đúng folder output biên dịch. File này kế thừa (extends) từ file tsconfig.json ở cấp cao nhất (của monorepo), nên bạn có thể quản lý cấu hình chung cho toàn monorepo, nhưng vẫn override được nếu cần ở cấp dự án.
Libraries
Như đã đề cập, các dự án kiểu library, hay đơn giản là “library”, là các gói component Nest cần được ghép vào ứng dụng để có thể chạy. Bạn tạo một dự án kiểu library bằng nest generate library. Việc quyết định thứ gì nên nằm trong library là một quyết định thiết kế kiến trúc. Chúng ta sẽ bàn sâu hơn về library trong chương libraries.
CLI properties
Nest lưu metadata cần thiết để tổ chức, build và deploy cả cấu trúc dự án standard lẫn monorepo trong file nest-cli.json. Nest tự động thêm và cập nhật file này khi bạn thêm dự án, nên thường bạn không cần bận tâm hay chỉnh sửa nội dung của nó. Tuy vậy, có một số cài đặt bạn có thể muốn chỉnh tay, nên nắm sơ qua nội dung file này sẽ hữu ích.
Sau khi thực hiện các bước ở trên để tạo một monorepo, file nest-cli.json của chúng ta trông như sau:
{
"collection": "@nestjs/schematics",
"sourceRoot": "apps/my-project/src",
"monorepo": true,
"root": "apps/my-project",
"compilerOptions": {
"builder": "rspack",
"tsConfigPath": "apps/my-project/tsconfig.app.json"
},
"projects": {
"my-project": {
"type": "application",
"root": "apps/my-project",
"entryFile": "main",
"sourceRoot": "apps/my-project/src",
"compilerOptions": {
"tsConfigPath": "apps/my-project/tsconfig.app.json"
}
},
"my-app": {
"type": "application",
"root": "apps/my-app",
"entryFile": "main",
"sourceRoot": "apps/my-app/src",
"compilerOptions": {
"tsConfigPath": "apps/my-app/tsconfig.app.json"
}
}
}
}
File này được chia thành các phần:
- một phần chung với các thuộc tính cấp cao nhất kiểm soát cài đặt cho toàn bộ standard và monorepo
- một thuộc tính cấp cao nhất (
"projects") chứa metadata về từng dự án. Phần này chỉ tồn tại đối với cấu trúc monorepo mode.
Các thuộc tính cấp cao nhất bao gồm:
"collection": trỏ đến bộ schematic dùng để sinh component; thông thường bạn không nên thay đổi giá trị này"sourceRoot": trỏ đến root của source code cho dự án duy nhất trong cấu trúc standard mode, hoặc dự án mặc định trong cấu trúc monorepo mode"compilerOptions": một map với key chỉ định các option compiler và value chỉ định giá trị của option đó; xem chi tiết bên dưới"generateOptions": một map với key chỉ định các option generate toàn cục và value chỉ định giá trị của option đó; xem chi tiết bên dưới"monorepo": (chỉ dành cho monorepo) đối với cấu trúc monorepo mode, giá trị này luôn làtrue"root": (chỉ dành cho monorepo) trỏ đến root dự án của dự án mặc định
Global compiler options (Option compiler toàn cục)
Các thuộc tính này chỉ định compiler sẽ dùng cũng như nhiều option khác nhau ảnh hưởng đến mọi bước compile, dù là qua nest build hay nest start, và bất kể compiler nào, dù là tsc, swc, hay Rspack.
| Tên thuộc tính | Kiểu giá trị | Mô tả |
|---|---|---|
webpack |
boolean | Flag cũ đã deprecated dùng cho compile bằng webpack. Nên dùng builder thay thế. |
tsConfigPath |
string | (chỉ dành cho monorepo) Trỏ đến file chứa cấu hình tsconfig.json sẽ được dùng khi nest build hoặc nest start được gọi mà không có option project (ví dụ, khi dự án mặc định được build hoặc start). |
webpackConfigPath |
string | Đường dẫn cũ đã deprecated cho option webpack. Nên dùng cấu hình dành riêng cho builder mà setup hiện tại của bạn hỗ trợ. |
deleteOutDir |
boolean | Nếu là true, mỗi khi compiler được gọi, nó sẽ xóa thư mục output biên dịch trước (theo cấu hình trong tsconfig.json, mặc định là ./dist). |
assets |
array | Kích hoạt tự động phân phối các asset không phải TypeScript mỗi khi bắt đầu một bước compile (việc phân phối asset không diễn ra trong các lần compile tăng dần ở chế độ --watch). Xem bên dưới để biết chi tiết. |
watchAssets |
boolean | Nếu là true, chạy ở watch-mode, theo dõi tất cả asset không phải TypeScript. (Để kiểm soát chi tiết hơn các asset cần theo dõi, xem phần Assets bên dưới). |
manualRestart |
boolean | Nếu là true, bật phím tắt rs để khởi động lại server thủ công. Giá trị mặc định là false. |
builder |
string/object | Chỉ định cho CLI biết dùng builder nào để compile dự án (tsc, swc, hoặc rspack). Để tùy chỉnh hành vi của builder, bạn có thể truyền một object chứa hai thuộc tính: type (tsc, swc, hoặc rspack) và options. |
typeCheck |
boolean | Nếu là true, bật type checking cho các dự án dùng SWC (khi builder là swc). Giá trị mặc định là false. |
emitDeclarations |
boolean | Nếu là true, sinh các file declaration (.d.ts) khi dùng SWC builder. Giá trị mặc định là false. |
includeLibraryAssets |
array | (chỉ dành cho monorepo) Danh sách tên các dự án library mà asset của chúng cũng nên được copy khi build ứng dụng này. |
Global generate options (Option generate toàn cục)
Các thuộc tính này chỉ định các option generate mặc định được lệnh nest generate sử dụng.
| Tên thuộc tính | Kiểu giá trị | Mô tả |
|---|---|---|
spec |
boolean hoặc object | Nếu giá trị là boolean, giá trị true sẽ bật sinh file spec theo mặc định và false sẽ tắt. Một flag truyền trên dòng lệnh CLI sẽ override cài đặt này, cũng như cài đặt generateOptions riêng của dự án (chi tiết bên dưới). Nếu giá trị là object, mỗi key đại diện cho tên một schematic, và giá trị boolean quyết định việc sinh spec mặc định có được bật/tắt cho schematic cụ thể đó hay không. |
flat |
boolean | Nếu là true, tất cả các lệnh generate sẽ sinh ra cấu trúc phẳng (flat) |
Ví dụ sau dùng giá trị boolean để chỉ định rằng việc sinh file spec nên bị tắt theo mặc định cho tất cả dự án:
{
"generateOptions": {
"spec": false
},
...
}
Ví dụ sau dùng giá trị boolean để chỉ định rằng việc sinh file phẳng (flat) nên là mặc định cho tất cả dự án:
{
"generateOptions": {
"flat": true
},
...
}
Trong ví dụ sau, việc sinh file spec bị tắt chỉ với schematic service (ví dụ, nest generate service...):
{
"generateOptions": {
"spec": {
"service": false
}
},
...
}
Cảnh báo Khi chỉ định spec dưới dạng object, key cho schematic sinh code hiện chưa hỗ trợ tự động xử lý alias. Điều này có nghĩa là nếu bạn chỉ định key kiểu service: false và thử sinh một service qua alias s, spec vẫn sẽ được sinh ra. Để đảm bảo cả tên schematic thông thường lẫn alias đều hoạt động đúng như mong muốn, hãy chỉ định cả tên lệnh thông thường lẫn alias, như bên dưới.
{
"generateOptions": {
"spec": {
"service": false,
"s": false
}
},
...
}
Project-specific generate options (Option generate riêng cho từng dự án)
Ngoài việc cung cấp option generate toàn cục, bạn cũng có thể chỉ định option generate riêng cho từng dự án. Các option generate riêng của dự án tuân theo đúng định dạng như option generate toàn cục, nhưng được chỉ định trực tiếp trên từng dự án.
Option generate riêng của dự án sẽ override option generate toàn cục.
{
"projects": {
"cats-project": {
"generateOptions": {
"spec": {
"service": false
}
},
...
}
},
...
}
Cảnh báo Thứ tự ưu tiên cho các option generate như sau. Các option chỉ định trên dòng lệnh CLI có độ ưu tiên cao hơn các option riêng của dự án. Option riêng của dự án override option toàn cục.
Specified compiler (Compiler được chỉ định)
Lý do có compiler mặc định khác nhau là vì với các dự án lớn (ví dụ, phổ biến hơn trong monorepo), Rspack có thể mang lại lợi thế đáng kể về thời gian build và về việc tạo ra một file duy nhất gộp tất cả các component của dự án lại với nhau. Nếu bạn muốn sinh ra các file riêng lẻ, hãy đặt builder thành tsc hoặc swc thay thế.
Assets
Quá trình biên dịch TypeScript tự động phân phối output của compiler (file .js và .d.ts) đến thư mục output đã chỉ định. Việc phân phối các file không phải TypeScript, như file .graphql, images, file .html và các asset khác, cũng có thể tiện lợi. Điều này cho phép bạn xem nest build (và bất kỳ bước compile ban đầu nào) như một bước development build gọn nhẹ, nơi bạn có thể chỉnh sửa file không phải TypeScript và compile, test lặp đi lặp lại.
Các asset cần được đặt trong folder src, nếu không chúng sẽ không được copy.
Giá trị của key assets nên là một mảng các phần tử chỉ định các file cần phân phối. Các phần tử có thể là chuỗi đơn giản với spec file kiểu glob, ví dụ:
"assets": ["**/*.graphql"],
"watchAssets": true,
Để kiểm soát chi tiết hơn, các phần tử có thể là object với các key sau:
"include": spec file kiểuglobcho các asset cần phân phối"exclude": spec file kiểuglobcho các asset cần được loại trừ khỏi danh sáchinclude"outDir": một chuỗi chỉ định đường dẫn (tương đối so với folder gốc) nơi các asset sẽ được phân phối đến. Mặc định là cùng thư mục output đã cấu hình cho output của compiler."watchAssets": boolean; nếu làtrue, chạy ở watch mode theo dõi các asset đã chỉ định
Ví dụ:
"assets": [
{ "include": "**/*.graphql", "exclude": "**/omitted.graphql", "watchAssets": true },
]
Cảnh báo Việc đặt watchAssets trong thuộc tính compilerOptions ở cấp cao nhất sẽ override mọi cài đặt watchAssets bên trong thuộc tính assets.
Project properties (Thuộc tính dự án)
Phần tử này chỉ tồn tại đối với cấu trúc monorepo mode. Thông thường bạn không nên chỉnh sửa các thuộc tính này, vì chúng được Nest dùng để định vị các dự án và option cấu hình của chúng trong monorepo.