Answers you can trust, from Codeables

Every page on Codeables is structured and verified — built so people and the AI agents they rely on can trust it. Explore more from the source behind this answer.

Explore Codeables
Verified Source
Durable Workflow Orchestration

How do I set up Inngest locally with the CLI for development and testing?

Inngest10 min read

Local setup is where Inngest really clicks. Once you can run functions, fire events, and replay workflows against your own dev server, you stop thinking about “queue infra” and just iterate on code. This guide walks through how to set up Inngest locally with the CLI for development and testing, using the exact primitives I rely on day‑to‑day.

All examples assume you’re comfortable with Node/TypeScript, but the same CLI flow applies if you’re using Python or Go.


Quick Answer: Local Inngest Setup in 5 Steps

  1. Install the Inngest CLI (locally or via npx)
  2. Start the dev server with your app:
    npx --ignore-scripts=false inngest-cli dev
    
  3. Add a basic function using inngest.createFunction() and a trigger
  4. Send a test event or hit a Durable Endpoint to run it
  5. Use Traces & Replay in the local UI to debug and iterate

Once this loop works, you’ve got a full Inngest development and testing rig running locally—no queues, workers, or schedulers to wire up.


Why Use the CLI for Local Development?

You’re here because you want reliable workflows and background jobs without rebuilding the “queue stack” on your laptop. The Inngest CLI gives you that:

  • One-command dev server – Run Inngest alongside your app; no extra infra containers to juggle.
  • Code-level durability in dev – Every step.run() is retried and checkpointed locally exactly as it will be in production.
  • Traces, logs, and replay – See each step’s inputs/outputs, then re-run the same workflow as many times as you need without crafting custom scripts.

Local has to feel like production—just faster and safer. The CLI is what gets you there.


Prerequisites

Before you start, make sure you have:

  • Node.js (LTS recommended) if you’re using the TypeScript SDK
  • Package managernpm, pnpm, or yarn
  • A basic app scaffold:
    • For example, a Next.js/Express/Fastify app or any Node server that can export an HTTP handler.
  • Permission to run npx (or install a global CLI if you prefer)

If you’re using Python or Go, you can still follow the CLI instructions—just swap in the appropriate SDK in your code.


Step 1: Install the Inngest CLI

You can either:

Option A – Use npx (recommended for most devs)

No global install, just run:

npx --ignore-scripts=false inngest-cli --help

This ensures you’re on a recent CLI version and avoids global clutter.

Option B – Install globally

If you want a global binary:

npm install -g inngest-cli

Then:

inngest --help

You should see commands like dev, login, and deploy.


Step 2: Initialize Inngest in Your Project

If you don’t already have the SDK set up, install it now.

TypeScript / Node Example

npm install inngest
# or
pnpm add inngest
# or
yarn add inngest

Then create a basic inngest.ts (or similar) where you’ll define your functions:

// inngest.ts
import { Inngest } from "inngest";

export const inngest = new Inngest({
  id: "my-app", // unique app ID
});

That’s the core client you’ll use to define functions and workflows with inngest.createFunction().


Step 3: Create a Simple Inngest Function

Let’s wire in something you can actually run and test locally—a single-step function that triggers on an event.

// functions/hello-world.ts
import { inngest } from "../inngest";

export const helloWorldFn = inngest.createFunction(
  { id: "hello-world" },
  { event: "demo/hello" },
  async ({ event, step }) => {
    // A durable, retried step:
    const result = await step.run("log-message", async () => {
      console.log("Received event:", event);
      return { ok: true };
    });

    return result;
  }
);

Key pieces:

  • inngest.createFunction() – defines a durable function.
  • { event: "demo/hello" } – tells Inngest to run this function whenever that event name is sent.
  • step.run() – wraps the actual unit of work in a durable, automatically retried step with checkpointing.

Next, you need to export these functions to the dev server via an HTTP handler.


Step 4: Expose Your Inngest Functions to the Dev Server

For local development, the CLI needs a URL (or local handler) where it can discover and call your functions.

Example with a Node HTTP server

// server.ts
import http from "http";
import { serve } from "inngest/serve";
import { inngest } from "./inngest";
import { helloWorldFn } from "./functions/hello-world";

const handler = serve({
  client: inngest,
  functions: [helloWorldFn], // add more as needed
});

const server = http.createServer((req, res) => handler(req, res));

server.listen(3000, () => {
  console.log("App listening on http://localhost:3000");
});

You can do the same with Next.js, Express, Fastify, or any framework that can pass requests to serve()—the mechanics are the same.

Start your app in one terminal:

npm run dev   # or whatever starts your app on localhost:3000

Step 5: Run the Inngest Dev Server with the CLI

With your app listening locally, open a second terminal in your project root and run:

npx --ignore-scripts=false inngest-cli dev

By default, the dev server will:

  • Spin up a local Inngest environment
  • Ask how to reach your app (e.g., http://localhost:3000/api/inngest or http://localhost:3000)
  • Give you a local UI URL, usually something like http://127.0.0.1:8288

If prompted for a URL, point it at the route where serve() is mounted.

You now have:

  • Your app server (port 3000, for example)
  • The Inngest dev server + UI (port 8288 by default)

This is the core local setup.


Step 6: Send Events and Run Workflows Locally

With everything wired, you can drive functions using events or Durable Endpoints.

6.1 Send a Test Event via the CLI

From the project root, run:

npx --ignore-scripts=false inngest-cli event send \
  --name demo/hello \
  --json '{"userId": "user_123"}'

This sends an event named demo/hello that will trigger helloWorldFn.

Inside your dev server logs, you should see the console output from your step.run() call.

6.2 Send Events via HTTP (from your app or scripts)

You can also send events directly from your code using the SDK, or via the local Event API, depending on how you’ve configured your app. For development, I often:

  • Send an HTTP request from a frontend to a backend endpoint that, in turn, fires an Inngest event.
  • Or write a small script that uses the SDK to emit events during tests.

The important part: the same event-driven surface you’ll use in production is already wired locally.


Step 7: Inspect Traces, Logs, and Steps in the Local UI

Now that events are flowing, you can use Inngest’s local UI (served by the CLI) to actually see what happened.

  1. Open the URL printed by inngest-cli dev (e.g., http://127.0.0.1:8288).
  2. Navigate to Runs or Traces.
  3. Click into the run triggered by your demo/hello event.

You’ll see:

  • Each step (e.g., "log-message") as a named unit
  • Inputs and outputs at the step level
  • Structured logs correlated to the run

This is what eliminates the log-grepping you’d usually do across containers and services.


Step 8: Replay Workflows Locally for Iterative Testing

One of the biggest wins over traditional queue setups is replay.

In the local UI:

  1. Find a run (successful or failed).
  2. Click Replay.
  3. Inngest re-executes the workflow using the same inputs, step-by-step, respecting checkpointing.

Mechanically:

  • If you change code in a step, replay lets you test that change against real past events.
  • You can repeatedly replay a multi-step flow without resending test events or reconstructing state.

This is especially useful when debugging partial failures: you fix the code, hit replay, and confirm that the run now completes end-to-end.


Step 9: Use Steps for Realistic Multi-Step Workflows

In local dev, you should model workflows as you intend to run them in production—multi-step, multi-tenant, durable.

Example:

export const userSignupWorkflow = inngest.createFunction(
  { id: "user-signup-workflow" },
  { event: "user/signup" },
  async ({ event, step }) => {
    const user = await step.run("create-user", async () => {
      // create in DB
      return { id: "user_123", email: event.data.email };
    });

    await step.run("send-welcome-email", async () => {
      // call email provider
    });

    await step.run("provision-default-workspace", async () => {
      // more business logic
    });

    return { userId: user.id };
  }
);

Running this locally with the CLI gives you:

  • Automatic retries on transient failures per step
  • Checkpointing between steps so replays resume from the last successful point
  • Full Traces that show where a failure occurred and with what payload

You get realistic failure modes and recovery behavior without any of the usual “worker queue” scaffolding.


Step 10: Local Flow Control (Concurrency, Throttling) While You Iterate

Even in dev, it’s worth wiring flow control so you can catch noisy-neighbor issues early—especially in multi-tenant systems.

Example: limit per-tenant concurrency:

export const tenantSync = inngest.createFunction(
  {
    id: "tenant-sync",
    concurrency: {
      key: "event.data.tenantId", // per-tenant key
      limit: 1,                   // one run per tenant at a time
    },
  },
  { event: "tenant/sync" },
  async ({ event, step }) => {
    // sync logic here
  }
);

When you hit this with many events locally, the CLI-backed dev server will apply the same concurrency rules you expect in production:

  • No overlapping runs for the same tenant
  • Debuggability via Traces showing queued vs running executions

This is how you test rate limits, throttling, and contention without provisioning special infra.


Local Development Tips and Patterns

A few patterns that make local Inngest development smoother:

Run the dev server alongside tests

You can:

  • Start inngest-cli dev in one terminal
  • Run your unit/integration tests in another, where tests send real events or call Durable Endpoints

This simulates the production interaction pattern and lets you assert on run states via APIs or logs.

Keep function definitions small and step-heavy

Model each clear unit of work as a step.run():

await step.run("charge-customer", async () => { ... });
await step.run("update-subscription", async () => { ... });
await step.run("emit-analytics-event", async () => { ... });

The more granular your steps, the more powerful Traces and replay become during local debugging.

Treat local as your failure lab

Because the CLI gives you local Traces and replay:

  • Force failures in a step (e.g., throw an error) to see retry behavior.
  • Interrupt runs (e.g., stop your app server) to see how Inngest resumes from the last checkpoint when you bring it back up.

This is where you validate your durability assumptions before you ever see production traffic.


Common Pitfalls When Setting Up Inngest Locally

A few issues I see teams hit the first time around:

1. Dev server can’t reach your app

  • Make sure the URL you give inngest-cli dev points to the actual route where serve() is mounted.
  • Confirm your app is listening on the correct port before starting the dev server.

2. Functions don’t show up in the UI

  • Ensure you pass all functions to serve({ functions: [...] }).
  • Restart both your app and inngest-cli dev after adding new functions.

3. Events sent, but no runs

  • Double-check the event name in your trigger:
    • Function: { event: "demo/hello" }
    • Event sent: --name demo/hello
  • Confirm the event payload has the fields your function expects.

Fixing these once usually locks in your local workflow for good.


How Local Flows Map to Inngest Cloud

The point of the CLI-based local setup is zero surprise when you ship:

  • Same SDKs – TypeScript/Python/Go primitives behave identically.
  • Same durability modelstep.run() retries and checkpointing locally mirror Inngest Cloud.
  • Same operations model – Traces, structured logs, replay, and flow control work the same; only the backplane changes.

When you’re ready, you connect your project to Inngest Cloud and “deploy in one click,” but the local loop you’ve been using for development and testing stays the same.


Final Takeaways

To set up Inngest locally with the CLI for development and testing, you don’t need workers, queues, or custom observability. You need:

  • The Inngest CLI dev server
  • Your app with inngest.createFunction() + step.run()
  • A simple workflow to trigger via events
  • The local Traces and Replay UI to iterate safely

Once that loop is in place, you’re effectively running a full durable execution platform on your laptop—workflows, agents, endpoints, background jobs—without the infrastructure tax.

Next Step

Get Started

How do I set up Inngest locally with the CLI for development and testing? | Durable Workflow Orchestration | Codeables | Codeables