Skip to content

React-like Hooks for NestJS. Access request-scoped context (Request, User, Services) anywhere without injection hell.

License

Notifications You must be signed in to change notification settings

lblblong/nest-hook

Repository files navigation

nest-hook

中文版

🚀 React-like Hooks for NestJS. Access request-scoped context (Request, User, Services) anywhere without injection hell.

npm version License

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.

✨ Features

  • 🎣 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 than cls-hooked.

📦 Installation

npm install nest-hook
# or
yarn add nest-hook
# or
pnpm add nest-hook

Requirements: Node.js >= 14.0.0.

🔨 Setup

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 {}

📖 Usage

1. Built-in Hooks

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)

2. Type Safety (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 typing

Strict Usage (Fastify):

import { FastifyRequest } from 'fastify';
import { useRequest } from 'nest-hook';

const req = useRequest<FastifyRequest>();
req.raw.url; // ✅ Full Fastify typing

3. Create Custom Hooks

Define 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 value

Step 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.`);
  }
}

4. Dynamic Service Resolution

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();
}

💡 How it works

This library uses Node.js AsyncLocalStorage.

  1. NestHooksModule registers a global middleware.
  2. For every request, a new Map (Store) is initialized.
  3. This Store is unique to the request and is preserved across async/await calls.
  4. createHook generates getters/setters that access this request-specific Store.

⚠️ Best Practices

  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.

License

MIT

About

React-like Hooks for NestJS. Access request-scoped context (Request, User, Services) anywhere without injection hell.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published