Nodaro Docs
DocumentationNode ReferenceModelsAI Agents (MCP)DevelopersSelf-hostingResearch

Architecture

How Nodaro is built, for operators and contributors. The editor, the Fastify API, the BullMQ workers, Redis, storage, the three auth modes and the editions.

Nodaro is a REST-first AI workflow engine. A browser editor lets users connect AI nodes — image and video generation, video composition, text models and audio — into a graph. The graph is stored in Supabase Postgres, and when it runs, a Fastify API sorts it and hands each node's work to queue workers on Redis. Model providers do the generation, and the results land in S3-compatible storage.

The same code serves three editions, Community, Business and Cloud, and accepts three kinds of access tokens. This page is for operators who run Nodaro and for contributors who change it.

The system at a glance

node jobsEditor and appsBrowserSDK, CLI, MCPAPI token or OAuthAPIFastify, /v1SupabasePostgres and sign-inRedisBullMQ queuesOrchestratorRuns the graphWorkersMedia and renderStorageS3-compatible
Clients call the API. The API keeps data in Supabase and queues runs in Redis. The orchestrator walks each workflow's graph and hands node jobs to the workers, which call the model providers and save results to storage.

The repository

The public repository, github.com/nodaroai/app.nodaro.ai, is one npm workspaces monorepo:

FolderWhat lives there
backend/The Fastify API, the BullMQ workers and the orchestrator. Node.js 22, TypeScript.
frontend/The browser app: the editor, the app runner and the admin panel. React 19, React Router 7, React Flow.
packages/Shared code and the published npm packages @nodaro/shared, @nodaro/sdk and @nodaro/cli, and the Remotion video compositions.
supabase/The database schema, as forward-only SQL migrations.
docs/The product documentation, including design notes.
tools/Operator scripts: the key generator, the contract probe, backup and restore.
examples/Examples, such as a GitHub Actions workflow that updates a server over SSH.

Frontend

The frontend is a Vite single-page app. In the Docker image, the web server, Caddy, serves it as static files. It holds four products in one bundle:

  • The editor — the React Flow canvas, a settings panel per node family, and a graph executor that runs workflows in the browser during editing.
  • The app runner — shows a published workflow as a simple form of inputs and results.
  • The OAuth consent screen — where users approve third-party apps and MCP clients, at /oauth/authorize.
  • The admin panel — on the Business and Cloud editions only.

Server state lives in React Query, interface state in Zustand, and canvas state in React Flow's own store.

API

The API is a Fastify server on Node.js 22, written in TypeScript. Routes are Fastify plugins, and every endpoint declares a schema for its request and response. The same schemas validate each request and produce the OpenAPI 3.1 document at /v1/openapi.json. GET /v1/nodes lists the node types that the install can run.

A single authentication hook runs before every route: see Authentication.

Processes and queues

The backend ships as five Node.js processes in one image:

ProcessRole
API serverThe HTTP API.
Media workerTakes per-node jobs from the queue, calls the model providers and uploads results to storage. More than 40 job types, such as image and video generation, ffmpeg processing and audio. Default concurrency: 50, because it mostly waits on the network.
Render workerThe Remotion renderer, for compositions, motion graphics, Lottie overlays, 3D titles and composites. It runs a headless Chrome and is limited by CPU. Default concurrency: 2.
OrchestratorRuns whole workflows: reads a run, sorts its graph and runs it level by level. Default concurrency: 20.
Pipeline workerThe Story to Video pipeline. It ships in every edition, but runs only on Nodaro Cloud and exits at once elsewhere.

In Docker, one start script launches them all, and Caddy fronts the API. In a scaled install, each can run in its own container. They coordinate only through the BullMQ queues in Redis and the run records in Postgres. See Scaling.

How a workflow runs

When a user clicks Run in the editor, there are two paths:

  1. In the browser. During editing, the editor runs the graph itself and calls one API endpoint per node. You see each result as soon as its node finishes.
  2. On the server. Triggered runs, from a webhook, a schedule or the API, app runs, and explicit server runs go to the orchestrator. It saves progress in the run's record, so any client can follow it.

The orchestrator:

  1. Loads the workflow's nodes and connections.
  2. Sorts the graph into levels. One level holds the nodes whose inputs are all ready.
  3. Runs each level's nodes in parallel, up to the user's own parallelism and the server's ceiling, MAX_CONCURRENT_NODES_PER_EXECUTION.
  4. Runs each node in one of three ways:
WayWhereExamplesWhy
QueuedA job for the media or render workerGenerate Image, Generate Video, Text to Speech, Combine Videos, Render VideoLong work or external calls, decoupled from the orchestrator for back-pressure
Direct HTTPAn internal call to an API routePrompt, Motion Graphics, the prompt helper, the social publishing nodesQuick text model calls, with no queue overhead
InlineInside the orchestratorCombine Text, Split Text, CompositePure logic with no external calls
  1. After each node, passes its output to the nodes that depend on it, and saves the run's progress with one database write per node.

A run can stop in two ways. Cancelled stops at once: nodes already running are abandoned, and credits already charged are not rolled back. Stopping finishes the current level, then stops before the next one.

Limits. Each node may take up to 90 minutes, and a whole run up to 120 minutes. A node that declares its own time budget, today Apply EDL, sized from the edit it renders, gets that budget instead, and the run's limit grows by the same amount. If the orchestrator restarts during a run, a node with a budget whose worker is still alive is resumed instead of run twice. Sub-workflows run recursively, up to 5 levels deep, with cycle detection.

Storage and database

  • Media — every generated image, video and audio file goes to S3-compatible storage and is referenced by its key. Nodaro never deletes stored media by itself.
  • Database — Supabase Postgres holds profiles, projects, workflows, runs and their progress, jobs, media records, credit transactions, OAuth apps and their tokens, personal API tokens and encrypted social connections. Every table enforces row-level security, so the database itself keeps users to their own rows.
  • Migrations — the schema lives in supabase/migrations/, applied in filename order and only forward.
  • Redis — holds the queues and small shared caches. Its job state is short-lived: see Backups and restore.

Authentication

The API accepts three kinds of bearer tokens and tells them apart by their shape:

TokenComes fromActs as
eyJ…, a JWTA Supabase sign-in session in the editorThe signed-in user
ndr_app_<64 hex>An OAuth app that a user authorizedThe user who authorized the app, limited to the granted scopes
ndr_<64 hex>A personal API tokenThe token's owner

The processes of one install also authenticate to each other with INTERNAL_ORCHESTRATOR_SECRET, compared in constant time. The web server strips that header from outside requests.

For each request, the API checks in this order:

  1. Public routes, such as /v1/openapi.json and incoming webhooks, skip authentication.
  2. The internal secret, for calls between the processes.
  3. An OAuth app token, which must not be revoked or expired.
  4. A Supabase session token. The user's role is read from their profile and cached for 5 minutes.
  5. Anything else answers 401.

Personal API tokens are checked by the routes that accept them, which also apply rate limits and limit a token to its workflows. See Authentication and OAuth apps.

Editions in the code

One codebase serves all three editions:

  • Community (EDITION=community, the default) — self-hosted, with no admin panel, no credit ledger and no billing. Anyone who signs up is a regular user.
  • Business (EDITION=business) — adds the admin panel and user management, still self-hosted and without billing.
  • Cloud (EDITION=cloud) — adds credits, billing and credit prices set by admins. It powers app.nodaro.ai and is not meant for self-hosting.

Routes check the edition's capabilities, not its name: an admin route runs only on an edition with an admin panel, and credit code runs only on an edition with credits. The check comes first in the route, before any other logic. The browser app reads its edition at build time, from VITE_EDITION. See Editions and surface profiles.

Status and streaming

Job and run status is polled, every 2 to 5 seconds: it changes on a scale of seconds, and polling is simple to deploy. Streaming output — text from the text models, workflow runs and pipelines — uses Server-Sent Events. That is why a reverse proxy in front of Nodaro must not buffer responses.

Why these choices

  • Fastify — first-class TypeScript types, schema-driven validation, and plugins that keep each route in its own scope.
  • BullMQ — a mature queue on Redis with retries, backoff and concurrency controls, in the same Node.js stack.
  • REST — easy OAuth scoping per route, a generated OpenAPI document, and a simple model for third-party clients.
  • Supabase — Postgres, sign-in, realtime and row-level security in one service. Row-level security replaces much custom authorization code.
  • React Flow — a proven canvas for node graphs, which Nodaro extends heavily.
  • Remotion — declarative React compositions that render the same on the server as in the editor's preview.
  • One provider layer — every model call goes through one abstraction, so a model can move to another provider without changes to feature code.

Frequently asked questions

Last updated on

On this page