Skip to Content
DashboardsReferenceServer library

Server library

@microboxlabs/miot-dashboard-server is both the standalone server and a library. Mount it in your own Node.js backend and you keep the same access rules, with your own identity, storage and data.

Version 0.6.0 needs contract 0.6.0 and Node.js 20.19 or later (22.13 or later for SQLite).

Entries

EntryHoldsRuns on
.Interfaces, access control, query service, import and migration helpersAnywhere
/httpcreateDashboardHandler: Request in, Response outWeb Request and Response
/nextcreateNextRouteHandlers for App Router routesNode.js runtime
/identityJWT and ticket verificationNode.js
/store-sqlSQLite and PostgreSQL storesNode.js
/store-cloudS3 and Google Cloud Storage document storesNode.js
/vault-sql, /vault-httpCredential stores: encrypted in SQL, or asked from a hostNode.js
/queriesQuery executors and catalog providersNode.js
/serverThe standalone listener, probes and docsNode.js
/testingIn-memory implementations for tests and demosAnywhere

Mount it

import { createDashboardHandler } from "@microboxlabs/miot-dashboard-server/http"; const handle = createDashboardHandler({ identity, // who is calling tenants, // may they act in this organization scopes, // their role in this scope store, // where dashboards live basePath: "/api/dashboards", });

In Next.js:

app/api/dashboards/[...path]/route.ts
import { createNextRouteHandlers } from "@microboxlabs/miot-dashboard-server/next"; export const runtime = "nodejs"; export const { GET, PUT, DELETE, OPTIONS } = createNextRouteHandlers({ basePath: "/api/dashboards", identity, tenants, scopes, store, });

runtime = "nodejs" is required: the stores need Node.js. basePath must match where the route is mounted. Export OPTIONS if browsers call it from another origin.

Interfaces you provide

InterfaceAnswers
IdentityResolverWho is calling?
TenantAuthorityMay they act in the organization the address names?
ScopeAuthorityWhat role do they have in the scope?
ServerDashboardStoreWhere are documents and permissions kept?
CredentialsVaultWhich secret authenticates a datasource?
AuditSinkWhere do audit events go?
CapabilityPolicyOptional. How roles become capabilities. It can only take away.

createAllowedGroupsPolicy() adds allowedGroups support. Pass it as policy.

Query executors

Pass one as queries: { operations } to enable the query route.

Factory (from /queries)Runs
createHttpGetOperationExecutorRead-only HTTP GET operations, such as PostgREST functions, against approved origins
createBigQueryOperationExecutorSaved BigQuery SQL, with a dry run and a billing cap
createRemotePlanOperationExecutorAsks a host for the plan, then runs HTTP GET or BigQuery locally
createHttpDashboardOperationExecutorSends the authorized request to a host endpoint that runs it

Each executor gets a trusted resolve(request) from you, which must check the organization’s connection and return the plan, or null to deny. Never build a plan from viewer input.

createDashboardQueryService gives the same authorization and limits to code outside the HTTP handler.

Query catalog

Pass queryCatalog: { list({ identity, ref, signal }) } to enable the editor’s catalog. createRemoteQueryCatalog({ url, proxyKey }) reads it from a host endpoint.

Import existing dashboards

import { importDashboards } from "@microboxlabs/miot-dashboard-server"; const report = await importDashboards({ source, store, dryRun: true });

source.read() yields existing dashboards. dryRun defaults to true. It validates every document, skips dashboards that already exist, writes with revision zero so a concurrent create becomes a conflict, keeps the original creator, and reports imported, skipped, refused and failed.

migratePlannerQueries and plannerOperationContracts convert planner dashboards. See Migrate planner dashboards.

Limits you can set

OptionDefault
maxBodyBytes (handler)1 MiB
maxConcurrent (query service)8
timeoutMs20,000
maxRows5,000
maxBytes2 MiB

The complete API is in the package README .

Last updated on