Quickstart
Deploy a function and debug a production bug in 5 minutes.
What you'll build
A user signup function that writes to Postgres, sends an email, and charges Stripe. You'll introduce a deliberate failure, spot it with flux tail, and root-cause it with flux why.
Coming from Express / FastAPI / Rails?
Flux is a runtime — your code runs inside it. That's what makes automatic recording possible. But your business logic stays the same: req.body becomes input, res.json() becomes return, db.query() becomes ctx.db.query(). You can start with one endpoint and keep your existing backend running alongside Flux.
1. Install the CLI
$ curl -fsSL https://fluxbase.co/install | bash
✔ flux 1.0.0 installed to /usr/local/bin/flux
2. Initialise a project
$ flux init my-backend
$ cd my-backend
$ ls
functions/
create_user.ts
send_welcome.ts
flux.toml
3. Look at the function
Open functions/create_user.ts:
export default defineFunction({
name: "create_user",
handler: async ({ input, ctx }) => {
// validate
if (!input.email) throw new Error("email required");
// write to database
const [user] = await ctx.db.query({
table: "users",
operation: "insert",
data: { email: input.email, plan: "free" },
});
// trigger async welcome email
await ctx.queue.push("send_welcome", { userId: user.id });
return { userId: user.id, status: "ok" };
},
});
Notice: no logging setup, no trace SDK, no middleware. The runtime instruments everything automatically.
4. Deploy
$ flux deploy
Deploying 2 functions…
✔ create_user → localhost:4000/create_user
✔ send_welcome (async)
✔ Deployed in 18s deploy:d_7f3a9
5. Trigger a request
In a second terminal, run flux tail to watch live:
$ flux tail
Streaming requests…
In your original terminal:
$ curl -X POST https://localhost:4000/create_user -H "Content-Type: application/json" -d '{"email":"alice@example.com"}'
{"userId":"u_42","status":"ok"}
x-request-id: 4f9a3b2c
Your flux tail terminal shows:
✔ POST /create_user 201 88ms req:4f9a3b2c
6. Introduce a failure (deliberate)
Simulate what happens when Stripe is down. Add a timeout to create_user.ts:
// add before return:
await ctx.tools.call("stripe.charge", {
amount: 0, // <— this will timeout (demo)
currency: "usd",
});
$ flux deploy
Call it again:
$ curl -X POST https://localhost:4000/create_user -d '{"email":"bob@example.com"}'
{"error":"internal_error"}
flux tail shows:
✗ POST /create_user 500 10044ms req:550e8400
└─ stripe.charge timeout
7. Root-cause it
$ flux why 550e8400
ROOT CAUSE
stripe.charge timed out after 10s
LOCATION
functions/create_user.ts:18
SPAN
stripe.charge POST /v1/charges 10002ms ✗ timeout
DATABASE CHANGES
users email=bob@example.com, plan=free (inserted, then rolled back)
SUGGESTION
→ Add a 5s timeout and retry with idempotency key
You just debugged a production failure in one command.
Root cause, exact file and line, data changes, an actionable suggestion. Without looking at any logs.
8. Go deeper
See the full span tree:
$ flux trace 550e8400
Trace 550e8400 POST /create_user 500
▸ gateway 4ms
auth ✔ rate_limit ✔
▸ create_user 10044ms
▸ db:insert(users) 14ms
▸ stripe.charge 10002ms ✗ timeout
▸ send_welcome SKIP (not queued — request failed)
── total: 10052ms ───────────────────
Compare the failing request to the passing one:
$ flux trace diff 4f9a3b2c 550e8400
SPAN BEFORE AFTER DELTA
gateway 3ms 4ms +1ms
create_user 81ms 10044ms +9963ms ✗
stripe.charge 12ms 10002ms +9990ms ✗
See what the failed request wrote to the database before it was rolled back:
$ flux state history users --id 43
users id=43 (2 mutations)
2026-03-10 14:25:00 INSERT email=bob@example.com, plan=free req:550e8400
2026-03-10 14:25:01 DELETE (rolled back) req:550e8400
Next steps
- Full CLI reference — every command with examples
- Incident replay — test your fix against the real incident
- Bug bisect — find which commit introduced a regression
- Architecture — how the recording layer works
- Example projects — Todo API, Webhook Worker, AI Backend