🚀 React-like Hooks for NestJS. Access request-scoped context (Request, User, Services) anywhere without injection hell.
Stop passing req, user, or transaction through every layer of your application. nest-hook uses Node.js native AsyncLocalStorage to provide a clean, type-safe, and performant way to manage request context.
- 🎣 React-style Hooks:
useRequest(),useUser(),useService(). - 🛡 Type Safe: Fully typed custom hooks with
createHook<T>(). - 🦄 Framework Agnostic: Works with Express and Fastify out of the box (no hard dependencies).
- 💉 Zero Injection: No more
@Inject(REQUEST)in your services. - 🌐 Cross-Module Access: Easily resolve any provider dynamically with
useService. - 🚀 High Performance: Built on native
AsyncLocalStorage, faster and safer thancls-hooked.
npm install nest-hook
# or
yarn add nest-hook
# or
pnpm add nest-hookRequirements: Node.js >= 14.0.0.
Import NestHooksModule into your root AppModule. That's it! No middleware configuration needed.
// app.module.ts
import { Module } from '@nestjs/common';
import { NestHooksModule } from 'nest-hook';
@Module({
imports: [NestHooksModule], // 👈 Add this
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}Access Request/Response objects instantly in any Service, Repository, or Helper function.
import { Injectable } from '@nestjs/common';
import { useRequest, useClientIP, useToken } from 'nest-hook';
@Injectable()
export class AppService {
getHello(): string {
// No need to inject REQUEST in constructor!
const req = useRequest(); // Returns a generic BaseRequest
// Works directly (common properties are typed)
const ip = req.ip;
const body = req.body;
// Or use helper hooks
const clientIp = useClientIP();
const token = useToken();
return `Hello! IP: ${clientIp}`;
}
}Available Built-ins:
useRequest<T>()useResponse<T>()useModuleRef()useToken()(Extracts Bearer token)useHeader(key: string)(Case-insensitive lookup)useClientIP()(Smart IP detection for Proxies/Express/Fastify)
nest-hook is designed to be compatible with both Express and Fastify without importing their types by default.
Default Usage (Lazy Mode):
useRequest() returns a BaseRequest interface (with headers, body, query, etc.) and an index signature. This means accessing custom properties won't throw TypeScript errors.
const req = useRequest();
console.log(req.query); // ✅ Typed
console.log(req.someCustomProp); // ✅ Allowed (no error)Strict Usage (Express): Pass the generic to get full IntelliSense for Express methods.
import { Request } from 'express';
import { useRequest } from 'nest-hook';
const req = useRequest<Request>();
req.get('User-Agent'); // ✅ Full Express typingStrict Usage (Fastify):
import { FastifyRequest } from 'fastify';
import { useRequest } from 'nest-hook';
const req = useRequest<FastifyRequest>();
req.raw.url; // ✅ Full Fastify typingDefine your own hooks to store and retrieve data (like the current user) safely across the request lifecycle.
Step 1: Define the Hook
// src/hooks/user.hook.ts
import { createHook } from 'nest-hook';
import { User } from './user.entity';
// Define a unique key
const USER_KEY = Symbol('USER');
// Create the hook pair
const userContext = createHook<User>(USER_KEY);
// Export simplified functions
export const useUser = userContext.use; // Returns User | undefined
export const useRequiredUser = userContext.useRequired; // Returns User or throws error
export const setUser = userContext.set; // Sets the valueStep 2: Set Data (e.g., in a Guard)
// src/auth/auth.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { setUser } from '../hooks/user.hook';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext) {
// ... validate logic ...
const user = { id: 1, name: 'John' };
// 👇 Save user to context
setUser(user);
return true;
}
}Step 3: Use Data (Anywhere)
// src/posts/posts.service.ts
import { Injectable } from '@nestjs/common';
import { useRequiredUser } from '../hooks/user.hook';
@Injectable()
export class PostsService {
createPost() {
// 👇 Get current user securely
const user = useRequiredUser();
console.log(`User ${user.id} is creating a post.`);
}
}useService automatically handles { strict: false }, allowing you to resolve any provider from the root container, even if it's not exported by a module.
import { useService } from 'nest-hook';
import { UserService } from './user/user.service';
export function logUserActivity(action: string) {
// 👇 Magically get the UserService instance outside of DI
const userService = useService(UserService);
const user = userService.getCurrentUser();
}This library uses Node.js AsyncLocalStorage.
NestHooksModuleregisters a global middleware.- For every request, a new
Map(Store) is initialized. - This Store is unique to the request and is preserved across async/await calls.
createHookgenerates getters/setters that access this request-specific Store.
- Request Scope Only: These hooks only work inside a request lifecycle (Controllers, Services, Guards). Calling them during startup (e.g.,
onModuleInit,constructor) will throw an error:Hooks context is not initialized. - Avoid Overuse: Dependency Injection is still the core pattern of NestJS. Use hooks primarily for Context Data (User, Tenant, Request Info) or strictly for helper utilities.
MIT