# Creating middleware

Creating middleware means defining the lifecycles and subsequent hooks in those lifecycles to run code in. Lifecycles are actions such as a function run or sending events, and individual hooks within those are where we run code, usually with a *before* and *after* step.

> **Callout:** Support for Middleware in the Go SDK in planned.

A middleware is created by extending the `Middleware.BaseMiddleware` class.

**`class MyMiddleware extends Middleware.BaseMiddleware`**

```ts
import { Middleware } from "inngest";

// Create a new middleware
class MyMiddleware extends Middleware.BaseMiddleware {
  id = "my-middleware";
  // Override hook methods here
}

// Register it on the client
const inngest = new Inngest({
  id: "my-app",
  middleware: [MyMiddleware],
});
```

A Middleware is created using the `inngest.Middleware` class.

**`class MyMiddleware(inngest.Middleware):`**

```py
import inngest

class MyMiddleware(inngest.Middleware):
    def __init__(
        self,
        client: inngest.Inngest,
        raw_request: object,
    ) -> None:
        #  ...

    async def before_send_events( self, events: list[inngest.Event]) -> None:
        print(f"Sending {len(events)} events")

    async def after_send_events(self, result: inngest.SendEventsResult) -> None:
        print("Done sending events")

inngest_client = inngest.Inngest(
    app_id="my_app",
    middleware=[MyMiddleware],
)
```

## Initialization

As you can see above, we start with a class that extends `Middleware.BaseMiddleware`. A fresh instance is created for every request, so you can safely use instance properties (`this`) to store per-request state.

```ts
import { Middleware } from "inngest";

class MyMiddleware extends Middleware.BaseMiddleware {
  id = "my-middleware";
  // A new instance is created for every request
  // Override hook methods here
}
```

As you can see above, we start with the `__init__` method, which is called when the client is initialized.

```py
import inngest

    class MyMiddleware(inngest.Middleware):
        def __init__(
            self,
            client: inngest.Inngest,
            raw_request: object,
        ) -> None:
            # This runs when the client is initialized
            # Use this to set up anything your middleware needs
            #  ...
```

Hook methods can be synchronous or `async` functions. For one-time setup like establishing a database connection, use the static `onRegister` hook which runs when the middleware class is registered with a client or function.

```ts
class MyMiddleware extends Middleware.BaseMiddleware {
  id = "my-middleware";
  static onRegister({ client }: Middleware.OnRegisterArgs) {
    // One-time setup, e.g. initialize a database connection
  }
}
```

Function registration, lifecycles, and hooks can all be with synchronous or `async` functions. This makes it easy for our initialization handler to do some async work, like setting up a database connection.

```py
import inngest

    class MyMiddleware(inngest.Middleware):
        def __init__(
            self,
            client: inngest.Inngest,
            raw_request: object,
        ) -> None:
            #  ...connect to database
```

All lifecycle and hook functions can be synchronous or `async` functions - the SDK will always wait until a middleware's function has resolved before continuing to the next one.

> **Callout:** As it's possible for an application to use multiple Inngest clients, it's recommended to always initialize dependencies within the initializer function/method, instead of in the global scope.

## Specifying lifecycles and hooks

Override the hook methods you need directly on your middleware class. See the [Middleware lifecycle reference](/docs-markdown/reference/typescript/v4/middleware/lifecycle) for a full list of available hooks.

```ts
import { Middleware } from "inngest";

class MyMiddleware extends Middleware.BaseMiddleware {
  id = "my-middleware";

  // Observable hook: runs before the function handler on the first attempt
  onRunStart({ ctx, functionInfo }: Middleware.OnRunStartArgs) {
    console.log(`Starting ${functionInfo.id}`);
  }

  // Wrapping hook: wraps function execution
  async wrapFunctionHandler({ next }: Middleware.WrapFunctionHandlerArgs) {
    console.log("before");
    const result = await next();
    console.log("after");
    return result;
  }

  // Transform hook: modify function input (e.g. dependency injection)
  transformFunctionInput(args: Middleware.TransformFunctionInputArgs) {
    return {
      ...args,
      ctx: {
        ...args.ctx,
        // Add custom properties here
      },
    };
  }
}
```

Hooks you don't define have zero overhead: the SDK skips them entirely. A fresh instance is created for every request, so you can safely use instance properties (`this`) to store per-request state without worrying about leaks between runs.

Learn more about hooks with:

- [Lifecycle](/docs-markdown/reference/typescript/v4/middleware/lifecycle) - middleware ordering and see all available hooks
- [Examples](/docs-markdown/reference/typescript/v4/middleware/examples) - real-world middleware examples

You might have notice that our custom middleware defines custom method such as `before_send_events` and `after_send_events`. Those methods, called hooks, enable your middleware
to hook itself to specific steps of the Function and Steps execution lifecycle.

```py
import inngest

class MyMiddleware(inngest.Middleware):
    def __init__(
        self,
        client: inngest.Inngest,
        raw_request: object,
    ) -> None:
        #  ...

    async def before_send_events( self, events: list[inngest.Event]) -> None:
        # called before an event is sent from within a Function or Step
        print(f"Sending {len(events)} events")

    async def after_send_events(self, result: inngest.SendEventsResult) -> None:
        # called after an event is sent from within a Function or Step
        print("Done sending events")

```

You can find the [full list of available hooks in the Python SDK reference](/docs-markdown/reference/python/middleware/lifecycle).

## Adding configuration

It's common for middleware to require additional customization or options from developers. For this, we recommend creating a function that takes in some options and returns the middleware.

```ts {{ title: "inngest/middleware/myMiddleware.ts" }}
import { Middleware } from "inngest";

export function createMyMiddleware(logEventOutput: string) {
  return class MyMiddleware extends Middleware.BaseMiddleware {
    id = "my-middleware";
    onRunComplete({ ctx, functionInfo, output }: Middleware.OnRunCompleteArgs) {
      if (ctx.event.name === logEventOutput) {
        console.log(
          `${logEventOutput} output: ${JSON.stringify(output)}`
        );
      }
    }
  };
}
```

```ts
import { createMyMiddleware } from "./middleware/myMiddleware";

export const inngest = new Inngest({
  id: "my-client",
  middleware: [createMyMiddleware("app/user.created")],
});
```

> **Callout:** Make sure to let TypeScript infer the output of the function instead of strictly typing it; this helps Inngest understand changes to input and output of arguments. See Middleware lifecycle for more information.

Adding configuration to a custom middleware can be achieved by adding a `factory()` class method, leveraging the [Factory pattern](https://en.wikipedia.org/wiki/Factory_method_pattern).

For example, let's add a `secret_key` configuration option to our `MyMiddleware` middleware:

```py
import inngest

class MyMiddleware(inngest.Middleware):
    def __init__(
        self,
        client: inngest.Inngest,
        raw_request: object,
    ) -> None:
        #  ...

    @classmethod
    def factory(
        cls,
        secret_key: typing.Union[bytes, str],
    ) -> typing.Callable[[inngest.Inngest, object], MyMiddleware]:
        def _factory(
            client: inngest.Inngest,
            raw_request: object,
        ) -> MyMiddleware:
            return cls(
                client,
                raw_request,
                secret_key,
            )

        return _factory

    async def before_send_events( self, events: list[inngest.Event]) -> None:
        # called before an event is sent from within a Function or Step
        print(f"Sending {len(events)} events")

    async def after_send_events(self, result: inngest.SendEventsResult) -> None:
        # called after an event is sent from within a Function or Step
        print("Done sending events")
```

Our middleware can now be registered as follow:

```py
inngest_client = inngest.Inngest(
    app_id="my_app",
    middleware=[MyMiddleware.factory(_secret_key)],
)
```

## Next steps

Check out our pre-built middleware and examples:

**Dependency Injection**: [Provide shared client instances (ex, OpenAI) to your Inngest Functions.]('/docs-markdown/features/middleware/dependency-injection')

**Encryption Middleware**: [End-to-end encryption for events, step output, and function output.]('/docs-markdown/features/middleware/encryption-middleware')

**Sentry Middleware**: [Quickly setup Sentry for your Inngest Functions.]('/docs/features/middleware/sentry-middleware')

**Datadog middleware**: [Add tracing with Datadog under a few minutes.]('/docs/examples/track-failures-in-datadog')

**Cloudflare Workers & Hono middleware**: [Access environment variables within Inngest functions.]('/docs/examples/middleware/cloudflare-workers-environment-variables')