Skip to content

ALIRAZA47/nest-req-ctx

Repository files navigation

@kitstack/nest-req-ctx

A type-safe request context management package for NestJS using AsyncLocalStorage. Supports both Express and Fastify adapters.

Features

  • Request Context Storage - Store and access request-scoped data anywhere in your application
  • Type-Safe - Full TypeScript support with generics and type inference
  • Express & Fastify Support - Works with both HTTP adapters out of the box
  • Powerful Decorators - Access request data, headers, and context values via decorators
  • Dot Notation Support - Access deeply nested properties with dot notation (e.g., user.profile.email)
  • Extensible - Create custom context services by extending the base class
  • Multiple Setup Methods - Initialize context via middleware, guard, or interceptor

Installation

Option 1: Interactive CLI (recommended for first-time setup)

From your NestJS project directory, run:

npx @kitstack/nest-req-ctx

The CLI will ask for your preferences (HTTP adapter, setup type, global module, etc.), install the package, and add RequestContextModule to your app.module.ts.

Option 2: Manual install

npm install @kitstack/nest-req-ctx

Quick Start

1. Import the Module

// app.module.ts
import { Module } from "@nestjs/common";
import { RequestContextModule } from "@kitstack/nest-req-ctx";

@Module({
  imports: [
    RequestContextModule.forRoot({
      isGlobal: true, // Makes the module available everywhere
      adapter: "auto", // Auto-detect Express or Fastify
    }),
  ],
})
export class AppModule {}

2. Use Decorators in Controllers

import { Controller, Get } from "@nestjs/common";
import { Req, Header, RequestKey, ContextValue } from "@kitstack/nest-req-ctx";
import { Request } from "express";

@Controller()
export class AppController {
  @Get()
  handler(
    @Req() request: Request,
    @Header("authorization") auth: string,
    @RequestKey("user") user: User,
    @RequestKey<string>("user.email") email: string,
    @ContextValue<string>("tenantId") tenantId: string,
  ) {
    return { user, email, tenantId };
  }
}

3. Inject Context Service

import { Injectable } from "@nestjs/common";
import { RequestContextService, InjectContext } from "@kitstack/nest-req-ctx";

@Injectable()
export class AppService {
  constructor(@InjectContext() private readonly ctx: RequestContextService) {}

  getCurrentUser() {
    return this.ctx.get("user");
  }

  getUserEmail() {
    return this.ctx.getByPath<string>("user.email");
  }
}

Module Configuration

Basic Configuration

RequestContextModule.forRoot({
  isGlobal: true, // Register globally (default: true)
  adapter: "express", // 'express' | 'fastify' | 'auto'
  setupType: "middleware", // 'middleware' | 'guard' | 'interceptor'
  setRequest: true, // Store request object in context
  exclude: ["/health"], // Routes to exclude (middleware only)
  setup: (ctx, req) => {
    // Custom setup function
    ctx.set("requestId", req.headers["x-request-id"]);
  },
});

Async Configuration

RequestContextModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: (config: ConfigService) => ({
    adapter: config.get("HTTP_ADAPTER"),
  }),
  inject: [ConfigService],
});

Decorators

@Req()

Get the full request object:

@Get()
handler(@Req() request: Request) {
  return { path: request.path };
}

@Headers() / @Header()

Get all headers or a specific header:

@Get()
handler(
  @Headers() allHeaders: Record<string, string>,
  @Header('authorization') auth: string,
  @Header('x-request-id') requestId: string,
) {
  return { auth, requestId };
}

@RequestKey()

Get a value from the request object. Supports dot notation:

@Get()
handler(
  @RequestKey('user') user: User,
  @RequestKey('user.email') email: string,
  @RequestKey<string>('user.profile.name') name: string,
) {
  return { user, email, name };
}

@ContextValue()

Get a value from the context store. Supports dot notation and generics:

@Get()
handler(
  @ContextValue('user') user: User,
  @ContextValue<string>('user.email') email: string,
  @ContextValue<string>('tenant.id') tenantId: string,
) {
  return { user, email, tenantId };
}

@InjectContext()

Inject the RequestContextService:

@Injectable()
export class MyService {
  constructor(@InjectContext() private ctx: RequestContextService) {}
}

RequestContextService API

Core Methods

// Set a value
ctx.set("key", value);

// Get a value
const value = ctx.get("key");
const typedValue = ctx.get<User>("user");

// Get value by path (dot notation)
const email = ctx.getByPath<string>("user.profile.email");

// Check if key exists
const hasUser = ctx.has("user");

// Delete a key
ctx.delete("key");

// Get all values
const store = ctx.getAll();

// Check if context is active
const isActive = ctx.isActive();

Request Methods

// Get the request object
const request = ctx.getRequest();

// Get a header
const auth = ctx.getHeader("authorization");

// Get all headers
const headers = ctx.getHeaders();

// Get a request property (supports dot notation)
const user = ctx.getRequestProperty<User>("user");
const email = ctx.getRequestProperty<string>("user.email");

Extending the Context

Create a custom context service for domain-specific functionality:

import { Injectable } from "@nestjs/common";
import {
  BaseRequestContext,
  RequestContextService,
  InjectContext,
} from "@kitstack/nest-req-ctx";

interface AppContextStore {
  user: User;
  tenant: Tenant;
  requestId: string;
}

@Injectable()
export class AppRequestContext extends BaseRequestContext<AppContextStore> {
  constructor(@InjectContext() ctx: RequestContextService<AppContextStore>) {
    super(ctx);
  }

  get currentUser(): User | undefined {
    return this.get("user");
  }

  set currentUser(user: User) {
    this.set("user", user);
  }

  get currentTenant(): Tenant | undefined {
    return this.get("tenant");
  }

  hasRole(role: string): boolean {
    return this.currentUser?.roles.includes(role) ?? false;
  }

  isAdmin(): boolean {
    return this.hasRole("admin");
  }
}

Use it in your application:

@Controller()
export class AppController {
  constructor(private readonly appContext: AppRequestContext) {}

  @Get()
  handler() {
    return {
      isAuthenticated: this.appContext.isActive,
      isAdmin: this.appContext.isAdmin(),
      user: this.appContext.currentUser,
    };
  }
}

Using with Fastify

// main.ts
import { NestFactory } from "@nestjs/core";
import {
  FastifyAdapter,
  NestFastifyApplication,
} from "@nestjs/platform-fastify";
import { AppModule } from "./app.module";

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
  );
  await app.listen(3000);
}

// app.module.ts
@Module({
  imports: [
    RequestContextModule.forRoot({
      adapter: "fastify",
    }),
  ],
})
export class AppModule {}

TypeScript Utilities

Type-Safe Path Access

import { PathsOf, DeepValue } from "@kitstack/nest-req-ctx";

interface MyStore {
  user: {
    id: string;
    profile: {
      email: string;
      name: string;
    };
  };
}

// PathsOf generates all valid paths
type Paths = PathsOf<MyStore>;
// 'user' | 'user.id' | 'user.profile' | 'user.profile.email' | 'user.profile.name'

// DeepValue gets the type at a path
type Email = DeepValue<MyStore, "user.profile.email">; // string

Typed Decorator Factories

import { createTypedContextValue } from "@kitstack/nest-req-ctx";

interface MyStore {
  user: { email: string };
  tenant: { id: string };
}

const TypedContext = createTypedContextValue<MyStore>();

@Controller()
export class AppController {
  @Get()
  handler(
    @TypedContext("user.email") email: string,
    @TypedContext("tenant.id") tenantId: string,
  ) {
    return { email, tenantId };
  }
}

Setting Context Values

Via Middleware

@Injectable()
export class AuthMiddleware implements NestMiddleware {
  constructor(private ctx: RequestContextService) {}

  use(req: Request, res: Response, next: NextFunction) {
    const user = this.validateToken(req.headers.authorization);
    this.ctx.set("user", user);
    next();
  }
}

Via Guard

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private ctx: RequestContextService) {}

  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const user = this.validateToken(request.headers.authorization);
    this.ctx.set("user", user);
    return !!user;
  }
}

Via Module Setup

RequestContextModule.forRoot({
  setup: (ctx, req) => {
    ctx.set("requestId", req.headers["x-request-id"] || uuid());
    ctx.set("correlationId", req.headers["x-correlation-id"]);
  },
});

API Reference

Exports

Export Type Description
RequestContextModule Module Main module with forRoot/forRootAsync
RequestContextService Service Core context service
BaseRequestContext Class Base class for custom contexts
@Req() Decorator Get request object
@Headers() Decorator Get all headers
@Header(key) Decorator Get specific header
@RequestKey(key) Decorator Get request property
@ContextValue(key) Decorator Get context value
@InjectContext() Decorator Inject context service

Type Exports

Export Description
PathsOf<T> All dot-notation paths of type T
DeepValue<T, P> Value type at path P in type T
UnifiedRequest Express or Fastify request type
RequestContextModuleOptions Module configuration options
AdapterType 'express' | 'fastify' | 'auto'

License

MIT

About

A type-safe request context management package for NestJS using AsyncLocalStorage. Supports both Express and Fastify adapters.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors