Introduction
NestJS is a rapidly gaining popularity framework built on ideas for IoC / DI, modular design and decorators. Thanks to the latter, Nest has a concise and expressive syntax, which improves the usability of development.
โ , .
, , . , , Nest.
Guard โ , CanActivate
@UseGuard
.
@Injectable()
export class RoleGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
return getRole(request) === 'superuser'
}
}
@Controller()
export class MyController {
@Post('secure-path')
@UseGuards(RoleGuard)
async method() {
return
}
}
superuser
โ , .
Reflector
, reflect-metadata.
@Injectable()
export class RoleGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const role = this.reflector.get<string>('role', context.getHandler());
const request = context.switchToHttp().getRequest();
return getRole(request) === role
}
}
@Controller()
export class MyController {
@Post('secure-path')
@SetMetadata('role', 'superuser')
@UseGuards(RoleGuard)
async test() {
return
}
}
.
- -. .
const Role = (role) => applyDecorators(UseGuards(RoleGuard), SetMetadata('role', role))
:
const Role = role => (proto, propName, descriptor) => {
UseGuards(RoleGuard)(proto, propName, descriptor)
SetMetadata('role', role)(proto, propName, descriptor)
}
@Controller()
export class MyController {
@Post('secure-path')
@Role('superuser')
async test() {
return
}
}
, .
@Controller()
@UseGuards(RoleGuard)
export class MyController {
@Post('secure-path')
@Role('superuser')
async test1() {
return
}
@Post('almost-securest-path')
@Role('superuser')
async test2() {
return
}
@Post('securest-path')
@Role('superuser')
async test3() {
return
}
}
, . , , -.
โ โ .
typescript , .
type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;
const Role = (role: string): MethodDecorator | ClassDecorator => (...args) => {
if (typeof args[0] === 'function') {
//
const ctor = args[0]
//
const proto = ctor.prototype
//
const methods = Object
.getOwnPropertyNames(proto)
.filter(prop => prop !== 'constructor')
//
methods.forEach((propName) => {
RoleMethodDecorator(
proto,
propName,
Object.getOwnPropertyDescriptor(proto, propName),
role,
)
})
} else {
const [proto, propName, descriptor] = args
RoleMethodDecorator(proto, propName, descriptor, role)
}
}
, : lukehorvat/decorator-utils, qiwi/decorator-utils.
.
import { constructDecorator, CLASS, METHOD } from '@qiwi/decorator-utils'
const Role = constructDecorator(
({ targetType, descriptor, proto, propName, args: [role] }) => {
if (targetType === METHOD) {
RoleMethodDecorator(proto, propName, descriptor, role)
}
if (targetType === CLASS) {
const methods = Object.getOwnPropertyNames(proto)
methods.forEach((propName) => {
RoleMethodDecorator(
proto,
propName,
Object.getOwnPropertyDescriptor(proto, propName),
role,
)
})
}
},
)
:
@DecForClass
, @DecForMethood
, @DecForParam
@Dec
.
, , - , @Role
.
.
, createParamDecorator .
/ ( ParamsTokenFactory RouterExecutionContext).
//
if (typeof args[2] === 'number') {
const [proto, propName, paramIndex] = args
createParamDecorator((_data: unknown, ctx: ExecutionContext) => {
return getRole(ctx.switchToHttp().getRequest())
})()(proto, propName, paramIndex)
}
, , , .
. , , .
class SomeController {
@RequestSize(1000)
@RequestSize(5000)
@Post('foo')
method(@Body() body) {
}
}
class SomeController {
@Port(9092)
@Port(8080)
@Post('foo')
method(@Body() body) {
}
}
.
class SomeController {
@Post('securest-path')
@Role('superuser')
@Role('usert')
@Role('otheruser')
method(@Role() role) {
}
}
, reflect-metadata
:
import { ExecutionContext, createParamDecorator } from '@nestjs/common'
import { constructDecorator, METHOD, PARAM } from '@qiwi/decorator-utils'
@Injectable()
export class RoleGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean | Promise<boolean> {
const roleMetadata = Reflect.getMetadata(
'roleMetadata',
context.getClass().prototype,
)
const request = context.switchToHttp().getRequest()
const role = getRole(request)
return roleMetadata.find(({ value }) => value === role)
}
}
const RoleMethodDecorator = (proto, propName, decsriptor, role) => {
UseGuards(RoleGuard)(proto, propName, decsriptor)
const meta = Reflect.getMetadata('roleMetadata', proto) || []
Reflect.defineMetadata(
'roleMetadata',
[
...meta, {
repeatable: true,
value: role,
},
],
proto,
)
}
export const Role = constructDecorator(
({ targetType, descriptor, proto, propName, paramIndex, args: [role] }) => {
if (targetType === METHOD) {
RoleMethodDecorator(proto, propName, descriptor, role)
}
if (targetType === PARAM) {
createParamDecorator((_data: unknown, ctx: ExecutionContext) =>
getRole(ctx.switchToHttp().getRequest()),
)()(proto, propName, paramIndex)
}
},
)
Nest , . , , , . , @Controller
ยซยป
JSON-RPC.
, , : , Nest.
import {
ControllerOptions,
Controller,
Post,
Req,
Res,
HttpCode,
HttpStatus,
} from '@nestjs/common'
import { Request, Response } from 'express'
import { Extender } from '@qiwi/json-rpc-common'
import { JsonRpcMiddleware } from 'expressjs-json-rpc'
export const JsonRpcController = (
prefixOrOptions?: string | ControllerOptions,
): ClassDecorator => {
return <TFunction extends Function>(target: TFunction) => {
const extend: Extender = (base) => {
@Controller(prefixOrOptions as any)
@JsonRpcMiddleware()
class Extended extends base {
@Post('/')
@HttpCode(HttpStatus.OK)
rpc(@Req() req: Request, @Res() res: Response): any {
return this.middleware(req, res)
}
}
return Extended
}
return extend(target as any)
}
}
@Req()
rpc-method
, , @JsonRpcMethod
.
, :
import {
JsonRpcController,
JsonRpcMethod,
IJsonRpcId,
IJsonRpcParams,
} from 'nestjs-json-rpc'
@JsonRpcController('/jsonrpc/endpoint')
export class SomeJsonRpcController {
@JsonRpcMethod('some-method')
doSomething(
@JsonRpcId() id: IJsonRpcId,
@JsonRpcParams() params: IJsonRpcParams,
) {
const { foo } = params
if (foo === 'bar') {
return new JsonRpcError(-100, '"foo" param should not be equal "bar"')
}
return 'ok'
}
@JsonRpcMethod('other-method')
doElse(@JsonRpcId() id: IJsonRpcId) {
return 'ok'
}
}
Nest . . , , . , , .
, , , .