
Inngest Middleware
- 3k installs
- 27 repo stars
- Updated July 2, 2026
- inngest/inngest-skills
inngest-middleware is an agent skill that guides Inngest v4 middleware for logging, Sentry, encryption, dependency injection, and custom telemetry across durable functions.
About
inngest-middleware is an agent skill for cross-cutting concerns on Inngest durable functions through client-level and function-level middleware hooks. It documents structured logging, tracing, Sentry error tracking, payload encryption via @inngest/middleware-encryption, dependency injection with dependencyInjectionMiddleware, custom telemetry, and uniform behavior across many functions. The v4 middleware lifecycle covers onFunctionRun hooks such as beforeExecution, afterExecution, and transformOutput plus onSendEvent transformInput for outbound event payloads. Official packages include encryption middleware with key rotation support and sentryMiddleware requiring Sentry 8 or newer. Custom InngestMiddleware examples show metrics histograms and robust error handling that logs middleware failures without breaking function execution. Best practices require one concern per middleware, graceful error handling, performance awareness, proper typing, and testing with createMockContext and createMockFunction. The skill warns not to install @inngest/realtime on v4 because step.realtime.publish is built in. Developers reach for it when wiring logging, Sentry, encryption, dependency injection,.
- Documents v4 middleware lifecycle hooks for function runs and outbound event transforms.
- Covers built-in dependencyInjectionMiddleware plus @inngest/middleware-encryption and @inngest/middleware-sentry package
- Shows custom metrics middleware with beforeExecution, afterExecution, and transformOutput counters.
- Warns v4 projects must not use @inngest/realtime middleware because realtime publish is native.
- Best practices emphasize one concern per middleware, graceful failures, and thorough unit testing.
Inngest Middleware by the numbers
- 2,957 all-time installs (skills.sh)
- +54 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #153 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 4, 2026 (Skillselion catalog sync)
inngest-middleware capabilities & compatibility
- Capabilities
- custom inngestmiddleware lifecycle hooks · built in dependency injection middleware · official encryption and sentry packages · metrics and performance tracking patterns · middleware error handling without breaking funct
- Use cases
- orchestration · api development
What inngest-middleware says it does
Middleware allows code to run at various points in an Inngest client's lifecycle - during function execution, event sending, and more.
For Realtime use the `inngest-realtime` skill, NOT this one.
npx skills add https://github.com/inngest/inngest-skills --skill inngest-middlewareAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3k |
|---|---|
| repo stars | ★ 27 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 2, 2026 |
| Repository | inngest/inngest-skills ↗ |
How do I add logging, error tracking, encryption, or shared clients uniformly across many Inngest functions?
Add logging, Sentry, encryption, dependency injection, and custom telemetry middleware across Inngest durable functions.
Who is it for?
Developers adding observability, encryption, or dependency injection across Inngest durable function fleets.
Skip if: Skip when configuring Inngest Realtime on v4 or when the task is only function business logic without cross-cutting hooks.
When should I use this skill?
User adds Inngest middleware for logging, Sentry, encryption, dependency injection, metrics, or uniform error handling.
What you get
Client and function middleware configured with v4 lifecycle hooks, official encryption and Sentry packages, and tested custom middleware patterns.
- Configured Inngest middleware client setup
Files
Inngest Middleware
Master Inngest middleware to handle cross-cutting concerns like logging, error tracking, dependency injection, and data transformation. Middleware runs at key points in the function lifecycle, enabling powerful patterns for observability and shared functionality.
These skills are focused on TypeScript. For Python or Go, refer to the Inngest documentation for language-specific guidance. Core concepts apply across all languages.
Note: The middleware system was significantly rewritten in v4. The lifecycle hooks documented here reflect the v4 API. If migrating from v3, consult the migration guide for details on breaking changes.
⚠ For Realtime use the `inngest-realtime` skill, NOT this one. Inngest v3 usedrealtimeMiddleware()from@inngest/realtimeto inject apublisharg into function handlers. v4 ships realtime natively —step.realtime.publishis built-in, no middleware required. Do NOT install@inngest/realtimeon a v4 project (it's a v3-era package and producesTypeError: Cls is not a constructorat runtime). See theinngest-realtimeskill for the v4 pattern.
What is Middleware?
Middleware allows code to run at various points in an Inngest client's lifecycle - during function execution, event sending, and more. Think of middleware as hooks into the Inngest execution pipeline.
When to use middleware:
- Observability: Add logging, tracing, or metrics
- Dependency injection: Share client instances across functions
- Data transformation: Encrypt/decrypt, validate, or enrich data
- Error handling: Custom error tracking and alerting
- Authentication: Validate user context or permissions
Middleware Lifecycle
Middleware can be registered at client-level (affects all functions) or function-level (affects specific functions).
Execution Order
const inngest = new Inngest({
id: "my-app",
middleware: [
loggingMiddleware, // Runs 1st
errorMiddleware // Runs 2nd
]
});
inngest.createFunction(
{
id: "example",
middleware: [
authMiddleware, // Runs 3rd
metricsMiddleware // Runs 4th
],
triggers: [{ event: "test" }]
},
async () => {
/* function code */
}
);Order matters: Client middleware runs first, then function middleware, in the order specified.
Creating Custom Middleware
Basic Middleware Structure
import { InngestMiddleware } from "inngest";
const loggingMiddleware = new InngestMiddleware({
name: "Logging Middleware",
init() {
// Setup phase - runs when client initializes
const logger = setupLogger();
return {
// Function execution lifecycle
// Note: `fn` is loosely typed in middleware generics; fn.id works at runtime
onFunctionRun({ ctx, fn }) {
return {
beforeExecution() {
logger.info("Function starting", {
functionId: fn.id,
eventName: ctx.event.name,
runId: ctx.runId
});
},
afterExecution() {
logger.info("Function completed", {
functionId: fn.id,
runId: ctx.runId
});
},
transformOutput({ result }) {
// Log function output
logger.debug("Function output", {
functionId: fn.id,
output: result.data
});
// Return unmodified result
return { result };
}
};
},
// Event sending lifecycle
onSendEvent() {
return {
transformInput({ payloads }) {
logger.info("Sending events", {
count: payloads.length,
events: payloads.map((p) => p.name)
});
// Spread to convert readonly array to mutable array
return { payloads: [...payloads] };
}
};
}
};
}
});Python Implementation
Python middleware follows a similar pattern. See Dependency Injection Reference for complete Python examples.
````
Dependency Injection
Share expensive or stateful clients across all functions. See [Dependency Injection Reference](./references/dependency-injection.md) for detailed patterns.
Quick Example - Built-in DI
import { dependencyInjectionMiddleware } from "inngest";
const inngest = new Inngest({
id: 'my-app',
middleware: [
dependencyInjectionMiddleware({
openai: new OpenAI(),
db: new PrismaClient(),
}),
],
});
// Functions automatically get injected dependencies
inngest.createFunction(
{ id: "ai-summary", triggers: [{ event: "document/uploaded" }] },
async ({ event, openai, db }) => {
// Dependencies available in function context
const summary = await openai.chat.completions.create({
messages: [{ role: "user", content: event.data.content }],
model: "gpt-4",
});
await db.document.update({
where: { id: event.data.documentId },
data: { summary: summary.choices[0].message.content }
});
}
);Middleware Packages
Beyond dependencyInjectionMiddleware (built-in, shown above), Inngest provides official middleware as separate packages. See [Middleware Reference](./references/built-in-middleware.md) for complete details.
Encryption Middleware
npm install @inngest/middleware-encryptionimport { encryptionMiddleware } from "@inngest/middleware-encryption";
const inngest = new Inngest({
id: "my-app",
middleware: [
encryptionMiddleware({
key: process.env.ENCRYPTION_KEY
})
]
});Automatically encrypts all step data, function output, and event data.encrypted field. Supports key rotation via fallbackDecryptionKeys.
Sentry Error Tracking
npm install @inngest/middleware-sentryimport * as Sentry from "@sentry/node";
import { sentryMiddleware } from "@inngest/middleware-sentry";
Sentry.init({
/* your Sentry config */
});
const inngest = new Inngest({
id: "my-app",
middleware: [sentryMiddleware()]
});Captures exceptions, adds tracing to each function run, and includes function ID and event names as context. Requires @sentry/*@>=8.0.0.
Common Middleware Patterns
Metrics and Performance Tracking
const metricsMiddleware = new InngestMiddleware({
name: "Metrics Tracking",
init() {
return {
onFunctionRun({ ctx, fn }) {
let startTime: number;
return {
beforeExecution() {
startTime = Date.now();
metrics.increment("inngest.step.started", {
function: fn.id,
event: ctx.event.name
});
},
afterExecution() {
const duration = Date.now() - startTime;
metrics.histogram("inngest.step.duration", duration, {
function: fn.id,
event: ctx.event.name
});
},
transformOutput({ result }) {
const status = result.error ? "error" : "success";
metrics.increment("inngest.step.completed", {
function: fn.id,
status: status
});
return { result };
}
};
}
};
}
});Advanced Patterns
Authentication: Validate tokens and inject user context Conditional logic: Apply middleware based on event type or function Circuit breakers: Prevent cascading failures from external services
Configuration-Based Middleware
Create reusable middleware with configuration options for different environments and use cases. See reference documentation for complete examples.
Best Practices
Design Principles
1. Keep middleware focused: One concern per middleware 2. Handle errors gracefully: Don't let middleware crash functions 3. Consider performance: Middleware runs on every execution 4. Use proper typing: Let TypeScript infer middleware types 5. Test thoroughly: Middleware affects all functions that use it
Common Use Cases to Implement
- Retry logic for transient failures
- Circuit breakers for external service calls
- Request/response logging for debugging
- User context enrichment from external sources
- Feature flags for gradual rollouts
- Custom authentication and authorization checks
Error Handling in Middleware
const robustMiddleware = new InngestMiddleware({
name: "Robust Middleware",
init() {
return {
onFunctionRun({ ctx, fn }) {
return {
transformOutput({ result }) {
try {
// Your middleware logic here
return performTransformation(result);
} catch (middlewareError) {
// Log error but don't break the function
console.error("Middleware error:", middlewareError);
// Return original result on middleware failure
return { result };
}
}
};
}
};
}
});Testing Middleware
Use Inngest's testing utilities (createMockContext, createMockFunction) to unit test middleware behavior.
For complete implementation examples and advanced patterns, see:
- Dependency Injection Reference
- Built-in Middleware Reference
Inngest Middleware Reference
Inngest provides dependencyInjectionMiddleware as a built-in export from the inngest package. Encryption and Sentry middleware are available as separate packages that must be installed independently.
Important:encryptionMiddlewareis from@inngest/middleware-encryptionandsentryMiddlewareis from@inngest/middleware-sentry— they are not exported from the coreinngestpackage.
Encryption Middleware (@inngest/middleware-encryption)
Install the package:
npm install @inngest/middleware-encryptionimport { Inngest } from "inngest";
import { encryptionMiddleware } from "@inngest/middleware-encryption";
const inngest = new Inngest({
id: "my-app",
middleware: [
encryptionMiddleware({
key: process.env.ENCRYPTION_KEY, // Encryption key from environment
})
]
});What gets encrypted by default:
- All step data
- All function output
- Event data in the
data.encryptedfield (customizable viaeventEncryptionField)
Additional options:
eventEncryptionField: Customize which event data field to encrypt (default:data.encrypted)decryptOnly: Disable encryption while maintaining decryption for migration scenariosfallbackDecryptionKeys: Array of previous keys for key rotation support
// Key rotation example
encryptionMiddleware({
key: process.env.NEW_ENCRYPTION_KEY,
fallbackDecryptionKeys: [process.env.OLD_ENCRYPTION_KEY],
})Custom Encryption Implementation
For more control, create custom encryption middleware:
import { InngestMiddleware } from "inngest";
import { createCipher, createDecipher, randomBytes } from "crypto";
const createCustomEncryptionMiddleware = (encryptionKey: string) => {
const algorithm = "aes-256-gcm";
const encrypt = (text: string): string => {
const iv = randomBytes(16);
const cipher = createCipher(algorithm, encryptionKey);
cipher.setAAD(Buffer.from("inngest-data"));
let encrypted = cipher.update(text, "utf8", "hex");
encrypted += cipher.final("hex");
const authTag = cipher.getAuthTag();
return iv.toString("hex") + ":" + authTag.toString("hex") + ":" + encrypted;
};
const decrypt = (encrypted: string): string => {
const [ivHex, authTagHex, encryptedText] = encrypted.split(":");
const iv = Buffer.from(ivHex, "hex");
const authTag = Buffer.from(authTagHex, "hex");
const decipher = createDecipher(algorithm, encryptionKey);
decipher.setAAD(Buffer.from("inngest-data"));
decipher.setAuthTag(authTag);
let decrypted = decipher.update(encryptedText, "hex", "utf8");
decrypted += decipher.final("utf8");
return decrypted;
};
return new InngestMiddleware({
name: "Custom Encryption",
init() {
return {
onFunctionRun({ ctx }) {
return {
transformInput() {
// Decrypt sensitive event data
if (ctx.event.data.encrypted_fields) {
const decryptedFields = {};
for (const [key, encryptedValue] of Object.entries(
ctx.event.data.encrypted_fields
)) {
decryptedFields[key] = decrypt(encryptedValue as string);
}
return {
ctx: {
event: {
...ctx.event,
data: {
...ctx.event.data,
...decryptedFields,
encrypted_fields: undefined // Remove encrypted versions
}
}
}
};
}
return {};
},
transformOutput({ result }) {
// Encrypt sensitive output fields
if (result.data?.sensitiveData) {
const encrypted = encrypt(
JSON.stringify(result.data.sensitiveData)
);
return {
result: {
...result,
data: {
...result.data,
encrypted_output: encrypted,
sensitiveData: undefined // Remove plaintext
}
}
};
}
return { result };
}
};
}
};
}
});
};
// Usage
const inngest = new Inngest({
id: "my-app",
middleware: [createCustomEncryptionMiddleware(process.env.ENCRYPTION_KEY)]
});Sentry Middleware (@inngest/middleware-sentry)
Install the package:
npm install @inngest/middleware-sentryRequires @sentry/*@>=8.0.0 and inngest@>=3.0.0.
import * as Sentry from "@sentry/node";
import { Inngest } from "inngest";
import { sentryMiddleware } from "@inngest/middleware-sentry";
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
});
const inngest = new Inngest({
id: "my-app",
middleware: [sentryMiddleware()]
});What it provides:
- Captures exceptions for reporting
- Adds tracing to each function run
- Includes context like function ID and event names with each exception and trace
Custom Sentry Implementation
For more control over error tracking, create custom middleware:
import { InngestMiddleware } from "inngest";
import * as Sentry from "@sentry/node";
const createCustomSentryMiddleware = (sentryConfig: {
dsn: string;
environment: string;
sampleRate?: number;
}) => {
return new InngestMiddleware({
name: "Custom Sentry Error Tracking",
init() {
Sentry.init({
dsn: sentryConfig.dsn,
environment: sentryConfig.environment,
tracesSampleRate: sentryConfig.sampleRate || 0.1,
integrations: [
// Add custom integrations
new Sentry.Integrations.Http({ tracing: true })
]
});
return {
onFunctionRun({ ctx, fn }) {
return {
beforeExecution() {
// Set Sentry context for this function execution
Sentry.configureScope((scope) => {
scope.setTag("inngest.function", fn.id);
scope.setTag("inngest.event", ctx.event.name);
scope.setTag("inngest.runId", ctx.runId);
scope.setTag("inngest.attempt", ctx.attempt.toString());
scope.setContext("inngest", {
functionId: fn.id,
eventName: ctx.event.name,
eventData: ctx.event.data,
runId: ctx.runId,
attempt: ctx.attempt,
timestamp: ctx.event.ts
});
scope.setUser({
id: ctx.event.user?.id || "unknown",
email: ctx.event.user?.email
});
});
// Start Sentry transaction
const transaction = Sentry.startTransaction({
name: `inngest.function.${fn.id}`,
op: "function.execution"
});
Sentry.getCurrentHub().configureScope((scope) =>
scope.setSpan(transaction)
);
},
afterExecution() {
// Finish Sentry transaction
const transaction = Sentry.getCurrentHub()
.getScope()
?.getTransaction();
transaction?.finish();
},
transformOutput({ result, step }) {
// Capture errors with rich context
if (result.error) {
Sentry.withScope((scope) => {
if (step) {
scope.setTag("inngest.step", step.displayName);
scope.setContext("step", {
id: step.id,
name: step.displayName,
attempt: step.attempt
});
}
scope.setLevel("error");
scope.setContext("errorDetails", {
stepOutput: result.data,
errorMessage: result.error.message,
errorStack: result.error.stack
});
Sentry.captureException(result.error);
});
}
// Capture warnings for non-fatal issues
if (result.data?.warnings?.length > 0) {
result.data.warnings.forEach((warning) => {
Sentry.addBreadcrumb({
message: warning,
level: "warning",
category: "inngest.warning"
});
});
}
return { result };
}
};
},
onSendEvent() {
return {
transformInput({ payloads }) {
// Track event sending
Sentry.addBreadcrumb({
message: `Sending ${payloads.length} events`,
level: "info",
category: "inngest.send_event",
data: {
eventCount: payloads.length,
eventNames: payloads.map((p) => p.name)
}
});
// Spread to convert readonly array to mutable
return { payloads: [...payloads] };
}
};
}
};
}
});
};
// Usage
const inngest = new Inngest({
id: "my-app",
middleware: [
createCustomSentryMiddleware({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
sampleRate: process.env.NODE_ENV === "production" ? 0.1 : 1.0
})
]
});Custom Error Tracking
If you don't use Sentry, create custom error tracking:
const createErrorTrackingMiddleware = (config: {
apiKey: string;
endpoint: string;
enableInDevelopment?: boolean;
}) => {
const shouldTrack =
config.enableInDevelopment || process.env.NODE_ENV === "production";
const reportError = async (error: Error, context: any) => {
if (!shouldTrack) return;
try {
await fetch(config.endpoint, {
method: "POST",
headers: {
Authorization: `Bearer ${config.apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
error: {
message: error.message,
stack: error.stack,
name: error.name
},
context,
timestamp: new Date().toISOString()
})
});
} catch (reportingError) {
console.error("Failed to report error:", reportingError);
}
};
return new InngestMiddleware({
name: "Custom Error Tracking",
init() {
return {
onFunctionRun({ ctx, fn }) {
return {
transformOutput({ result, step }) {
if (result.error) {
reportError(result.error, {
function: fn.id,
event: ctx.event.name,
runId: ctx.runId,
attempt: ctx.attempt,
step: step?.displayName,
eventData: ctx.event.data
});
}
return { result };
}
};
}
};
}
});
};Combining Middleware
Use multiple middleware together:
import { Inngest, dependencyInjectionMiddleware } from "inngest";
import { encryptionMiddleware } from "@inngest/middleware-encryption";
import { sentryMiddleware } from "@inngest/middleware-sentry";
const inngest = new Inngest({
id: "my-app",
middleware: [
// Order matters - dependencies first (built-in export from "inngest")
dependencyInjectionMiddleware({
db: new PrismaClient(),
redis: createRedisClient()
}),
// Then encryption for data protection (from "@inngest/middleware-encryption")
encryptionMiddleware({
key: process.env.ENCRYPTION_KEY,
}),
// Finally error tracking (from "@inngest/middleware-sentry")
sentryMiddleware()
]
});Best Practices
Middleware Ordering
1. Dependencies first - Inject services other middleware might need 2. Data transformation - Encryption, validation, enrichment 3. Observability - Logging, metrics, error tracking 4. Business logic - Custom middleware for specific use cases
Error Handling
- Always wrap error tracking in try-catch blocks
- Don't let middleware errors crash your functions
- Log middleware failures for debugging
- Provide fallbacks when external services are unavailable
Performance Considerations
- Built-in middleware is optimized for common use cases
- Custom middleware should be lightweight and fast
- Consider the overhead of external API calls in middleware
- Use caching and connection pooling appropriately
Dependency Injection with Inngest Middleware
Detailed patterns for sharing expensive or stateful clients across all functions using Inngest middleware.
Built-in Dependency Injection (TypeScript)
Inngest provides built-in dependency injection middleware that automatically injects dependencies into function contexts:
import { dependencyInjectionMiddleware } from "inngest";
import OpenAI from "openai";
import { PrismaClient } from "@prisma/client";
import { createClient } from "redis";
const inngest = new Inngest({
id: "my-app",
middleware: [
dependencyInjectionMiddleware({
openai: new OpenAI({
apiKey: process.env.OPENAI_API_KEY
}),
db: new PrismaClient(),
redis: createClient({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT
})
})
]
});
// Functions automatically get injected dependencies
inngest.createFunction(
{ id: "ai-summary", triggers: [{ event: "document/uploaded" }] },
async ({ event, openai, db, redis }) => {
// All dependencies available in function context
const summary = await openai.chat.completions.create({
messages: [{ role: "user", content: event.data.content }],
model: "gpt-4"
});
await db.document.update({
where: { id: event.data.documentId },
data: { summary: summary.choices[0].message.content }
});
// Cache the result
await redis.setex(
`summary:${event.data.documentId}`,
3600,
summary.choices[0].message.content
);
}
);Custom Dependency Injection
For more control over dependency injection, create custom middleware:
import { InngestMiddleware } from "inngest";
import Stripe from "stripe";
const createDependencyMiddleware = (deps: Record<string, any>) => {
return new InngestMiddleware({
name: "Dependency Injection",
init() {
return {
onFunctionRun() {
return {
transformInput() {
return {
ctx: deps // Inject dependencies into context
};
}
};
}
};
}
});
};
// Usage with multiple services
const inngest = new Inngest({
id: "my-app",
middleware: [
createDependencyMiddleware({
stripe: new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: "2023-10-16"
}),
analytics: createAnalyticsClient({
apiKey: process.env.ANALYTICS_API_KEY
}),
notifications: createNotificationService({
apiKey: process.env.NOTIFICATION_API_KEY,
from: process.env.FROM_EMAIL
})
})
]
});
// Function with injected dependencies
inngest.createFunction(
{ id: "process-payment", triggers: [{ event: "checkout/completed" }] },
async ({ event, stripe, analytics, notifications }) => {
// Create payment intent
const paymentIntent = await stripe.paymentIntents.create({
amount: event.data.amount,
currency: "usd",
customer: event.data.customerId
});
// Track analytics
await analytics.track("payment_processed", {
userId: event.data.userId,
amount: event.data.amount
});
// Send confirmation
await notifications.send({
to: event.data.userEmail,
template: "payment_confirmation",
data: { amount: event.data.amount }
});
}
);Python Dependency Injection
Implement dependency injection in Python using custom middleware:
import inngest
import typing
from openai import OpenAI
from sqlalchemy import create_engine
from redis import Redis
class DependencyMiddleware(inngest.Middleware):
def __init__(
self,
client: inngest.Inngest,
raw_request: object,
) -> None:
# Initialize shared dependencies once
self.openai = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
self.db_engine = create_engine(os.environ["DATABASE_URL"])
self.redis = Redis.from_url(os.environ["REDIS_URL"])
def transform_input(
self,
ctx: inngest.Context,
fn: inngest.Function,
steps: typing.Any,
) -> None:
# Inject dependencies into context
ctx.openai = self.openai # type: ignore
ctx.db = self.db_engine # type: ignore
ctx.redis = self.redis # type: ignore
# Create client with dependency injection
inngest_client = inngest.Inngest(
app_id="my_app",
middleware=[DependencyMiddleware],
)
@inngest_client.create_function(
fn_id="ai-analysis",
trigger=inngest.TriggerEvent(event="data/uploaded"),
)
async def analyze_data(ctx: inngest.Context, step: inngest.StepTools):
# Use injected dependencies
analysis = await ctx.openai.chat.completions.create(
messages=[{"role": "user", "content": ctx.event.data.text}],
model="gpt-4",
)
# Store result in database
with ctx.db.begin() as conn:
conn.execute(
"INSERT INTO analyses (id, content, result) VALUES (%s, %s, %s)",
(ctx.run_id, ctx.event.data.text, analysis.choices[0].message.content)
)
# Cache result
ctx.redis.setex(
f"analysis:{ctx.run_id}",
3600,
analysis.choices[0].message.content
)
return {"analysis": analysis.choices[0].message.content}Advanced Dependency Patterns
Lazy Loading Dependencies
Only initialize expensive clients when needed:
const createLazyDependencyMiddleware = () => {
let openai: OpenAI | undefined;
let stripe: Stripe | undefined;
return new InngestMiddleware({
name: "Lazy Dependency Injection",
init() {
return {
onFunctionRun() {
return {
transformInput() {
return {
ctx: {
// Lazy getters
get openai() {
if (!openai) {
openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY
});
}
return openai;
},
get stripe() {
if (!stripe) {
stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: "2023-10-16"
});
}
return stripe;
}
}
};
}
};
}
};
}
});
};Scoped Dependencies
Create function-scoped instances:
const createScopedDependencyMiddleware = () => {
return new InngestMiddleware({
name: "Scoped Dependencies",
init() {
return {
onFunctionRun({ ctx }) {
return {
transformInput() {
// Create new instances per function execution
return {
ctx: {
logger: createLogger({
runId: ctx.runId,
functionId: ctx.function.id
}),
tracer: createTracer({
traceId: ctx.runId,
service: ctx.function.id
})
}
};
}
};
}
};
}
});
};Conditional Dependencies
Inject different dependencies based on context:
const createConditionalDependencyMiddleware = () => {
return new InngestMiddleware({
name: "Conditional Dependencies",
init() {
const prodDatabase = createDatabaseClient(process.env.DATABASE_URL);
const testDatabase = createTestDatabaseClient();
return {
onFunctionRun({ ctx }) {
return {
transformInput() {
const isTest = ctx.event.name.includes("/test");
const isProduction = process.env.NODE_ENV === "production";
return {
ctx: {
db: isTest ? testDatabase : prodDatabase,
analytics: isProduction
? createAnalyticsClient()
: createMockAnalytics(),
cache: isProduction
? createRedisClient()
: createMemoryCache()
}
};
}
};
}
};
}
});
};Best Practices
Resource Management
- Pool connections: Use connection pools for databases
- Reuse instances: Don't create new clients on every function call
- Handle cleanup: Properly close connections in middleware teardown
Error Handling
const robustDependencyMiddleware = new InngestMiddleware({
name: "Robust Dependencies",
init() {
let db: any;
const getDatabase = () => {
if (!db) {
try {
db = createDatabaseClient();
} catch (error) {
console.error("Failed to initialize database:", error);
// Return mock or throw based on your needs
throw new Error("Database unavailable");
}
}
return db;
};
return {
onFunctionRun() {
return {
transformInput() {
return {
ctx: {
get db() {
return getDatabase();
}
}
};
}
};
}
};
}
});Testing with Dependencies
// Create test-friendly middleware
const createTestableMiddleware = (overrides: Record<string, any> = {}) => {
return new InngestMiddleware({
name: "Testable Dependencies",
init() {
return {
onFunctionRun() {
return {
transformInput() {
return {
ctx: {
db: overrides.db || createDatabaseClient(),
openai: overrides.openai || new OpenAI()
// Add more dependencies as needed
}
};
}
};
}
};
}
});
};
// In tests
const mockDb = createMockDatabase();
const mockOpenAI = createMockOpenAI();
const testMiddleware = createTestableMiddleware({
db: mockDb,
openai: mockOpenAI
});Related skills
Forks & variants (1)
Inngest Middleware has 1 known copy in the catalog totaling 34 installs. They canonicalize to this original listing.
- joelhooks - 34 installs
How it compares
Use inngest-middleware when wiring Inngest-specific encryption or Sentry packages rather than generic Express middleware patterns.
FAQ
Should I use @inngest/realtime middleware on Inngest v4?
No. v4 ships realtime natively via step.realtime.publish; the v3 realtime middleware package causes runtime errors on v4.
What is the middleware execution order?
Client middleware runs first in array order, then function-level middleware runs in the order specified on createFunction.
Which official middleware packages exist?
@inngest/middleware-encryption for step data encryption and @inngest/middleware-sentry for Sentry tracing with Sentry 8 or newer.
Is Inngest Middleware safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.