Ablauf

Getting Started

Create your first Ablauf workflow in minutes. No external infrastructure required.

Getting Started

This guide walks you through creating your first durable workflow with Ablauf. By the end, you'll have a working Cloudflare Worker that runs workflows on the edge.

Prerequisites

Before you start, make sure you have:

New to Cloudflare Workers? Check out their getting started guide first.

Installation

Install Ablauf alongside Hono and Zod:

npm install @der-ablauf/workflows hono zod
  • @der-ablauf/workflows — The workflow engine
  • hono — Lightweight web framework for Workers
  • zod — Runtime validation (used internally; you can also use z.infer for type extraction)

Build Your First Workflow

Define a Workflow

Create a simple greeting workflow using the defineWorkflow() API. This is the recommended way to define workflows—less boilerplate than classes, full type safety.

src/workflows/greeting.ts
import { defineWorkflow } from '@der-ablauf/workflows';

export const GreetingWorkflow = defineWorkflow((t) => ({
	type: 'greeting',
	input: t.object({
		name: t.string(),
	}),
	run: async (step, payload) => {
		// This step is durable—if the worker crashes, it replays with cached results
		const message = await step.do('create-greeting', async () => {
			return `Hello, ${payload.name}! Welcome aboard.`;
		});

		// Simulate some work
		await step.sleep('wait-a-moment', '2s');

		// Return the final result
		return { message, completedAt: new Date().toISOString() };
	},
}));

The step.do() block ensures the greeting is generated once and cached. If the workflow restarts, it won't regenerate the message—it'll use the stored result.

Set Up the Worker

Create a Hono app and initialize Ablauf with your workflow. The Ablauf class connects to your Durable Object binding and registers your workflows.

src/index.ts
import { Hono } from 'hono';
import { Ablauf } from '@der-ablauf/workflows';
import { env } from 'cloudflare:workers';
import { GreetingWorkflow } from './workflows/greeting';

// Initialize Ablauf with the WORKFLOW_RUNNER binding
const ablauf = new Ablauf(env.WORKFLOW_RUNNER, {
	workflows: [GreetingWorkflow],
});

const app = new Hono();

// Create a new greeting workflow
app.post('/greet', async (c) => {
	const { name } = await c.req.json();

	const workflow = await ablauf.create(GreetingWorkflow, {
		id: crypto.randomUUID(),
		payload: { name },
	});

	const status = await workflow.getStatus();
	return c.json(status);
});

// Get workflow status
app.get('/workflows/:id', async (c) => {
	const id = c.req.param('id');
	const workflow = ablauf.get(GreetingWorkflow, { id });
	const status = await workflow.getStatus();
	return c.json(status);
});

// Export the Worker and Durable Object class
export default { fetch: app.fetch };
export const WorkflowRunner = ablauf.createWorkflowRunner();

The createWorkflowRunner() call must be exported as WorkflowRunner to match your Durable Object binding name in wrangler.jsonc.

Configure Wrangler

Tell Cloudflare about your Durable Object. This is what makes your workflows durable—the WorkflowRunner class gets its own SQLite database.

wrangler.jsonc
{
	"name": "my-ablauf-app",
	"main": "src/index.ts",
	"compatibility_date": "2025-01-01",
	"compatibility_flags": ["nodejs_compat"],
	"rules": [
		{
			"type": "Text",
			"globs": ["**/*.sql"],
			"fallthrough": true,
		},
	],
	"durable_objects": {
		"bindings": [
			{
				"class_name": "WorkflowRunner",
				"name": "WORKFLOW_RUNNER",
			},
		],
	},
	"migrations": [
		{
			"new_sqlite_classes": ["WorkflowRunner"],
			"tag": "v1",
		},
	],
}

Key fields:

  • rules — Tells wrangler to bundle .sql files as text modules. Ablauf uses SQL migrations internally, and without this rule wrangler won't know how to handle them.
  • durable_objects.bindings — Registers the Durable Object class and binding name
  • migrations — Enables SQLite storage for the Durable Object (required for Ablauf)
  • compatibility_flags — Enables Node.js compatibility for crypto and other APIs

Run Locally

Start the local development server:

npx wrangler dev

Wrangler will start a local server (usually on http://localhost:8787). Your Durable Objects run in a local environment with real SQLite storage.

Test Your Workflow

Create a workflow:

curl -X POST http://localhost:8787/greet \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice"}'

Response:

{
	"id": "some-uuid",
	"type": "greeting",
	"status": "running",
	"payload": { "name": "Alice" },
	"createdAt": "2025-02-14T12:00:00.000Z"
}

Wait a couple seconds, then check the status:

curl http://localhost:8787/workflows/some-uuid

Response:

{
	"id": "some-uuid",
	"type": "greeting",
	"status": "completed",
	"payload": { "name": "Alice" },
	"result": {
		"message": "Hello, Alice! Welcome aboard.",
		"completedAt": "2025-02-14T12:00:02.000Z"
	},
	"createdAt": "2025-02-14T12:00:00.000Z",
	"completedAt": "2025-02-14T12:00:02.000Z"
}

Congratulations! You've just run a durable workflow on the edge.

What Just Happened?

Let's break down what Ablauf did behind the scenes:

  1. Created a workflow instance — Your workflow got a unique ID and its payload was validated against the Zod schema.
  2. Executed the first step — The create-greeting step ran and its result was saved to SQLite.
  3. Slept for 2 seconds — Ablauf set a Durable Object alarm for 2 seconds in the future, then released resources.
  4. Woke up and replayed — After 2 seconds, the alarm fired. Ablauf replayed the workflow, used the cached greeting, and completed.

If your worker had crashed during the sleep, the workflow would have resumed automatically. That's the magic of durable execution.

What's Next?

Now that you've built your first workflow, explore the full power of Ablauf:

  • Workflows — Learn defineWorkflow(), typed events, and SSE updates
  • Steps — Master step.do() retry policies, step.sleep() / step.sleepUntil() timing, and step.waitForEvent() timeouts
  • Events — Send external events to running workflows (webhooks, user actions, etc.)
  • Dashboard — Monitor and debug your workflows in real-time

Stuck? Join our Discord or open an issue on GitHub. We're here to help.

On this page