Custom decorators
Extend the framework with the same primitives NestJS documents—metadata decorators via SetMetadata and
transport-aware parameter decorators with createHttpParamDecorator and createGraphqlParamDecorator.
These helpers integrate with the router and schema builder so your decorators behave like first-party ones.
Metadata decorators
Wrap class and handler definitions with domain-specific metadata. Under the hood SetMetadata stores values
using reflect-metadata, allowing guards, interceptors, or any runtime component to look them up.
import { SetMetadata } from '@nl-framework/core';import { Controller, Get, UseGuards } from '@nl-framework/http';export const Roles = (...roles: string[]) => SetMetadata('roles', roles);@Controller('/admin')@Roles('admin')export class AdminController { @Get() @Roles('moderator') dashboard() { return { ok: true }; }}import 'reflect-metadata';import { CanActivate } from '@nl-framework/core';import { HttpExecutionContext } from '@nl-framework/http';export class RolesGuard implements CanActivate { canActivate(context: HttpExecutionContext) { const handler = context.getHandler(); const controller = context.getClass(); const handlerRoles = Reflect.getMetadata('roles', handler) ?? []; const controllerRoles = Reflect.getMetadata('roles', controller) ?? []; if (!handlerRoles.length && !controllerRoles.length) { return true; } const request = context.getRequest(); const currentRole = request.headers.get('x-role'); const allowed = [...controllerRoles, ...handlerRoles]; return currentRole ? allowed.includes(currentRole) : false; }}HTTP parameter decorators
Use createHttpParamDecorator() to capture values from RequestContext. Decorators participate in the
same metadata pipeline as @Param() or @Body(), so pipes and validation keep working.
import { Controller, Get, createHttpParamDecorator } from '@nl-framework/http';export const CurrentUser = createHttpParamDecorator((property, ctx) => { const user = { id: ctx.headers.get('x-user-id'), role: ctx.headers.get('x-user-role'), }; if (!property) { return user; } return user[property];});@Controller('/profile')export class ProfileController { @Get() me(@CurrentUser('id') id: string, @CurrentUser() user: Record<string, unknown>) { return { id, user }; }}GraphQL parameter decorators
GraphQL resolvers can define decorators with createGraphqlParamDecorator(). The factory receives the parent value,
sanitized args, the shared context object, and GraphQLResolveInfo, mirroring NestJS's ExecutionContext.
import { ObjectType, Field } from '@nl-framework/graphql';import { Resolver, Query, createGraphqlParamDecorator } from '@nl-framework/graphql';export const CurrentTenant = createGraphqlParamDecorator((property, ctx) => { const tenant = (ctx.context?.tenant ?? {}) as Record<string, unknown>; if (!property) { return tenant; } return tenant[property];});@ObjectType()class TenantInfo { @Field() id!: string; @Field() plan!: string;}@Resolver(() => TenantInfo)export class TenantResolver { @Query(() => TenantInfo) info(@CurrentTenant('plan') plan: string, @CurrentTenant('id') id: string) { return { id, plan }; }}- Metadata decorators work for both legacy and stage-3 decorators—the framework adds initializers automatically.
- Custom parameter decorators can still leverage pipes; any data you pass into the decorator is forwarded to pipe metadata.
- GraphQL factories run after arguments are transformed, so you never have to manually validate DTOs.