workflow
Version:
Workflow SDK - Build durable, resilient, and observable workflows
273 lines (193 loc) • 8.33 kB
text/mdx
---
title: Fastify
description: Set up your first durable workflow in a Fastify application.
type: guide
summary: Set up Workflow SDK in a Fastify app.
prerequisites:
- /docs/getting-started
related:
- /docs/foundations/workflows-and-steps
---
<CopyPrompt
text="In this Fastify app, run `npm i workflow fastify nitro rollup` and, if using TypeScript, `npm i -D @types/node typescript`. Create `nitro.config.ts` with `modules: ["workflow/nitro"]`, `vercel: { entryFormat: "node" }`, and `routes: { "/**": { handler: "./src/index.ts", format: "node" } }`. Add package scripts `dev: "nitro dev"` and `build: "nitro build"`. Create `workflows/user-signup.ts` with `"use workflow"`, `sleep`, and `"use step"` helpers. Add `src/index.ts` with a Fastify app, POST `/api/signup`, `start(handleUserSignup, [email])`, `await app.ready()`, and an exported request handler. Run `npm run dev`, call `curl -X POST --json '{"email":"hello@example.com"}' http://localhost:3000/api/signup`, and inspect with `npx workflow inspect runs --web`."
/>
This guide will walk through setting up your first workflow in a Fastify app. Along the way, you'll learn more about the concepts that are fundamental to using the Workflow SDK in your own projects.
---
<Steps>
<Step>
## Create your Fastify project
Start by creating a new Fastify project.
```bash
mkdir my-workflow-app
```
Enter the newly made directory:
```bash
cd my-workflow-app
```
Initialize the project:
```bash
npm init --y
```
### Install `workflow`, `fastify` and `nitro`
```package-install
npm i workflow fastify nitro rollup
```
<Callout>
By default, Fastify doesn't include a build system. Nitro adds one which enables compiling workflows, runs, and deploys for development and production. Learn more about [Nitro](https://v3.nitro.build).
</Callout>
If using TypeScript, you need to install the `/node` and `typescript` packages
```bash
npm i -D /node typescript
```
### Configure Nitro
Create a new file `nitro.config.ts` for your Nitro configuration with module `workflow/nitro`. This enables usage of the `"use workflow"` and `"use step"` directives
```typescript title="nitro.config.ts" lineNumbers
import { defineNitroConfig } from "nitro/config";
export default defineNitroConfig({
modules: ["workflow/nitro"],
vercel: { entryFormat: "node" },
routes: {
"/**": { handler: "./src/index.ts", format: "node" },
},
});
```
<Details>
<Summary>Setup IntelliSense for TypeScript (Optional)</Summary>
To enable helpful hints in your IDE, set up the workflow plugin in `tsconfig.json`:
```json title="tsconfig.json" lineNumbers
{
"compilerOptions": {
// ... rest of your TypeScript config
"plugins": [
{
"name": "workflow" // [!code highlight]
}
]
}
}
```
</Details>
### Update `package.json`
To use the Nitro builder, update your `package.json` to include the following scripts:
```json title="package.json" lineNumbers
{
// ...
"scripts": {
"dev": "nitro dev",
"build": "nitro build"
},
// ...
}
```
</Step>
<Step>
## Create your first workflow
Create a new file for our first workflow:
```typescript title="workflows/user-signup.ts" lineNumbers
import { sleep } from "workflow";
export async function handleUserSignup(email: string) {
"use workflow"; // [!code highlight]
const user = await createUser(email);
await sendWelcomeEmail(user);
await sleep("5s"); // Pause for 5s - doesn't consume any resources
await sendOnboardingEmail(user);
return { userId: user.id, status: "onboarded" };
}
```
We'll fill in those functions next. The current code does the following:
- We define a **workflow** function with the directive `"use workflow"`. Think of the workflow function as the _orchestrator_ of individual **steps**.
- The Workflow SDK's `sleep` function allows us to suspend execution of the workflow without using up any resources. A sleep can be a few seconds, hours, days, or even months long.
## Create your workflow steps
Define the missing functions:
```typescript title="workflows/user-signup.ts" lineNumbers
import { FatalError } from "workflow";
// Our workflow function defined earlier
async function createUser(email: string) {
"use step"; // [!code highlight]
console.log(`Creating user with email: ${email}`);
return { id: crypto.randomUUID(), email };
}
async function sendWelcomeEmail(user: { id: string; email: string }) {
"use step"; // [!code highlight]
console.log(`Sending welcome email to user: ${user.id}`);
if (Math.random() < 0.3) {
// Steps retry on unhandled errors
throw new Error("Retryable!");
}
}
async function sendOnboardingEmail(user: { id: string; email: string }) {
"use step"; // [!code highlight]
if (!user.email.includes("@")) {
// FatalError skips retries
throw new FatalError("Invalid Email");
}
console.log(`Sending onboarding email to user: ${user.id}`);
}
```
Taking a look at this code:
- Business logic lives inside **steps**. When a workflow invokes a step, the workflow suspends while the step runs with full Node.js access. The combined handler may execute it inline; queued retries and continuations return through the same flow route.
- If a step throws an error, like in `sendWelcomeEmail`, the step will automatically be retried until it succeeds (or hits the step's max retry count).
- Steps can throw a `FatalError` if an error is intentional and should not be retried.
<Callout>
We'll dive deeper into workflows, steps, and other ways to suspend or handle
events in [Foundations](/docs/foundations).
</Callout>
</Step>
<Step>
## Create your route handler
To invoke your new workflow, we'll create both the Fastify app and a new API route handler at `src/index.ts` with the following code:
```typescript title="src/index.ts"
import Fastify from "fastify";
import { start } from "workflow/api";
import { handleUserSignup } from "../workflows/user-signup.js";
const app = Fastify({ logger: true });
app.post("/api/signup", async (req, reply) => {
const { email } = req.body as { email: string };
await start(handleUserSignup, [email]);
return reply.send({ message: "User signup workflow started" });
});
// Wait for Fastify to be ready before handling requests
await app.ready();
export default (req: any, res: any) => {
app.server.emit("request", req, res);
};
```
This route handler creates a `POST` request endpoint at `/api/signup` that will trigger your workflow.
</Step>
<Step>
## Run in development
To start your development server, run the following command in your terminal in the Fastify root directory:
```bash
npm run dev
```
Once your development server is running, you can trigger your workflow by running this command in the terminal:
```bash
curl -X POST --json '{"email":"hello@example.com"}' http://localhost:3000/api/signup
```
Check the Fastify development server logs to see your workflow execute as well as the steps that are being processed.
Additionally, you can use the [Workflow SDK CLI or Web UI](/docs/observability) to inspect your workflow runs and steps in detail.
```bash
npx workflow inspect runs # add '--web' for an interactive Web based UI
```

</Step>
</Steps>
---
## Deploying to production
Workflow SDK apps currently work best when deployed to [Vercel](https://vercel.com/home) and need no special configuration.
<FluidComputeCallout />
Check the [Deploying](/docs/deploying) section to learn how your workflows can be deployed elsewhere.
## Troubleshooting
### `start()` says it received an invalid workflow function
If you see this error:
```text
'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive.
```
Check both of these first:
1. The workflow function includes `"use workflow"`.
2. Your Nitro config includes the `workflow/nitro` module.
See [start-invalid-workflow-function](/docs/errors/start-invalid-workflow-function) for full examples and fixes.
## Next steps
- Learn more about the [Foundations](/docs/foundations).
- Check [Errors](/docs/errors) if you encounter issues.
- Explore the [API Reference](/docs/api-reference).