Getting started
Add FluxRun to your framework, then capture real executions.
Install the SDK, set the three server env vars, expose the agent route, then wrap the API path that should appear in FluxRun. The SDK already knows the production ingest endpoints, so normal apps do not set an ingest URL.
Install the SDK
npm install fluxrunCreate token and keys
Create an app in FluxRun, create a Flux project token, then create the public/private replay key pair for the app.
FLUX_PROJECT_TOKEN=fbproj_... FLUX_PUBLIC_KEY=base64-public-key FLUX_PRIVATE_KEY=base64-private-keyKeep
FLUX_PRIVATE_KEYonly on the server runtime that exposes traced routes and the agent route. UseFLUX_INGEST_URLonly for private or self-hosted ingest overrides.Expose the shared agent route
The dashboard calls this route for decrypt and replay with a short-lived token. The agent verifies that request with FluxRun by using
FLUX_PROJECT_TOKEN.// app/api/flux-agent/route.ts import { fluxAgent } from 'fluxrun'; export const runtime = 'nodejs'; const corsHeaders = { 'Access-Control-Allow-Origin': 'https://app.fluxrun.dev', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 'Access-Control-Allow-Methods': 'POST, OPTIONS', }; export async function OPTIONS() { return new Response(null, { headers: corsHeaders }); } export async function POST(req: Request) { const result = await fluxAgent(await req.json(), { authorization: req.headers.get('authorization'), }); return Response.json(result, { headers: corsHeaders }); }Choose your framework adapter
Every adapter uses the same handler shape: return a status and body, read normalized request data from
flux.request, and put live SDK clients or database handles behindfluxHost.
Next.js route handlers
Next.js App Router
fluxrun/adapters/next- Wrap next.config.ts with the Flux plugin so host-module calls are rewritten before Next compiles the route.
- Use the Node.js runtime for traced routes and the agent route.
- For local smoke tests, set FLUX_INGEST_URL=/api/flux-ingest; FluxRun posts to /api/flux-ingest/v1/executions.
// next.config.ts
import type { NextConfig } from 'next';
import { withFluxNextJsPlugin } from 'fluxrun/build';
const nextConfig: NextConfig = {};
const withFlux = withFluxNextJsPlugin();
export default withFlux(nextConfig);
// app/api/orders/route.ts
import { withFluxNextJs } from 'fluxrun/adapters/next';
import { fluxHost } from 'fluxrun';
import { prisma } from '@/lib/prisma';
export const runtime = 'nodejs';
type OrderInput = { amount: number; currency: string };
const db = fluxHost('db', {
createOrder: async (input: OrderInput) => prisma.order.create({ data: input }),
});
export const POST = withFluxNextJs(
'orders.create',
async (flux) => {
const body = flux.request.body as { amount: number; currency: string };
const order = await db.createOrder(body);
return { status: 201, body: { id: order.id } };
},
{ host: { db } },
);
// app/api/flux-ingest/v1/executions/route.ts
type LocalFluxBatch = {
executionId?: string;
summary?: { executionId?: string };
events?: unknown[];
};
export const runtime = 'nodejs';
export async function POST(req: Request) {
const batch = (await req.json()) as LocalFluxBatch;
return Response.json({
ok: true,
executionId: batch.executionId ?? batch.summary?.executionId ?? 'unknown',
events: batch.events?.length ?? 0,
});
}Express middleware
Express
fluxrun/adapters/express- Run express.json() before the Flux middleware when you need request bodies.
import express from 'express';
import { fluxAgent, fluxHost } from 'fluxrun';
import { withFluxExpress } from 'fluxrun/adapters/express';
const app = express();
app.use(express.json());
const db = fluxHost('db', {
createOrder: (input: { amount: number }) => prisma.order.create({ data: input }),
});
app.post(
'/api/orders',
withFluxExpress(
'orders.create',
async (flux) => {
const order = await flux.db.createOrder(flux.request.body as { amount: number });
return { status: 201, body: { id: order.id } };
},
{ host: { db } },
),
);
app.post('/api/flux-agent', async (req, res) => {
res.json(await fluxAgent(req.body, { authorization: req.headers.authorization }));
});Fastify route handler
Fastify
fluxrun/adapters/fastify- Fastify parses JSON request bodies before the handler by default.
import Fastify from 'fastify';
import { fluxAgent, fluxHost } from 'fluxrun';
import { withFluxFastify } from 'fluxrun/adapters/fastify';
const app = Fastify();
const audit = fluxHost('audit', {
write: (event: unknown) => auditClient.write(event),
});
app.post(
'/api/orders',
withFluxFastify(
'orders.fastify.create',
async (flux) => {
await flux.audit.write({ path: flux.request.pathname, body: flux.request.body });
return { status: 201, body: { ok: true } };
},
{ host: { audit } },
),
);
app.post('/api/flux-agent', async (request, reply) => {
const authorization = Array.isArray(request.headers.authorization)
? request.headers.authorization[0]
: request.headers.authorization;
return reply.send(await fluxAgent(request.body, { authorization }));
});Hono handler
Hono
fluxrun/adapters/hono- Use host modules for bindings and clients that cannot be serialized into the runtime.
import { Hono } from 'hono';
import { fluxAgent, fluxHost } from 'fluxrun';
import { withFluxHono } from 'fluxrun/adapters/hono';
const app = new Hono();
const billing = fluxHost('billing', {
lookup: (id: string) => fetch('https://billing.example/' + id).then((r) => r.json()),
});
app.get(
'/api/accounts',
withFluxHono(
'accounts.lookup',
async (flux) => ({
status: 200,
body: await flux.billing.lookup(flux.request.searchParams['id'] ?? ''),
}),
{ host: { billing } },
),
);
app.post('/api/flux-agent', async (c) =>
c.json(
await fluxAgent(await c.req.json(), {
authorization: c.req.header('authorization') ?? null,
}),
),
);Koa middleware
Koa
fluxrun/adapters/koa- Install and run koa-bodyparser before Flux when you need flux.request.body.
import Koa from 'koa';
import Router from '@koa/router';
import bodyParser from 'koa-bodyparser';
import { fluxAgent, fluxHost } from 'fluxrun';
import { withFluxKoa } from 'fluxrun/adapters/koa';
const app = new Koa();
const router = new Router();
app.use(bodyParser());
const mail = fluxHost('mail', {
send: (to: string) => resend.emails.send({ to, subject: 'Hello' }),
});
router.post(
'/api/invite',
withFluxKoa(
'invite.send',
async (flux) => {
const body = flux.request.body as { email: string };
await flux.mail.send(body.email);
return { status: 202, body: { queued: true } };
},
{ host: { mail } },
),
);
router.post('/api/flux-agent', async (ctx) => {
ctx.body = await fluxAgent(ctx.request.body, {
authorization: ctx.get('authorization') || null,
});
});
app.use(router.routes());Node.js or Bun fetch handlers
Web Request runtimes
fluxrun/adapters/web- Use this adapter when your runtime exposes standard Request and Response objects.
- Do not advertise Cloudflare Workers support until the SDK has an edge-safe QuickJS build.
import { fluxAgent, fluxHost } from 'fluxrun';
import { withFluxWeb } from 'fluxrun/adapters/web';
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
if (new URL(request.url).pathname === '/api/flux-agent') {
return Response.json(
await fluxAgent(await request.json(), {
authorization: request.headers.get('authorization'),
}),
);
}
const queue = fluxHost('queue', {
send: (message: unknown) => env.ORDERS.send(message),
});
const orders = withFluxWeb(
'worker.orders',
async (flux) => {
await flux.queue.send({ token: flux.env.API_TOKEN, body: flux.request.body });
return { status: 202, body: { queued: true } };
},
{ host: { queue } },
);
return orders(request, env, ctx);
},
};API Gateway HTTP API v2 or Lambda Function URL
AWS Lambda
fluxrun/adapters/aws- Expose the agent as a separate Lambda route or function URL using the same env vars.
import { fluxAgent, fluxHost } from 'fluxrun';
import { withFluxLambda } from 'fluxrun/adapters/aws';
const crm = fluxHost('crm', {
findAccount: (region: string) => crmClient.accounts.find({ region }),
});
export const handler = withFluxLambda(
'lambda.accounts',
async (flux) => {
const region = flux.request.searchParams['region'] ?? 'us';
return { status: 200, body: await flux.crm.findAccount(region) };
},
{ host: { crm } },
);
export const agent = withFluxLambda('lambda.agent', async (flux) => ({
status: 200,
body: await fluxAgent(flux.request.body),
}));Advanced client adapters
Postgres, Redis, and MongoDB
These are drop-in client adapters for specific data calls. Prefer a fluxHost module for normal app code because it makes the boundary explicit in executions and replay.
PostgreSQL
fluxrun/adapters/pg import { Pool } from 'fluxrun/adapters/pg';
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const { rows } = await pool.query('select * from users where id = $1', [id]);Redis
fluxrun/adapters/redis import Redis from 'fluxrun/adapters/redis';
const redis = new Redis(process.env.REDIS_URL);
await redis.set('order:last', order.id);MongoDB
fluxrun/adapters/mongo import { MongoClient, ObjectId } from 'fluxrun/adapters/mongo';
const client = new MongoClient(process.env.MONGO_URL);
const user = await client.db('app').collection('users').findOne({ _id: new ObjectId(id) });Server actions & native mode
Next.js server actions, webhooks, and fluxTrack
Use withFluxNextServerAction from fluxrun/adapters/next-server-action for mutations via form actions. Runs in native mode so redirect(), revalidatePath(), and cookies() work normally.
Works with both plain actions and useActionState.
import { withFluxNextServerAction } from 'fluxrun/adapters/next-server-action';
export const createOrder = withFluxNextServerAction(
'orders.create',
async (formData: FormData) => {
const amount = formData.get('amount');
await db.createOrder({ amount: Number(amount), currency: 'usd' });
redirect('/orders');
},
{ host: { db } },
);For webhooks that need raw body access (HMAC verification), use { rawBody: true }.
For middleware or auth guards that need the original Request object,
use { passRequest: true, mode: 'native' } and access it via flux.request.raw.
import { withFluxNextJs, fluxTrack } from 'fluxrun';
// Webhook with HMAC verification
export const POST = withFluxNextJs(
'webhooks.github',
async (flux) => {
const sig = flux.request.headers['x-hub-signature-256'];
verifyHmac(sig, flux.request.rawBody!);
// ...
},
{ rawBody: true },
);
// Native recording for middleware/auth
export const GET = withFluxNextJs(
'api.protected',
async (flux) => {
const req = flux.request.raw; // original Request
const ip = req.headers.get('x-forwarded-for');
// ... auth logic with fluxTrack ...
},
{ passRequest: true, mode: 'native' },
);Manual fallback
Use fluxFunc when no framework adapter fits
Manually wrap the function you want to trace, call it from your existing handler, and expose the same agent route. This is the path for custom servers, job runners, cron workers, and frameworks without a dedicated adapter.
import { fluxFunc, fluxHost, fluxAgent } from 'fluxrun';
const payments = fluxHost('payments', {
charge: (order: Order) => paymentClient.charge(order),
});
const createOrder = fluxFunc(
async (order: Order) => {
const charge = await payments.charge(order);
return { ok: true, chargeId: charge.id };
},
'orders.create',
{ host: { payments } },
);
export async function handleOrder(req: Request) {
const result = await createOrder(await req.json());
return Response.json(result, { status: 201 });
}
export async function handleFluxAgent(req: Request) {
return Response.json(
await fluxAgent(await req.json(), {
authorization: req.headers.get('authorization'),
}),
);
}Verify and trigger
Add await fluxCheck() at startup to verify project token, public key, and ingest connectivity. Then save the
deployed agent URL in FluxRun, call the wrapped API path like a normal user, then open app.fluxrun.dev . A successful setup shows a new execution under the app with captured request, return,
console, errors, and replay actions.