A lightweight, zero-ops JSON micro-database and API server. Store schemaless JSON documents in named sets and collections, query them with filters, optionally validate against JSON Schema, and add JSON-path indexes for speed. Ships with a built-in dashboard and an MCP interface.
- Storage: SQLite with WAL, one physical table per set:
data_<set>storing{id, collection, data JSON, created_at, updated_at}. - API: Clean REST endpoints for documents, collections, sets, indexing, and schemas.
- Query: JSON where filters with rich operators ($eq, $ne, $gt, $gte, $lt, $lte, $like, $ilike, $startsWith, $istartsWith, $endsWith, $iendsWith, $contains, $icontains, $in, $nin, $between, $isNull, $notNull), plus order, limit/offset, and pagination header.
- Indexes: Async JSON-path indexes tracked in metadata and usage-counted for observability.
- Validation: Optional per-collection JSON Schema validation on create/update/replace.
- Dashboard: Single-page UI served from
/for exploring data and testing APIs. - MCP: Two flavors: HTTP endpoints at
/mcpand a standalone stdio MCP server (cmd/micro-api-mcp).
# Persist DB to ./data.db and expose on 8080
docker run --rm -p 8080:8080 \
-v "$(pwd)":/data \
-e DB_PATH=/data/data.db \
ghcr.io/fernandezvara/microapi:latest- Health:
curl http://localhost:8080/health - Dashboard: open http://localhost:8080/
See docker-compose.yaml. Start both services:
docker compose up --buildThe Compose file mounts a community n8n node from ../n8n-nodes-microapi for convenience.
make run # builds and runs ./cmd/micro-api
make test # run tests
make css # builds web/static/style.css via Tailwind (requires Node + npx)Go 1.24+ recommended. Config is read from environment and optional .env.
Defined in internal/config/config.go.
- PORT (default
8080): HTTP port. - DB_PATH (default
./data.db): SQLite file path. - MAX_REQUEST_SIZE (default
1048576): Max request body bytes. - ALLOW_DELETE_SETS (default
false): EnableDELETE /{set}. - ALLOW_DELETE_COLLECTIONS (default
false): EnableDELETE /{set}/{collection}. - CORS (default empty): CSV of allowed Origins. Empty means reflect any Origin.
- DEV (default
false): Dev mode flag (currently used for minor toggles).
CORS exposes the X-Total-Items header to browsers.
- Set: Top-level namespace. Backed by table
data_<set>. - Collection: Logical group inside a set. Stored in the
collectioncolumn. - Document: Arbitrary JSON in
datacolumn plus generated metadata timestamps.
SQLite is opened with WAL mode, foreign keys ON, busy timeout, and synchronous NORMAL (internal/database/connection.go). Per-set tables have helpful indexes on collection and (collection, created_at).
Every response uses internal/models.APIResponse:
{ "success": true, "data": ..., "error": null }- Errors return
success:falsewitherrormessage and appropriate HTTP status. - Document responses include
_metaunless suppressed (see below).
Base path is /. Names ({set}, {collection}) must match ^[a-zA-Z0-9_]+$.
- GET
/health→{ status: "ok", version: <string> }
- GET
/serves the UI. Assets:/style.css,/favicon.ico,/logo.svg.
- GET
/_sets→ summary of sets with collection/doc counts. - GET
/{set}→ per-collection stats for the set. - DELETE
/{set}→ drops the set table and metadata. RequiresALLOW_DELETE_SETS=true.
Example:
curl http://localhost:8080/_sets
curl http://localhost:8080/myset- POST
/{set}/{collection}→ create document. - GET
/{set}/{collection}→ query documents (see Query section). - GET
/{set}/{collection}/{id}→ fetch one. - PUT
/{set}/{collection}/{id}→ replace document (full body). - PATCH
/{set}/{collection}/{id}→ merge patch. - DELETE
/{set}/{collection}/{id}→ delete by id. - DELETE
/{set}/{collection}→ delete all or filtered (requiresALLOW_DELETE_COLLECTIONS=true).
Metadata in responses:
_meta.id,_meta.created_at,_meta.updated_atare added by default.- Suppress with
?meta=0on GET/Query endpoints.
Endpoint: GET /{set}/{collection} with query params:
where: JSON string. Example:{"user.name": {"$eq": "Alice"}}order_by:created_at,updated_at, or a JSON path like$.user.agelimit: integer > 0offset: integer ≥ 0debug=1: addsX-Query-Planheader withEXPLAIN QUERY PLANsummary
Pagination:
- Response header
X-Total-Itemsincludes the total count ignoring limit/offset.
Supported operators in where:
- Comparisons:
$eq,$ne,$gt,$gte,$lt,$lte - Text pattern:
$like(SQL LIKE pattern e.g. "%foo%")$ilike(case-insensitive LIKE)$startsWith,$istartsWith$endsWith,$iendsWith$contains,$icontains(wraps value with%...%)
- Sets:
$in,$nin(expects an array of values) - Range:
$between(expects[min, max]) - Null checks:
$isNull,$notNull(value ignored)
Notes:
$inwith an empty array matches no rows;$ninwith an empty array matches all rows.- Case-insensitive operators use
LOWER(...)under the hood. - In
where, you can use dot paths likeuser.ageor JSONPath (e.g.$.user.age). - For
order_byand index endpoints, JSONPath is accepted (e.g.$.user.age).
Paths:
wherekeys can use dot notation (user.age) or JSONPath ($.user.age).order_byand index endpoints accept JSONPath (e.g.$.user.age).
- Malformed
wherereturns HTTP 400 with a friendly message:"malformed where clause: expected a JSON object where keys are field paths and values are operator objects"
- Unsupported operator returns HTTP 400, e.g.
"unsupported operator: $foo". - Empty results always return an empty array
[](nevernull). - Multi-argument expectations:
$in/$ninrequire an array value.$betweenrequires a two-element array[min, max].
Examples of bad requests:
# Malformed JSON
curl "http://localhost:8080/myset/users?where=not-json"
# Unsupported operator
curl "http://localhost:8080/myset/users?where={\"age\":{\"$foo\":1}}"
# Wrong shape for $between (should be [min,max])
curl "http://localhost:8080/myset/users?where={\"age\":{\"$between\":42}}"Examples:
# All docs where user.age >= 18, newest first, first page of 10
curl "http://localhost:8080/myset/users?where={\"user.age\":{\"$gte\":18}}&order_by=created_at&limit=10&offset=0"
# Suppress metadata
curl "http://localhost:8080/myset/users?where={}&meta=0"
# Text pattern matching
curl "http://localhost:8080/myset/users?where={\"user.name\":{\"$contains\":\"Ada\"}}"
curl "http://localhost:8080/myset/users?where={\"user.name\":{\"$icontains\":\"ada\"}}" # case-insensitive
curl "http://localhost:8080/myset/users?where={\"user.name\":{\"$startsWith\":\"An\"}}"
curl "http://localhost:8080/myset/users?where={\"user.name\":{\"$istartsWith\":\"an\"}}" # case-insensitive
curl "http://localhost:8080/myset/users?where={\"user.email\":{\"$endsWith\":\"@example.com\"}}"
curl "http://localhost:8080/myset/users?where={\"user.email\":{\"$iendsWith\":\"@EXAMPLE.COM\"}}" # case-insensitive
# Set membership
curl "http://localhost:8080/myset/orders?where={\"status\":{\"$in\":[\"new\",\"processing\"]}}"
curl "http://localhost:8080/myset/orders?where={\"status\":{\"$nin\":[\"cancelled\"]}}"
# Range
curl "http://localhost:8080/myset/products?where={\"price\":{\"$between\":[10,20]}}"
# Null checks
curl "http://localhost:8080/myset/users?where={\"archivedAt\":{\"$isNull\":true}}"
curl "http://localhost:8080/myset/users?where={\"archivedAt\":{\"$notNull\":true}}"
# Order by JSON path
curl "http://localhost:8080/myset/users?order_by=$.user.age"
# Pagination and debug header
curl -i "http://localhost:8080/myset/users?limit=5&offset=5&debug=1"Create, inspect, and remove JSON-path indexes per collection. Index creation is asynchronous.
- POST
/{set}/{collection}/_index- Body:
{ "path": "$.user.age" }or{ "paths": ["$.user.age", "$.country"] } - Response:
202 Accepted,{ name, status: "creating" }
- Body:
- GET
/{set}/{collection}/_indexes→ list index metadata. - GET
/{set}/{collection}/_index/{path}→ status for an index.pathis URL-encoded JSONPath. Example for$.user.age:%24.user.age
- DELETE
/{set}/{collection}/_index/{path}orDELETE .../_index/{path}?paths=p1,p2→ drops index and metadata.
Index metadata fields (internal/database/index.go):
paths(CSV),status(creating|ready|error),error,usage_count,last_used_at,created_at.
- PUT
/{set}/{collection}/_schema- Body: JSON Schema to enable validation, or
null/empty to remove schema. - Response:
{ schema: <echoed-or-null> }
- Body: JSON Schema to enable validation, or
- GET
/{set}/{collection}/_info- Response:
{ schema, indexes, stats: { count, created_at? } }
- Response:
Documents are validated on create/replace/update when a schema is set.
- Name validation: set/collection must match
^[a-zA-Z0-9_]+$. - Reserved fields: top-level keys starting with
_are reserved in documents._metain request bodies is ignored/validated and never stored. - Body limit:
MAX_REQUEST_SIZEenforced via middleware. - CORS: Allow list via
CORSenv var;X-Total-Itemsis exposed for pagination.
- Multi-stage
Dockerfilebuilds a static binary and exposes port 8080. - Published images:
ghcr.io/fernandezvara/microapi:<tag>with multi-arch manifest (latesttag points to the newest release). Makefiletargets:build,run,test,css.
# Create a document
curl -X POST http://localhost:8080/myset/users \
-H 'Content-Type: application/json' \
-d '{"user":{"name":"Ada","age":37}}'
# Get it back (replace <id>)
curl http://localhost:8080/myset/users/<id>
# Query (age >= 30), order by JSON path
curl "http://localhost:8080/myset/users?where={\"user.age\":{\"$gte\":30}}&order_by=$.user.age"
# Create an index
curl -X POST http://localhost:8080/myset/users/_index \
-H 'Content-Type: application/json' \
-d '{"path":"$.user.age"}'
# Set a JSON Schema
curl -X PUT http://localhost:8080/myset/users/_schema \
-H 'Content-Type: application/json' \
-d '{"type":"object","properties":{"user":{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"}}}},"required":["user"]}'cmd/micro-api/: HTTP server main.cmd/micro-api-mcp/: MCP stdio server main.internal/server/server.go: router and middleware wiring.internal/handlers/: REST and MCP handlers.internal/query/: where parser and SQL builder.internal/database/: connection, migrations, indexes, per-set table helpers.internal/validation/: JSON Schema persistence and validation.web/static/: dashboard (dashboard.html,style.css).docker-compose.yaml: local stack with optional n8n.
n8n integration is provided via the n8n-nodes-microapi community node. repo: https://github.com/fernandezvara/n8n-nodes-microapi
MIT. See LICENSE.