Kiểm tra yêu cầu NestJS mà không tin riêng kiểu TypeScript
Dùng lớp DTO và ValidationPipe để kiểm tra JSON lúc chạy, phân biệt instance nội bộ, đối tượng trả về và tùy chọn chuyển kiểu ngầm.

Chú thích kiểu TypeScript không kiểm tra yêu cầu JSON lúc chạy. Nếu bạn đang xây endpoint NestJS đầu tiên, hãy dùng lớp DTO cụ thể có quy tắc kiểm tra và cấu hình ValidationPipe. Sau đó quyết định endpoint sẽ từ chối giá trị sai kiểu hay chủ động chuyển nó sang kiểu khác. Đây là hai hợp đồng khác nhau dù cuối cùng handler đều nhìn thấy một number theo TypeScript.
Hướng dẫn validation của NestJS mô tả pipe và cách tích hợp với class-validator, class-transformer. Ví dụ giả định hai gói này đã được cài trong ứng dụng NestJS. Đây là endpoint đơn hàng minh họa, không phải cấu hình production của Minh đã được xác minh.
Kiểu biến mất trước khi yêu cầu tới
Xét handler nhận một interface có quantity: number. TypeScript có thể kiểm tra lời gọi trong mã ứng dụng được biên dịch, nhưng client HTTP không tham gia lần biên dịch đó. Nó có thể gửi {"quantity":"two"}, {"quantity":null} hoặc một đối tượng lồng nhau. Bộ phân tích tạo giá trị JavaScript từ các byte đã nhận.
Interface cũng không tồn tại dưới dạng constructor lúc chạy. Cơ chế validation dựa vào metadata của lớp cần một lớp có thể kiểm tra được. Hãy import DTO dưới dạng giá trị thực ở nơi framework cần metadata; import chỉ có kiểu không cung cấp constructor sau biên dịch.
DTO đơn giản sau yêu cầu SKU không rỗng và quantity là số nguyên trong khoảng cho phép:
import { IsInt, IsString, Length, Max, Min } from 'class-validator';
export class CreateOrderDto {
@IsString()
@Length(1, 80)
sku!: string;
@IsInt()
@Min(1)
@Max(100)
quantity!: number;
}
Giới hạn số lượng là chính sách ví dụ, không phải giới hạn đơn hàng phổ quát. Giới hạn độ dài chuỗi cũng không chứng minh SKU tồn tại. Đó là câu hỏi riêng cho logic nghiệp vụ về sau.
Chọn rõ cách xử lý trường lạ
Khi bật whitelist, pipe có thể bỏ thuộc tính không có decorator validation. Thêm forbidNonWhitelisted có thể khiến nó từ chối các thuộc tính đó. Với API ghi công khai, từ chối thường giúp phát hiện lỗi client rõ hơn so với âm thầm bỏ trường.
import { ValidationPipe } from '@nestjs/common';
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: false
}));
Cấu hình này chủ động không bật chuyển kiểu giá trị yêu cầu tổng quát. Số JSON 2 và chuỗi JSON "2" vẫn là hai đầu vào khác nhau dưới quy tắc số nguyên của DTO. Kiểm tra hành vi bằng phiên bản thực tế của dự án, nhất là khi đã có tùy chọn chuyển kiểu toàn cục hoặc custom pipe khác.
Whitelist dựa vào metadata validation, không dựa vào mọi thuộc tính được khai báo trong lớp. Nếu trường hợp lệ không có decorator validation, nó có thể bị coi là ngoài whitelist. Rà soát DTO cùng pipe thay vì cho rằng khai báo lớp tự nó định nghĩa ngôn ngữ yêu cầu được chấp nhận.
Pipe bảo vệ các giả định về cấu trúc của handler. Nghiệp vụ vẫn cần phân quyền và kiểm tra trạng thái bền vững.
Xem giá trị mà pipe đã cấu hình trả về
Fixture nhỏ có thể gọi trực tiếp cùng pipe với constructor DTO cụ thể. Cách này kiểm tra ranh giới validation mà không tuyên bố đã triển khai HTTP controller, authentication guard hay dịch vụ cơ sở dữ liệu. Biên dịch DTO bằng thiết lập decorator và metadata mà ứng dụng Nest dùng, đồng thời nạp reflect-metadata trước khi lớp có decorator được thực thi.
const pipe = new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: false
});
const output = await pipe.transform(
{ sku: 'sku-1', quantity: 2 },
{ type: 'body', metatype: CreateOrderDto }
);
console.log(output.quantity, output instanceof CreateOrderDto);
// With the tested configuration: 2 false
Với Nest common 11.1.6, class-validator 0.14.2 và class-transformer 0.5.1, giá trị được chấp nhận là đối tượng thường có quantity dạng số. Nó không phải instance của CreateOrderDto. DTO mô tả cấu trúc được chấp nhận, nhưng transform=false không hứa đưa instance DTO cho handler. Đừng đặt phương thức nghiệp vụ trên lớp yêu cầu rồi gọi chỉ vì chú thích kiểu của handler mang tên lớp đó.
Fixture chấp nhận quantity 1 và 100, từ chối 0, 101, số lẻ, null, quantity thiếu và chuỗi chứa số. Trường role thêm vào bị từ chối theo forbidNonWhitelisted. Đây là quy tắc của đúng cấu hình ví dụ, không phải mọi thiết lập ValidationPipe có thể có. Test xem giá trị và trạng thái ngoại lệ, không phụ thuộc câu thông báo tiếng Anh chính xác của thư viện.
Chuỗi SKU chỉ có khoảng trắng cũng vượt qua các decorator cấu trúc này. Kiểm tra độ dài không chứng minh giá trị chỉ đến mặt hàng có thật, và DTO không cắt khoảng trắng. Nếu sản phẩm cấm tên chỉ gồm khoảng trắng, hãy chọn quy tắc đó rõ ràng rồi kiểm tra. Nếu khoảng trắng được giữ như phần có nghĩa của định danh, tự cắt nó lại là lỗi khác. Bước tra SKU về sau vẫn phải quyết định định danh đúng cấu trúc có tồn tại và khả dụng cho người gọi hay không.
Mã ValidationPipe được cố định theo phiên bản tạo instance để kiểm tra ngay cả khi cờ chuyển đổi giá trị trả về là false. Với cấu hình này, nó chuyển instance đã kiểm tra về đối tượng thường. Vì vậy transform=false không bảo đảm rằng toàn bộ pipeline tuyệt đối không có chuyển đổi nào. Decorator chuyển kiểu tường minh hoặc tùy chọn enableImplicitConversion có thể đổi giá trị mà validator kiểm tra.
Trong biến thể có kiểm soát riêng, bật chuyển kiểu ngầm chấp nhận quantity là chuỗi “2” thành số 2, ngay cả khi cờ giá trị trả về là false. Chỉ đổi transform thành true mà không bật chuyển kiểu ngầm vẫn từ chối chuỗi số đó với DTO body này. Tài liệu class-transformer mô tả chuyển kiểu ngầm là tùy chọn riêng, mặc định tắt. Xem cả hai thiết lập và decorator DTO khi chẩn đoán yêu cầu được chấp nhận ngoài dự kiến.
Chuyển kiểu là quyết định của sản phẩm
Tham số query và path đến dưới dạng chuỗi. Endpoint có thể chủ động phân tích số trang thành số nguyên, nhưng cần quy định cách xử lý khoảng trắng, dấu, số thập phân và giá trị ngoài khoảng. Bộ phân tích nhận "2.5" rồi cắt thành 2 đã thay đổi yêu cầu của client.
Với body JSON, client vốn có thể gửi số và boolean đúng kiểu. Tự chuyển mọi chuỗi có thể che lỗi client. Tính truthy của JavaScript đặc biệt không phù hợp để hiểu boolean dạng chữ: chuỗi không rỗng "false" vẫn truthy. Dùng bộ phân tích có quy tắc hẹp khi boolean dạng chữ thuộc hợp đồng đầu vào.
Nếu bật transformation, hãy kiểm tra cả giá trị được chuyển tới handler lẫn phản hồi HTTP. Test chỉ xem phản hồi có thể qua dù handler nhận kiểu bất ngờ. Chuyển đổi cần một nơi chịu trách nhiệm được ghi rõ, tránh pipe, controller và service mỗi nơi diễn giải lại cùng trường theo cách khác.
Đối tượng lồng nhau cần metadata riêng
DTO có items: OrderItemDto[] không tự kiểm tra từng phần tử chỉ nhờ thuộc tính mang kiểu TypeScript đó. Kiểm tra lồng nhau cần decorator thích hợp và metadata chuyển đổi lúc chạy. Hướng dẫn NestJS mô tả các ràng buộc dựa trên lớp này cùng giới hạn của generic và interface khi thực thi.
Giữ ví dụ đầu đủ nhỏ để xem rõ trước khi thêm mảng lồng nhau. Khi thêm, kiểm tra phần tử con sai, thuộc tính con lạ, mảng rỗng và mảng quá lớn. Giới hạn byte yêu cầu trước khi dành tài nguyên dựng và kiểm tra một đồ thị đối tượng lớn.
Tài liệu đối tượng của JSON Schema là đối chiếu hữu ích khi suy nghĩ về thuộc tính bắt buộc và thuộc tính bổ sung. Thư viện DTO và tài liệu JSON Schema là hai cách triển khai hợp đồng đầu vào khác nhau; đừng cho rằng mặc định của chúng giống nhau nếu chưa kiểm tra.
Custom validator bất đồng bộ có thể tra trạng thái ngoài, nhưng việc đó tạo ranh giới lỗi khác với kiểm tra số nguyên. Tra cứu timeout không chứng minh giá trị yêu cầu không hợp lệ. Giữ kiểm tra nghiệp vụ dễ biến động trong thao tác service có chính sách timeout và lỗi rõ ràng, nhất là khi quyết định cuối phải được thực thi trong giao dịch.
Trả lỗi client dùng được
Lỗi validation nên chỉ ra trường hoặc mã ổn định mà không làm lộ tên lớp nội bộ và stack trace không cần thiết. Nếu ứng dụng tập trung hóa phản hồi Problem Details, ánh xạ ngoại lệ của pipe sang định dạng công khai đó. Giữ thông tin lỗi thật nhưng tránh khiến client phụ thuộc câu chữ tiếng Anh của một thư viện.
Thử body hợp lệ, quantity dạng chuỗi, 0, số lẻ và trường role không dự kiến. Khẳng định yêu cầu sai không gọi service tạo đơn. Sau đó thử cấu trúc hợp lệ với SKU không tồn tại và xác nhận tầng nghiệp vụ từ chối theo đường lỗi đã quy định.
Kiểm tra lúc chạy làm ranh giới trung thực: handler chỉ nhận giá trị đã qua quy tắc cấu trúc được cấu hình. Nó không chứng minh người gọi được mua mặt hàng hoặc tồn kho còn đủ lúc giao dịch commit.