Skip to content

Instantly share code, notes, and snippets.

@Deathspike
Last active March 28, 2023 08:06
Show Gist options
  • Save Deathspike/faba48728ba3c3d986f10e3f63ada372 to your computer and use it in GitHub Desktop.
Save Deathspike/faba48728ba3c3d986f10e3f63ada372 to your computer and use it in GitHub Desktop.
nestjs response validation example (using express)
import * as app from '.';
import * as api from '@nestjs/common';
@api.Controller()
export class TestController {
@api.Get()
@app.ResponseValidator(app.TestDto)
get() {
return new app.TestDto();
}
}
import * as clv from 'class-validator';
export class TestDto {
@clv.IsString()
readonly name!: string;
}
import * as app from '.';
import * as api from '@nestjs/common';
import * as clt from 'class-transformer';
export function ResponseValidator<T>(cls: api.Type<T>, options?: clt.ClassTransformOptions) {
return api.UseInterceptors(new app.ResponseValidatorInterceptor(cls, options));
}
import * as api from '@nestjs/common';
import * as clt from 'class-transformer';
import * as clv from 'class-validator';
import * as rxo from 'rxjs/operators';
import express from 'express';
export class ResponseValidatorInterceptor<T> implements api.NestInterceptor {
private readonly _cls: api.Type<T>;
private readonly _options?: clt.ClassTransformOptions;
constructor(cls: api.Type<T>, options?: clt.ClassTransformOptions) {
this._cls = cls;
this._options = options;
}
intercept(context: api.ExecutionContext, next: api.CallHandler) {
return next.handle().pipe(rxo.map(async (value: T) => {
const validationErrors = await (value instanceof this._cls
? clv.validate(value)
: clv.validate(clt.plainToClass(this._cls, value, this._options)));
if (validationErrors.length) {
const errors = flatten(validationErrors);
const message = 'Validation failed';
const response = context.switchToHttp().getResponse<express.Response>();
const statusCode = 500;
response.status(statusCode);
return {statusCode, message, errors, value};
} else {
return value;
}
}));
}
}
function flatten(errors: Array<clv.ValidationError>, previousProperty?: string) {
const result: Array<{constraints: Record<string, string>, property: string}> = [];
errors.forEach(error => map(error, result, previousProperty));
return result;
}
function map(error: clv.ValidationError, result: Array<{constraints: Record<string, string>, property: string}>, previousProperty?: string) {
const property = previousProperty ? `${previousProperty}.${error.property}` : error.property;
if (error.constraints) result.push(({property, constraints: error.constraints}))
if (error.children) result.push(...flatten(error.children, property));
}
@Deathspike
Copy link
Author

This response validator is an interceptor that validates your return value, and returns a 500 status when the return value does not match your schema. It makes sure that your API cannot send incorrect data to your consumers. Use as you see fit; MIT license.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment