☰ 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)

Streaming file (Streaming files)

Bản dịch từ docs.nestjs.com/techniques/streaming-files

Lưu ý Chương này trình bày cách bạn có thể streaming file từ HTTP application. Các ví dụ dưới đây không áp dụng cho ứng dụng GraphQL hoặc Microservice.

Có những lúc bạn muốn trả về một file từ REST API cho client. Để làm điều này với Nest, thông thường bạn sẽ làm như sau:

@Controller('file')
export class FileController {
  @Get()
  getFile(@Res() res: Response) {
    const file = createReadStream(join(process.cwd(), 'package.json'));
    file.pipe(res);
  }
}

Nhưng làm theo cách này thì bạn sẽ mất khả năng dùng interceptor logic chạy sau controller (post-controller interceptor). Để xử lý vấn đề này, bạn có thể trả về một instance của StreamableFile và bên dưới, framework sẽ tự lo việc pipe response.

Ghi chú người dịch Cũng giống Express, nếu bạn tự res.pipe() như đoạn code trên, response coi như đã được gửi trực tiếp qua tay bạn — interceptor đăng ký ở tầng NestJS (transform response, logging response, v.v.) sẽ không có cơ hội can thiệp vào dữ liệu trả về, vì Nest không còn kiểm soát response nữa. Trả về StreamableFile giúp bạn giữ được luồng xử lý chuẩn của Nest (qua interceptor) mà vẫn stream được file.

Class StreamableFile (Streamable File class)

StreamableFile là một class giữ (hold) stream sẽ được trả về. Để tạo một StreamableFile mới, bạn truyền vào constructor của StreamableFile một Buffer hoặc một Stream.

Gợi ý Class StreamableFile có thể import từ @nestjs/common.

Hỗ trợ đa nền tảng (Cross-platform support)

Fastify, theo mặc định, đã hỗ trợ gửi file mà không cần gọi stream.pipe(res), nên bạn không cần dùng class StreamableFile chút nào khi dùng Fastify. Tuy nhiên, Nest hỗ trợ dùng StreamableFile trên cả hai loại nền tảng, nên nếu sau này bạn chuyển đổi qua lại giữa Express và Fastify thì cũng không cần lo về tính tương thích giữa hai engine.

Ghi chú người dịch Đây là một ví dụ điển hình của triết lý “platform-agnostic” trong Nest: cùng một đoạn code business logic (trả về StreamableFile) chạy được trên cả Express và Fastify, dù cơ chế xử lý stream bên dưới của hai adapter khác nhau. Nếu bạn viết code phụ thuộc trực tiếp vào res của Express (như ví dụ đầu bài), code đó sẽ không chạy đúng khi đổi sang Fastify.

Ví dụ (Example)

Bạn có thể xem một ví dụ đơn giản về việc trả package.json như một file thay vì JSON dưới đây, nhưng ý tưởng này áp dụng tự nhiên cho hình ảnh, tài liệu, và bất kỳ loại file nào khác.

import { Controller, Get, StreamableFile } from '@nestjs/common';
import { createReadStream } from 'node:fs';
import { join } from 'node:path';

@Controller('file')
export class FileController {
  @Get()
  getFile(): StreamableFile {
    const file = createReadStream(join(process.cwd(), 'package.json'));
    return new StreamableFile(file);
  }
}

Content type mặc định (giá trị của header response HTTP Content-Type) là application/octet-stream. Nếu bạn cần tùy chỉnh giá trị này, bạn có thể dùng option type của StreamableFile, hoặc dùng method res.set hoặc decorator @Header(), như sau:

import { Controller, Get, StreamableFile, Res } from '@nestjs/common';
import { createReadStream } from 'node:fs';
import { join } from 'node:path';
import type { Response } from 'express'; // Giả sử chúng ta đang dùng ExpressJS HTTP Adapter

@Controller('file')
export class FileController {
  @Get()
  getFile(): StreamableFile {
    const file = createReadStream(join(process.cwd(), 'package.json'));
    return new StreamableFile(file, {
      type: 'application/json',
      disposition: 'attachment; filename="package.json"',
      // Nếu bạn muốn đặt giá trị Content-Length thành một giá trị khác thay vì độ dài của file:
      // length: 123,
    });
  }

  // Hoặc thậm chí:
  @Get()
  getFileChangingResponseObjDirectly(@Res({ passthrough: true }) res: Response): StreamableFile {
    const file = createReadStream(join(process.cwd(), 'package.json'));
    res.set({
      'Content-Type': 'application/json',
      'Content-Disposition': 'attachment; filename="package.json"',
    });
    return new StreamableFile(file);
  }

  // Hoặc thậm chí:
  @Get()
  @Header('Content-Type', 'application/json')
  @Header('Content-Disposition', 'attachment; filename="package.json"')
  getFileUsingStaticValues(): StreamableFile {
    const file = createReadStream(join(process.cwd(), 'package.json'));
    return new StreamableFile(file);
  }  
}

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.