Installation
Set up Restaq step by step, from install to your first mounted webhook route
You'll need Node >=22 and TypeScript >=5.
1. Install the package
pnpm add restaqnpm install restaqyarn add restaqbun add restaqrestaq includes the engine, framework adapters (restaq/next-js, restaq/express, restaq/hono, restaq/nestjs), and the relay CLI — this guide adds a database as you go. No separate CLI install, ever — see CLI for the full command reference, including a zero-install npx restaq@latest init path if you don't even have a project yet.
2. Set environment variables
Create a .env file in the root of your project and add the following environment variable:
RESTAQ_BASE_URL=http://localhost:3000 # Base URL of your appRESTAQ_BASE_URL is how the CLI (relay trigger, relay replay) knows where your app is running once it's mounted (step 7).
3. Create a relay instance
Create relay.ts at your project root. This file stays wiring-only — it creates the relay and nothing else. Every relay needs a database; the fastest way to get started is SQLite, which needs no separate server:
pnpm add better-sqlite3npm install better-sqlite3yarn add better-sqlite3bun add better-sqlite3// relay.ts
import { restaq as createRestaq } from 'restaq';
import Database from 'better-sqlite3';
import { registerHandlers } from './relay.handlers';
export const restaq = createRestaq({
database: new Database('restaq.db'),
});
// The concrete relay type, including any typed event catalogs inferred from
// plugins you add later — exported so relay.handlers.ts can register
// handlers with full event.data typing.
export type AppRelay = typeof restaq;
registerHandlers(restaq);Migrations apply automatically the first time this relay processes an event — nothing else to run for local development.
4. Swap in Postgres or MySQL for production
SQLite is fine for local development and small single-instance deployments, but not once you're running multiple instances. Swap in Postgres or MySQL by passing a different client — the dialect is detected automatically:
// relay.ts
import { restaq as createRestaq } from 'restaq';
import { Pool } from 'pg';
import { registerHandlers } from './relay.handlers';
export const restaq = createRestaq({
database: new Pool({ connectionString: process.env.DATABASE_URL! }),
});
export type AppRelay = typeof restaq;
registerHandlers(restaq);See Database for the MySQL setup and the full list of supported clients.
5. Apply migrations in production
Migrations still apply automatically outside production, but in production run them explicitly before scaling up new instances. The relay command comes bundled with restaq from step 1 — nothing else to install:
pnpm exec relay migratenpx relay migrateyarn relay migratebunx relay migrateThis loads your relay.ts and applies whatever schema your chosen dialect needs. It's idempotent — safe to run again after every deploy.
6. Register a handler
relay.on(...) calls don't belong in relay.ts — as an app grows, that file would turn into a dumping ground for every event's business logic. Put handlers in their own module instead:
// relay.handlers.ts
import type { AppRelay } from './relay';
export function registerHandlers(relay: AppRelay): void {
relay.on('order.placed', async (event, ctx) => {
const payment = await ctx.step.run('charge-payment', async () => {
return { orderId: event.data.orderId, amount: event.data.amount };
});
await ctx.step.run('send-confirmation', async () => {
ctx.log.info('order confirmed', payment);
});
});
}Each ctx.step.run(...) checkpoints its result — if the handler throws and retries, a step that already completed returns its cached output instead of re-running.
7. Mount the handler
To receive real webhook deliveries, add a catch-all route on your server. restaq ships an adapter for each framework below — nothing extra to install:
// app/api/webhook/[...all]/route.ts
import { toNextJsHandler } from 'restaq/next-js';
import { restaq } from '@/relay';
export const { POST } = toNextJsHandler(restaq);import express from 'express';
import { toExpressHandler } from 'restaq/express';
import { restaq } from './relay';
const app = express();
app.post('/api/webhook/:provider', toExpressHandler(restaq));
app.listen(3000);import { Hono } from 'hono';
import { toHonoHandler } from 'restaq/hono';
import { restaq } from './relay';
const app = new Hono();
app.post('/api/webhook/:provider', toHonoHandler(restaq));
export default app;import { Controller, Post, Req, Res } from '@nestjs/common';
import { toNestJsHandler, type NestJsRelayRequest, type NestJsRelayResponse } from 'restaq/nestjs';
import { restaq } from './relay';
@Controller('api/webhook')
export class RelayController {
private readonly handler = toNestJsHandler(restaq);
@Post(':provider')
post(@Req() req: NestJsRelayRequest, @Res() res: NestJsRelayResponse) {
return this.handler(req, res);
}
}Each of these serves every plugin at /api/webhook/<provider> — e.g. /api/webhook/stripe, /api/webhook/github — determined entirely by wherever you mount the route, there's no base-path setting to configure. See Next.js, Express, Hono, or NestJS for framework-specific details (raw body handling, custom param names, management endpoints).
On something else? restaq.handler is still a plain (req: Request, ctx: { params: { all: string[] } }) => Promise<Response> function you can wire in yourself.
Optional: scaffold this with the CLI
relay init walks you through an interactive setup instead of doing steps 3 and 6 by hand — it asks for your database and any provider plugins, then generates relay.ts + relay.handlers.ts and installs exactly what you chose. Nothing pre-installed? One command:
npx restaq@latest initAlready have restaq in your project? relay is already available:
pnpm exec relay initNext steps
- Basic usage: see Basic Usage for the everyday patterns — registering handlers for real provider events,
ctx.step.run, ingesting via webhook vs. directly, and inspecting/replaying via the CLI. - Receive real webhooks: install a provider plugin — Stripe, GitHub, Clerk, Shopify, or Resend — only once you're ready to verify real signed deliveries.
- Event types: learn how Typed Events work and what autocomplete you get once you add a provider plugin.
- Durable steps: dive into Durable Steps to understand caching, retries, and audit trails.
- Replay & restart: understand the three retry operations and when to use each.
- CLI reference:
relay triggerandrelay replayread theRESTAQ_BASE_URLyou set in step 2 to know where to POST — see the CLI reference for every command and env var it reads.