How to use query parameters in Nest.js? - javascript

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'
}
}

Related

How to create and use a terminal command with nest-commander?

So, I have this NestJS project, and for learning purposes I want to create a command with nest-commander that would be executable on terminal (that way I could call a function from other services), also for learning purposes, whenever I call this command, it should call a function on a service file that get's a user from the database.
It would look like this :
> run myCommand -username UsernameString
Whenever that command is called from the terminal, I would call getUser() from AnotherService to find my user with that specific UsernameString.
I read the docs and couldn't figure much of it, so...
How do I call a command from terminal?
Is it possible to call the same command within the application?
So basically if we take this example:
import { Module } from '#nestjs/common';
import { Command, CommandFactory, CommandRunner, Option } from 'nest-commander';
interface BasicCommandOptions {
string?: string;
boolean?: boolean;
number?: number;
}
#Command({ name: 'basic', description: 'A parameter parse' })
export class BasicCommand extends CommandRunner {
async run(
passedParam: string[],
options?: BasicCommandOptions,
): Promise<void> {
if (options?.number) {
this.runWithNumber(passedParam, options.number);
} else if (options?.string) {
this.runWithString(passedParam, options.string);
} else {
this.runWithNone(passedParam);
}
}
#Option({
flags: '-n, --number [number]',
description: 'A basic number parser',
})
parseNumber(val: string): number {
return Number(val);
}
#Option({
flags: '-s, --string [string]',
description: 'A string return',
})
parseString(val: string): string {
return val;
}
#Option({
flags: '-b, --boolean [boolean]',
description: 'A boolean parser',
})
parseBoolean(val: string): boolean {
return JSON.parse(val);
}
runWithString(param: string[], option: string): void {
console.log({ param, string: option });
}
runWithNumber(param: string[], option: number): void {
console.log({ param, number: option });
}
runWithNone(param: string[]): void {
console.log({ param });
}
}
#Module({
providers: [BasicCommand],
})
export class AppModule {}
async function bootstrap() {
await CommandFactory.run(AppModule);
}
bootstrap();
You can run it using that method:
ts-node ./test.ts basic -s test-value -n 1234
First you call the name of the command then the params

Combine Multiple Param Decorator

I am trying to combine multiple Parameter Decorator for my application.
In my application there are several custom decorators.
My Sample Controller.
#Controller('api')
export class MyController {
#Get('mainresponse')
public getResponse(
#Query() query: Partial<IBean>,
#Headers('user-agent') userAgent: string,
#MyFirstDocrator() firstDecorator: string,
#MySecondDocrator() secondDecorator: string,
#Headers('Referer') referer: string,
#Cookies('session') sessionId?: string,
): Observable<IResponse> {
......
}
}
I am trying to combine these decorators into 1 and return an object with all the decorators value.
Something like this
#Get('mainresponse')
public getResponse(
#MyCombinedDecorator() decorator: any
): Observable<IResponse> {
......
}
But when I am using applyDecorators it is giving me the return value of the last passed decorator.
export const MyCombinedDecorator = () => applyDecorators(Headers('user-agent') as PropertyDecorator, MyFirstDocrator() as PropertyDecorator, Query() as PropertyDecorator);

NestJS: How to access both Body and Param in custom validator?

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.

How to make param required in NestJS?

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.

Subscribe Observable<Type>

Subscribe to Observable:
checkAllowEmail(control: FormControl) {
this.userService.getUserByEmail(control.value)
.subscribe((user: UserDto) => {
console.log(user);
if (user !== undefined) {
console.log(this.isAllowEmail);
this.isAllowEmail = false;
console.log(this.isAllowEmail);
}
});
}
Return Observable from method:
getUserByEmail(email: string): Observable<UserDto> {
return this.http.get(`http://localhost:9092/api/v1/users?email=${email}`)
.map((response: Response) => response.json())
.map((user: UserDto) => user ? user : undefined);
}
Class UserDto:
export class UserDto {
constructor(
public email: string,
public name: string,
public role: string
) {}
}
Response from BE-side:
{"name":"art","email":"art#mail.ru","role":"user"}
Why I can change variable isAllowEmail to false in if statement in checkAllowEmail method?
I don't think you still have to use the map function, when using the HttpClient (since angular version 4). Try using
getUserByEmail(email: string): Observable<UserDto> {
return this.http.get<UserDto>(`http://localhost:9092/api/v1/users?email=${email}`);
}
Else use something like Postman or even curl from the console to verify that you api returns the correct/expected responses.
Also check if you use 'use strict'; in that case this will refere to the callable you pass to the subscribe method and not the surrounding component class.

Categories