Package Exports
- nestjs-inngest
- nestjs-inngest/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (nestjs-inngest) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
NestJS Inngest Integration
๐ Simple, unified NestJS integration for Inngest with multi-platform support and type safety.
โจ Features
Core Features
- ๐ Seamless NestJS Integration - Native dependency injection and NestJS patterns
- โก Multi-Platform Support - Works with both Express and Fastify HTTP platforms
- ๐ Type Safety - Full TypeScript support with typed event definitions and handlers
- ๐ฏ Decorator-Based - Simple
@InngestFunction
decorator for defining serverless functions - ๐ Automatic Discovery - Zero-config function registration and discovery
- ๐๏ธ Unified API - Single
createServe()
method works with both platforms - ๐ฎ Controller Integration - Custom NestJS controller support with
createControllerHandler()
Developer Experience
- ๐ง Simplified Configuration - Streamlined module setup with essential options only
- ๐ Error Handling - Comprehensive error reporting with detailed validation messages
- ๐ Debug Logging - Enhanced logging for development and troubleshooting
- ๐งช Basic Testing - Simple testing utilities for unit and integration tests
- ๐ฏ Flexible Integration - Choose between automatic middleware/plugin or custom controller integration
Quick Start
1. Module Setup
First, configure the InngestModule
in your NestJS application:
import { Module } from "@nestjs/common";
import { InngestModule } from "nestjs-inngest";
@Module({
imports: [
InngestModule.forRoot({
appId: "my-nestjs-app",
// signingKey and eventKey not required in local, but required in Inngest-Cloud or your self-host, you need add NODE_ENV === "development" into .env file
signingKey: process.env.INNGEST_SIGNING_KEY, // Inngest authenticates to me when calling my functions
eventKey: process.env.INNGEST_EVENT_KEY, // I authenticate to Inngest when sending events
}),
],
})
export class AppModule {}
2. HTTP Platform Setup
This module supports both Express and Fastify HTTP platforms with a unified API. You can choose between automatic integration (middleware/plugin) or controller-based integration for more control.
Option A: Automatic Integration (Recommended)
import { NestFactory } from "@nestjs/core";
import { NestExpressApplication } from "@nestjs/platform-express";
import {
NestFastifyApplication,
FastifyAdapter,
} from "@nestjs/platform-fastify";
import { InngestService } from "nestjs-inngest";
import { AppModule } from "./app.module";
async function bootstrap() {
// Platform switch - change this to switch platforms
const USE_FASTIFY = false; // Set to true for Fastify, false for Express
if (USE_FASTIFY) {
// Create Fastify application
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter()
);
// Setup Inngest Fastify plugin
const inngestService = app.get(InngestService);
const { plugin, options } = await inngestService.createServe("fastify");
await app.register(plugin, options);
await app.listen(3000, "0.0.0.0");
} else {
// Create Express application
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
bodyParser: false,
});
// Configure Express body parser
app.useBodyParser("json", { limit: "10mb" });
// Setup Inngest Express middleware
const inngestService = app.get(InngestService);
const serveMiddleware = await inngestService.createServe("express");
app.use("/api/inngest", serveMiddleware);
await app.listen(3000);
}
}
bootstrap();
Option B: Controller-Based Integration
For more control over the Inngest webhook endpoint, you can use a custom NestJS controller:
Express Controller Example:
// express-inngest.controller.ts
import { Controller, Post, Put, Get, Req, Res, Logger } from "@nestjs/common";
import { InngestService } from "nestjs-inngest";
@Controller("api/inngest")
export class ExpressInngestController {
private readonly logger = new Logger(ExpressInngestController.name);
constructor(private readonly inngestService: InngestService) {}
@Get()
async handleIntrospection(
@Req() req: any,
@Res({ passthrough: false }) res: any
) {
return this.handleInngestRequest(req, res, "GET");
}
@Post()
async handleWebhook(@Req() req: any, @Res({ passthrough: false }) res: any) {
return this.handleInngestRequest(req, res, "POST");
}
@Put()
async handlePutWebhook(
@Req() req: any,
@Res({ passthrough: false }) res: any
) {
return this.handleInngestRequest(req, res, "PUT");
}
private async handleInngestRequest(req: any, res: any, method: string) {
try {
// Use the controller-specific handler for Express
const controllerHandler =
await this.inngestService.createControllerHandler("express");
return controllerHandler(req, res);
} catch (error) {
this.logger.error("Error in Inngest controller:", error);
return res.status(500).json({ error: "Internal Server Error" });
}
}
}
Fastify Controller Example:
// fastify-inngest.controller.ts
import { Controller, Post, Put, Get, Req, Res, Logger } from "@nestjs/common";
import { InngestService } from "nestjs-inngest";
import type { FastifyRequest, FastifyReply } from "fastify";
@Controller("api/inngest")
export class FastifyInngestController {
private readonly logger = new Logger(FastifyInngestController.name);
constructor(private readonly inngestService: InngestService) {}
@Get()
async handleIntrospection(
@Req() req: FastifyRequest,
@Res({ passthrough: false }) res: FastifyReply
) {
return this.handleInngestRequest(req, res, "GET");
}
@Post()
async handleWebhook(
@Req() req: FastifyRequest,
@Res({ passthrough: false }) res: FastifyReply
) {
return this.handleInngestRequest(req, res, "POST");
}
@Put()
async handlePutWebhook(
@Req() req: FastifyRequest,
@Res({ passthrough: false }) res: FastifyReply
) {
return this.handleInngestRequest(req, res, "PUT");
}
private async handleInngestRequest(
req: FastifyRequest,
res: FastifyReply,
method: string
) {
try {
// Use the controller-specific handler for Fastify
const controllerHandler =
await this.inngestService.createControllerHandler("fastify");
return await controllerHandler(req, res);
} catch (error) {
this.logger.error("Error in Fastify Inngest controller:", error);
return res.status(500).send({
error: "Internal Server Error",
message: error.message,
});
}
}
}
Main Application (Fastify with Controller):
// main.ts
import { NestFactory } from "@nestjs/core";
import { FastifyAdapter } from "@nestjs/platform-fastify";
import type { NestFastifyApplication } from "@nestjs/platform-fastify";
import { AppModule } from "./app.module";
async function bootstrap() {
// Create Fastify application
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter()
);
await app.listen(3000, "0.0.0.0");
console.log(
"๐ Fastify app with Inngest controller running on http://localhost:3000"
);
console.log("๐ฏ Inngest webhook: http://localhost:3000/api/inngest");
}
bootstrap();
Don't forget to add the controller to your module:
// app.module.ts
import { Module } from "@nestjs/common";
import { InngestModule } from "nestjs-inngest";
import { FastifyInngestController } from "./fastify-inngest.controller";
@Module({
imports: [
InngestModule.forRoot({
appId: "my-fastify-app",
signingKey: process.env.INNGEST_SIGNING_KEY,
eventKey: process.env.INNGEST_EVENT_KEY,
}),
],
controllers: [FastifyInngestController],
})
export class AppModule {}
Integration Method Comparison
Feature | Automatic Integration | Controller Integration |
---|---|---|
Setup Complexity | โ Simple | ๐ก Medium |
Control Level | ๐ก Standard | โ Full Control |
Custom Logic | โ Limited | โ Full Support |
Error Handling | ๐ก Basic | โ Custom |
Middleware Support | โ Built-in | โ NestJS Decorators |
Best For | Quick setup, standard use cases | Custom logic, advanced error handling |
Note: Both
createServe()
andcreateControllerHandler()
methods provide a unified API that works with both Express and Fastify platforms!
3. Define Event Types (Optional but Recommended)
Create type-safe event definitions:
import { EventTypes } from "nestjs-inngest";
export type MyEventTypes = EventTypes<{
"user.created": { userId: string; email: string; name: string };
"order.placed": { orderId: string; userId: string; total: number };
"email.send": { to: string; template: string; data: any };
}>;
4. Create Inngest Functions
Use the @InngestFunction
decorator to define serverless functions:
import { Injectable } from "@nestjs/common";
import { InngestFunction, InngestFunctionContext } from "nestjs-inngest";
@Injectable()
export class UserService {
@InngestFunction({
id: "user-welcome-flow",
name: "User Welcome Flow",
triggers: [{ event: "user.created" }],
})
async handleUserCreated(
event: any,
{ step, logger, runId, attempt }: InngestFunctionContext
) {
const { userId, email, name } = event.data;
// Step 1: Create user profile
const profile = await step.run("create-profile", async () => {
return this.createUserProfile(userId, { email, name });
});
// Step 2: Send welcome email
await step.run("send-welcome-email", async () => {
return this.sendEmail({
to: email,
template: "welcome",
data: { name, userId },
});
});
// Step 3: Send event notification
await step.sendEvent("send-notification", {
name: "user.notification",
data: { userId, type: "welcome", email },
});
return { success: true, userId, profileId: profile.id };
}
private async createUserProfile(userId: string, data: any) {
// Your implementation
}
private async sendEmail(params: any) {
// Your implementation
}
}
5. Send Events
Inject the InngestService
to send events:
import { Injectable } from "@nestjs/common";
import { InngestService } from "nestjs-inngest";
@Injectable()
export class AuthService {
constructor(private readonly inngestService: InngestService) {}
async createUser(userData: any) {
// Create user in database
const user = await this.userRepository.save(userData);
// Trigger Inngest function
await this.inngestService.send({
name: "user.created",
data: {
userId: user.id,
email: user.email,
name: user.name,
},
});
return user;
}
}
Installation
npm install nestjs-inngest inngest
# or
yarn add nestjs-inngest inngest
# or
pnpm add nestjs-inngest inngest
Configuration
Basic Configuration
InngestModule.forRoot({
appId: "my-app", // Required: Your app identifier
signingKey: "your-key", // Required: For webhook verification
eventKey: "your-event-key", // Required: For sending events
endpoint: "/api/inngest", // Optional: Webhook endpoint path
env: "production", // Optional: Environment
timeout: 30000, // Optional: Function timeout in ms
});
Async Configuration
InngestModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
appId: configService.get("INNGEST_APP_ID"),
signingKey: configService.get("INNGEST_SIGNING_KEY"),
eventKey: configService.get("INNGEST_EVENT_KEY"),
env: configService.get("NODE_ENV"),
}),
inject: [ConfigService],
});
Configuration Options
Complete Parameter Reference
// Synchronous configuration
InngestModule.forRoot({
// === REQUIRED PARAMETERS ===
appId: "my-app", // ๐ต Inngest: Your app identifier
// === INNGEST CORE PARAMETERS ===
signingKey: process.env.INNGEST_SIGNING_KEY, // ๐ต Inngest: Webhook signature verification
eventKey: process.env.INNGEST_EVENT_KEY, // ๐ต Inngest: Event sending authentication
baseUrl: "https://api.inngest.com", // ๐ต Inngest: API base URL (defaults to Inngest cloud)
isDev: process.env.NODE_ENV === "development", // ๐ต Inngest: Development mode flag
// === CONNECTION METHODS ===
enableConnect: false, // ๐ Extension: Use connect mode vs serve mode (default: false)
// === HTTP & ROUTING ===
endpoint: "/api/inngest", // ๐ Extension: Webhook endpoint path (default: "/api/inngest")
// === PERFORMANCE & LIMITS ===
timeout: 30000, // ๐ต Inngest: Function timeout in ms (default: 30000)
maxBatchSize: 100, // ๐ Extension: Max events per batch (default: 100)
// === DEVELOPMENT & DEBUGGING ===
logger: true, // ๐ Extension: Enable detailed logging (default: true)
env: "development", // ๐ Extension: Environment setting ("production"|"development"|"test")
strict: false, // ๐ Extension: Enhanced validation (default: false)
// === RETRY CONFIGURATION ===
retry: {
maxAttempts: 3, // ๐ Extension: Maximum retry attempts (default: 3)
initialDelay: 1000, // ๐ Extension: Initial delay between retries in ms (default: 1000)
maxDelay: 30000, // ๐ Extension: Maximum delay between retries in ms (default: 30000)
backoff: "exponential", // ๐ Extension: Backoff strategy ("exponential"|"linear"|"fixed")
backoffMultiplier: 2, // ๐ Extension: Backoff multiplier (default: 2)
},
// === DEVELOPMENT MODE (Advanced) ===
development: {
enabled: true, // ๐ Extension: Enable development features
mockExternalCalls: false, // ๐ Extension: Mock external service calls
localWebhookUrl: "http://localhost:3000", // ๐ Extension: Custom local webhook URL
disableSignatureVerification: true, // ๐ Extension: Skip signature validation
enableIntrospection: true, // ๐ Extension: Function debugging tools
autoRegisterFunctions: true, // ๐ Extension: Auto-discover functions
developmentTimeout: 60000, // ๐ Extension: Extended timeout for debugging
enableStepDebugging: true, // ๐ Extension: Step-by-step execution logs
},
});
// Inngest SDK
if (process.env.NODE_ENV === "development" || process.env.INNGEST_DEV === "1") {
// auto disabled
signatureVerification = false;
}
// Asynchronous configuration with environment variables
InngestModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
appId: configService.get("INNGEST_APP_ID"),
signingKey: configService.get("INNGEST_SIGNING_KEY"),
eventKey: configService.get("INNGEST_EVENT_KEY"),
isDev: configService.get("NODE_ENV") === "development",
enableConnect: configService.get("INNGEST_USE_CONNECT") === "true",
// ... other parameters
}),
inject: [ConfigService],
});
Parameter Categories
๐ต Inngest Core Parameters - Native Inngest SDK parameters:
appId
,signingKey
,eventKey
,baseUrl
,isDev
,timeout
๐ NestJS Extension Parameters - Our enhancements for better NestJS integration:
- Connection management:
enableConnect
,endpoint
- Development tools:
logger
,env
,strict
,development.*
- Performance:
maxBatchSize
,retry.*
Quick Examples
Minimal Setup:
InngestModule.forRoot({
appId: "my-app",
// signingKey and eventKey not required in local, but required in Inngest-Cloud or your self-host, you need add NODE_ENV === "development" into .env file
signingKey: process.env.INNGEST_SIGNING_KEY, // Inngest authenticates to me when calling my functions
eventKey: process.env.INNGEST_EVENT_KEY, // I authenticate to Inngest when sending events
});
Development Setup:
InngestModule.forRoot({
appId: "my-dev-app",
isDev: true,
development: {
enabled: true,
disableSignatureVerification: true,
},
});
Production Setup:
InngestModule.forRoot({
appId: "my-prod-app",
signingKey: process.env.INNGEST_SIGNING_KEY,
eventKey: process.env.INNGEST_EVENT_KEY,
env: "production",
logger: false,
strict: true,
retry: { maxAttempts: 5 },
});
Step Functions
Inngest provides step functions for reliable, resumable workflows:
@InngestFunction({
id: 'user-workflow',
triggers: [{ event: 'user.created' }],
})
async handleUserWorkflow(event: any, { step }: any) {
// Steps are automatically retried on failure
const profile = await step.run('create-profile', async () => {
return this.createUserProfile(event.data);
});
// Sleep/delay execution
await step.sleep('wait-before-email', '1 minute');
// Send events to trigger other functions
await step.sendEvent('send-welcome-email', {
name: 'email.send',
data: { userId: profile.id, email: event.data.email }
});
return { success: true, profileId: profile.id };
}
Type Safety (Optional)
For applications requiring strict type safety, use the typed decorator with your event schema:
import { TypedInngestFunction, EventRegistry } from "nestjs-inngest";
// Define your event types
interface MyEventRegistry extends EventRegistry {
"user.created": { userId: string; email: string; name: string };
"order.completed": { orderId: string; amount: number; userId: string };
"email.sent": { to: string; subject: string; success: boolean };
}
@Injectable()
export class UserService {
@TypedInngestFunction<MyEventRegistry, "user.created">({
id: "user-welcome-flow",
triggers: [{ event: "user.created" }],
config: {
retries: 3,
timeout: 30000,
priority: 1,
},
})
async handleUserCreated({
event,
step,
}: TypedEventContext<MyEventRegistry, "user.created">) {
// event.data is strictly typed as { userId: string; email: string; name: string }
const { userId, email, name } = event.data;
return await step.run("create-profile", async () => {
return this.createUserProfile({ userId, email, name });
});
}
}
Advanced Type Helpers
Create event-specific decorators:
import { createEventDecorator, CronFunction } from "nestjs-inngest";
// Create a user-created specific decorator
const UserCreatedFunction = createEventDecorator<MyEventRegistry, 'user.created'>('user.created');
// Use the specific decorator
@UserCreatedFunction({
id: "send-welcome-email",
name: "Send Welcome Email"
})
async sendWelcomeEmail({ event }: TypedEventContext<MyEventRegistry, 'user.created'>) {
// Automatically typed for user.created events
}
// Cron-based functions
@CronFunction({
id: "daily-cleanup",
cron: "0 2 * * *", // 2 AM daily
timezone: "UTC"
})
async dailyCleanup() {
// Scheduled function
}
Performance Optimization (Optional)
For applications with many functions or high-performance requirements, you can use the optimized decorator:
import { OptimizedInngestFunction } from "nestjs-inngest";
@Injectable()
export class UserService {
@OptimizedInngestFunction({
id: "user-welcome-flow",
triggers: [{ event: "user.created" }],
})
async handleUserCreated(event: any, { step }: any) {
// Same functionality as @InngestFunction, but with:
// - Multi-layer caching for faster metadata processing
// - Memory optimization with object pooling
// - Batch validation and lookup capabilities
// - Performance monitoring and statistics
return await step.run("process-user", async () => {
return this.processUser(event.data);
});
}
}
Performance Monitoring
Monitor decorator performance in high-load scenarios:
import {
getDecoratorPerformanceStats,
clearOptimizedCaches,
} from "nestjs-inngest";
// Get performance statistics
const stats = getDecoratorPerformanceStats();
console.log({
registeredClasses: stats.registeredClasses,
totalFunctions: stats.totalFunctions,
cacheHitRate: stats.cacheHitRate,
memoryUsage: stats.memoryUsage,
});
// Clear caches when needed (e.g., during testing)
clearOptimizedCaches();
Decorator Comparison
Feature | @InngestFunction |
@TypedInngestFunction |
@OptimizedInngestFunction |
@CronFunction |
---|---|---|---|---|
Type Safety | โ Basic (any ) |
โ Strict TypeScript | โ Basic (any ) |
โ Typed Config |
Performance | ๐ข Standard | ๐ข Standard | โ Optimized | ๐ข Standard |
Event Validation | ๐ก Runtime only | โ Compile + Runtime | ๐ก Runtime only | โ Compile Time |
Memory Usage | ๐ข Low | ๐ข Low | ๐ก Higher (caching) | ๐ข Low |
Complexity | ๐ข Simple | ๐ก Medium | ๐ก Medium | ๐ข Simple |
IDE Support | ๐ก Basic | โ Full IntelliSense | ๐ก Basic | โ Full IntelliSense |
Trigger Types | โ Event + Cron | โ Event + Cron | โ Event + Cron | ๐ฏ Cron Only |
Best For | General use | Type-safe apps | High-performance apps | Scheduled tasks |
When to Use Each Decorator
@InngestFunction
(Recommended for most cases)
- โ General purpose applications
- โ Quick prototyping and development
- โ Simple event handling
- โ When type safety is not critical
@TypedInngestFunction
(For type-safe applications)
- โ Large applications with complex event schemas
- โ Teams requiring strict type safety
- โ When you want compile-time event validation
- โ Better IDE support and IntelliSense
@OptimizedInngestFunction
(For high-performance scenarios)
- โ Applications with 50+ Inngest functions
- โ High-frequency function registration/discovery
- โ Performance-critical environments
- โ When you need performance monitoring
@CronFunction
(For scheduled tasks)
- โ Pure cron-based scheduled tasks
- โ When you want clean, explicit cron syntax
- โ Type-safe cron configuration with timezone support
- โ Priority and timeout configuration for scheduled jobs
- โ Cleaner than manually configuring cron triggers
CronFunction Examples
import { CronFunction } from "nestjs-inngest";
@Injectable()
export class ScheduledTasksService {
// Daily cleanup at 2 AM UTC
@CronFunction({
id: "daily-cleanup",
name: "Daily Cleanup Task",
cron: "0 2 * * *",
timezone: "UTC",
retries: 2,
timeout: 300000, // 5 minutes
priority: 1, // High priority
})
async dailyCleanup() {
// Cleanup logic here
return { cleaned: true, timestamp: new Date() };
}
// Weekly report every Monday at 9 AM
@CronFunction({
id: "weekly-report",
cron: "0 9 * * 1", // Monday 9 AM
timezone: "America/New_York",
})
async weeklyReport() {
// Generate weekly report
}
// High-frequency monitoring every 30 minutes
@CronFunction({
id: "health-check",
cron: "*/30 * * * *", // Every 30 minutes
timeout: 5000,
retries: 1,
})
async healthCheck() {
// Health monitoring logic
}
}
Testing
Mock the InngestService
for unit testing:
const mockInngestService = {
send: jest.fn(),
getClient: jest.fn(),
};
// Use in your test modules
providers: [{ provide: InngestService, useValue: mockInngestService }];
Key Concepts
@InngestFunction
: Standard decorator for general-purpose event handling@TypedInngestFunction
: Type-safe decorator with strict TypeScript event validation@OptimizedInngestFunction
: Performance-optimized decorator for high-load applicationsInngestService
: Main service for sending events and creating integrationssend()
- Send events to InngestgetClient()
- Get underlying Inngest clientcreateServe(platform)
- Create middleware/plugin integrationcreateControllerHandler(platform)
- Create controller-based integration
- Step Functions: Use
step.run()
,step.sleep()
,step.sendEvent()
for reliable workflows - Events: Send events with
{ name: string, data: any }
structure
Troubleshooting
- Functions not registering: Ensure services are imported in your module and decorated with
@Injectable()
- Webhook errors: Verify
signingKey
configuration and endpoint URL - Debug mode: Set
logger: true
andisDev: true
for detailed logging
Planned for Future Versions
- Performance optimizations (connection pooling, memory management, request optimization)
- Enhanced testing utilities and mocks
- Typed function decorators (
@TypedInngestFunction
) - Advanced logging and monitoring
- Circuit breakers and resilient error handling
- Event sourcing and audit trails
License
MIT License - see CHANGELOG.md for version history.