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.
Related
I have a nestjs project that is mostly in RESTful structure. Everything works fine, but my concern is that some of the routes check for the presence of some query parameters to fetch data.
for instance
#Get('/some-resources')
async getSomeResource(
#Query() query: any
): Promise<HTTPResponseDTO>{
const startDate = query.startDate ? DateTime.fromISO(query.startDate).startOf('day').toISO(): null;
const endDate = query.endDate ? DateTime.fromISO(query.endDate).endOf('day').toISO() : null;
.
.
.
const result = await this.someResourceService.findAll(startDate, endDate,...)
}
Now my question is, is there a cleaner approach to this? Because this can get become a pain to maintain when we have many resources.
As mentioned by Micael Levi, you should be able to do this by creating your own custom pipe. Assuming that what you posted works, you should be able to do something along the lines of:
#Get('/some-resources')
async getSomeResource(
#Query('startDate', ParseDateIsoPipe) startDate?: string,
#Query('endDate', ParseDateIsoPipe) endDate?: string
): Promise<HTTPResponseDTO>{
<code>
}
With your ParseDateIsoPipe as follows (Note that you will still need to import DateTime from the package you are using):
import { PipeTransform, Injectable, ArgumentMetadata } from '#nestjs/common';
#Injectable()
export class ParseDateIsoPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
return value ? DateTime.fromISO(value).startOf('day').toISO(): null;
}
}
You can use the built-in validation pipe: https://docs.nestjs.com/techniques/validation with the auto validation feature.
I'm making an api which looks like:
http://example.com/story/:storyId/analytics?from={date}&to={date}
The storyId is part of the path, but from and to are query parameters.
I've got a DTO, like this:
class GetStoryAnalyticsDTO {
storyId: string
from: Date
to: Date
}
(validators omitted for brevity)
And I'm using it like this in my controler:
#Get()
getStoryAnalytics(#Query() query: GetStoryAnalyticsRequestDto): Promise<MyResponse> {...}
But, (obviously!), this only extracts the from and to parameters.
Is there any way to extract both from the query and the path to get all the vars in one dto?
If not, it's not a massive hassle - I can just add #Param storyId: string to the controller and it's all good :)
You could make a custom decorator like #QueryParam() that pulls together req.query and req.params. It could look something like this:
export const QueryParam = createParamDecorator((data: unknown, context: ExecutionContext) => {
const req = context.switchToHttp().getRequest();
return { ...req.query, ...req.params };
});
And just make sure to add validateCustomDecorators on the ValidationPipe if you want it to auto validate for you. Otherwise, you're good to start using it.
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
}
I have simple custom decorator:
export const User: () => ParameterDecorator = createParamDecorator(
(data: any, req): UserIdentity => {
const user = getUser(req);
return user;
},
);
And now, I need to validate if we have email in user object.
The problem is that I can't update my current decorator.
Could I extend my current decorator?
Create a new decorator based on the previous one or create a new decorator and combine it?
Yes, you can do "decorator composition" with Nest, but this might not be a perfect solution for your case, depending on what you intend to do when user has no email property.
As per the example from the documentation:
import { applyDecorators } from '#nestjs/common';
export function Auth(...roles: Role[]) {
return applyDecorators(
SetMetadata('roles', roles),
UseGuards(AuthGuard, RolesGuard),
ApiBearerAuth(),
ApiUnauthorizedResponse({ description: 'Unauthorized"' }),
);
}
In this example, Auth is a decorator that can be used to combine all the one passed in applyDecorators.
Thus, I'd recommend extending your decorator using a pipe.
As stated by the documentation:
Nest treats custom param decorators in the same fashion as the built-in ones (#Body(), #Param() and #Query()). This means that pipes are executed for the custom annotated parameters as well (in our examples, the user argument). Moreover, you can apply the pipe directly to the custom decorator:
#Get()
async findOne(#User(new ValidationPipe()) user: UserEntity) {
console.log(user);
}
In this example, User is a custom parameter decorator. And ValidationPipe is passed, but you can imagine passing any pipe.
I am writing a NestJS application. Some of the endpoints support sorting e.g. http://127.0.0.1:3000/api/v1/members?sort=-id&take=100 Which means sort by id descending.
This parameter arrives as a #Query parameter and is passed to my service. This service transforms it into an object which is used by TypeORM:
{
id: 'DESC'
}
I don't want to call this conversion method manually every time I need sorting.
I've tried an intercepter but this one could not easily change the request parameters into the desired object.
A pipe worked but then I still need to add #Query(new SortPipe()) for every endpoint definition.
Another option is in the repository itself. The NestJS documentation is very well written, but misses guidance in where to put what.
Is there someone who had a similar issue with converting Query parameters before they are used in NestJS, and can explain what approach is the best within NestJS?
This question might look like an opinion based question, however I am looking for the way it is supposed to be done with the NestJS philosophy in mind.
Pipes are probably the easiest way to accomplish this. Instead of adding your pipe for every endpoint definition you can add a global pipe that will be called on every endpoint. In your main.ts:
async function bootstrap() {
...
app.useGlobalPipes(new SortPipe());
...
}
You can then create a pipe like this:
import { PipeTransform, Injectable, ArgumentMetadata } from '#nestjs/common';
#Injectable()
export class SortPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
const { type } = metadata;
// Make sure to only run your logic on queries
if (type === 'query') return this.transformQuery(value);
return value;
}
transformQuery(query: any) {
if (typeof query !== 'object' || !value) return query;
const { sort } = query;
if (sort) query.sort = convertForTypeOrm(sort);
return query;
}
}
If you do not want sort value on ALL endpoints to be automatically converted, you can pass custom parameter to #Query(), for example #Query('sort'). And then:
transform(value: any, metadata: ArgumentMetadata) {
const { type, data } = metadata;
// Make sure to only run your logic on queries when 'sort' is supplied
if (type === 'query' && data === 'sort') return this.transformQuery(value);
return value;
}