nodrix
Self-managed IoT telemetry, realtime dashboards and device control on Cloudflare.
nodrix is a self-hosted Blynk/Ubidots alternative built on Cloudflare (D1, Durable Objects, KV, R2, Workers). Free tier eligible within limits. Inspect the source and license in the linked repository.
Source & license
Upstream license: MIT
License TL;DR
You can use it, change it, self-host it and sell it. Keep the original copyright and license notice with copies of the code. You don’t have to publish your changes. The authors don’t promise it will work.
Explain MIT in plain English →Summary of the main license. Separate packages and assets can have different terms.
Inspect repository ↗Read this project’s actual license ↗Repository owner
See the upstream repository for the original creator and contributors.
Maintain this project? Maintainer verification →Cloudflare hosting
Free tier eligible within limits
The reviewed Cloudflare deployment is eligible for Free-plan allowances for the stated small workload and feature scope. Usage limits, CPU, required account setup and separate services apply.
Hosting requirements
- Provide your own physical hardware and network connection; Cloudflare runs the telemetry/control platform.
- Use the pinned repository build with explicitly provisioned D1/KV/R2 IDs; the one-click carrier otherwise pulls a moving upstream revision.
- Keep Worker requests/10 ms CPU, D1/KV/R2 and SQLite DO requests/duration/storage within free quotas; low telemetry frequency and bounded history are essential.
- Provisioning Workflow shares Worker request/CPU quotas and includes 3,000 steps/day plus 1 GB-month state on Free; retained instance state counts.
- Optional email, chat and OAuth integrations have separate provider accounts/costs; core owner password setup does not require them.
- Workers Free dynamic requests are shared across this account (100,000/day), with 10 ms CPU per invocation; workload fit is conditional and has not been measured.
- D1 Free allowance: 5 million rows read/day, 100,000 rows written/day and 5 GB total storage; unindexed scans and history retention consume quota.
- KV Free allowance: 100,000 keys read/day and 1,000 each writes/deletes/list requests/day; this may constrain updates before Worker request limits.
- R2 Standard allowance: 10 GB-month storage, 1 million Class A and 10 million Class B operations/month; account activation may require billing setup, and other storage classes are excluded.
- Only SQLite Durable Objects qualify for Workers Free. Keep DO requests below 100,000/day, active duration below 13,000 GB-s/day and SQLite storage/operations inside the captured allowances.
Sources checked 01/10/2026
Repository snapshot: 8fd634b. Hosting eligibility reflects the deployment documentation and listed assumptions.
- blynk ↗
every widget is a framework-agnostic Web Component you can lift straight into your own app. - 🎮 **Two-way control** — toggles, sliders, color pickers, and buttons write values back to hardware via short polls or a control socket. - 🤖 **Visual automations** — variable, schedule, sunrise/sunset, and event triggers run conditions and actions: webhooks, code snippets, and service integrations. - 🔌 **Integrations** — fan out to HTTP, email, and chat (Slack, Telegram, Discord, and more). - 📖 **Clean read API** — latest state, time-series, and variable listings behi
- ubidots ↗
olor pickers, and buttons write values back to hardware via short polls or a control socket. - 🤖 **Visual automations** — variable, schedule, sunrise/sunset, and event triggers run conditions and actions: webhooks, code snippets, and service integrations. - 🔌 **Integrations** — fan out to HTTP, email, and chat (Slack, Telegram, Discord, and more). - 📖 **Clean read API** — latest state, time-series, and variable listings behind one token. - 🧠 **Native MCP server** — read telemetry and control hardware from an AI assistant, on devices you are not physically next
- workers ↗
name = "nodrix" main = "worker/src/index.ts" compatibility_date = "2025-05-01" compatibility_flags = ["nodejs_compat"] # This is the LOCAL-DEV config. Production deploys use deploy/wrangler.toml — # the Deploy to Cloudflare button clones only the deploy/ subdir as the user's # repo root (see deploy/wrangler.toml). The two must be kept in sync when # bindings change; they can't be symlinked (the subdir must be self-contained). # # Build pipeline. scripts/build-from-upstream.sh, whe
- d1 ↗
riables, dashboards, tokens). # NEVER any telemetry point. See plan §3. [[d1_databases]] binding = "DB" database_name = "nodrix" database_id = "PLACEHOLDER_FILLED_BY_DEPLOY_OR_WRANGLER" migrations_dir = "worker/src/platform/db/migrations" # R2 — cold telemetry history, NDJSON partitioned by project + hour. [[r2_buckets]] binding = "R2" bucket_name = "nodrix-telemetry" # KV — read cache and OAuth provider storage (aliased to OAUTH_KV in worker entry). [[kv_namespaces]] binding = "KV" id = "PLACEHOLDER_FILLED_BY_DEPLOY_OR_WRANGLER" # Durable Objects. [[durable_ob
- kv ↗
ON partitioned by project + hour. [[r2_buckets]] binding = "R2" bucket_name = "nodrix-telemetry" # KV — read cache and OAuth provider storage (aliased to OAUTH_KV in worker entry). [[kv_namespaces]] binding = "KV" id = "PLACEHOLDER_FILLED_BY_DEPLOY_OR_WRANGLER" # Durable Objects. [[durable_objects.bindings]] name = "PROJECT_DO" class_name = "ProjectDO" [[durable_objects.bindings]] name = "DASHBOARD_DO" class_name = "DashboardDO" # Singleton scheduler: one alarm at the next schedule/sunset fire time. [[durable_objects.bindings]] name = "SCHEDULER_DO" class_name
- r2 ↗
"PLACEHOLDER_FILLED_BY_DEPLOY_OR_WRANGLER" migrations_dir = "worker/src/platform/db/migrations" # R2 — cold telemetry history, NDJSON partitioned by project + hour. [[r2_buckets]] binding = "R2" bucket_name = "nodrix-telemetry" # KV — read cache and OAuth provider storage (aliased to OAUTH_KV in worker entry). [[kv_namespaces]] binding = "KV" id = "PLACEHOLDER_FILLED_BY_DEPLOY_OR_WRANGLER" # Durable Objects. [[durable_objects.bindings]] name = "PROJECT_DO" class_name = "ProjectDO" [[durable_objects.bindings]] name = "DASHBOARD_DO" class_name = "DashboardDO"
- durable-objects ↗
"PLACEHOLDER_FILLED_BY_DEPLOY_OR_WRANGLER" # Durable Objects. [[durable_objects.bindings]] name = "PROJECT_DO" class_name = "ProjectDO" [[durable_objects.bindings]] name = "DASHBOARD_DO" class_name = "DashboardDO" # Singleton scheduler: one alarm at the next schedule/sunset fire time. [[durable_objects.bindings]] name = "SCHEDULER_DO" class_name = "SchedulerDO" # MCP server agent (Streamable HTTP). The `agents` library keys one DO instance # per client session, so requests never funnel through a singleton. Only spun up # when the owner-gated /v1/mcp endpoint i
- workflows ↗
riven by the SchedulerDO alarm, not a Workflow.) [[workflows]] name = "nodrix-provision" binding = "PROVISION" class_name = "Provision" # Plaintext vars. Settings → Version & updates polls the upstream repo to tell # owners when a newer version is available (latest release, or default-branch # HEAD on the edge channel). Fork the upstream and override this if you're # tracking a different canonical source. [vars] NODRIX_UPSTREAM_REPO = "decoded-cipher/nodrix" # No secrets are required at deploy time. The signing secret used for # session cookies and OAuth-secret
- free-tier-eligible ↗
name = "nodrix" main = "worker/src/index.ts" compatibility_date = "2025-05-01" compatibility_flags = ["nodejs_compat"] # This is the LOCAL-DEV config. Production deploys use deploy/wrangler.toml — # the Deploy to Cloudflare button clones only the deploy/ subdir as the user's # repo root (see deploy/wrangler.toml). The two must be kept in sync when # bindings change; they can't be symlinked (the subdir must be self-contained). # # Build pipeline. scripts/build-from-
- free-tier-eligible ↗
ount Manager. | | Requests<sup>1, 2, 3, 4</sup> | Duration | CPU time | | --- | --- | --- | --- | | **Free** | 100,000 per day | No charge for duration | 10 milliseconds of CPU time per invocation | | **Standard** | 10 million included per month <br> +$0.30 per additional million | No charge or limit for duration | 30 million CPU milliseconds included per month<br> +$0.02 per additional million CPU milliseconds<br><br> Max of [5 minutes of CPU time](https://developers.cloudflare.com/workers/platform/limits/#account-plan-limits) per invocation (default: 30 second
- free-tier-eligible ↗
rs Paid](https://developers.cloudflare.com/workers/platform/pricing/#workers) | | --- | --- | --- | | Rows read | 5 million / day | First 25 billion / month included + $0.001 / million rows | | Rows written | 100,000 / day | First 50 million / month included + $1.00 / million rows | | Storage (per GB stored) | 5 GB (total) | First 5 GB included + $0.75 / GB-mo | Track your D1 usage To accurately track your usage, use the [meta object](https://developers.cloudflare.com/d1/worker-api/return-object/), [GraphQL Analytics API](https://developers.cloudflare.com/d1/obs
- free-tier-eligible ↗
flare.com/workers/platform/pricing/). | | Free plan<sup>1</sup> | Paid plan | | --- | --- | --- | | Keys read | 100,000 / day | 10 million/month, + $0.50/million | | Keys written | 1,000 / day | 1 million/month, + $5.00/million | | Keys deleted | 1,000 / day | 1 million/month, + $5.00/million | | List requests | 1,000 / day | 1 million/month, + $5.00/million | | Stored data | 1 GB | 1 GB, + $0.50/ GB-month | <sup>1</sup> The Workers Free plan includes limited Workers KV usage. All limits reset daily at 00:00 UTC. If you exceed any one of these limits, further o
- free-tier-eligible ↗
f you have retrieved data (for infrequent access storage) for 1.1 GB, you will be billed for 2 GB. ### Free tier You can use the following amount of storage and operations each month for free. | | Free | | --- | --- | | Storage | 10 GB-month / month | | Class A Operations | 1 million requests / month | | Class B Operations | 10 million requests / month | | Egress (data transfer to Internet) | Free <sup>[1](#user-content-fn-1)</sup> | Caution The free tier only applies to Standard storage, and does not apply to Infrequent Access storage. ### Storage usage S
- free-tier-eligible ↗
ute and storage. Note Durable Objects are available both on Workers Free and Workers Paid plans. - **Workers Free plan**: Only Durable Objects with [SQLite storage backend](https://developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/#create-sqlite-backed-durable-object-class) are available. - **Workers Paid plan**: Durable Objects with the SQLite storage backend are available. The [key-value storage backend](https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/#storage-backends) is only avail
- free-tier-eligible ↗
CPU time. Note Step count does not include rollback handlers or retries. ### Workflows pricing | Unit | Workers Free | Workers Paid | | --- | --- | --- | | Requests (millions) | 100,000 per day ([shared with Workers requests](https://developers.cloudflare.com/workers/platform/pricing/#workers)) | 10 million included per month + $0.30 per additional million | | CPU time (ms) | 10 milliseconds of CPU time per invocation | 30 million CPU milliseconds included per month + $0.02 per additional million CPU milliseconds | | Storage (GB-mo) | 1 GB-month | 1 GB-month
- MIT ↗
MIT License Copyright (c) 2026 Arjun Krishna Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this
- architecture ↗
name = "nodrix" main = "worker/src/index.ts" compatibility_date = "2025-05-01" compatibility_flags = ["nodejs_compat"] # This is the LOCAL-DEV config. Production deploys use deploy/wrangler.toml — # the Deploy to Cloudflare button clones only the deploy/ subdir as the user's # repo root (see deploy/wrangler.toml). The two must be kept in sync when # bindings change; they can't be symlinked (the subdir must be self-contained). # # Build pipeline. scripts/build-from-upstream.sh, whe
What it can replace
Compare the workflow you need. These mappings describe overlap; full feature parity requires a separate comparison.
Editorial workflow alternative: HTTPS/WebSocket telemetry dashboards, widgets and two-way control for hardware you already own; no hardware supply or managed-cloud parity.
See supporting source ↗Editorial workflow alternative: Self-managed telemetry variables, time-series dashboards and visual automations; no managed-service SLA or MQTT compatibility claim.
See supporting source ↗How it works
The shape of nodrix on Cloudflare, and how it stacks up against the rented tools it replaces.
Architecture
Diagram of deployment declarations at the reviewed commit. Each app has its own entrypoint; declared resources do not prove runtime calls. Follow file and line sources below.
View upstream source ↗Configuration and workflow sources
Reviewed commit 8fd634b3bf16. Files were read as data; upstream applications and CI jobs were not executed.
Partial source coverage: 58 files outside collection bounds; 0 collection or parsing issues. Dynamic imports and generated entrypoints may need manual review.
Deployment configuration · 2 files
Cloudflare Workers · compatibility 2025-05-01
nodrix · default
Entrypoint: worker/src/index.ts
Build: [ -f scripts/build-from-upstream.sh ] || cd ..; bash scripts/build-from-upstream.sh
Static assets: web/dist · single-page-application · Worker first: true
Cron triggers (UTC): 0 0 * * *
DB→ D1KV→ KVR2→ R2PROJECT_DO→ Durable Objects · class ProjectDODASHBOARD_DO→ Durable Objects · class DashboardDOSCHEDULER_DO→ Durable Objects · class SchedulerDOMCP_OBJECT→ Durable Objects · class NodrixMcpAgentPROVISION→ Workflows · class ProvisionASSETS→ Static assets
Cloudflare Workers · compatibility 2025-05-01
nodrix · default
Entrypoint: worker/src/index.ts
Build: curl -fsSL https://raw.githubusercontent.com/decoded-cipher/nodrix/master/scripts/build-from-upstream.sh | bash
Static assets: web/dist · single-page-application · Worker first: true
Cron triggers (UTC): 0 0 * * *
DB→ D1KV→ KVR2→ R2PROJECT_DO→ Durable Objects · class ProjectDODASHBOARD_DO→ Durable Objects · class DashboardDOSCHEDULER_DO→ Durable Objects · class SchedulerDOMCP_OBJECT→ Durable Objects · class NodrixMcpAgentPROVISION→ Workflows · class ProvisionASSETS→ Static assets
Named environments are separate deployments. Bindings are shown only where declared. Configured routes are URL patterns, not verified application endpoints.
Runtime source · handlers, binding usage and workflow steps
Observed TypeScript/JavaScript declarations from Worker entrypoints and resolved relative imports. Calls and workflow steps may run conditionally; their listed order is not a proven end-to-end request flow. Router declarations may be mounted under a prefix or may not be registered. This shows code wiring, not a successful deployment or runtime test. Dynamic wiring, aliases and generated code may not resolve.
- L50 · fetch handler exported · references KV · calls oauthProvider.fetch
- L55 · scheduled handler exported · calls sendHeartbeat
Environment references: env.KV
- L35 · getOrCreateInstanceId calls (conditional paths may differ): getSetting, nanoid, setSetting
- L44 · buildPayload calls (conditional paths may differ): first, env.DB.prepare, Object.entries, Number, all, filter, results.map, VALID_KINDS.has, getSetting
- L80 · post calls (conditional paths may differ): fetch, JSON.stringify
- L92 · sendInstall calls (conditional paths may differ): ctx.waitUntil, catch, getOrCreateInstanceId, post, buildPayload
- L102 · sendHeartbeat calls (conditional paths may differ): ctx.waitUntil, catch, getSetting, post, buildPayload, first, env.DB.prepare, getOrCreateInstanceId
Environment references: env.DB · env.NODRIX_USAGE_STATS_URL
- L687 · safeParse calls (conditional paths may differ): JSON.parse
- L695 · hourBucket calls (conditional paths may differ): d.getUTCFullYear, padStart, String, d.getUTCMonth, d.getUTCDate, d.getUTCHours
Environment references: env.DB · env.R2
- L66 · soonest calls (conditional paths may differ): Math.min
- L80 · stub calls (conditional paths may differ): env.SCHEDULER_DO.get, env.SCHEDULER_DO.idFromName
- L85 · rescheduleScheduler calls (conditional paths may differ): reschedule, stub, console.error
- L89 · ensureScheduler calls (conditional paths may differ): ensure, stub, console.error
- L93 · armSchedulerFor calls (conditional paths may differ): armFor, stub, console.error
Environment references: env.SCHEDULER_DO
- L16 · Provision extends WorkflowEntrypoint
- L18 · step.do("run-migrations")
- L23 · step.do("sanity-check-r2")
- L29 · step.do("sanity-check-kv")
Environment references: env.DB · env.R2 · env.KV
- L45 · registerRoutes calls (conditional paths may differ): app.get, c.json, first, c.env.DB.prepare, app.route, app.all, serveDashboardSeo, c.req.param, c.env.ASSETS.fetch
Environment references: c.env.DB · c.env.ASSETS
- L24 · authenticateMcp calls (conditional paths may differ): mcpEnabled, json, extractBearer, unauthorized, lookupUserToken
- L43 · touchToken calls (conditional paths may differ): touchTokenLastUsed
- L52 · mcpBearerHandler calls (conditional paths may differ): authenticateMcp, c.executionCtx.waitUntil, touchToken, mcpHandler.fetch
- L60 · json calls (conditional paths may differ): JSON.stringify
- L67 · unauthorized calls (conditional paths may differ): JSON.stringify
- L42 · registerReadTools calls (conditional paths may differ): server.registerTool, run, getProject, listAccessibleProjects, actorOf, project.optional, resolveProjectId, listVariables, getState, describe, z.string, optional, regex, getSeries, listDashboards, getDashboard, Array.isArray, TRIGGER_CATALOG.map, CONDITION_CATALOG.map, ACTION_CATALOG.map
- L29 · registerWriteTools calls (conditional paths may differ): actorOf, server.registerTool, z.string, run, createProject, actor, optional, nullable, updateProject, scopeProjectId, createVariable, updateVariable, describe, z.any, setVariableControl, createDashboard, parseStructured, z.number, updateDashboard, z.enum
- L13 · registerResources calls (conditional paths may differ): getProject, listAccessibleProjects, actorOf, server.registerResource, map, accessibleProjects, resolveProjectId, String, getState, JSON.stringify, listVariables
- L15 · getSetting calls (conditional paths may differ): env.KV.get, first, bind, env.DB.prepare, env.KV.put
- L45 · setSetting calls (conditional paths may differ): Math.floor, Date.now, run, bind, env.DB.prepare, env.KV.delete
Environment references: env.KV · env.DB
- L31 · newId calls (conditional paths may differ): nanoid
- L37 · newToken calls (conditional paths may differ): crypto.getRandomValues, base64url
- L44 · shareToken calls (conditional paths may differ): nanoid
- L48 · sha256Hex calls (conditional paths may differ): crypto.subtle.digest, encode, join, map, padStart, b.toString
- L53 · base64url calls (conditional paths may differ): String.fromCharCode, replace, replaceAll, btoa
- L9 · projectStub calls (conditional paths may differ): env.PROJECT_DO.get, env.PROJECT_DO.idFromName
- L13 · dashboardStub calls (conditional paths may differ): env.DASHBOARD_DO.get, env.DASHBOARD_DO.idFromName
Environment references: env.PROJECT_DO · env.DASHBOARD_DO
- L40 · runAutomation calls (conditional paths may differ): toGraph, nodesById, byId.get, entryNode, Number, defaultSetVariable, defaultEmitEvent, defaultGetVariable, defaultScheduleDelay, countActionNodes, visited.has, visited.add, delaySeconds, scheduleDelay, Date.now, VALID_ACTION_KINDS.has, runActionNode, VALID_CONDITION_KINDS.has, evalCondition, outgoingEdges
- L127 · dispatchEvent calls (conditional paths may differ): all, bind, env.DB.prepare, triggerNodes, toGraph, Math.floor, Date.now, runAutomation, defaultEmitEvent
- L166 · delaySeconds calls (conditional paths may differ): Number, String, Number.isFinite, Math.min, Math.max, Math.round
- L173 · defaultScheduleDelay calls (conditional paths may differ): first, bind, env.DB.prepare, run, newId, JSON.stringify, Math.floor, Date.now, armSchedulerFor
- L196 · defaultSetVariable calls (conditional paths may differ): addControl, projectStub, newId
- L202 · defaultEmitEvent calls (conditional paths may differ): dispatchEvent
- L208 · defaultGetVariable calls (conditional paths may differ): getLatestState, projectStub, rows.find
- L215 · recordRun calls (conditional paths may differ): Math.floor, Date.now, run, bind, env.DB.prepare
Environment references: env.DB
- L8 · matchVariableCondition calls (conditional paths may differ): valuesEqual, evalOp
- L27 · evalOp calls (conditional paths may differ): looseEqual, toNum
- L41 · toNum calls (conditional paths may differ): Number, Number.isNaN
- L47 · looseEqual calls (conditional paths may differ): Boolean, toNum, String
- L57 · valuesEqual calls (conditional paths may differ): looseEqual
- L17 · toGraph calls (conditional paths may differ): JSON.parse, isGraph
Build and deployment pipeline · 1 GitHub Actions workflows
Repository CI declarations, separate from runtime request processing. Job dependencies and conditions are shown as written; long commands are shortened with an ellipsis; a workflow file does not prove a recent successful run.
Triggers: workflow_dispatch, schedule
Analyze (${{ matrix.language }}) · no job dependencies declared
- Checkout repository
actions/checkout@v4 - Initialize CodeQL
github/codeql-action/init@v4 - Perform CodeQL Analysis
github/codeql-action/analyze@v4
build:version: bun scripts/gen-version.tsbuild:migrations: bun scripts/gen-migrations.tsbuild: bun run build:version && bun run build:migrations && bun run --filter @nodrix/web build && bun run --filter @nodrix/worker builddeploy:platform: bun run build && bun run --filter @nodrix/worker deploy
build: vue-tsc --noEmit && vite build
build: wrangler deploy --config ../wrangler.toml --dry-run --outdir=distdeploy: wrangler deploy --config ../wrangler.toml
Repository README
View original on GitHub ↗Full upstream document by @decoded-cipher · README.md · snapshot 8fd634b
nodrix
The IoT platform Cloudflare didn't build. Point your hardware at one endpoint over HTTPS or WebSocket, watch variables appear on their own, build realtime drag-and-drop dashboards, automate, and read it all back through a clean API — entirely on infrastructure you own. nodrix is single-tenant and open source: it deploys into your Cloudflare account on Workers, Durable Objects, D1, and R2.
Features
- 📡 Telemetry over HTTPS or WebSocket — hardware POSTs JSON to a project; variables auto-create on first sight. No schema to define, no MQTT broker to run.
- 📊 Realtime dashboards — a drag-and-drop widget grid streams updates over hibernating WebSockets; share any dashboard read-only by public link.
- 🧩 Embeddable widgets — every widget is a framework-agnostic Web Component you can lift straight into your own app.
- 🎮 Two-way control — toggles, sliders, color pickers, and buttons write values back to hardware via short polls or a control socket.
- 🤖 Visual automations — variable, schedule, sunrise/sunset, and event triggers run conditions and actions: webhooks, code snippets, and service integrations.
- 🔌 Integrations — fan out to HTTP, email, and chat (Slack, Telegram, Discord, and more).
- 📖 Clean read API — latest state, time-series, and variable listings behind one token.
- 🧠 Native MCP server — read telemetry and control hardware from an AI assistant, on devices you are not physically next to. Owner-gated and off by default; see AI assistants (MCP).
- 👥 Multi-user — owner / admin / member roles, email invites, and social sign-in (Google, GitHub).
- 📝 Audit log — every privileged action recorded and paginated in the UI.
Quick start
Deploy to your Cloudflare account — one click, or
bun run deploy:platformfrom a clone.Create the owner account — the first visit prompts a "Create owner account" page; the first signup becomes
owner.Create a project and mint a project token from the dashboard.
Send telemetry — variables are created the moment data arrives:
curl -X POST https://<your-worker>/v1/telemetry \ -H "Authorization: Bearer $NODRIX_TOKEN" \ -H "Content-Type: application/json" \ -d '{"metrics":{"temperature":23.4,"humidity":61}}'Read it back:
curl https://<your-worker>/v1/projects/<project>/state \ -H "Authorization: Bearer $NODRIX_TOKEN"
AI assistants (MCP)
nodrix ships a native Model Context Protocol server, so an assistant can read a project's telemetry and — once you allow it — act on the hardware. Because devices report to your deployment rather than to your laptop, the board does not need to be plugged into the machine running the assistant; it can be in another building.
Two endpoints, both Streamable HTTP:
| Endpoint | Auth | For |
|---|---|---|
/v1/mcp |
Bearer token | CLI and IDE clients |
/v1/mcp/oauth |
OAuth | claude.ai-style connectors |
Both are off by default. The owner flips them in Settings → More:
mcp_enabled— the master switch. While it is off,/v1/mcpreturns 404, so a disabled server looks absent rather than merely forbidden.mcp_write_enabled— gates the management and control tools. Until it is on, even an admin-scope token gets read-only tools, so an assistant cannot command hardware by default.
12 read tools — list_projects, list_variables, get_state, get_series, list_dashboards, get_dashboard, list_widget_types, list_widgets, list_block_types, list_integration_kinds, list_automations, list_integrations.
16 write tools — creating and updating projects, variables, dashboards, widgets, automations, and integrations, plus set_variable (how an assistant turns a relay on), run_automation, emit_event, and test_integration.
There are no delete tools, by design. Every tool resolves its target project through the token's scope before it runs, so it cannot reach a project the token cannot.
Architecture
- Worker (worker/) — a single Hono app. Durable Objects for Project, Dashboard, Scheduler, and the MCP agent; one Workflow for provisioning; D1 (metadata), R2 (telemetry history), KV (read cache + JWKS).
- Web (web/) — Vue 3 + Tailwind + Reka UI admin panel and drag-and-drop dashboard builder, built and served as Worker static assets.
- Shared (shared/) — framework-agnostic Web Component widgets, the integration catalog, and automation blocks, consumed by both web and worker so there is a single source of truth.
- Deploy (deploy/) — the small config carrier behind the one-click Deploy to Cloudflare.
worker/ Cloudflare Worker — API, Durable Objects, Workflow
web/ Vue 3 admin panel + dashboard builder
shared/ Web Component widgets, integration catalog, automation blocks
deploy/ One-click Deploy to Cloudflare config
scripts/ Build, version, and migration generators
Storage
| Store | Holds |
|---|---|
| Project DO (SQLite) | Latest variable state, recent ring buffer, pending control writes, flush cursor |
| R2 | Cold telemetry history (NDJSON, partitioned by project + hour) |
| D1 | Users, sessions, accounts, projects, variables, dashboards, tokens, automations, integrations, audit log, OAuth provider config (metadata only — never any telemetry point) |
| KV | Cached /state responses and JWKS |
| Dashboard DO | Per-dashboard subscriptions + hibernated WebSockets |
| Scheduler DO | One alarm at the next schedule/sunset automation fire time |
Authentication
Better Auth handles sign-in. Email + password is on by default; Google and GitHub OAuth can be enabled at runtime from Settings → Sign-in providers (the owner enters a client ID + secret per provider, and the login page shows the matching buttons immediately).
The first signup on a fresh deployment becomes owner. After that, registration is closed: the owner invites people from Users, each with an owner / admin / member role. Sessions are cookie-based and persist 30 days; each device is a separate session, listed and revokable from Users.
Development
nodrix uses Bun.
bun install
bun run dev # worker (wrangler dev)
bun run dev:web # web (vite)
bun run typecheck
bun run build
bun run deploy:platform # build + deploy the worker
Links
- Site — https://nodrix.live
- Changelog — https://nodrix.live/changelog
- Roadmap — https://nodrix.live/roadmap
License
MIT © Arjun Krishna
Frequently asked about nodrix
What is nodrix?+
nodrix is a self-hosted Blynk/Ubidots alternative built on the Cloudflare developer platform. Self-managed IoT telemetry, realtime dashboards and device control on Cloudflare.
What does nodrix replace?+
nodrix is listed as an alternative to Blynk, Ubidots. Compare the features and tradeoffs before migrating.
What Cloudflare primitives does nodrix use?+
nodrix is built on D1, Durable Objects, KV, R2, Workers, Workflows.
How much does nodrix cost to run?+
The reviewed Cloudflare deployment is eligible for Free-plan allowances for the stated small workload and feature scope. Usage limits, CPU, required account setup and separate services apply. Provide your own physical hardware and network connection; Cloudflare runs the telemetry/control platform. Use the pinned repository build with explicitly provisioned D1/KV/R2 IDs; the one-click carrier otherwise pulls a moving upstream revision. Keep Worker requests/10 ms CPU, D1/KV/R2 and SQLite DO requests/duration/storage within free quotas; low telemetry frequency and bounded history are essential. Provisioning Workflow shares Worker request/CPU quotas and includes 3,000 steps/day plus 1 GB-month state on Free; retained instance state counts. Optional email, chat and OAuth integrations have separate provider accounts/costs; core owner password setup does not require them. Workers Free dynamic requests are shared across this account (100,000/day), with 10 ms CPU per invocation; workload fit is conditional and has not been measured. D1 Free allowance: 5 million rows read/day, 100,000 rows written/day and 5 GB total storage; unindexed scans and history retention consume quota. KV Free allowance: 100,000 keys read/day and 1,000 each writes/deletes/list requests/day; this may constrain updates before Worker request limits. R2 Standard allowance: 10 GB-month storage, 1 million Class A and 10 million Class B operations/month; account activation may require billing setup, and other storage classes are excluded. Only SQLite Durable Objects qualify for Workers Free. Keep DO requests below 100,000/day, active duration below 13,000 GB-s/day and SQLite storage/operations inside the captured allowances. Check current Cloudflare pricing before deploying.
Is nodrix open source?+
The upstream repository declares the MIT license. Read its terms at https://raw.githubusercontent.com/decoded-cipher/nodrix/8fd634b3bf166635f2212907904a7a8dd4972381/LICENSE. Source code and contributor credit are available at https://github.com/decoded-cipher/nodrix.


Discussion · 0
sign in to comment →