Quick Start

Build your first durable workflow with StepKit in minutes

Getting Started

This guide will walk you through getting started with StepKit to build and deploy durable workflows.


Installation

Install the StepKit Inngest driver. The Inngest driver gives you the best local development experience with built-in observability through the Inngest Dev Server.

npm install @stepkit/inngest inngest

Create a Client

Create a file to initialize your StepKit client, which will be used to create workflows.

client.ts
import { InngestClient } from "@stepkit/inngest";

export const client = new InngestClient({ 
  id: "my-app",
});

Build Your First Workflow

Let's create a workflow that processes an order. This workflow will:

  1. Reserve inventory
  2. Process payment
  3. Wait before sending a confirmation
  4. Send an order confirmation email

Create a new file for your workflow:

workflows/process-order.ts
import { z } from "zod";
import { client } from "./client";

export const processOrder = client.workflow(
  {
    id: "process-order",
    
    // Type-safe inputs & runtime validation
    inputSchema: z.object({
      orderId: z.string(),
      items: z.array(z.string()),
      email: z.string().email(),
      amount: z.number(),
    }),
  },
  async ({ input }, step) => {
    // Step 1: Reserve inventory
    const inventory = await step.run("reserve-inventory", async () => {
      console.log(`Reserving items: ${input.data.items.join(", ")}`);
      
      // Simulate inventory check
      const available = input.data.items.every(() => Math.random() > 0.1);
      
      if (!available) {
        throw new Error("Item out of stock - will retry");
      }
      
      return { reserved: true, items: input.data.items };
    });

    // Step 2: Process payment
    const payment = await step.run("process-payment", async () => {
      console.log(`Processing payment of $${input.data.amount}`);
      
      // Simulate payment processing
      const paymentId = crypto.randomUUID();
      
      return {
        id: paymentId,
        amount: input.data.amount,
        status: "completed",
      };
    });

    // Step 3: Wait 30 seconds before confirmation
    // This doesn't consume any resources while waiting!
    await step.sleep("wait-before-confirm", 7000);

    // Step 4: Send confirmation email
    await step.run("send-confirmation", async () => {
      console.log(`Sending order confirmation to ${input.data.email}`);
      console.log(`Order ${input.data.orderId} completed!`);
      
      return { emailSent: true };
    });

    // Return the final result
    return {
      orderId: input.data.orderId,
      paymentId: payment.id,
      status: "completed",
      items: inventory.items,
    };
  }
);

Understanding This Code

Workflows are durable functions that orchestrate your business logic:

  • We pass an inputSchema that provides runtime validation and statically types the workflow's input (the workflow arguments)
  • Each step.run() creates a checkpoint - if your workflow fails, it resumes from the last successful step
  • step.sleep() pauses execution without consuming resources

Steps are atomic, retryable operations:

  • If a step throws an error, it automatically retries with exponential backoff
  • Each step runs exactly once successfully - results are cached
  • Steps are similar to any function of your application; they can use all the available dependencies and use the same runtime to perform async operations, database calls, or API requests.

Run Your Workflow

Now let's invoke the workflow. Create a file to test it:

main.ts
import express from "express";
import { serve } from "inngest/express";
import { inngestify } from "@stepkit/inngest";
import { client } from "./client";
import { processOrder } from "./workflows/process-order";

const app = express();
app.use(express.json());

// Mount Inngest endpoint
app.use("/api/inngest", serve(inngestify(client, [processOrder])));

const PORT = 3000;

app.listen(PORT, () => {
  console.log(`🚀 Server running on http://localhost:${PORT}`);
  console.log(`🔍 Inngest endpoint at http://localhost:${PORT}/api/inngest`);
});

Run the Inngest Dev Server

The Inngest Dev Server provides a local UI for testing, debugging, and observing your workflows.

In one terminal, start your server:

npx tsx main.ts

In a second terminal, start the Inngest Dev Server by running:

npx inngest-cli@latest dev -u http://localhost:3000/api/inngest

Open http://localhost:8288 to see the Dev Server UI.


Testing with the Inngest Dev Server

In the Inngest Dev Server:

  1. Click on the "Functions" tab
  2. On the process-order function, click the "Invoke" button
  3. Enter the test data
{
  "data": {
    "orderId": "ORDER-123",
    "items": ["laptop", "mouse", "keyboard"],
    "email": "customer@example.com",
    "amount": 1299.99
  }
}
  1. Click "Invoke Function". You will be navigated to the "Runs" page and you will see your workflow running.

You'll see:

  • Each step execution in real-time
  • Step results and outputs
  • Timing information
  • Retry history if steps fail

Next Steps

Now that you've built your first workflow, explore more:

For testing and CLI tools: If you're building CLI applications or need simple testing, check out the local drivers (In-Memory and Filesystem).