[中文版](README_zh.md)
npm install nest-hook🚀 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.
- 🦄 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 than cls-hooked.
``bash`
npm install nest-hookor
yarn add nest-hookor
pnpm add nest-hook
> Requirements: Node.js >= 14.0.0.
Import NestHooksModule into your root AppModule. That's it! No middleware configuration needed.
`typescript
// 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.
`typescript
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:
- useRequestuseResponse
- 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.
`typescript`
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.
`typescript
import { Request } from 'express';
import { useRequest } from 'nest-hook';
const req = useRequest
req.get('User-Agent'); // ✅ Full Express typing
`
Strict Usage (Fastify):
`typescript
import { FastifyRequest } from 'fastify';
import { useRequest } from 'nest-hook';
const req = useRequest
req.raw.url; // ✅ Full Fastify typing
`
---
Define your own hooks to store and retrieve data (like the current user) safely across the request lifecycle.
Step 1: Define the Hook
`typescript
// 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
// 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 value
`
Step 2: Set Data (e.g., in a Guard)
`typescript
// 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)
`typescript
// 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.
`typescript
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.
1. NestHooksModule registers a global middleware.Map
2. For every request, a new (Store) is initialized.createHook
3. This Store is unique to the request and is preserved across async/await calls.
4. generates getters/setters that access this request-specific Store.
1. 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`.
2. 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