Files
docs.nestjs.com/content/custom-decorators.md
2020-11-20 09:24:38 +01:00

220 lines
7.4 KiB
Markdown

### Custom route decorators
Nest is built around a language feature called **decorators**. Decorators are a well-known concept in a lot of commonly used programming languages, but in the JavaScript world, they're still relatively new. In order to better understand how decorators work, we recommend reading [this article](https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841). Here's a simple definition:
<blockquote class="external">
An ES2016 decorator is an expression which returns a function and can take a target, name and property descriptor as arguments.
You apply it by prefixing the decorator with an <code>@</code> character and placing this at the very top of what
you are trying to decorate. Decorators can be defined for either a class, a method or a property.
</blockquote>
#### Param decorators
Nest provides a set of useful **param decorators** that you can use together with the HTTP route handlers. Below is a list of the provided decorators and the plain Express (or Fastify) objects they represent
<table>
<tbody>
<tr>
<td><code>@Request(), @Req()</code></td>
<td><code>req</code></td>
</tr>
<tr>
<td><code>@Response(), @Res()</code></td>
<td><code>res</code></td>
</tr>
<tr>
<td><code>@Next()</code></td>
<td><code>next</code></td>
</tr>
<tr>
<td><code>@Session()</code></td>
<td><code>req.session</code></td>
</tr>
<tr>
<td><code>@Param(param?: string)</code></td>
<td><code>req.params</code> / <code>req.params[param]</code></td>
</tr>
<tr>
<td><code>@Body(param?: string)</code></td>
<td><code>req.body</code> / <code>req.body[param]</code></td>
</tr>
<tr>
<td><code>@Query(param?: string)</code></td>
<td><code>req.query</code> / <code>req.query[param]</code></td>
</tr>
<tr>
<td><code>@Headers(param?: string)</code></td>
<td><code>req.headers</code> / <code>req.headers[param]</code></td>
</tr>
<tr>
<td><code>@Ip()</code></td>
<td><code>req.ip</code></td>
</tr>
<tr>
<td><code>@HostParam()</code></td>
<td><code>req.hosts</code></td>
</tr>
</tbody>
</table>
Additionally, you can create your own **custom decorators**. Why is this useful?
In the node.js world, it's common practice to attach properties to the **request** object. Then you manually extract them in each route handler, using code like the following:
```typescript
const user = req.user;
```
In order to make your code more readable and transparent, you can create a `@User()` decorator and reuse it across all of your controllers.
```typescript
@@filename(user.decorator)
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
```
Then, you can simply use it wherever it fits your requirements.
```typescript
@@filename()
@Get()
async findOne(@User() user: UserEntity) {
console.log(user);
}
@@switch
@Get()
@Bind(User())
async findOne(user) {
console.log(user);
}
```
#### Passing data
When the behavior of your decorator depends on some conditions, you can use the `data` parameter to pass an argument to the decorator's factory function. One use case for this is a custom decorator that extracts properties from the request object by key. Let's assume, for example, that our <a href="techniques/authentication#implementing-passport-strategies">authentication layer</a> validates requests and attaches a user entity to the request object. The user entity for an authenticated request might look like:
```json
{
"id": 101,
"firstName": "Alan",
"lastName": "Turing",
"email": "alan@email.com",
"roles": ["admin"]
}
```
Let's define a decorator that takes a property name as key, and returns the associated value if it exists (or undefined if it doesn't exist, or if the `user` object has not been created).
```typescript
@@filename(user.decorator)
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user?.[data] : user;
},
);
@@switch
import { createParamDecorator } from '@nestjs/common';
export const User = createParamDecorator((data, ctx) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user && user[data] : user;
});
```
Here's how you could then access a particular property via the `@User()` decorator in the controller:
```typescript
@@filename()
@Get()
async findOne(@User('firstName') firstName: string) {
console.log(`Hello ${firstName}`);
}
@@switch
@Get()
@Bind(User('firstName'))
async findOne(firstName) {
console.log(`Hello ${firstName}`);
}
```
You can use this same decorator with different keys to access different properties. If the `user` object is deep or complex, this can make for easier and more readable request handler implementations.
> info **Hint** For TypeScript users, note that `createParamDecorator<T>()` is a generic. This means you can explicitly enforce type safety, for example `createParamDecorator<string>((data, ctx) => ...)`. Alternatively, specify a parameter type in the factory function, for example `createParamDecorator((data: string, ctx) => ...)`. If you omit both, the type for `data` will be `any`.
#### Working with pipes
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:
```typescript
@@filename()
@Get()
async findOne(
@User(new ValidationPipe({ validateCustomDecorators: true }))
user: UserEntity,
) {
console.log(user);
}
@@switch
@Get()
@Bind(User(new ValidationPipe({ validateCustomDecorators: true })))
async findOne(user) {
console.log(user);
}
```
> info **Hint** Note that `validateCustomDecorators` option must be set to true. `ValidationPipe` does not validate arguments annotated with the custom decorators by default.
#### Decorator composition
Nest provides a helper method to compose multiple decorators. For example, suppose you want to combine all decorators related to authentication into a single decorator. This could be done with the following construction:
```typescript
@@filename(auth.decorator)
import { applyDecorators } from '@nestjs/common';
export function Auth(...roles: Role[]) {
return applyDecorators(
SetMetadata('roles', roles),
UseGuards(AuthGuard, RolesGuard),
ApiBearerAuth(),
ApiUnauthorizedResponse({ description: 'Unauthorized' }),
);
}
@@switch
import { applyDecorators } from '@nestjs/common';
export function Auth(...roles) {
return applyDecorators(
SetMetadata('roles', roles),
UseGuards(AuthGuard, RolesGuard),
ApiBearerAuth(),
ApiUnauthorizedResponse({ description: 'Unauthorized' }),
);
}
```
You can then use this custom `@Auth()` decorator as follows:
```typescript
@Get('users')
@Auth('admin')
findAllUsers() {}
```
This has the effect of applying all four decorators with a single declaration.
> warning **Warning** The `@ApiHideProperty()` decorator from the `@nestjs/swagger` package is not composable and won't work properly with the `applyDecorators` function.