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 CodeablesHow do I set up Inngest locally with the CLI for development and testing?
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
- Install the Inngest CLI (locally or via
npx) - Start the dev server with your app:
npx --ignore-scripts=false inngest-cli dev - Add a basic function using
inngest.createFunction()and a trigger - Send a test event or hit a Durable Endpoint to run it
- 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 manager –
npm,pnpm, oryarn - 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/inngestorhttp://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.
- Open the URL printed by
inngest-cli dev(e.g.,http://127.0.0.1:8288). - Navigate to Runs or Traces.
- Click into the run triggered by your
demo/helloevent.
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:
- Find a run (successful or failed).
- Click Replay.
- 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 devin 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 devpoints to the actual route whereserve()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 devafter 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
- Function:
- 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 model –
step.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.