Serving Inngest Functions

With Inngest, you define functions or workflows using its SDK right in your codebase and serve them through an HTTP endpoint in your application. Inngest uses this endpoint to reliably execute functions in response to an event.

This page covers how to serve the Inngest API in your project.

Exposing Inngest Functions

Inngest provides a serve() handler which adds an API endpoint to your router. You expose your functions to Inngest through this HTTP endpoint. To make automated deploys much easier, the endpoint needs to be defined at /api/inngest (though you can change the API path).

./api/inngest.ts

// All serve handlers have the same arguments:
serve({
  client: inngest, // a client created with new Inngest()
  functions: [fnA, fnB], // an array of Inngest functions to serve, created with inngest.createFunction()
  /* Optional extra configuration */
});

You will find the complete list of supported frameworks below.

Supported Platforms

Below you will find a list of framework-specific bindings, as well as instructions on adding bindings to custom platforms!

Want us to add support for another framework? Open an issue on GitHub or tell us about it on our Discord.

Framework: Astro v3.8.0+

Add the following to ./src/pages/api/inngest.ts:

import { serve } from "inngest/astro";
import { functions, inngest } from "../../inngest";

export const { GET, POST, PUT } = serve({
  client: inngest,
  functions,
});

See the Astro example for more information.

Framework: AWS Lambda v1.5.0+

We recommend using Lambda function URLs to trigger your functions, as these require no other configuration or cost.

Alternatively, you can use an API Gateway to route requests to your Lambda. The handler supports API Gateway V1 and API Gateway V2. If you are running API Gateway behind a proxy or have some other configuration, you may have to specify the serveHost and servePath options when calling serve() to ensure Inngest knows the URL where you are serving your functions. See Configuring the API path for more details.

import { serve } from "inngest/lambda";
import { inngest } from "./client";
import fnA from "./fnA"; // Your own function

export const handler = serve({
  client: inngest,
  functions: [fnA],
});

Bun.serve()

You can use the inngest/bun handler with Bun.serve() for a lightweight Inngest server:

import { serve } from "inngest/bun";
import { functions, inngest } from "./inngest";

Bun.serve({
  port: 3000,
  fetch(request: Request) {
    const url = new URL(request.url);

    if (url.pathname === "/api/inngest") {
      return serve({ client: inngest, functions })(request);
    }

    return new Response("Not found", { status: 404 });
  },
});

See the Bun example for more information.

Framework: Cloudflare Pages Functions

You can import the Inngest API server when using Cloudflare pages functions within /functions/api/inngest.js:

import { serve } from "inngest/cloudflare";
import { inngest } from "../../inngest/client";
import fnA from "../../inngest/fnA"; // Your own function

export const onRequest = serve({
  client: inngest,
  functions: [fnA],
});

Framework: Cloudflare Workers v3.19.15+

You can export "inngest/cloudflare"'s serve() as your Cloudflare Worker:

import { serve } from "inngest/cloudflare";
import { inngest } from "./client";
import fnA from "./fnA";

export default {
  fetch: serve({
    client: inngest,
    functions: [fnA],
  }),
};

When developing locally with Wrangler and the --remote flag, your code is deployed and run remotely. To use this with a local Inngest Dev Server, you must use a tool such as ngrok or localtunnel to allow access to the Dev Server from the internet.

ngrok http 8288
# wrangler.toml
[vars]
INNGEST_DEV = "https://YOUR_TUNNEL_URL.ngrok.app"
INNGEST_SERVE_HOST = "http://localhost:3000" # force the Dev Server to access the app at this local URL

Framework: DigitalOcean Functions

The DigitalOcean serve function allows you to deploy Inngest to DigitalOcean serverless functions. Because DigitalOcean does not provide the request URL in its function arguments, you must include the function URL and path when configuring your handler:

import { serve } from "inngest/digitalocean";
import { inngest } from "./src/inngest/client";
import fnA from "./src/inngest/fnA"; // Your own function

const main = serve({
  client: inngest,
  functions: [fnA],
  // Your digitalocean hostname.  This is required otherwise your functions won't work.
  serveHost: "https://faas-sfo3-your-url.doserverless.co",
  // And your DO path, also required.
  servePath: "/api/v1/web/fn-your-uuid/inngest",
});

// IMPORTANT: Makes the function available as a module in the project.
// This is required for any functions that require external dependencies.
module.exports.main = main;

Framework: Express

You can serve Inngest functions within your existing Express app, deployed to any hosting provider like Render, Fly, AWS, K8S, and others:

import { serve } from "inngest/express";
import { inngest } from "./src/inngest/client";
import fnA from "./src/inngest/fnA"; // Your own function

// Important:  ensure you add JSON middleware to process incoming JSON POST payloads.
app.use(express.json());
app.use(
  // Expose the middleware on our recommended path at `/api/inngest`.
  "/api/inngest",
  serve({ client: inngest, functions: [fnA] })
);

You must ensure you're using the express.json() middleware otherwise your functions won't be executed. Note - You may need to set express.json()'s limit option to something higher than the default 100kb to support larger event payloads and function state.

See the Express example for more information.

Framework: Fastify v2.6.0+

You can serve Inngest functions within your existing Fastify app.

We recommend using the exported inngestFastify plugin, though we also expose a generic serve() function if you'd like to manually create a route.

import Fastify from "fastify";
import inngestFastify from "inngest/fastify";
import { inngest, fnA } from "./inngest";

const fastify = Fastify();

fastify.register(inngestFastify, {
  client: inngest,
  functions: [fnA],
  options: {},
});

fastify.listen({ port: 3000 }, function (err, address) {
  if (err) {
    fastify.log.error(err);
    process.exit(1);
  }
});

See the Fastify example for more information.

Framework: Fresh (Deno)

Inngest works with Deno's Fresh framework via the esm.sh CDN. Add the serve handler to ./api/inngest.ts as follows:

import { serve } from "https://esm.sh/inngest/deno/fresh";
import { inngest } from "./src/inngest/client.ts";
import fnA from "./src/inngest/fnA"; // Your own function

export const handler = serve({
  client: inngest,
  functions: [fnA],
});

Framework: Google Cloud Functions

Google's Functions Framework has an Express-compatible API which enables you to use the Express serve handler to deploy your functions to Google's Cloud Functions or Cloud Run. This is an example of a function

import * as ff from "@google-cloud/functions-framework";
import { serve } from "inngest/express";
import { inngest } from "./src/inngest/client";
import fnA from "./src/inngest/fnA"; // Your own function

ff.http(
  "inngest",
  serve({
    client: inngest,
    functions: [fnA],
  })
);

You can run this locally with npx @google-cloud/functions-framework --target=inngest which will serve your Inngest functions on port 8080.

See the Google Cloud Functions example for more information.

Framework: Firebase Cloud Functions

Based on the Google Cloud Function architecture, the Firebase Cloud Functions provide a different API to serve functions using onRequest:

import { onRequest } from "firebase-functions/v2/https";

import { serve } from "inngest/express";
import { inngest as inngestClient } from "./inngest/client";

export const inngest = onRequest(
  serve({
    client: inngestClient,
    functions: [/* ...functions... */],
  })
);

Firebase Cloud Functions require configuring INNGEST_SERVE_PATH with the custom function path.

For example, for a project named inngest-firebase-functions deployed on the us-central1 region, the INNGEST_SERVE_PATH value will be as follows:

/inngest-firebase-functions/us-central1/inngest/

To serve your Firebase Cloud Function locally, use the following command:

firebase emulators:start

Please note that you'll need to start your Inngest Local Dev Server with the -u flag to match our Firebase Cloud Function's custom path as follows:

npx inngest-cli@latest dev -u http://127.0.0.1:5001/inngest-firebase-functions/us-central1/inngest

The above command example features a project named inngest-firebase-functions deployed on the us-central1 region.

Framework: H3 v2.7.0+

Inngest supports H3 and frameworks built upon it. Here's a simple H3 server that hosts serves an Inngest function.

index.js

import { createApp, eventHandler, toNodeListener } from "h3";
import { serve } from "inngest/h3";
import { createServer } from "node:http";
import { inngest } from "./inngest/client";
import fnA from "./inngest/fnA";

const app = createApp();
app.use(
  "/api/inngest",
  eventHandler(
    serve({
      client: inngest,
      functions: [fnA],
    })
  )
);

createServer(toNodeListener(app)).listen(process.env.PORT || 3000);

See the github.com/unjs/h3 repository for more information about how to host an H3 endpoint.

Framework: Hono

Add the following to ./src/index.ts:

import { Hono } from "hono";
import { serve } from "inngest/hono";
import { functions, inngest } from "./inngest";

const app = new Hono();

app.on(
  ["GET", "PUT", "POST"],
  "/api/inngest",
  serve({
    client: inngest,
    functions,
  })
);

export default app;

See the Hono example for more information.

Framework: Koa v3.6.0+

Add the following to your routing file:

import { serve } from "inngest/koa";
import Koa from "koa";
import bodyParser from "koa-bodyparser";
import { functions, inngest } from "./inngest";

const app = new Koa();
app.use(bodyParser()); // make sure we're parsing incoming JSON

const handler = serve({
  client: inngest,
  functions,
});

app.use((ctx) => {
  if (ctx.request.path === "/api/inngest") {
    return handler(ctx);
  }
});

See the Koa example for more information.

Framework: NestJS

Add the following to ./src/main.ts:

import { Logger } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { serve } from 'inngest/express';

import { inngest } from '@modules/common/inngest/client';
import { getInngestFunctions } from '@modules/common/inngest/functions';

import { AppModule } from './app.module';
import { AppService } from './app.service';

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule, {
    bodyParser: true,
  });

  // Setup inngest
  app.useBodyParser('json', { limit: '10mb' });

  // Inject Dependencies into inngest functions

  const logger = app.get(Logger);
  const appService = app.get(AppService);

  // Pass dependencies into this function
  const inngestFunctions = getInngestFunctions({
    appService,
    logger,
  });

  // Register inngest endpoint
  app.use(
    '/api/inngest',
    serve({
      client: inngest,
      functions: inngestFunctions,
    }),
  );

  // Start listening for http requests
  await app.listen(3000);
}

bootstrap();

See the NestJS example for more information.

Framework: Next.js

Inngest has first class support for Next.js API routes, allowing you to easily create the Inngest API. Both the App Router and the Pages Router are supported. For the App Router, Inngest requires GET, POST, and PUT methods.

// src/app/api/inngest/route.ts
import { serve } from "inngest/next";
import { inngest } from "../../../inngest/client";
import fnA from "../../../inngest/fnA"; // Your own functions

export const { GET, POST, PUT } = serve({
  client: inngest,
  functions: [fnA],
});

Streaming v1.8.0+

Next.js Edge Functions hosted on Vercel can also stream responses back to Inngest, giving you a much higher request timeout of 15 minutes (up from 10 seconds on the Vercel Hobby plan!).

To enable this, set your runtime to "edge" (see Quickstart for Using Edge Functions | Vercel Docs) and add the streaming: "allow" option to your serve handler:

Next.js 13+

export const runtime = "edge";

export default serve({
  client: inngest,
  functions: [...fns],
  streaming: "allow",
});
Older versions (Next.js 12)
export const config = {
  runtime: "edge",
};

const handler = serve({
  client: inngest,
  functions: [...fns],
  streaming: "allow",
});

For more information, check out the Streaming page.

Framework: Nitro v3.24.0

Add the following to ./server/routes/api/inngest.ts:

import { serve } from "inngest/nitro";
import { inngest } from "~~/inngest/client";
import fnA from "~~/inngest/fnA"; // Your own function

export default eventHandler(
  serve({
    client: inngest,
    functions: [fnA],
  })
);

See the Nitro example for more information.

Framework: Nuxt v0.9.2+

Inngest has first class support for Nuxt server routes, allowing you to easily create the Inngest API.

Add the following within ./server/api/inngest.ts:

import { serve } from "inngest/nuxt";
import { inngest } from "~~/inngest/client";
import fnA from "~~/inngest/fnA"; // Your own function

export default defineEventHandler(
  serve({
    client: inngest,
    functions: [fnA],
  })
);

See the Nuxt example for more information.

Framework: Redwood

Add the following to api/src/functions/inngest.ts:

import { serve } from "inngest/redwood";
import { inngest } from "src/inngest/client";
import fnA from "src/inngest/fnA"; // Your own function

export const handler = serve({
  client: inngest,
  functions: [fnA],
  servePath: "/api/inngest",
});

You should also update your redwood.toml to add apiUrl = "/api", ensuring your API is served at the /api root.

Framework: Remix

Add the following to ./app/routes/api.inngest.ts:

// app/routes/api.inngest.ts
import { serve } from "inngest/remix";
import { inngest } from "~/inngest/client";
import fnA from "~/inngest/fnA";

const handler = serve({
  client: inngest,
  functions: [fnA],
});

export { handler as action, handler as loader };

See the Remix example for more information.

Streaming v2.3.0+

Remix Edge Functions hosted on Vercel can also stream responses back to Inngest, giving you a much higher request timeout of 15 minutes (up from 10 seconds on the Vercel Hobby plan!).

To enable this, set your runtime to "edge" (see Quickstart for Using Edge Functions | Vercel Docs) and add the streaming: "allow" option to your serve handler:

export const config = {
  runtime: "edge",
};

const handler = serve({
  client: inngest,
  functions: [...fns],
  streaming: "allow",
});

For more information, check out the Streaming page.

Framework: SvelteKit v3.5.0+

Add the following to ./src/routes/api/inngest/+server.ts:

import { functions, inngest } from '$lib/inngest';
import { serve } from 'inngest/sveltekit';

const inngestServe = serve({ client: inngest, functions });
export const GET = inngestServe.GET;
export const POST = inngestServe.POST;
export const PUT = inngestServe.PUT;

See the SvelteKit example for more information.

Custom frameworks

If the framework that your application uses is not included in the above list of first-party supported frameworks, you can create a custom serve handler.

To create your own handler, check out the example handler in our SDK's open source repository to understand how it works. Here's an example of a custom handler being created and used:

import { Inngest, InngestCommHandler, type ServeHandlerOptions } from "inngest";

const serve = (options: ServeHandlerOptions) => {
  const handler = new InngestCommHandler({
    frameworkName: "edge",
    fetch: fetch.bind(globalThis),
    ...options,
    handler: (req: Request) => {
      return {
        body: () => req.json(),
        headers: (key) => req.headers.get(key),
        method: () => req.method,
        url: () => new URL(req.url, `https://${req.headers.get("host") || ""}`),
        transformResponse: ({ body, status, headers }) => {
          return new Response(body, { status, headers });
        },
      };
    },
  });

  return handler.createHandler();
};

const inngest = new Inngest({ id: "example-edge-app" });

const fn = inngest.createFunction(
  { id: "hello-world" },
  { event: "test/hello.world" },
  () => "Hello, World!"
);

export default serve({ client: inngest, functions: [fn] });

Signing key

You'll need to assign your signing key to an INNGEST_SIGNING_KEY environment variable in your hosting provider or .env file locally, which lets the SDK securely communicate with Inngest. If you can't provide this as a signing key, you can pass it in to serve when setting up your framework. Read the reference for more information.

Reference

For more information about the serve handler, read the the reference guide, which includes: