I have a controller using #Controller('tasks') decorator. Inside this controller, I have a route #Get('/week'), normally the request should go localhost:4000/tasks/week but it is returning a bad request:
{
"statusCode": 400,
"error": "Bad Request",
"message": "Validation failed (numeric string is expected)"
}
Below is my code:
#Controller('tasks')
#UseGuards(AuthGuard())
export class TasksController {
constructor(private tasksService: TasksService) { }
#Get('/:id')
getTaskById(#Param('id', ParseIntPipe) id: number): Promise<Task> {
return this.tasksService.getTaskById(id);
}
#Get('/week')
getTasksByWeek(#GetUser() user: User): Promise<Task[]> {
return this.tasksService.getTasksByWeek(user);
}
Removing the /week from Get() decorator works but not adding it.
Expected result: return data
Actual result:
{
"statusCode": 400,
"error": "Bad Request",
"message": "Validation failed (numeric string is expected)"
}
I found a fix for this issue. Routes such as Get('week') or routes that accept parameters should be lined before routes with base controller route.
Assume we have a route #GET() like below that gets all the tasks:
#Get()
getTasks(#GetUser() user: User): Promise<Task[]> {
return this.tasksService.getTasksByWeek(user);
}
How it should be in the code:
#Controller('tasks')
#UseGuards(AuthGuard())
export class TasksController {
constructor(private tasksService: TasksService) { }
#Get('/:id')
getTaskById(#Param('id', ParseIntPipe) id: number): Promise<Task> {
return this.tasksService.getTaskById(id);
}
#Get('/week')
getTasksByWeek(#GetUser() user: User): Promise<Task[]> {
return this.tasksService.getTasksByWeek(user);
}
#Get()
getTasks(#GetUser() user: User): Promise<Task[]> {
return this.tasksService.getTasksByWeek(user);
}
As answers given are not really explaining the "why" of this behaviour, here's a quick explanation:
It's just a matter of ordering and what route is met First: "week" is mistaken with an ":id", like 123, 204...etc.
As your router tries the first route that meet route requirements (pattern in this case), your request is forwarded to /id route. It happens in every http framework with a router.
Validation occurs later on. So in short, the only thing that matters in route selection, is pattern, not validation set in it.
This ordering is really important, as you'll face same exact issue in all major http framework with a router.
Just put #Get("any-string") route above route #Get(":id") that takes the parameter.
if you are also beginner in Nestjs like me, then make sure you are not committing these basic mistakes,
argument passed from playground/FrontEnd and parameter received at Resolver function should be same e.g
at both sending and receiving ends here, string should be itemCategoryId
If parameter to be received in Resolver function is nullable, then you should mention that I am providing example of this for Nestjs, you can google the syntax for other frameworks
Related
I am new to nestjs and tring to apply Get and Post simultaneously on a method in my controller.
For simplicity, I just post the core logic code snippet:
Customized Decorator
import { Get, Post } from "#nestjs/common";
import { RenderReact } from 'my-personal-package';
export function Page(path: string, view?: React.ComponentType, methodDecorators?: ((path?: string | string[]) => MethodDecorator)[]): MethodDecorator {
return (target: any, key: string, desc: PropertyDescriptor) => {
const decorators = [
Get(path), // Add Get first.
Post(path) // Add Post then.
];
if (view) {
decorators.push(RenderReact(view)); // RenderReact will return a MethodDecorator as well.
}
decorators.forEach(decorate => decorate(target, key, desc));
return desc;
};
}
Controller method:
#Page("my-path", ThisIsMyPageFunctionalComponent, [Post]) // Post was from #nestjs/common
async return() {
// method logic
}
The array "decorators" at the very beginning in the Page function,
Add Get, then Post, Only Post works.
Add Post, then Get, Only Get works.
How can we apply Get/Post simultaneously here?
As #Micael Levi mentioned above, as the machanism of how decorator factory works, we can not apply both Get and Post in this way. I have tried it for a long time.
Please refer to the question here, like #Kim Kern posted
We extracted common logic into a method.
Separate the method Get and Post which will call the common logic.
I want to use regexp in validation in nestjs.
For example:
RegExp
pagePattern = '[a-z0-9\-]+';
Method
#Get('/:article')
getIndex(
#Param('article')
) {
}
What can I use?
ValidationPipe?
I would create a DTO class like
class ArticleParamDTO {
#Matches('[a-z0-9\-]+') // comes from class-validator
article: string;
}
And then you can use it in the route handler like
#Get(':article')
getIndex(#Param() { article }: ArticleParamDto) {
}
And then as long as you use the ValidationPipe it will all work. Anything that doesn't match will cause a 400 BadRequest
I want to overrride the following route which was generated by nestjsx:
GET /offer-event-matchings/{id}
To get the id from the CrudRequest I wrote the following code.
#Override()
getOne(#ParsedRequest() req: CrudRequest): Promise<GetUserDto> {
const id = req.parsed.search.$and[1]['id']['$eq'];
return this.service.getOfferEventMatching(id);
}
It works but I think and hope there is a better and more beautiful way to get the id from the CrudRequest object?
The bottom of the section Routes Override in the docs mentions you can use the typical decorators as well, so the easiest would be to use Param:
getOne(
#ParsedRequest() req: CrudRequest,
#Param('id') id: string
): Promise<GetUserDto> {
// code here
}
Whilst studying NestJS I encountered an issue, I have the following DTO:
export default class SearchNotesDto {
query: string;
createdAfter: Date;
createdBefore: Date;
}
Which I wish to get when a GET request is made to an endpoint, which is handled by the following function in my controller:
#Get()
getNotes(#Query() searchNotesDto: SearchNotesDto): Note[] {
if (Object.keys(searchNotesDto).length) {
return this.notesService.searchNotes(searchNotesDto);
}
return this.notesService.getAllNotes();
}
My problem is that createdAfter and createdBefore are strings in searchNotesDto, and I wish to work with the Date object, is there a way to implicitly convert those fields to a Date?
#Query will serialize all properties to type string because that's how query string works in terms of HTTP Request. You will need to utilize a Pipe to transform your query to the right shape of data.
https://docs.nestjs.com/pipes
export class SearchNotePipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
// value will be your `searchNotesDto`
const notesDto = new SearchNotesDto();
// do your transformation here
return notesDto;
}
}
I have a class CreateFolderDto with two readonly fields:
export class CreateFolderDto {
public readonly name: string
public readonly user_id: number
}
I have a controller which is:
#UseGuards(AuthGuard('jwt'))
#Post()
public create(#Request() req, #Body() createFolderDto: CreateFolderDto) {
return this.folderService.create(createFolderDto)
}
The request send to my controller is a good one, I only send the name in json format with an accessToken in the header. The accessToken permit me to get my user_id from the request with req.user.id.
The DTO field user_id is not automatically filled. I would like to fill it automatically.
Is it a way to auto-fill my createFolderDto.user_id variable ?
#Body only wraps actual request body into instance of the CreateFolderDto class. As the body which comes to your endpoint has no such a field, you need to add it manually.
Normally, aggregated fields could be added with custom constructor of your DTO:
export class CreateFolderDto {
public readonly name: string
public readonly session_uuid: string
constructor(bodyValue: any = {}) {
this.name = bodyValue.name
this.session_uuid = generateUuid()
}
}
But in your case, user is attached to request itself, so I believe you have the following options:
Check out your code which attaches the user to request itself. If you are using JWT Auth described in NestJS docs, you cannot do this that way.
You can write custom Interceptor:
Injectable()
export class ExtendBodyWithUserId implements NestInterceptor {
async intercept(context: ExecutionContext, next: CallHandler) {
const request = context.switchToHttp().getRequest()
request.body.user_id = request.user
return next.handle()
}
}
// usage
#UseGuards(AuthGuard('jwt'))
#UseInterceptors(ExtendBodyWithUserId)
#Post()
public create(#Request() req, #Body() createFolderDto: CreateFolderDto) {
return this.folderService.create(createFolderDto)
}
Last but not least, some personal recommendation. Consider how much you will use this interceptor as an extension, as too many of 'extras' like this bloat the codebase.
I would recommend to change the folderService signature to:
create(createFolderDto: CreateFolderDto, user: User), where folder dto has only the name, without user-related entry. You keep the consistency, separation and clear intentions. In the implementation of create you can just pass user.id further.
And going this way, you don't have to write custom interceptors.
Pick your way and may the consistency in your codebase be with you!