for NestJS v8, v9 and v10
In NestJS we can create specialized kind of parameters decorators using the createParamDecorator
function from @nestjs/common
. For example:
import { createParamDecorator, ExecutionContext } from '@nestjs/common'
export const CurrentUser = createParamDecorator<
keyof User | undefined, // the type of `data`
ExecutionContext, // the type of `ctx`
>(
(data, ctx) => {
const request = ctx.switchToHttp().getRequest()
const user = request.user
return typeof data === 'undefined'
? user[data]
: user
}
)
And then you can use that CurrentUser
parameter decorator later in controller class's method, as follows:
// ...
@Get()
getCurrentUser(
@CurrentUser() user: User,
@CurrentUser('name') username: string,
) {
return { user, username }
}
The issue
If your project has a bunch of those decorators, it might be hard to know what would be the type of their resolved values, right? I mean, how do you know that @CurrentUser()
is a "bind" for request.user
without some documentation or by reading the source? Also, what is the type of that request.user
? Due to how TypeScript legacy decorators works, there's no way to typescript compiler infer some type from such param decorator.
My solution
You could see in my other article that I've leverage on TypeScript declaration merging feature like this:
Along with generics, we can now easily couple the decorator with "its" type.
We just need to declare and export a type alias with the same name of our param decorator. See:
import { createParamDecorator, ExecutionContext } from '@nestjs/common'
export const CurrentUser = createParamDecorator<
keyof User | undefined, // the type of data
ExecutionContext, // the type of ctx
>(
(data, ctx) => {
const request = ctx.switchToHttp().getRequest()
const user = request.user
return typeof data === 'undefined'
? user[data]
: user
}
)
// -------- THIS IS NEW:
export type CurrentUser<Prop extends keyof User | undefined = undefined> =
Prop extends keyof User ? User[Prop] : User
@Get()
getCurrentUser(
@CurrentUser() user: CurrentUser,
@CurrentUser('name') username: CurrentUser<'name'>
) {
return { user, username }
}
Advantages
The ones I've seen so far:
- No one needs to recall what is the expected type of the resolved value by those parameters decorators. Just use the same name of the decorator.
- One source of truth of the expected type of such param decorator. If we change the implementation of that decorator in the future (and also the type of the returned object), we won't have to touch other parts of our codebase (unless we got some breaking change, of course).
- No need to import multiple types just for the sake of type safety.
Disadvantages
The ones I've seen so far:
- I didn't see this pattern often in the wild, so I won't expect it to be intuitive.
- If you use some pipe like this:
@CurrentUser(MyPipe) somethingElse: any
, thatsomethingElse
parameter might not have theCurrentUser
type anymore. So this pattern is restrict to those decorators that are not meant to use with pipes. -
User
type is clear thanCurrentUser
one if you are familiar withUser
entity already. Thus, by reading the code outside of some code editor, it migth be a bit hard to find what thatCurrentUser
mean. But I think that this is just a matter of getting used of.
Top comments (4)
Maybe I didn't catch the essence of the issue, but it seems to me that the following code does the same thing as suggested in the summary, but simpler and more readable:
As you can see, username is typed by
User['name']
.here's an example of a real world use case for such pattern that I've been using so far at work:
that
@Authorize()
is a decorator made for that project. To me, it would be less easy for newcomers to know what is the type ofauthorize
parameter. Now they just have to use the same name as the decorator factory being used ^^yep
The essence is that you have to know that
@CurrentUser()
resolves toUser
, in first place. Imagine have a tons of those decorators out there.If there is a need for a large number of decorators for parameters, most likely you need to move parameter handling to HTTP interceptors.
In good frameworks (if you know what I mean ;) in route methods you can get parameter values by tokens, exactly the same as it is done in constructors: