Custom param decorator which transform param to database entity - nestjs

In Laravel (php) has route /article/:article, and in controller method I get the model:
function getArticle(ArticleModel $article) {...}
How to make this in NestJS?
My controller:
#Controller('/articles')
export class ArticlesController {
#Get('/:article/edit')
editArticle(#Param('article') articleId: number) {...}
}
How to transform #Param('article') to custom decorator #ArticleParam() which will return my Article entity by id in request?

You can implement a custom pipe that injects a TypeORM repository and returns the database entity when prompted with an ID, something like this:
#Injectable()
export class ArticlePipe implements PipeTransform {
constructor(#InjectRepository(Article) private repository: Repository<Article>) {}
transform(value: id, metadata: ArgumentsMetadata): Promise<Article|null> {
return this.repository.findOneBy({ id });
}
}
Then use it like
#Get('/article/:id')
getArticle(#Param('id', ArticlePipe) article: Article) { ... }
You just need to make sure to use the pipe only on modules that provide the Article EntityRepository.
Then, if you need the specific #ArticleParam, it should be like this:
export function ArticleParam = () => applyDecorators(
Param(ArticlePipe)
)

Related

How to get method and its metadata from NestJS provider?

I'm making a NestJS wrapper for Typegoose because the existing one is complete deprecated and has one critical drawback that I want to fix in my implementation.
Problem: there is #EventTrackerFor(schema: AnyClass) that takes Typegoose class. It's implemented like this:
export const EventTrackerFor = (schema: AnyClass) =>
applyDecorators(Injectable, SetMetadata('tracker-for', schema.name));
Also, there are #Pre(eventName: PreEvents) and Post(eventName: PostEvents) decorators:
export const Post = (eventName: PreEvents) => SetMetadata('post', eventName);
export const Pre = (eventName: PostEvents) => SetMetadata('pre', eventName);
And as a result, library user will do it like that:
#EventTrackerFor(User)
class UserEventTracker {
constructor(private readonly anyService: AnyService) {}
#Pre(PreEvents.SAVE)
#Post(PostEvents.SAVE)
logOnAndAfterCreate() {
console.log('user created')
}
}
// ------------------------ Any module
#Module({
imports: [MyModule.forFeature([ {schema: User} ])],
providers: [UserEventTracker]
})
class AnyModule {}
I need to get value from #EventTrackerFor somehow and methods of the provider, which are decorated with #Pre() and #Post decorators, including values that passed inside them.
I was looking for a clue in different packages like #nestjs/bull, which has similar logics inside, but they have so much code, so I could not understand how do they do it.
Project repository: https://github.com/GrapeoffJS/kindagoose

Nestjs extend/combine decorators?

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.

NestJS deserializing #Query() to a DTO with complex types

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

Auto-fill DTO fields with other data than request body data

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!

How to properly design API module in TypeScript?

I want to design a TypeScript (2.7) module for accessing external IS, let's call it InfoSys. I used the following approach.
I created info-sys.ts which defines a API class and related interfaces and enums, like:
class Api {
constructor(private endpoint: Endpoint) {
// ...
}
}
enum Endpoint {
CONTACTS = "contacts"
}
interface Contact {
name: string;
}
Now I want to export all the stuff under specific name. So I appended the export statement:
export const InfoSys = {
Api,
Endpoint,
Contact
};
When I try to use the module in another file, like:
import { InfoSys } from "info-sys";
// this line throws error: "Cannot find namespace 'InfoSys'"
private api: InfoSys.Api;
// but this line is ok
api = new InfoSys.Api(InfoSys.Endpoint.CONTACTS);
The way that works is the following - to export every piece individually:
export class Api {
constructor(private endpoint: Endpoint) {
// ...
}
}
export enum Endpoint {
CONTACTS = "contacts"
}
export interface Contact {
name: string;
}
and import them all to a single variable:
import * as InfoSys from "info-sys";
But the name of the variable can be whatever. It is not critical for functionality but I want to force developers, who will use the info-sys module, to use a specific name while accessing it (for easier readability and maintainability). How to properly design such module?
You can use namespace:
export namespace InfoSys {
Api,
Endpoint,
Contact
};
In general, this approach should be avoided. But in your case, it is fine as you are delivering things that are tightly related.
If Api is the single entry point to all these, I would also recommend this:
export class InfoSysApi { ... }
export namespace InfoSysApi {
export enum Endpoint = { ... }
export interface Contact { ... }
}
UPDATE:
To make sure I get the point through, DON'T do the following:
export namespace Foo {
export function X() { return 'x' }
export function Y() { return 'y' }
}
Only use export namespace to export "tugged in types", not values.
In TypeScript handbook: https://www.typescriptlang.org/docs/handbook/declaration-merging.html
Although the table says namespace can contain values, it is considered bad practice if you are writing ESM (import/export).
Namespace and ESM are two different mechanisms to achieve similar result.
Don't mix them together.

Resources