Dynamic REST API generation for relational databases with FastAPI and SQLAlchemy reflection.
Turn an existing database into a schema-aware CRUD service in minutes. db2api-service reflects tables at runtime, exposes generic REST endpoints, and handles schema drift with automatic metadata refresh and retry.
- Zero per-table route code for standard CRUD APIs.
- Works with SQLite, PostgreSQL, MySQL, and SQL Server.
- Built for internal tools, admin backends, rapid prototypes, and legacy database projects.
- Useful as a lightweight backend for automation testing, QA environments, and test data workflows.
Automation testing teams, QA platform engineers, and test infrastructure teams can use db2api-service as a dynamic test data API, database-backed integration testing backend, regression testing service, schema-aware CRUD layer, and disposable environment helper for CI pipelines and end-to-end test platforms.
Point the service at an existing relational database and immediately query reflected tables through HTTP:
GET /api/users?status=active&order_by=created_at&desc=true&limit=10{
"table": "users",
"total": 42,
"limit": 10,
"offset": 0,
"filters": {
"status": "active"
},
"items": [
{
"id": 101,
"name": "Alice",
"status": "active",
"created_at": "2026-05-30T09:15:00"
}
]
}This service targets SQLAlchemy-compatible relational databases. It is not a universal adapter for every database product.
- Explicitly supported in this project: SQLite, PostgreSQL, MySQL, SQL Server.
- Other SQLAlchemy-compatible relational databases may also work if reflection and standard CRUD semantics are available.
- Not supported out of the box: MongoDB, Redis, Cassandra, Elasticsearch, and other non-relational stores.
In practice, support depends on three things:
- SQLAlchemy must provide a working dialect and DBAPI driver for the target database.
- Table reflection must correctly return columns and primary keys.
- Standard insert, select, update, and delete semantics must be valid for the target tables.
- Scans the configured database and exposes CRUD endpoints for each table.
- Validates request payload field names against the latest reflected schema.
- Loads table metadata once at startup and does not rely on timed refresh.
- Automatically refreshes metadata and retries once when schema drift causes a request to mismatch cached structure.
- Still provides an explicit schema refresh endpoint when manual reload is needed.
- Useful for automated API, integration, regression, and test-environment validation against existing databases.
- Rapidly exposing internal relational databases as REST APIs.
- Building admin tools and internal tooling without writing per-table endpoints.
- Automated testing that needs predictable CRUD access to a real database schema.
- Regression testing for schema changes, especially when tables evolve during development.
- Legacy database projects where writing and maintaining custom API layers would be expensive.
- Use this when you already have a relational database and want an API now, not after building a custom CRUD layer for every table.
- Compared with hand-written FastAPI endpoints, it reduces repetitive route, validation, and metadata boilerplate for standard database operations.
- Compared with code generation, it keeps the API tied to the live reflected schema instead of generated files that can drift from the database.
- Compared with one-off test helpers, it gives automation and QA teams a reusable HTTP surface for seeding, querying, and validating real test data.
- Compared with admin-only tooling, it is designed to be scriptable from CI pipelines, integration suites, and end-to-end tests.
The service does not generate separate Python files for each table. Instead, it keeps an in-memory reflected table registry and routes all CRUD requests through generic handlers:
GET /metadatareturns the currently reflected tables and columns.POST /admin/refreshforces a full metadata refresh.GET /api/{table}lists rows.GET /api/{table}/{pk}fetches a single row.POST /api/{table}creates a row.PATCH /api/{table}/{pk}updates a row.DELETE /api/{table}/{pk}deletes a row.
GET /api/{table} supports filtering, sorting, and pagination through query string parameters.
- Equality filters: any query parameter matching a column name is treated as a filter, except
limit,offset,order_by,desc, andrefresh. Example:GET /api/users?name=Alice&status=active. - Sorting: use
order_by=column_nameto choose the sort field anddesc=truefor descending order. Only single-column sorting is supported. Iforder_byis not provided and the table has a primary key, results default to primary-key ascending order. - Pagination: use
limitto control the number of rows returned, default50, andoffsetto skip rows, default0. The effectivelimitwill not exceedMAX_PAGE_SIZE, which defaults to200. - Forced refresh: use
refresh=trueto refresh metadata before executing the query. - Error handling: if a filter field or
order_byreferences a missing column, the API returns400.
Example request:
GET /api/users?name=Alice&order_by=created_at&desc=true&limit=20&offset=40The response includes these fields:
table: the queried table name.total: total number of rows matching the filters.limit: effective page size used for the query.offset: pagination offset.filters: filters actually applied.items: the current page of rows.
For composite primary keys, use a comma-separated identity in primary key order, for example GET /api/order_items/1001,7.
-
Create and activate a virtual environment.
-
Install the project:
pip install -e .[test]
Install the matching optional driver extras when needed:
pip install -e .[postgresql] pip install -e .[mysql] pip install -e .[sqlserver]
-
Copy
.env.exampleto.envand setDATABASE_URL. -
Start the service:
uvicorn db2api_service.main:app --reload -
Open
http://127.0.0.1:8000/docsfor Swagger UI.
This project can also serve as a test helper service for teams that want to exercise real database-backed API flows without manually building a dedicated CRUD layer first.
- Use it in integration tests to seed, query, update, and delete relational test data through HTTP.
- Use it in end-to-end tests to validate UI or workflow behavior against a disposable database.
- Use it in regression suites to catch breaking schema changes when columns are added, removed, or renamed.
- Use it in QA environments to expose temporary database fixtures through a consistent API surface.
Because the service reflects the schema dynamically and can refresh metadata after drift-related failures, it is especially useful when test databases change frequently during development.
You can run the project test suite with:
pytest- SQLite:
sqlite:///./demo.db - PostgreSQL:
postgresql+psycopg://user:password@host:5432/dbname - MySQL:
mysql+pymysql://user:password@host:3306/dbname - SQL Server:
mssql+pyodbc://user:password@host:1433/dbname?driver=ODBC+Driver+18+for+SQL+Server&TrustServerCertificate=yes
Install matching optional driver dependencies when needed:
pip install -e .[postgres]
pip install -e .[mysql]
pip install -e .[sqlserver]For SQL Server, you also need Microsoft's ODBC driver installed on the host machine.
- Tables without a primary key still support collection reads and inserts, but row-level read, update, and delete operations require a primary key.
- The service does not use timed schema refresh. After startup, if a schema-drift-related error occurs, it automatically refreshes metadata and retries once.
GET /api/{table}supports simple equality filters, single-column sorting, and pagination. See “List query parameters” above for details.- The service reflects one configured schema at a time through
SCHEMA_NAME, which fits PostgreSQL, SQL Server, and Oracle-style schema layouts well. GET /healthandGET /metadataexpose the active SQLAlchemy dialect and driver so you can confirm the connected backend.
This project is licensed under the MIT License and built to be shared, extended, and improved in the open. Contributions, bug reports, feature ideas, discussions, and real-world feedback are welcome.