Getting Started
Install OpenWorkflow and create your first durable workflow in minutes
Getting Started
This guide will walk you through installing OpenWorkflow, setting up a PostgreSQL backend, and creating your first durable workflow.
Prerequisites
Before you begin, ensure you have:
- Node.js: Version 20 or higher
- PostgreSQL: A running PostgreSQL instance (local or remote)
Installation
Install the core OpenWorkflow package and the PostgreSQL backend:
npm install openworkflow @openworkflow/backend-postgrespnpm add openworkflow @openworkflow/backend-postgresyarn add openworkflow @openworkflow/backend-postgresbun add openworkflow @openworkflow/backend-postgresDatabase Setup
Database Required
OpenWorkflow requires a PostgreSQL database to store workflow state. All workflow runs and step attempts are persisted to ensure durability across crashes.
If you don't have PostgreSQL installed, you can use Docker:
docker run -d \
--name openworkflow-postgres \
-e POSTGRES_PASSWORD=postgres \
-p 5432:5432 \
postgres:16Connection String
You'll need a PostgreSQL connection URL in this format:
postgresql://username:password@hostname:port/databaseExample for local development:
postgresql://postgres:postgres@localhost:5432/postgresStore this in an environment variable:
export DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"Create Your First Workflow
Connect to the Backend
Create a new file called workflow.ts:
import { BackendPostgres } from "@openworkflow/backend-postgres";
import { OpenWorkflow } from "openworkflow";
const databaseUrl = process.env.DATABASE_URL!;
const backend = await BackendPostgres.connect(databaseUrl);
const ow = new OpenWorkflow({ backend });The backend will automatically create the necessary database tables (workflow_runs and step_attempts) on first connection.
Define a Workflow
A workflow is a durable function that orchestrates multiple steps:
const sendWelcomeEmail = ow.defineWorkflow(
{ name: "send-welcome-email" },
async ({ input, step }) => {
// Step 1: Fetch user from database
const user = await step.run({ name: "fetch-user" }, async () => {
return await db.users.findOne({ id: input.userId });
});
// Step 2: Send email
await step.run({ name: "send-email" }, async () => {
return await emailService.send({
to: user.email,
subject: "Welcome!",
html: "<h1>Welcome to our app!</h1>",
});
});
// Step 3: Mark email as sent
await step.run({ name: "mark-sent" }, async () => {
await db.users.update(input.userId, { welcomeEmailSent: true });
});
return { success: true, userId: input.userId };
},
);Each step.run() creates a checkpoint. If the workflow crashes, it will resume from the last completed step.
Start a Worker
Workers are background processes that execute workflows. Start one in the same file or a separate process:
const worker = ow.newWorker();
await worker.start();
console.log("Worker started. Waiting for workflows...");Run the Workflow
Trigger the workflow from your application code:
// Trigger the workflow asynchronously
const runHandle = await sendWelcomeEmail.run({ userId: "user_123" });
console.log(`Workflow started with ID: ${runHandle.workflowRun.id}`);
// Optional: Wait for the result
const result = await runHandle.result();
console.log("Workflow completed:", result);Complete Example
Here's the full code:
import { BackendPostgres } from "@openworkflow/backend-postgres";
import { OpenWorkflow } from "openworkflow";
// Connect to backend
const backend = await BackendPostgres.connect(process.env.DATABASE_URL!);
const ow = new OpenWorkflow({ backend });
// Define workflow
const greetUser = ow.defineWorkflow(
{ name: "greet-user" },
async ({ input, step }) => {
const greeting = await step.run({ name: "generate-greeting" }, async () => {
return `Hello, ${input.name}!`;
});
const timestamp = await step.run({ name: "get-timestamp" }, async () => {
return new Date().toISOString();
});
return { greeting, timestamp };
},
);
// Start worker
const worker = ow.newWorker({ concurrency: 10 });
await worker.start();
// Run workflow
const run = await greetUser.run({ name: "Alice" });
const result = await run.result();
console.log(result); // { greeting: "Hello, Alice!", timestamp: "..." }
// Graceful shutdown
await worker.stop();
await backend.stop();Run Your Workflow
Execute the script:
node workflow.tsYou should see:
Worker started. Waiting for workflows...
{ greeting: "Hello, Alice!", timestamp: "2024-01-15T10:30:00.000Z" }What Happens Under the Hood
- Workflow Creation: A row is inserted into the
workflow_runstable with statuspending - Worker Claims: The worker polls the database and claims the workflow
- Step Execution: Each step is executed and recorded in
step_attempts - Completion: The workflow status is updated to
succeeded
If the worker crashes during execution, another worker will pick up the workflow and resume from the last completed step.
Next Steps
Core Concepts
Understand workflows, steps, workers, and the execution model
Type Safety
Learn how to add TypeScript types to your workflows
Parallel Execution
Run multiple steps concurrently with Promise.all
Production Guide
Deploy OpenWorkflow to production with confidence
Project Structure
Here's a recommended project structure for OpenWorkflow:
Common Patterns
Pattern 1: API Route Handler
Trigger workflows from your API:
app.post("/users/:id/welcome", async (req, res) => {
const run = await sendWelcomeEmail.run({ userId: req.params.id });
res.json({ workflowRunId: run.workflowRun.id });
});Pattern 2: Separate Worker Process
Run workers in a dedicated process:
import { BackendPostgres } from "@openworkflow/backend-postgres";
import { OpenWorkflow } from "openworkflow";
const backend = await BackendPostgres.connect(process.env.DATABASE_URL!);
const ow = new OpenWorkflow({ backend });
// Import all workflow definitions
import "./workflows/welcome-email.js";
import "./workflows/order-processing.js";
const worker = ow.newWorker({ concurrency: 20 });
await worker.start();
process.on("SIGTERM", async () => {
await worker.stop();
await backend.stop();
process.exit(0);
});Pattern 3: With TypeScript Types
Add type safety to your workflows:
interface SendEmailInput {
userId: string;
emailType: "welcome" | "confirmation";
}
interface SendEmailOutput {
success: boolean;
emailId: string;
}
const sendEmail = ow.defineWorkflow<SendEmailInput, SendEmailOutput>(
{ name: "send-email" },
async ({ input, step }) => {
// input is typed as SendEmailInput
// return must match SendEmailOutput
},
);Troubleshooting
Common Issues
Most issues are related to database connectivity or worker configuration. Check the solutions below.
Database Connection Errors
If you see connection errors:
- Verify PostgreSQL is running:
psql -U postgres -h localhost - Check your connection string format
- Ensure the database exists
Worker Not Picking Up Workflows
If workflows aren't executing:
- Ensure the worker is started:
await worker.start() - Check workflow names match between definition and run
- Verify the database connection is active
Steps Not Memoizing
If steps re-execute on retry:
- Ensure step names are unique within a workflow
- Don't use dynamic step names (they must be deterministic)
- Check that the backend connection is stable
Learn More
- Core Concepts - Deep dive into workflows, steps, and workers
- Architecture - Understand the execution model
- API Reference - Complete API documentation