Get the FREE Ultimate OpenClaw Setup Guide →

inngest-middleware

Scanned
npx machina-cli add skill inngest/inngest-skills/inngest-middleware --openclaw
Files (1)
SKILL.md
9.0 KB

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.

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

```typescript
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" },
  { 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 for complete details.

Encryption Middleware

npm install @inngest/middleware-encryption
import { 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-sentry
import * 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:

Source

git clone https://github.com/inngest/inngest-skills/blob/main/skills/inngest-middleware/SKILL.mdView on GitHub

Overview

Master Inngest middleware to handle cross-cutting concerns such as logging, error tracking, dependency injection, encryption, and data transformation. Middleware runs at key points in the function lifecycle, enabling observable, shared functionality across functions.

How This Skill Works

Middleware is registered at the client level or at the function level and exposes lifecycle hooks like onFunctionRun and onSendEvent. It runs in a defined order where client-level middleware executes before function-level middleware, enabling concerns such as logging, input/output transformation, and authentication to be applied consistently.

When to Use It

  • 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

Quick Start

  1. Step 1: Install and import InngestMiddleware and, if needed, encryption or Sentry packages, then initialize Inngest with a middleware array
  2. Step 2: Add client-level middleware for shared concerns and function-level middleware for per-function customization
  3. Step 3: Implement onFunctionRun and onSendEvent hooks to perform logging, transformation, and authentication checks, then test the flow

Best Practices

  • Name and version middleware clearly and keep a stable interface
  • Test client-level and function-level middleware together to verify order
  • Use dependencyInjectionMiddleware to share clients across functions
  • Leverage @inngest/middleware-encryption and @inngest/middleware-sentry for encryption and error tracking
  • Keep middleware focused and idempotent; avoid side effects in onSendEvent

Example Use Cases

  • Log function start and end times with onFunctionRun
  • Share a database or API client across functions using dependencyInjectionMiddleware
  • Encrypt payloads before sending events and decrypt on arrival
  • Capture and report errors to Sentry using the Sentry middleware package
  • Transform and enrich event payloads in onSendEvent before dispatch

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers