← back to landing

Documentation

Everything you need to install, configure and run iot bees on your own infrastructure.

// jump to
// 01

Overview

iot bees is a self-hosted platform that connects IoT message brokers (RabbitMQ, MQTT, Kafka) to time-series and file storage (InfluxDB, local logs) through configurable, schema-validated pipelines. You configure everything from a web UI; iot bees handles the connections, validation, transforms and persistence in a single Rust binary.

The point: stop writing one-off services every time a new sensor shows up. Define a source, a schema, and a store, glue them together as a pipeline, hit start.

Who it is for

  • Engineers running industrial, agricultural, energy or building deployments where data flows from many sensors into a single database.
  • Teams that want to own their data plane — no SaaS account, no egress to a third party, no telemetry.
  • Anyone tired of writing custom Python scripts to validate payloads and forward them to InfluxDB.

What it ships with

  • A REST API (Actix-web) for managing every pipeline resource.
  • An actor-based runtime (Actix on Tokio) that runs the pipelines, with N replicas per pipeline.
  • A Next.js web UI for the entire CRUD + lifecycle.
  • JWT auth with first-run admin onboarding.
  • OpenAPI / Swagger UI at /swagger-ui/.
// 02

Quickstart

The fastest path from zero to a running pipeline, assuming the backend and the web UI are already up.

Create your admin account (first run)

iot bees ships without any user. The very first time you visit the app, the login screen offers to create the admin account for you. Once that account exists, open registration is disabled — only the admin can run the instance.

  1. 01
    Open the app
    Visit http://localhost:3000/login (or whatever URL your deployment is at). The frontend asks the backend GET /auth/has-users; if it comes back as false, the form switches to create admin account.
  2. 02
    Fill the three fields
    Email (e.g. admin@your-company.com), name (the label shown in the top right of the app), password (8 characters minimum — longer is better; this is a self-hosted instance and you set the security bar).
  3. 03
    Submit
    Click + CREATE ADMIN. The backend hashes the password with Argon2id, stores the row, and signs a JWT. The frontend writes the JWT into an HttpOnly session cookie and redirects you to /app.
  4. 04
    From now on you log in normally
    Subsequent visits to /login show the regular login form — the backend remembers a user already exists and rejects further registration with 403 RegistrationDisabled. You can log out via the dropdown in the top right.
▲ note · forgot the password?
There is no password-reset endpoint in the MVP. If you lose the credentials, stop the backend, delete the row from the users table in your SQLite file (or delete data/iot-bee.db entirely if you have nothing else to keep), restart, and create a fresh admin.
// tip · creating extra accounts
Multi-user is not in the MVP. The single admin owns everything. If you need that, open an issue with your use case.

Build your first pipeline

Once you're inside /app, the overview page shows a setup checklist with five cards. Click each one in order — the page tracks which ones are done and hides the checklist when everything is set up.

  1. 01
    Open the app and create the admin
    Covered above. Once you're in /app the setup checklist takes over.
  2. 02
    Create a data source
    Go to /sources + NEW SOURCE. Pick a type (RabbitMQ, MQTT or Kafka), fill the connection details, save.
  3. 03
    Create a data store
    Go to /stores + NEW STORE. Pick LOCAL_LOG for the easiest demo, or INFLUX_DB if you have one running.
  4. 04
    Create a validation schema
    Go to /schemas + NEW SCHEMA. Use the load example button to bootstrap a 3-field schema you can edit.
  5. 05
    Wire a pipeline and start it
    Go to /pipelines + NEW PIPELINE. The 5-step wizard walks you through name → source → schema → store → replicas. Save, then click ▸ start. The status pill flips to RUNNING within 5 seconds.
// tip · press ⌘K
Open the command bar with ⌘K (or Ctrl+K) to jump anywhere or create anything without using the menu.
// 03

Installation

Prerequisites

  • Rust toolchain (stable, edition 2024). Install via rustup.rs.
  • Node 20+ and pnpm for the web UI: npm i -g pnpm.
  • A message broker the pipeline can consume from (any of RabbitMQ, MQTT, Kafka).
  • Optional: an InfluxDB instance, or use LOCAL_LOG while you experiment.

Clone and prepare

bash
$ git clone https://github.com/manuelmj/iot-bee.git
$ cd iot-bee
$ mkdir -p data
$ cp .env.example .env

Run database migrations

Migrations are bundled into the binary; on first start it applies any pending migration to the SQLite database automatically. If you want to apply them manually:

bash
$ cargo install sqlx-cli --features sqlite
$ sqlx migrate run --database-url sqlite://data/iot-bee.db

Run the backend

bash
$ JWT_SECRET=change-me-to-a-long-random-string make run
# equivalent to: cargo fmt && cargo check && RUST_LOG=info cargo run

The HTTP server starts at http://127.0.0.1:8080. Swagger UI lives at /swagger-ui/.

Run the web UI

bash
$ cd web
$ cp .env.local.example .env.local
$ pnpm install
$ pnpm dev   # http://localhost:3000

Open http://localhost:3000. The first visit to /login on a fresh database will offer to create the admin account.

// 04

Configuration

Backend environment variables

VariableRequiredDefaultDescription
DATABASE_URLyesSQLite connection string. Example: sqlite://data/iot-bee.db
JWT_SECRETyesHS256 signing secret. Use a long random value in production.
JWT_EXPIRES_IN_HOURSno24Access token lifetime in hours.
CORS_ORIGINSnohttp://localhost:3000Comma-separated list of origins allowed to call the API.
API_HOSTno127.0.0.1Bind address.
API_PORTno8080Bind port.
RUST_LOGnoinfoTracing filter. Try iot_bee=debug for more detail.

Web UI environment variables

VariableDefaultDescription
NEXT_PUBLIC_API_URLhttp://localhost:8080Backend URL surfaced in the footer / status indicator.
INTERNAL_API_URLhttp://localhost:8080Backend URL used by Next route handlers (server-side).
AUTH_COOKIE_NAMEiot_bee_sessionName of the HttpOnly session cookie.
AUTH_COOKIE_MAX_AGE_HOURS24Cookie lifetime, must match JWT_EXPIRES_IN_HOURS.
// 05

Concepts

Five resources make up an iot bees deployment. Get familiar with them — every UI page maps to one.

Data Source

A connection to a message broker. Each source has a name, a type (RABBIT_MQ, MQTT or KAFKA), and a config object with the broker-specific fields (host, queue/topic, group_id, etc.).

Validation Schema

Defines the contract of a message: which fields to expect, their type, range, default value and (optionally) a transformation expression. Messages that don't match the schema are rejected; matching messages get cleaned and forwarded.

json
{
  "name": "environmental_station",
  "schema": {
    "temperature": {
      "type": "float",
      "required": true,
      "default": null,
      "validation": { "min": -40, "max": 85 },
      "operation": null
    },
    "humidity": {
      "type": "float",
      "required": true,
      "validation": { "min": 0, "max": 100 },
      "operation": null
    }
  }
}

The operation field is an arithmetic AST: num, var and bin_op nodes (Add, Sub, Mul, Div) that runs after validation. Use it for unit conversion, calibration, etc.

Data Store

Where validated records land. Two persistence types:

  • INFLUX_DB — writes to a database + measurement. String fields listed in tag_fields become InfluxDB tags; everything else becomes fields.
  • LOCAL_LOG — appends newline-delimited JSON to a file. Great for local dev and small deployments.

Pipeline

Wires one source + one schema + one store. Has a name, a replication count (number of concurrent worker actors), and an optional group_id. A pipeline is the thing you start and stop at runtime.

Pipeline Group

Optional logical container. Use groups to organise pipelines by site, customer, or environment.

Pipeline Lifecycle

Pipelines live in three states:

  • RUNNING — actor hierarchy is active and consuming.
  • STOPPED — pipeline exists in DB but isn't consuming.
  • ERROR — pipeline crashed or failed to start; check the backend logs.
// 06

Using the web app

First-run register

Visit /login. If the database has no users, the form shows create admin account. Fill email + name + password (≥ 8 chars), submit. You land on /app with an HttpOnly session cookie.

Navigation

The top bar has six tabs:

  • overview — global stats and live status of all pipelines.
  • pipelines — list, create, start/stop.
  • sources, stores, schemas, groups — full CRUD for each resource.

On mobile the tabs collapse into a full-screen navigation overlay. Your user badge sits in the top-right with a dropdown showing email, role and sign-out.

Command bar

⌘K (or Ctrl+K) opens a command palette to navigate or create resources without touching the menu. On mobile, the command bar collapses to a floating ⌘ button at the bottom-right.

Editing schemas

The schema editor is a raw-JSON editor with a live preview panel. The load example button bootstraps a working three-field schema you can adapt. Click save; the preview updates as you type.

// tip
Why JSON and not a visual builder? Operations are AST expressions (e.g. { "type": "bin_op", "op": "Mul", "left": ..., "right": ... }). A visual editor for the full grammar is out of scope; raw JSON keeps the door open to every backend feature.
// 07

REST API

Every page in the web app is backed by a REST endpoint. Interactive Swagger UI is served at http://127.0.0.1:8080/swagger-ui/.

Authentication

All endpoints (except /auth/* and /swagger-ui/*) require an HS256 JWT in the Authorization header.

bash
$ curl -X POST http://127.0.0.1:8080/auth/login \
    -H "Content-Type: application/json" \
    -d '{"email":"admin@iot-bee.dev","password":"iotbee2026"}'

# response:
{
  "user": { "id": 1, "email": "...", "name": "...", "role": "admin" },
  "token": "eyJ0eXAi..."
}

$ curl http://127.0.0.1:8080/data-sources \
    -H "Authorization: Bearer eyJ0eXAi..."

Endpoint summary

ResourceBase pathDescription
Auth/authLogin, first-admin register, has-users probe, current user.
Connection Types/connection-typesRead-only list of supported source/store identifiers.
Data Sources/data-sourcesCRUD for broker connections.
Data Stores/data-storesCRUD for persistence destinations.
Validation Schemas/validation-schemasCRUD for schemas; PUT /{id}/name for rename.
Pipeline Groups/pipeline-groupsCreate / list / delete logical groups.
Pipelines/pipelinesCRUD plus PUT endpoints for relation assignments.
Pipeline Lifecycle/pipeline-lifecycleStart, stop, status (single + all).

Error format

Failures return JSON of the shape { "error": "<message>" } with the appropriate HTTP status (400, 401, 403, 404, 409, 500).

// 08

Architecture

Hexagonal layering with a top-level supervisor that owns one supervisor per pipeline. Each pipeline runs N replicas. Each replica is a chain of three actors (consumer → processor → store) connected through MPSC channels.

text
SystemActorSupervisor
  └─ PipelineSupervisor                    (one per pipeline)
        ├─ Replica 1: [consumer] → [processor] → [store]
        ├─ Replica 2: [consumer] → [processor] → [store]
        └─ Replica N: [consumer] → [processor] → [store]

Crate layout

text
crates/
├── domain/          pure entities, ports and error types
├── application/     use cases (one module per aggregate)
├── infrastructure/  SQLite repos, broker drivers, security (argon2 + jwt)
├── adapters/        Actix HTTP handlers + actor system
└── logging/         tracing wrapper

Failure isolation

If a single replica crashes, only that replica is restarted — the rest of the pipeline keeps streaming. Pipelines are isolated from each other; a runaway pipeline cannot take down the others or the HTTP server.

// 09

Deployment

The frontend and backend are intentionally decoupled — they only talk over HTTP. You can host them anywhere; the recommended split is the frontend on Vercel and the backend on a server you control (VPS, fly.io, Railway, your own metal).

Architecture summary

text
Browser
  └─ Next.js (Vercel)            ← serves landing, login, app, /api/proxy
        └─ HTTP                    ← server-to-server, with Bearer
              └─ iot-bee backend  ← Rust binary on YOUR server

The browser never calls the backend directly. All API calls go through a thin Next route handler at /api/proxy/[...path] that translates the HttpOnly session cookie into an Authorization: Bearer header. Three consequences:

  • The JWT never reaches client-side JavaScript (immune to XSS token theft).
  • No CORS headers are required for the normal flow — the only cross-origin call is server-to-server.
  • You can put the backend on a private network and expose only the Vercel frontend publicly, as long as Vercel functions can reach the backend.

Deploy the backend

Build a release binary on the target machine (or in a CI pipeline) and run it under a process supervisor of your choice (systemd, supervisord, fly.io machines, etc.).

bash
# on the server
$ cargo build --release
$ ./target/release/iot-bee   # listens on $API_HOST:$API_PORT

Required env on the backend:

VariableExample value
DATABASE_URLsqlite:///var/lib/iot-bee/data.db
JWT_SECRET<output of `openssl rand -hex 32`>
CORS_ORIGINShttps://iotbees.com
API_HOST0.0.0.0
API_PORT8080
// tip · put it behind a reverse proxy
Run nginx / Caddy / Traefik in front of the binary to terminate TLS and route traffic. Public URL is what Vercel needs to reach.

Deploy the frontend on Vercel

The web/ directory is a standard Next.js 15 project — no vercel.json required.

  1. 01
    Connect the repo
    Import the repository on Vercel and set the root directory to web. Framework auto-detects.
  2. 02
    Set environment variables
    In the Vercel project settings → Environment Variables, add the four below. Mark INTERNAL_API_URL and NEXT_PUBLIC_API_URL as required for both Production and Preview environments.
  3. 03
    Allow the frontend in the backend's CORS
    On the backend, add the Vercel deployment URL to CORS_ORIGINS (e.g. https://iotbees.com). Restart the backend so the new value takes effect.
  4. 04
    Deploy
    Push to the configured branch. Vercel builds and serves; the first request to /login on a fresh backend will offer to create the admin account.
Vercel env varValue
NEXT_PUBLIC_API_URLhttps://api.your-domain.com
INTERNAL_API_URLhttps://api.your-domain.com
AUTH_COOKIE_NAMEiot_bee_session
AUTH_COOKIE_MAX_AGE_HOURS24 (must match backend JWT_EXPIRES_IN_HOURS)
▲ note · public vs internal URL
Both env vars usually point to the same URL. They're kept separate in case you want server-side calls to use a private internal URL (e.g. inside a VPC) and the browser to receive a pretty public URL. NEXT_PUBLIC_* is baked at build time — change → redeploy.

Verifying the deployment

  1. Visit https://<vercel-url>/auth/has-users via the Next proxy isn't public — instead curl your backend directly and check it responds with { "has_users": false }.
  2. Open /login and create the admin. The redirect to /app proves the cookie + proxy chain works end-to-end.
  3. Open the backend status indicator in the top-right of the app — it should show Backend ONLINE.
// 10

Troubleshooting

Backend won't start: 'JWT_SECRET requerida'

The backend fails fast if JWT_SECRET is missing. Set it in your shell or in .env:

bash
$ export JWT_SECRET="$(openssl rand -hex 32)"

Login keeps showing 'create admin account'

This means the backend reports zero users. If you registered successfully and you still see the create-admin form, refresh; the page calls GET /auth/has-users on mount. If the form keeps appearing, your database is empty — check that the backend is talking to the same DATABASE_URL you registered against.

CORS errors in the browser

Add the front-end origin to CORS_ORIGINS on the backend and restart. In dev the default already includes http://localhost:3000.

Pipeline goes straight to ERROR

Most often this means the broker connection failed or the schema doesn't parse the incoming payload. Tail the backend logs with RUST_LOG=iot_bee=debug to see the exact reason; the actor reports the error to the supervisor before transitioning state.

Need more help?

Open an issue at github.com/manuelmj/iot-bee/issues with the relevant log snippet, your config (redact secrets!), and the steps to reproduce.