I've a scenario where I need values from both values in the param and body to perform custom validation. For example, I've a route /:photoId/tag that adds a tag to a photo.
However, before it can add a tag to a photo, it has to validate whether there is already an existing tag of the same name with the photo.
I have the following route in my controller:
#Post(':photoId/tag')
#UsePipes(new ValidationPipe())
async addTag(
#Param() params: AddTagParams,
#Body() addTagDto: AddTagDto
) {
// ...
}
Since the :photoId is provided as a param and the tag is provided in the body of the request, they can't access each other in the custom validator and I can't use both pieces of information to do a check against the database:
export class IsPhotoTagExistValidator implements ValidatorConstraintInterface {
async validate(val: any, args: ValidationArguments) {
// supposed to check whether a tag of the same name already exists on photo
// val only has the value of photoId but not the name of the tag from AddTagDto in Body
}
}
export class AddTagParams{
#IsInt()
#Validate(IsPhotoTagExistValidator) // this doesn't work because IsPhotoTagExistValidator can't access tag in AddTagDto
photoId: number
}
export class AddTagDto{
#IsString()
tag: string
}
As in the example above, the val in IsPhotoTagExistValidator is only the photoId. But I need both the photoId in Param and tag name in the Body to check whether the particular photoId already has that tag.
How should I access both the Body and Param in the custom validator function? If not, how should I approach this problem?
The only solution I have found so far was derived from this comment https://github.com/nestjs/nest/issues/528#issuecomment-497020970
context.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '#nestjs/common'
import { Observable } from 'rxjs'
/**
* Injects request data into the context, so that the ValidationPipe can use it.
*/
#Injectable()
export class ContextInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
next: CallHandler
): Observable<any> {
const request = context.switchToHttp().getRequest();
request.body.context = {
params: request.params,
query: request.query,
user: request.user,
};
return next.handle()
}
}
main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new ContextInterceptor());
// ...
}
If you use {whitelist: true} in ValidationPipe params you will need to allow context in your Dto objects.
this can be done by extending such Dto:
context-aware.dto.ts
import { Allow } from 'class-validator';
export class ContextAwareDto {
#Allow()
context?: {
params: any,
query: any,
user: any,
}
}
After this, you will be able to access request data when validating body in custom validator via validationArguments.object.context
You can easily adjust the above to access the context when validating params or query, although I find it sufficient to have this only during body validation.
Related
I want to define whether a function should contain an argument via an interface. The library I'm developing calls for many different methods to be generated, and hardcoding those methods would require too much maintenance; so I figured that types would be a good place to define such things.
Perhaps this is best explained with code. Here's a library that abstracts some rest API:
interface RequestInterface {
endpoint: string
body?: unknown
}
interface GetPosts extends RequestInterface {
endpoint: '/posts'
body: never
}
interface CreatePost extends RequestInterface {
endpoint: '/posts'
body: string
}
function Factory<R extends RequestInterface> (endpoint: R['endpoint']) {
return (body?: R['body']): void => {
console.log(`Hitting ${endpoint} with ${body}`)
}
}
const myLibrary = {
getPosts: Factory<GetPosts>('/posts'),
createPosts: Factory<CreatePost>('/posts'),
}
myLibrary.getPosts('something') // => Correctly errors
myLibrary.createPosts(999) // => Correctly errors
myLibrary.createPosts() // => I want this to error
In the above, I'm defining the endpoint and body of a particular type of request in my interfaces. Although the TypeScript compiler correctly guards me against passing the wrong argument types, it doesn't guard me against not passing a value when one is required.
I understand why TypeScript doesn't error (because the method defined in factory can be undefined according to my typings), but I figured the above code was a good way of describing what I want to achieve: a quick, declarative library of methods which satisfy a particular type.
A Possible Solution
If I'm willing to extend my interfaces from two separate interfaces (one or the other) then I can achieve something close to what I want using Construct Signatures:
interface RequestInterface {
endpoint: string
call: () => void
}
interface RequestInterfaceWithBody {
endpoint: string
call: {
(body: any): void
}
}
interface GetPosts extends RequestInterface {
endpoint: '/posts'
}
interface CreatePost extends RequestInterfaceWithBody {
endpoint: '/posts'
call: {
(body: string): void
}
}
function Factory<R extends RequestInterface|RequestInterfaceWithBody> (endpoint: R['endpoint']): R['call'] {
return (body): void => {
console.log(`Hitting ${endpoint} with ${body}`)
}
}
const myLibrary = {
getPosts: Factory<GetPosts>('/posts'),
createPosts: Factory<CreatePost>('/posts'),
}
myLibrary.getPosts() // => Correctly passes
myLibrary.getPosts('something') // => Correctly errors
myLibrary.createPosts(999) // => Correctly errors
myLibrary.createPosts() // => Correctly errors
myLibrary.createPosts('hi') // => Correctly passes
Aside from the fact that I need to pick between two "super" types before extending anything, a major problem with this is that the Construct Signature argument is not very accessible.
Although not demonstrated in the example, the types I create are also used elsewhere in my codebase, and the body is accessible (i.e GetPosts['body']). With the above, it is not easy to access and I'll probably need to create a separate re-usable type definition to achieve the same thing.
You almost hit the spot with your initial types. Two changes required:
Make body of GetPosts of type void
Make body of returned function required
interface RequestInterface {
endpoint: string;
body?: unknown;
}
interface GetPosts extends RequestInterface {
endpoint: "/posts";
body: void;
}
interface CreatePost extends RequestInterface {
endpoint: "/posts";
body: string;
}
function Factory<R extends RequestInterface>(endpoint: R["endpoint"]) {
return (body: R["body"]): void => {
console.log(`Hitting ${endpoint} with ${body}`);
};
}
const myLibrary = {
getPosts: Factory<GetPosts>("/posts"),
createPosts: Factory<CreatePost>("/posts"),
};
// #ts-expect-error
myLibrary.getPosts("something");
// #ts-expect-error
myLibrary.createPosts(999);
// #ts-expect-error
myLibrary.createPosts();
myLibrary.getPosts();
myLibrary.createPosts("Hello, StackOverflow!");
TS Playground
Explanation
never type tells compiler that this should never happen. So, it someone tries to use GetPosts, it's an error, since it should never happen. void (undefined in this case should be also fine) tells that value should not be there.
Making body required in returned function makes it required. But since it is void for GetPosts, you can call it like myLibrary.getPosts(undefined) or simply myLibrary.getPosts() which is equivalent
I am using graphql-request as a GraphQL client to query a headless CMS to fetch stuff, modify and return to the original request/query. headless cms is hosted separately fyi.
I have the following code :
#Query(returns => BlogPost)
async test() {
const endpoint = 'https://contentxx.com/api/content/project-dev/graphql'
const graphQLClient = new GraphQLClient(endpoint, {
headers: {
authorization: 'Bearer xxxxxxx',
},
})
const query = gql`
{
findContentContent(id: "9f5dde89-7f9b-4b9c-8669-1f0425b2b55d") {
id
flatData {
body
slug
subtitle
title
}
}
}`
return await graphQLClient.request(query);
}
BlogPost is a model having the types :
import { Field, ObjectType } from '#nestjs/graphql';
import { BaseModel } from './base.model';
import FlatDateType from '../resolvers/blogPost/types/flatDatatype.type';
#ObjectType()
export class BlogPost extends BaseModel {
#Field({ nullable: true })
id!: string;
#Field((type) => FlatDateType)
flatData: FlatDateType;
}
and FlatDateType has the following code
export default class FlatDateType {
body: string;
slug: string;
subtitle: string;
title: string;
}
it throws the following exception :
Error: Cannot determine a GraphQL output type for the "flatData". Make
sure your class is decorated with an appropriate decorator.
What is missing in here?
How is your graphql server supposed to understand the type of FlatDataType when there's no information about it being passed to the graphql parser? You need to add the graphql decorators to it as well. #ObjectType(), #Field(), etc.
FlatDataType is not defined as #ObjectType(), therefore type-graphql (or #nestjs/graphql) can't take it as an output in GraphQL.
NestJS uses validation with validation pipes and
#UsePipes(ValidationPipe)
If this fails it throws an exception. This is fine for REST APIs that return JSON.
How would one validate parameters when using HTML rendering and return
{ errors: ['First error'] }
to an hbs template?
You can create an Interceptor that transforms the validation error into an error response:
#Injectable()
export class ErrorsInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
call$: Observable<any>,
): Observable<any> {
return call$.pipe(
// Here you can map (or rethrow) errors
catchError(err => ({errors: [err.message]}),
),
);
}
}
You can use it by adding #UseInterceptors(ErrorsInterceptor) to your controller or its methods.
I've been driving myself half mad trying to find a "Nest like" way to do this while still retaining a degree of customisability, and I think I finally have it. Firstly, we want an error that has a reference to the exisiting class-validator errors, so we create a custom error class like so:
import { ValidationError } from 'class-validator';
export class ValidationFailedError extends Error {
validationErrors: ValidationError[];
target: any;
constructor(validationErrors) {
super();
this.validationErrors = validationErrors;
this.target = validationErrors[0].target
}
}
(We also have a reference to the class we tried to validate, so we can return our object as appropriate)
Then, in main.ts, we can set a custom exception factory like so:
app.useGlobalPipes(
new ValidationPipe({
exceptionFactory: (validationErrors: ValidationError[] = []) => {
return new ValidationFailedError(validationErrors);
},
}),
);
Next, we create an ExceptionFilter to catch our custom error like so:
#Catch(ValidationFailedError)
export class ValidationExceptionFilter implements ExceptionFilter {
view: string
objectName: string
constructor(view: string, objectName: string) {
this.view = view;
this.objectName = objectName;
}
async catch(exception: ValidationFailedError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
response.render(this.view, {
errors: exception.validationErrors,
[this.objectName]: exception.target,
url: request.url,
});
}
}
We also add an initializer, so we can specify what view to render and what the object's name is, so we can set up our filter on a controller method like so:
#Post(':postID')
#UseFilters(new ValidationExceptionFilter('blog-posts/edit', 'blogPost'))
#Redirect('/blog-posts', 301)
async update(
#Param('id') postID: string,
#Body() editBlogPostDto: EditBlogPostDto,
) {
await this.blogPostsService.update(postID, editBlogPostDto);
}
Hope this helps some folks, because I like NestJS, but it does seem like the docuemntation and tutorials are much more set up for JSON APIs than for more traditional full stack CRUD apps.
I am a freshman in Nest.js.
And my code as below
#Get('findByFilter/:params')
async findByFilter(#Query() query): Promise<Article[]> {
}
I have used postman to test this router
http://localhost:3000/article/findByFilter/bug?google=1&baidu=2
Actually, I can get the query result { google: '1', baidu: '2' }. But I'm not clear why the url has a string 'bug'?
If I delete that word just like
http://localhost:3000/article/findByFilter?google=1&baidu=2
then the postman will shows statusCode 404.
Actually, I don't need the word bug, how to custom the router to realize my destination just like http://localhost:3000/article/findByFilter?google=1&baidu=2
Here's another question is how to make mutiple router point to one method?
Query parameters
You have to remove :params for it to work as expected:
#Get('findByFilter')
async findByFilter(#Query() query): Promise<Article[]> {
// ...
}
Path parameters
The :param syntax is for path parameters and matches any string on a path:
#Get('products/:id')
getProduct(#Param('id') id) {
matches the routes
localhost:3000/products/1
localhost:3000/products/2abc
// ...
Route wildcards
To match multiple endpoints to the same method you can use route wildcards:
#Get('other|te*st')
will match
localhost:3000/other
localhost:3000/test
localhost:3000/te123st
// ...
If you have you parameter as part or url: /articles/${articleId}/details, you wold use #Param
#Get('/articles/:ARTICLE_ID/details')
async getDetails(
#Param('ARTICLE_ID') articleId: string
)
IF you want to provide query params /article/findByFilter/bug?google=1&baidu=2, you could use
#Get('/article/findByFilter/bug?')
async find(
#Query('google') google: number,
#Query('baidu') baidu: number,
)
We can use #Req()
import { Controller, Get, Req } from '#nestjs/common';
import { Request } from 'express';
(...)
#Get(':framework')
getData(#Req() request: Request): Object {
return {...request.params, ...request.query};
}
/nest?version=7
{
"framework": "nest",
"version": "7"
}
read more
You can use the #Req decorator, and use param object, see :
#Get()
findAll(
#Req() req: Request
): Promise<any[]> {
console.log(req.query);
// another code ....
}
For better explaining I wrote a pagination example with number transformer class:
class QueryDto {
#Type(() => Number)
#IsInt()
public readonly page: number;
#Type(() => Number)
#IsInt()
public readonly take: number;
}
#Injectable()
class QueryTransformPipe implements PipeTransform {
async transform(value: QueryRequestDto, { metatype }: ArgumentMetadata) {
if (!metatype) {
return value;
}
return plainToInstance(metatype, value);
}
}
#Controller()
class YourController {
#Get()
// also you can use it with pipe decorator
// #UsePipes(new QueryTransformPipe())
public async getData(#Query(new QueryTransformPipe()) query?: QueryRequestDto): Promise<any[]> {
// here you get instanceof QueryTransformPipe
// and typeof query.page === 'number' && typeof query.take === 'number'
}
}
I would like to make my route Query parameter required.
If it is missing I expect it to throw 404 HTTP error.
#Controller('')
export class AppController {
constructor() {}
#Get('/businessdata/messages')
public async getAllMessages(
#Query('startDate', ValidateDate) startDate: string,
#Query('endDate', ValidateDate) endDate: string,
): Promise<string> {
...
}
}
I'm using NestJs pipes to determine if a parameter is valid, but not if it exists And I'm not sure that Pipes are made for that.
So how can I check in NestJS if my param exists if not throw an error?
Use class-validator. Pipes are definitely made for that !
Example :
create-user.dto.ts
import { IsNotEmpty } from 'class-validator';
export class CreateUserDto {
#IsNotEmpty()
password: string;
}
For more information see class-validator documentation :
https://github.com/typestack/class-validator
And NestJS Pipes & Validation documentation :
https://docs.nestjs.com/pipes
https://docs.nestjs.com/techniques/validation
NestJS does not provide a decorator (like #Query) that detects undefined
value in request.query[key].
You can write custom decorator for that:
import { createParamDecorator, ExecutionContext, BadRequestException } from '#nestjs/common'
export const QueryRequired = createParamDecorator(
(key: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest()
const value = request.query[key]
if (value === undefined) {
throw new BadRequestException(`Missing required query param: '${key}'`)
}
return value
}
)
Then use #QueryRequired decorator as you would use #Query:
#Get()
async someMethod(#QueryRequired('requiredParam') requiredParam: string): Promise<any> {
...
}
There hava a easy way to valide you parameter, https://docs.nestjs.com/techniques/validation
In addition to Phi's answer, you can combine the use of class-validator with the following global validation pipe:
app.useGlobalPipes(
new ValidationPipe({
/*
If set to true, instead of stripping non-whitelisted
properties validator will throw an exception.
*/
forbidNonWhitelisted: true,
/*
If set to true, validator will strip validated (returned)
object of any properties that do not use any validation decorators.
*/
whitelist: true,
}),
);
I use this in order to only allow parameters defined in the DTO class so that it will throw an error when unknown parameters are sent with the request!
In Phie's example, a post request with a body like {password: 'mypassword'} will pass the validation when {password: 'mypassword', other: 'reject me!'} won't.