# Creating middleware
Source: https://www.inngest.com/docs/features/middleware/create

import {
  RiEyeOffLine,
  RiMistFill,
  RiPlugLine,
  RiTerminalBoxLine,
  RiKeyLine,
} from "@remixicon/react";

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.

<GuideSelector
  options={[
    { key: "typescript", title: "TypeScript (v2.0.0+)" },
    { key: "python", title: "Python (v0.3.0+)" }
  ]}>

<GuideSection show="typescript">
    A Middleware is created using the `InngestMiddleware` class.

    **`new InngestMiddleware(options): InngestMiddleware`**

    ```ts
    // Create a new middleware
    new InngestMiddleware({
      name: "My Middleware",
      init: () => {
          return {};
      },
    });

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

</GuideSection>

<GuideSection show="python">

    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],
    )
    ```

</GuideSection>

## Initialization

<GuideSection show="typescript">
As you can see above, we start with the `init` function, which is called when the client is initialized.

```ts
new InngestMiddleware({
  name: "Example Middleware",
  init() {
    // This runs when the client is initialized
    // Use this to set up anything your middleware needs
    return {};
  },
});
```
</GuideSection>

<GuideSection show="python">
    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
                #  ...
    ```
</GuideSection>

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.

<GuideSection show="typescript">
    ```ts
    new InngestMiddleware({
    name: "Example Middleware",
    async init() {
        await connectToDatabase();

        return {};
    },
    });
    ```
</GuideSection>

<GuideSection show="python">
    ```py
    import inngest

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

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.
</Callout>

## Specifying lifecycles and hooks

<GuideSection show="typescript">
    Notice we're returning an empty object `{}`. From here, we can instead return the lifecycles we want to use for this client. See the [Middleware - Lifecycle - Hook reference](/docs/reference/middleware/lifecycle#hook-reference) for a full list of available hooks.

    ```ts
    new InngestMiddleware({
    name: "Example Middleware",
    async init() {
        // 1. Use init to set up dependencies
        // 2. Use return values to group hooks by lifecycle: - "onFunctionRun" "onSendEvent"
        return {
        onFunctionRun({ ctx, fn, steps }) {
            // 3. Use the lifecycle function to pass dependencies into hooks
            // 4. Return any hooks that you want to define for this action
            return {
            // 5. Define the hook that runs at a specific stage for this lifecycle.
            beforeExecution() {
                // 6. Define your hook
            },
            };
        },
        };
    },
    });
    ```

    Here we use the `beforeExecution()` hook within the `onFunctionRun()` lifecycle.

    The use of [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) here means that our `onFunctionRun()` lifecycle can access anything from the middleware's initialization, like our `db` connection.

    `onFunctionRun()` here is also called for every function execution, meaning you can run code specific to this execution without maintaining any global state. We can even conditionally register hooks based on incoming arguments. For example, here we only register a hook for a specific event trigger:

    ```ts
    new InngestMiddleware({
    name: "Example Middleware",
    async init() {
        return {
        onFunctionRun({ ctx, fn, steps }) {
            // Register a hook only if this event is the trigger
            if (ctx.event.name === "app/user.created") {
            return {
                beforeExecution() {
                console.log("Function executing with user created event");
                },
            };
            }

            // Register no hooks if the trigger was not `app/user.created`
            return {};
        },
        };
    },
    });
    ```

    Learn more about hooks with:

    - [Lifecycle](/docs/reference/middleware/lifecycle) - middleware ordering and see all available hooks
    - [TypeScript](/docs/reference/middleware/typescript) - how to affect input and output types and values

</GuideSection>

<GuideSection show="python">
    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/reference/python/middleware/lifecycle).

</GuideSection>

## 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.

<GuideSection show="typescript">
    ```ts {{ title: "inngest/middleware/myMiddleware.ts" }}
    createMyMiddleware = (logEventOutput: string) => {
    return new InngestMiddleware({
        name: "My Middleware",
        init() {
        return {
            onFunctionRun({ ctx, fn, steps }) {
            if (ctx.event.name === logEventOutput) {
                return {
                transformOutput({ result, step }) {
                    console.log(
                    `${logEventOutput} output: ${JSON.stringify(result)}`
                    );
                },
                };
            }

            return {};
            },
        };
        },
    });
    };
    ```
    ```ts
    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 - TypeScript](/docs/reference/middleware/typescript) for more information.
    </Callout>

</GuideSection>

<GuideSection show="python">
    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)],
    )
    ```
</GuideSection>

## Next steps

Check out our pre-built middleware and examples:

<CardGroup cols={2}>
  <Card title="Dependency Injection" icon={<RiPlugLine className="text-basis h-4 w-4" />} href={'/docs/features/middleware/dependency-injection'}>
    Provide shared client instances (ex, OpenAI) to your Inngest Functions.
  </Card>
  <Card title="Encryption Middleware" icon={<RiEyeOffLine className="text-basis h-4 w-4" />} href={'/docs/features/middleware/encryption-middleware'}>
    End-to-end encryption for events, step output, and function output.
  </Card>
  <Card title="Sentry Middleware" icon={<RiMistFill className="text-basis h-4 w-4"/>} href={'/docs/features/middleware/sentry-middleware'}>
    Quickly setup Sentry for your Inngest Functions.
  </Card>
  <Card title="Datadog middleware" icon={<RiTerminalBoxLine className="text-basis h-4 w-4"/>} href={'/docs/examples/track-failures-in-datadog'}>
    Add tracing with Datadog under a few minutes.
  </Card>
  <Card title="Cloudflare Workers & Hono middleware" icon={<RiKeyLine className="text-basis h-4 w-4"/>} href={'/docs/examples/middleware/cloudflare-workers-environment-variables'}>
    Access environment variables within Inngest functions.
  </Card>
</CardGroup>

</GuideSelector>