Turn a MySQL or MariaDB schema into a JSON:API REST layer — without writing CRUD for every table.
Introspect → configure policies → activate. Filtering, relationships, field-level ACLs, JWT auth, generated OpenAPI. One install can host many independent APIs (apiId per database).
Current release: 1.6.0 · License: Apache-2.0 · Image: ghcr.io/dbapiator/dbapi
Note on history: This project started years ago as a single-developer tool for personal use. Early commit messages are sparse; more recent history is clearer.
Local (bundled MariaDB):
git clone https://github.com/dbAPIator/dbapi.git
cd dbapi && docker compose up -d
curl -sS http://localhost:8888/health
curl -sS 'http://localhost:8888/v1/data/customers?page[limit]=1'Against your own MySQL/MariaDB:
docker run -d --name dbapi -p 8888:80 \
-e DEPLOYMENT_MODE=single \
-e CONFIGS_DIR=/app/apis \
-e CONFIG_API_SECRET='change-me' \
-e DB_HOST=mysql.example.com \
-e DB_NAME=myapp \
-e DB_USER=dbapi \
-e DB_PASSWORD='secret' \
-v dbapi-configs:/app/apis \
ghcr.io/dbapiator/dbapi:1.6.0Example response:
GET /v1/data/customers/42{
"data": {
"type": "customers",
"id": "42",
"attributes": { "name": "Acme", "status": "active" },
"relationships": {
"orders": { "links": { "related": "/v1/data/customers/42/orders" } }
}
}
}More: 5-minute guide · Docker deployment · Tutorials
| Goal | Where to start |
|---|---|
| Develop from source | Quick start — Docker |
| Build a client | Using the API · AI guide |
| Provision / operate | Management API |
| Cut a release | Releasing |
Apps & integrations dbAPI Your database
┌──────────────────┐ ┌─────────────────────────────┐ ┌─────────────────┐
│ SPA, mobile, │ │ Control plane /mgmt/v1 │ │ MySQL / MariaDB │
│ scripts, BI │───▶│ create · configure · │───▶│ tables · views │
│ partner APIs │ │ validate · activate │ │ FKs · procedures│
└──────────────────┘ │ Data plane /v1/apis/…/data │ └─────────────────┘
│ JSON:API CRUD + policies │ ▲
└──────────────┬──────────────┘ │
│ per-API config │
▼ (introspection) │
┌──────────────────────────────┐ │
│ dbconfigs/{apiId}/ │─────────────┘
│ structure · policies · hooks │
└──────────────────────────────┘
dbAPI is for teams that already build on MySQL or MariaDB and need a governed HTTP API without maintaining CRUD code for every table. It works especially well when important rules live in the database (triggers, stored procedures, views) and clients need a stable, documented REST surface.
| Audience | Typical need |
|---|---|
| Developers & founders | Ship internal tools, admin panels, or small line-of-business apps quickly; keep transactional logic in SQL instead of duplicating it in application controllers. |
| Agencies & consultancies | Deliver repeatable JSON:API + OpenAPI projects on client-owned databases, with per-project policies and docs. |
| IT & operations teams | Modernize access to an existing MySQL estate (self-hosted or on-prem) without adopting a full vendor ERP or a cloud-only BaaS. |
| Products with many databases | Host several independent APIs (apiId) on one installation — dev/staging/prod, multiple products, or per-tenant schemas. |
Less suited if you need: a hosted Postgres platform with built-in auth and realtime (Supabase-style), GraphQL as the primary protocol, or a framework where all business logic stays in application code only.
Supported databases: MySQL and MariaDB (mysqli). Other engines are not supported yet.
dbAPI sits in the same broad category as schema-driven HTTP layers (PostgREST, Hasura, Directus): expose a database without hand-writing CRUD. The differences that usually decide the choice:
| dbAPI | PostgREST | Hasura | Directus | |
|---|---|---|---|---|
| Database | MySQL / MariaDB | PostgreSQL | PostgreSQL (primary) | Many (incl. MySQL) |
| API style | JSON:API REST + OpenAPI | REST / RPC | GraphQL (+ REST options) | REST (+ GraphQL) |
| Control plane | Management API: draft → validate → activate | Config / DB roles | Metadata + console | Admin app + API |
| Multi-API on one host | First-class (apiId per DB) |
Typically one schema service | One project / metadata set | One project |
| Field-level access | Per-table / per-field ACLs | DB roles + grants | Permissions engine | Roles + permissions |
| Admin / CMS UI | No (API + Swagger) | No | Console (dev/ops) | Yes (core product) |
| Best fit | Governed MySQL JSON:API, ops lifecycle | Postgres-native auto-REST | Realtime GraphQL on Postgres | Content/ops UI over SQL |
Choose dbAPI when you are on MySQL or MariaDB, want JSON:API clients and generated OpenAPI, and need several independent APIs (or draft/activate rollout) on one installation.
Choose PostgREST when the database is PostgreSQL and you want a thin, battle-tested REST/RPC layer close to SQL and DB roles.
Choose Hasura when GraphQL (and often realtime subscriptions) is the primary client contract on Postgres.
Choose Directus when you need a full admin/CMS UI and a broader “data platform” experience, not only an HTTP data plane.
These tools overlap; none is a strict superset. dbAPI is intentionally not a BaaS (hosted auth, realtime, managed DB) and not a low-code admin UI.
Point dbAPI at a database, introspect, set policies, activate — then consume tables and views as JSON:API resources with filtering, relationships, field-level permissions, and JWT auth. Schema changes go through rebuild; clients stay aligned via generated OpenAPI.
Consumers: SPAs, mobile apps, partner integrations, scripts, reporting tools.
Back-office systems where the database is the system of record: ERP-style modules, approval flows, document lifecycle, inventory, or membership admin. dbAPI provides HTTP and documentation; invariants and workflows can stay in SQL.
Add governed read/write access for integrations or new UIs without rewriting your monolith — same database, separate apiId, independent network and auth policies.
Create APIs in draft, test connections, introspect and rebuild schema, configure policies, run validation, then activate when ready. Deactivate without losing config. Fits operators and CI/CD pipelines that should not expose data endpoints until checks pass.
Run dev, staging, and production APIs — or separate products and tenants — as distinct apiId directories under dbconfigs/, each with its own connection, policies, and OpenAPI spec.
dbAPI is a standalone HTTP service (not embedded middleware). It is not a replacement for a full BaaS (auth platform, realtime, hosted database), a low-code admin UI, or a general microservices framework.
Building a REST API on top of an existing database usually means endless boilerplate: one controller per table, hand-maintained OpenAPI, ad hoc filter syntax, and security bolted on later. Schema changes break clients or require another deploy.
dbAPI flips that model:
- Schema is the source of truth — introspect from
information_schema, rebuild when the database evolves, preserve overrides inpatch.php. - Policy before data — APIs stay in
draftuntil connection, schema, and policies pass validation; only then does the data plane go live. - Standards-shaped responses — JSON:API documents with relationships, sparse fieldsets, and consistent errors.
- Operator-friendly control plane — plain JSON Management API with OpenAPI validation, separate from consumer-facing data routes.
| Control plane | Data plane | |
|---|---|---|
| Who | Operators, CI/CD, admins | Applications and end users |
| Path (multi-API) | /mgmt/v1/apis/... |
/v1/apis/{apiId}/data/... |
| Path (single-mode Docker) | /mgmt/v1/apis/default |
/v1/data/... |
| Format | Plain JSON | JSON:API |
| Purpose | Define whether and how an API exists | Read and write rows |
Control plane — Create an API (apiId), attach a connection, introspect and rebuild schema, set network and auth policies, register webhooks, run preflight checks, then activate. Until activation, data routes do not serve traffic. Deactivate or delete without losing your override files (unless you remove the config directory).
Data plane — Once active, clients call familiar REST shapes: list and fetch resources, create and update records, traverse foreign keys, filter and paginate. Legacy paths under /apis/... still work; new integrations should use /v1/apis/....
Full references: Management API · Using the data API
Every exposed table or view becomes a resource type. Foreign keys become relationships with stable names (preserved across schema rebuilds unless you override them). Each active API gets a cached openapi.json and works with the bundled Swagger UI.
GET /v1/apis/{apiId}/data/customers
GET /v1/apis/{apiId}/data/customers/42
GET /v1/apis/{apiId}/data/customers/42/orders
POST /v1/apis/{apiId}/data/customers- Filtering — compact expression language (
=,>,>=, contains, one-of, AND/OR, parentheses) compiled to safe SQL. - Sort —
sort=name,-created_at(per-resource when usinginclude). - Pagination —
page[offset]andpage[limit]with instance-level caps. - Sparse fieldsets —
fields[customers]=name,emailto trim payloads. - Includes — load related records in one round trip (
include=orders,account_manager_id), depth-limited. - Filter on relationships — e.g. customers that have orders matching a condition.
- Views — read-only resources; list and filter work; responses omit
idwhen there is no primary key. - Export — optional CSV or XLS on reads for spreadsheets and reporting.
- CSV import —
POSTcollection withContent-Type: text/csv(or multipart file) maps header columns to insertable attributes; same bulk limits and transactions as JSON create.
- Create / update / delete — single records or bulk operations where supported.
- Nested creates — attach related records in one JSON:API payload (configurable recursion depth).
- Duplicate handling —
onduplicate=error|ignore|updatewith field lists for upsert-style flows.field:addandfield:subcombine the inserted number with the stored column. - Filter-based updates and deletes — bulk changes on matching rows (with guardrails).
- IP ACLs for data endpoints (and separate rules for management/config traffic).
- Path-based authorization — restrict which URLs and HTTP methods callers may use.
- JWT authentication — issue tokens after a configurable SQL login query against the same database; optional guest/read modes; optional rotating refresh tokens (
POST .../auth/refresh,POST .../auth/logout). - Per-table and per-field ACLs — control read, insert, update, delete, sort, and search per column.
- Inactive APIs return 409 — configuration can continue while data traffic is blocked.
- Liveness — unauthenticated
GET /healthfor orchestrators (DockerHEALTHCHECKuses it).
Defaults are tunable via environment variables and dbapiator.php (and per-API query timeouts in connection.php):
| Guardrail | Typical default |
|---|---|
| Page size | 100 default, 1000 max |
| Filter expression size / AST complexity | 4096 chars, depth 20, 100 nodes |
| Bulk insert / update per request | 100 / 50 records |
| Request and query timeout | 60 seconds |
Nested include depth |
5 levels |
Every response can carry X-Request-Id for correlation; errors include meta.request_id when applicable.
Typical activation flow:
POST /mgmt/v1/apis → draft (save per-API credential once)
PUT .../connection → database credentials
POST .../connection:test → verify connectivity
POST .../schema:introspect → snapshot from information_schema
POST .../schema:rebuild → structure.php + openapi.json
PUT .../policies/auth → none, or dbAuth + JWT
PUT .../policies/data-network → IP rules for data clients
POST ...:validate → readiness report (required checks)
POST ...:activate → data plane live
Management capabilities include:
- API lifecycle — draft, active, inactive; clone, rename, delete (
forcewhen still active). - Schema tools — introspect, effective schema view, overrides via
patch.php, rebuild with warnings. - Policies — config-network (who may manage this API), data-network (who may call data routes), auth (mode and login query).
- Hooks — per-entity webhook targets for create/update/delete (delivered via Redis Streams when Redis is configured).
- Credential rotation — per-API config keys without recreating the API.
- Quick-create —
POST /mgmt/v1/apis?provision=immediatewith connection in one step when appropriate.
Authentication: instance key (X-Management-Key) for full admin access; per-API key (X-Api-Config-Key) scoped to one API’s configuration directory. Request bodies are validated against the Management OpenAPI spec.
- Webhooks — publish write events to Redis Streams for async dispatchers or downstream pipelines.
- Schema overrides — rename resources, hide tables, tune relationship names without forking generated structure by hand.
- OpenAPI everywhere — management specs at
src/public/management-openapi-multi.yamlandmanagement-openapi-single.yaml(served as/management-openapi.yamlfor the active mode); per-API spec generated on rebuild and served from disk (no regeneration per request). - Multi-API hosting — dev, staging, and tenant-specific APIs as separate
apiIddirectories underdbconfigs/.
Same minimal docker run as in Try it. Full docker run, Compose stacks, environment variables, upgrades, and troubleshooting: Docker deployment guide.
docker pull ghcr.io/dbapiator/dbapi:1.6.0Clone the repo and start the dev stack (live code mounts, bundled MariaDB):
docker compose up -d| Service | URL |
|---|---|
| dbAPI | http://localhost:8888/ |
| MariaDB | localhost:3308 (database myapp) |
For optional webhooks (Redis Streams + dispatcher), use docker/base/.
Local Compose uses single deployment mode (DEPLOYMENT_MODE=single). On first start the container waits for MySQL, auto-provisions the default API from DB_* environment variables, and serves data at /v1/data/....
MariaDB is seeded with the full data-plane test schema on first database init (docker/mysql-init/). To re-run the seed, remove the MySQL data volume first: rm -rf .docker_data/mysql && docker compose up -d.
# Liveness
curl -sS http://localhost:8888/health
# Service discovery
curl -sS http://localhost:8888/
# Data plane
curl -sS http://localhost:8888/v1/data/customers
# OpenAPI spec + Swagger UI
curl -sS http://localhost:8888/v1/swagger
open 'http://localhost:8888/swagger.html?url=v1/swagger'Management API (configure policies, rebuild schema, etc.) uses the fixed id default:
curl -sS http://localhost:8888/mgmt/v1/apis/default \
-H 'X-Management-Key: myverysecuresecret'Instance secret: CONFIG_API_SECRET in docker-compose.yml (default myverysecuresecret) → header X-Management-Key.
For multi-API hosting (dev/staging/prod on one install), omit DEPLOYMENT_MODE=single and use the standard flow:
curl -sS -X POST 'http://localhost:8888/mgmt/v1/apis?provision=immediate' \
-H 'Content-Type: application/json' \
-H 'X-Management-Key: myverysecuresecret' \
-d '{"name":"demo","connection":{"driver":"mysql","host":"mysql","port":3306,"database":"myapp","username":"user","password":"password"}}'Data endpoints: http://localhost:8888/v1/apis/demo/data/{resource}
Requirements: PHP 8.3+, mysqli, json, mbstring, curl, yaml.
- Point the web root at
src/(e.g.http://localhost/dbapi/src). - Ensure
dbconfigs/is writable by the web server user. - Configure
src/application/config/dbapiator.php—configs_dir, secrets, paging limits (or use env vars).
Swagger UI: {base}/swagger.html?url=management-openapi.yaml · per-API: {base}/swagger.html?url=apis/{apiId}/swagger
| Path | Purpose |
|---|---|
src/ |
PHP application (document root) |
dbconfigs/ |
Generated per-API configuration |
docs/ |
Guides (Docker, Management API, Releasing) |
Dockerfile |
Production container image |
docker-compose.yml |
Local dev stack (build from source) |
docker/base/ |
Copy-paste Compose starter for consumer projects |
.github/workflows/ |
CI — tests, security audit, Docker publish |
scripts/release.sh |
Version bump, changelog, git tag |
tests/ |
Schemathesis harness (optional) |
Application entry point: src/index.php.
Integration tests (PHPUnit + Guzzle) run against a live instance:
# Unit tests only (fast, no server/DB)
cd src && composer install && composer test:unit
# Multi-API mode (local Apache)
composer test:dataplane-setup # from src/ — loads DB + copies test env
cd src && composer test:integration -- --filter TestDataPlaneAPI
# Single-mode Docker
docker compose up -d
cp src/tests/test.env.single.example src/tests/test.env
cd src && ./vendor/bin/phpunit tests/TestSingleModeDataPlaneAPI.php| Suite | Focus |
|---|---|
composer test:unit |
Filter parser, OpenAPI, schema sync, health, helpers (~69 tests) |
TestManagementAPI |
Control plane lifecycle |
TestDataPlaneAPI |
Full data-plane coverage (multi-API, ~75 tests) |
TestSingleModeDataPlaneAPI |
Same scenarios via Docker /v1/data/... |
Details: management_api_test_plan.md · data_plane_test_plan.md
GitHub Actions run on push/PR to master and on release tags:
| Workflow | Trigger | What it does |
|---|---|---|
| Tests | Push, PR | PHPUnit unit tests; integration tests with MariaDB |
| Security | Push, PR, weekly | composer audit for dependency CVEs |
| Publish Docker image | Tag v*.*.* |
Build, push to ghcr.io/dbapiator/dbapi, Trivy container scan |
Dependabot opens weekly update PRs for Composer, GitHub Actions, and the Docker base image.
- 5-minute API guide — compose up, list, filter, create (also on dbapi.logimaxx.eu/guide.html)
- Tutorials — hands-on path (getting started → data plane → operate)
- Docker deployment guide — GHCR image,
docker run, Compose, env vars (also on dbapi.logimaxx.eu/deploy.html) - Management API — control plane reference
- Using the API — filters, pagination, relationships, writes
- AI integration guide — copy into consumer projects for Cursor / agents
- OpenAPI pipeline — how specs are generated and validated
- Changelog · Releasing — version history, tags, Docker publish
Developed by Sergiu Voicu, co-founder of LogiMaxx Systems.
- Website: dbapi.logimaxx.eu
- Contact: [email protected]
Apache-2.0 — see LICENSE.md.