self-hosted visual backend builder · Apache-2.0 core · alpha
Draw the backend.
Run it as compiled JavaScript.
Build REST APIs, webhooks and background jobs on a canvas, over your PostgreSQL, MySQL, MongoDB and Redis. Every save compiles to plain JavaScript that Bun workers run.
request
success · cache hit
failure · cache miss
error handler · any block throws
compileGraph() for this canvas: one module, one function per block, each wire a direct call.118 lines// runtime helpers (shared by every route) · 19 lines
/* fluxify-compiled-route-factory */
const $truthy = (v) => { const t = typeof v; return t === "bigint" || t === "number" || t === "string" || t === "boolean" ? !!v : (t === "object" && v !== null); };
const $isEmpty = (v) => v === null || v === undefined || v === "" || (typeof v === "object" && Object.keys(v).length === 0);
const $branchEnd = Symbol("branch");
const $endBranch = (output) => ({ [$branchEnd]: output });
const $parallel = (runs, settle) => new Promise((resolve, reject) => {
const out = new Array(runs.length);
let left = runs.length;
if (!left) return resolve(out);
const done = (i, value) => { out[i] = value; if (--left === 0) resolve(out); };
runs.forEach((run, i) => run.then(
(r) => (r && $branchEnd in r ? done(i, r[$branchEnd]) : resolve(r)),
(error) => (settle ? done(i, error?.toString()) : reject(error)),
));
});
const $endSuccess = (output) => ({ successful: true, continueIfFail: true, output });
const $endFailure = (error) => ({ successful: false, continueIfFail: false, error });
const $endBody = () => undefined;// entrypoint b1
async function $block_0($state, $input, $end) {
const { ctx, vars, scope: $scope } = $state;
let $in = $input;
return await $block_1($state, $in, $end);
}// kv_operations b2
async function $block_1($state, $input, $end) {
const { ctx, vars, scope: $scope } = $state;
let $in = $input;
$in = await lib.kvOperations(ctx, "redis-cache", "get", (await (async function (input, params) { with ($scope) {
return "user:" + getRouteParam("id")
} })($in, $state.params)), null, null, true);
return await $block_2($state, $in, $end);
}// if b3
async function $block_2($state, $input, $end) {
const { ctx, vars, scope: $scope } = $state;
let $in = $input;
if (!$isEmpty((await (async function (input, params) { with ($scope) {
return input
} })($in, $state.params)))) {
return await $block_3($state, $in, $end);
} else {
return await $block_4($state, $in, $end);
}
}// response b4
async function $block_3($state, $input, $end) {
const { ctx, vars, scope: $scope } = $state;
let $in = $input;
let $result_0 = { successful: true, continueIfFail: true, responded: true, output: { httpCode: "200", body: $in ?? null } };
return $result_0;
}// db_getsingle b5
async function $block_4($state, $input, $end) {
const { ctx, vars, scope: $scope } = $state;
let $in = $input;
$in = await lib.dbGetSingle(ctx, "pg-main", "users", [{ attribute: { "kind": "column", "value": "id" }, operator: "eq", value: { "kind": "literal", "value": (await (async function (input, params) { with ($scope) {
return getRouteParam("id")
} })($in, $state.params)) }, chain: "and" }], { joins: [], columns: ["*"], sort: [], strict: false });
(vars.outputs ??= {})["user"] = $in;
return await $block_5($state, $in, $end);
}// kv_operations b6
async function $block_5($state, $input, $end) {
const { ctx, vars, scope: $scope } = $state;
let $in = $input;
$in = await lib.kvOperations(ctx, "redis-cache", "set", (await (async function (input, params) { with ($scope) {
return "user:" + getRouteParam("id")
} })($in, $state.params)), (await (async function (input, params) { with ($scope) {
return outputs.user
} })($in, $state.params)), 60, false);
return await $block_6($state, $in, $end);
}// response b7
async function $block_6($state, $input, $end) {
const { ctx, vars, scope: $scope } = $state;
let $in = $input;
let $result_1 = { successful: true, continueIfFail: true, responded: true, output: { httpCode: "200", body: (await (async function (input, params) { with ($scope) {
return outputs.user;
} })($in, $state.params)) ?? null } };
return $result_1;
}// response b9
async function $block_7($state, $input, $end) {
const { ctx, vars, scope: $scope } = $state;
let $in = $input;
let $result_2 = { successful: true, continueIfFail: true, responded: true, output: { httpCode: "500", body: (await (async function (input, params) { with ($scope) {
return { message: "Could not load user", reason: input };
} })($in, $state.params)) ?? null } };
return $result_2;
}$run — the route's entry; the Error Handler is its catch
return async function $run(ctx, input) {
const vars = ctx.vars;
const $state = {
ctx,
vars,
scope: lib.scope(vars),
trace: ctx.trace,
params: undefined,
};
try {
return await $block_0($state, input, $endSuccess);
} catch ($error) {
const $err = $error?.toString();
return await $block_7($state, $err, $endFailure);
}
}# requests go to a simulated worker in your browser
Run the request: the first call misses Redis and reads Postgres; run it again within 60s to hit the cache.
no keys
The cache is part of your flow: two KV Operations blocks on a Redis integration.
No response yet.
Real block types, configs and compiler output (compiler.ts). The execution, data and timings are simulated in your browser.
Works with
- Databases
- PostgreSQL MySQL MongoDB
- Key-value
- Redis Valkey Dragonfly Memcached
- Queues
- Redis Streams RabbitMQ Kafka EE NATS JetStream EE Amazon SQS EE
- Telemetry
- OpenTelemetry Loki
- AI assistant
- OpenAI Anthropic Gemini Mistral OpenAI-compatible
EE Enterprise connector · all integrations
how it works
Compiled once on save. Requests run plain code.
Most visual flow tools read the flowchart on every call. Fluxify reads it once, when you save, and ships the result to workers.
-
01
Draw
Pick from 39 blocks and wire them socket to socket. Validation, branches and error paths are part of the drawing.
-
02
Save
The route compiles to one JavaScript module. If it doesn't compile, the old version keeps serving and the error shows in the editor.
-
03
Serve
Stateless Bun workers load the module from NATS KV and swap it in place. No restart, no dropped requests.
Same route, same machine: 1.6× the throughput and 43% less CPU per request than our previous interpreter. Full numbers and caveats
what you build
The plumbing every backend has.
Endpoints over a database, webhooks, glue between services, background jobs. The work you've written a hundred times — drawn once, served as compiled code.
All use cases- A REST API over your database validation, not-found branches, atomic updates
- Webhooks in, work later answer 202 fast, process in a workflow
- One endpoint for many services parallel calls, one response
- Process a queue in batches 500 events per run, dead-lettering
- Scheduled jobs without a cron box @daily, cron, one-off times
- Multi-step writes that can't half-happen retries and transactions
- Policies on every route API keys and headers as middleware
capabilities
More than boxes and arrows.
-
AI route builder
Describe a route; review the blocks it places. Bring your own model.
-
Code when you need it
JS Runner blocks, js: fields, and any npm package you add.
-
Error handling
Error Handler, transactions and retries, drawn as sockets.
-
Logic & data flow
Branches, switches, loops and parallel calls.
-
Workflows & triggers
Cron, intervals, queues and routes start background work, in batches.
-
Testing
Suites run the saved version, fake a DB call, never touch live traffic.
-
Observability
A span per block. Failures always kept. Export to OTLP or Loki.
-
Custom blocks & middleware
Package flows into your own blocks; run them around routes.
operations
What happens when things break.
-
Admin down, API up.
Workers hold compiled routes in memory. If the control plane stops you can't edit, but your endpoints keep answering. docs
-
Broken saves never ship.
A flow that fails to compile — a missing setting, a cycle — never replaces the version that's serving. docs
-
Workers can't reach Fluxify's database.
They receive compiled code over NATS and nothing else. Your flows still reach your own databases through integrations. docs
honest trade-offs
Fluxify vs writing it by hand.
what you get
- ✓ Request validation, routing and error paths without boilerplate
- ✓ Deploy by saving — a broken save never goes live
- ✓ A trace span for every block, without instrumenting anything
- ✓ Compiled JavaScript on the request path, not an interpreter
- ✓ Tests, schedules and queue consumers in the same tool
what you give up
- × It's alpha — expect breaking changes before 1.0
- × Code runs on Bun, not Node.js
- × No blocks that call an AI model from a route yet
- × Community runs one worker; more needs Non-commercial or Enterprise
- × It's another service to run — there's no hosted version
self-hosted only
One command to try it. Your servers to run it.
Try the Kit, then move to separate admin, orchestrator and worker images for production.
docker run -d --name fluxify \
-p 8080:8080 \
-v fluxify_data:/data \
-e SEED_USER_EMAIL=admin@example.com \
-e SEED_USER_PASSWORD=ChangeThisPassword123! \
ghcr.io/fluxify-rest/fluxify-kit:nightly
Open http://localhost:8080/_/admin/ui and sign in with the seed user. Published routes are
served from http://localhost:8080/. Keep the volume, or your data goes with the container.
:nightly is the latest alpha build.
Workers per edition: Community 1 · Non-commercial 2 · Enterprise no cap. Editions
faq
Questions backend devs ask first.
Can I write code when a block isn't enough?
Yes. The JS Runner block and any js: field run JavaScript on Bun, and scripts can import npm packages you add under Project Settings. You can also package a flow as your own custom block. Scripting and npm imports
Is it production-ready?
Fluxify is alpha: expect breaking changes before 1.0. Use the Kit to try it; production runs as separate admin, orchestrator and worker images on Docker or Kubernetes. Production setup
What's the licence?
Everything outside the ee folders is Apache-2.0 and free for any use, commercial included. Enterprise features are source-available: free for personal, education and non-profit use with LICENSE_KEY=NON_COMMERCIAL; commercial use needs a key, and keys aren't issued before 1.0. Editions and licensing
What does it need to run?
PostgreSQL for the admin, Redis or Valkey, and NATS JetStream. The Kit bundles all three in one container; in production only the admin connects to Postgres. Production requirements
Is there a hosted version?
No. Fluxify is self-hosted only — your servers, your data. Self-hosting options
Does AI run inside my routes?
Not yet. AI powers the assistant that builds routes on the canvas, with your own OpenAI, Anthropic, Gemini, Mistral or OpenAI-compatible model. There are no blocks that call a model from a route. How the AI assistant works
Can a coding agent set it up for me?
Yes. Point Claude Code, Cursor or Codex at docs.fluxify.rest/llms.txt with one of the deployment prompts; every docs page is also served as Markdown. Coding agent setup
Is there an API for the admin?
Yes — everything the editor does goes through the admin REST API, with OpenAPI docs served at /_/admin/api/openapi/ui. Admin API in the Kit docs
Apache-2.0 core. Self-hosted. Free.
Community is free for any use, commercial included, with no limits on routes, requests, projects or seats. Personal, education and non-profit projects can switch on every Enterprise feature for free.
latest release v0.0.2-alpha · 2026-09-23