relay init
Interactively scaffold relay.ts and relay.handlers.ts in your project.
Walks you through an interactive setup and scaffolds relay.ts + relay.handlers.ts, installing exactly the packages your choices need.
Usage
relay init [--force] [--dir <path>]Options
| Flag | Default | Description |
|---|---|---|
--force | false | Overwrite existing relay.ts / relay.handlers.ts |
--dir | current directory | Where to scaffold into |
What it asks
- App name — cosmetic, shown in the wizard's closing message
- Framework — skipped and auto-detected if
package.jsonhas anextdependency; otherwise choose Next.js, Express, Hono, NestJS, or a generic setup - Database — SQLite (recommended to get started), Postgres, or MySQL
- Plugins — Stripe, GitHub, Clerk, Shopify, Resend, or none
It then detects your package manager from whatever lockfile is present (pnpm-lock.yaml, yarn.lock, bun.lockb, or falling back to npm) and installs restaq, the driver package for your chosen database, and any plugin packages you selected.
Output
Creates ./relay.handlers.ts and ./relay.ts (handlers first, since config imports it). For example, choosing SQLite and Stripe:
// relay.handlers.ts
import type { AppRelay } from './relay';
// Handlers live here, not in relay.ts - that file stays
// wiring-only (storage, plugins, retry policy), while the business logic
// that runs when an event arrives lives in its own module. For a larger
// app, split this across multiple files (e.g. one per feature) and call
// each from registerHandlers.
export function registerHandlers(relay: AppRelay): void {
// Events from @restaq provider plugins are fully typed - relay.on()
// autocompletes their event names and types event.data per event:
//
// relay.on('stripe.charge.succeeded', async (event, ctx) => {
// const charge = event.data.object; // a typed Stripe.Charge
//
// const payment = await ctx.step.run('record-payment', async () => {
// return { chargeId: charge.id, amount: charge.amount };
// });
//
// ctx.log.info('payment recorded', payment);
// });
}// relay.ts
import { restaq as createRestaq } from 'restaq';
import Database from 'better-sqlite3';
import { stripe } from '@restaq/stripe';
import { registerHandlers } from './relay.handlers';
export const restaq = createRestaq({
database: new Database('restaq.db'),
plugins: [
stripe(), // reads STRIPE_WEBHOOK_SECRET automatically
],
});
// The concrete relay type, including the typed event catalog inferred from
// the plugins above - exported so relay.handlers.ts can register handlers
// with full event.data typing without re-deriving the plugins list.
export type AppRelay = typeof restaq;
registerHandlers(restaq);If Next.js was detected (or chosen), it also creates app/api/webhook/[...all]/route.ts:
import { toNextJsHandler } from 'restaq/next-js';
import { restaq } from '../../../../relay';
export const { POST } = toNextJsHandler(restaq);If Express, Hono, or NestJS was chosen, it creates the matching adapter file instead:
relay.express.tsrelay.hono.tsrelay.controller.ts
Next steps
- Add a
relay.on(...)call insideregisterHandlers()and callrestaq.ingest(...)to try it. - Not using one of the built-in framework adapters? Wire
restaq.handlerinto your framework's request handling yourself — see Installation step 7. - Ready to receive real webhooks? Set the matching
<PROVIDER>_WEBHOOK_SECRETenv var for any plugin you enabled. - Ready for production? See Database to swap SQLite for Postgres or MySQL, and run
relay migratebefore scaling up new instances.
See the full Installation guide for more.