☰ 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ữ
Kỹ thuật (Techniques)

Model-View-Controller

Bản dịch từ docs.nestjs.com/techniques/mvc

Theo mặc định, Nest dùng thư viện Express bên dưới. Do đó, mọi kỹ thuật để dùng pattern MVC (Model-View-Controller) trong Express đều áp dụng được cho Nest.

Đầu tiên, hãy scaffold một ứng dụng Nest đơn giản bằng công cụ CLI:

$ npm i -g @nestjs/cli
$ nest new project

Để tạo một app MVC, chúng ta cũng cần một template engine để render các HTML view:

$ npm install --save hbs

Chúng ta dùng engine hbs (Handlebars), nhưng bạn có thể dùng bất kỳ engine nào phù hợp với yêu cầu của bạn. Sau khi cài đặt xong, ta cần cấu hình Express instance bằng code sau:

import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'node:path';
import { AppModule } from './app.module.js';

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(
    AppModule,
  );

  app.useStaticAssets(join(import.meta.dirname, '..', 'public'));
  app.setBaseViewsDir(join(import.meta.dirname, '..', 'views'));
  app.setViewEngine('hbs');

  await app.listen(process.env.PORT ?? 3000);
}
await bootstrap();
import { NestFactory } from '@nestjs/core';
import { join } from 'node:path';
import { AppModule } from './app.module.js';

async function bootstrap() {
  const app = await NestFactory.create(
    AppModule,
  );

  app.useStaticAssets(join(import.meta.dirname, '..', 'public'));
  app.setBaseViewsDir(join(import.meta.dirname, '..', 'views'));
  app.setViewEngine('hbs');

  await app.listen(process.env.PORT ?? 3000);
}
await bootstrap();

Chúng ta đã báo cho Express biết rằng thư mục public sẽ dùng để lưu static asset, views sẽ chứa template, và engine hbs nên được dùng để render HTML output.

Ghi chú người dịch Về bản chất, pattern MVC ở đây không có gì đặc trưng riêng của Nest cả — Nest chỉ đứng ra tổ chức lại controller (phần “C”) theo class/decorator, còn việc render view vẫn hoàn toàn dựa vào cơ chế template engine của Express (giống hệt cách bạn dùng res.render() trong một app Express thuần, hoặc Blade trong Laravel). Nếu bạn đang xây REST API trả JSON (phần lớn use case thực tế với Nest), bạn sẽ không cần tới chương này — nó chỉ hữu ích khi bạn thực sự muốn Nest render HTML phía server (SSR truyền thống) thay vì làm backend cho SPA/frontend riêng.

Render template (Template rendering)

Bây giờ, hãy tạo một thư mục views và một template index.hbs bên trong nó. Trong template, ta sẽ in ra một message được truyền từ controller:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>App</title>
  </head>
  <body>
    {{ "{{ message }\}" }}
  </body>
</html>

Tiếp theo, mở file app.controller và thay method root() bằng code sau:

import { Get, Controller, Render } from '@nestjs/common';

@Controller()
export class AppController {
  @Get()
  @Render('index')
  root() {
    return { message: 'Hello world!' };
  }
}

Trong code này, ta chỉ định template cần dùng trong decorator @Render(), và giá trị trả về của method route handler được truyền cho template để render. Chú ý rằng giá trị trả về là một object có property message, khớp với placeholder message ta đã tạo trong template.

Trong khi ứng dụng đang chạy, mở browser và truy cập http://localhost:3000. Bạn sẽ thấy message Hello world!.

Thêm layout (Adding a layout)

Engine hbs hỗ trợ layout — các template wrapper chung mà từng view riêng lẻ sẽ được render vào trong đó. Để dùng layout, set local variable layout bằng method setLocal() (wrapper của Nest quanh app.locals của Express). Hãy sửa đoạn code trước như sau:

import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(
    AppModule,
  );

  app.setLocal('layout', 'layouts/app');
  app.useStaticAssets(join(__dirname, '..', 'public'));
  app.setBaseViewsDir(join(__dirname, '..', 'views'));
  app.setViewEngine('hbs');

  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
import { NestFactory } from '@nestjs/core';
import { join } from 'path';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(
    AppModule,
  );

  app.setLocal('layout', 'layouts/app');
  app.useStaticAssets(join(__dirname, '..', 'public'));
  app.setBaseViewsDir(join(__dirname, '..', 'views'));
  app.setViewEngine('hbs');

  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

Tiếp theo, tạo một thư mục layouts và thêm file app.hbs với nội dung sau:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>App</title>
  </head>
  <body>
    {{{body}}}
  </body>
</html>

Sau đó, cập nhật file index.hbs thành:

{{ "{{ message }\}" }}

Cấu trúc file kết quả sẽ trông như sau:

views
layouts
app.hbs
index.hbs

Render template động (Dynamic template rendering)

Nếu logic ứng dụng cần quyết định động (dynamically) template nào sẽ được render, ta nên dùng decorator @Res(), và cung cấp tên view trong route handler, thay vì trong decorator @Render():

Gợi ý Khi Nest phát hiện decorator @Res(), nó sẽ inject response object đặc thù của thư viện (library-specific). Ta có thể dùng object này để render template một cách động. Tìm hiểu thêm về API của response object tại đây.

import { Get, Controller, Res, Render } from '@nestjs/common';
import { Response } from 'express';
import { AppService } from './app.service.js';

@Controller()
export class AppController {
  constructor(private appService: AppService) {}

  @Get()
  root(@Res() res: Response) {
    return res.render(
      this.appService.getViewName(),
      { message: 'Hello world!' },
    );
  }
}

Ví dụ (Example)

Một ví dụ hoạt động thực tế có sẵn tại đây.

Fastify

Như đã đề cập trong chương này, ta có thể dùng bất kỳ HTTP provider tương thích nào cùng với Nest. Một trong số đó là Fastify. Để tạo một ứng dụng MVC với Fastify, cài các package sau:

$ npm i --save @fastify/static @fastify/view handlebars

Các bước tiếp theo hầu như giống với quy trình đã dùng với Express, chỉ có một vài khác biệt nhỏ đặc thù cho platform này. Sau khi cài đặt xong, mở file main.ts và cập nhật nội dung của nó:

import { NestFactory } from '@nestjs/core';
import { NestFastifyApplication, FastifyAdapter } from '@nestjs/platform-fastify';
import { AppModule } from './app.module.js';
import { join } from 'node:path';
import Handlebars from 'handlebars';

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
  );
  app.useStaticAssets({
    root: join(import.meta.dirname, '..', 'public'),
    prefix: '/public/',
  });
  app.setViewEngine({
    engine: {
      handlebars: Handlebars,
    },
    templates: join(import.meta.dirname, '..', 'views'),
  });
  await app.listen(process.env.PORT ?? 3000);
}
await bootstrap();
import { NestFactory } from '@nestjs/core';
import { FastifyAdapter } from '@nestjs/platform-fastify';
import { AppModule } from './app.module.js';
import { join } from 'node:path';
import Handlebars from 'handlebars';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, new FastifyAdapter());
  app.useStaticAssets({
    root: join(import.meta.dirname, '..', 'public'),
    prefix: '/public/',
  });
  app.setViewEngine({
    engine: {
      handlebars: Handlebars,
    },
    templates: join(import.meta.dirname, '..', 'views'),
  });
  await app.listen(process.env.PORT ?? 3000);
}
await bootstrap();

API của Fastify có một vài khác biệt, nhưng kết quả cuối cùng của các lệnh gọi method này là như nhau. Một khác biệt đáng chú ý là khi dùng Fastify, tên template bạn truyền vào decorator @Render() phải bao gồm cả phần đuôi file (file extension).

Đây là cách bạn thiết lập nó:

import { Get, Controller, Render } from '@nestjs/common';

@Controller()
export class AppController {
  @Get()
  @Render('index.hbs')
  root() {
    return { message: 'Hello world!' };
  }
}

Ngoài ra, bạn có thể dùng decorator @Res() để inject trực tiếp response và chỉ định view muốn render, như bên dưới:

import { Res } from '@nestjs/common';
import { FastifyReply } from 'fastify';

@Get()
root(@Res() res: FastifyReply) {
  return res.view('index.hbs', { title: 'Hello world!' });
}

Trong khi ứng dụng đang chạy, mở browser và truy cập http://localhost:3000. Bạn sẽ thấy message Hello world!.

Ví dụ (Example)

Một ví dụ hoạt động thực tế có sẵn 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.