A YAML-driven HTTP server that maps endpoints to SQL queries against PostgreSQL,
MySQL,
SQLite, and SQL Server. Define your API in YAML, run the server, and get a working REST API — with named parameters, path parameters, a JavaScript transform pipeline, and a mock mode that needs no database at all.
Using mise (macOS/Linux/Windows)
sql-http-proxy is installable directly from GitHub Releases via mise's github backend — no extra registry required:
mise use -g "github:mpyw/sql-http-proxy"Or pin it per project in mise.toml:
[tools] "github:mpyw/sql-http-proxy" = "latest"
Using Homebrew (macOS/Linux)
brew install mpyw/tap/sql-http-proxy
Using Scoop (Windows)
scoop bucket add mpyw https://github.com/mpyw/scoop-bucket.git scoop install sql-http-proxy
Manual .deb / .rpm install
Native packages are tracked by apt/dnf, so upgrades and removal stay clean. Download from GitHub Releases and hand the file to your package manager:
Debian/Ubuntu (.deb):
export VERSION=0.0.0 export ARCH=amd64 # or arm64 curl -LO "https://github.com/mpyw/sql-http-proxy/releases/download/v${VERSION}/sql-http-proxy_${VERSION}-1_${ARCH}.deb" sudo apt install "./sql-http-proxy_${VERSION}-1_${ARCH}.deb"
Red Hat/Fedora (.rpm):
export VERSION=0.0.0 export ARCH=x86_64 # or aarch64 curl -LO "https://github.com/mpyw/sql-http-proxy/releases/download/v${VERSION}/sql-http-proxy-${VERSION}-1.${ARCH}.rpm" sudo dnf install "./sql-http-proxy-${VERSION}-1.${ARCH}.rpm"
Downloading the tarball directly (macOS/Linux/Windows)
No package manager? Grab the archive for your platform from GitHub Releases:
export VERSION=0.0.0 export OS=linux # or darwin export ARCH=amd64 # or arm64 export BASE_URL="https://github.com/mpyw/sql-http-proxy/releases/download/v${VERSION}" # Download the archive and the release's checksum list curl -LO "${BASE_URL}/sql-http-proxy_${VERSION}_${OS}_${ARCH}.tar.gz" curl -LO "${BASE_URL}/checksums.txt" # Verify before installing (use `shasum -a 256 -c` on macOS) sha256sum --ignore-missing -c checksums.txt tar xzf "sql-http-proxy_${VERSION}_${OS}_${ARCH}.tar.gz" sudo mv sql-http-proxy /usr/local/bin/
On Windows, download sql-http-proxy_${VERSION}_windows_${ARCH}.zip and extract sql-http-proxy.exe somewhere on your PATH.
Using go install
Requires Go 1.27+.
go install github.com/mpyw/sql-http-proxy/cmd/sql-http-proxy@latest
Build tags trade drivers for a smaller binary. With no tags, every driver is included:
go install -tags postgres github.com/mpyw/sql-http-proxy/cmd/sql-http-proxy@latest # PostgreSQL only go install -tags sqlite github.com/mpyw/sql-http-proxy/cmd/sql-http-proxy@latest # SQLite only go install -tags mock github.com/mpyw/sql-http-proxy/cmd/sql-http-proxy@latest # Mock only (no database)
Tags combine: -tags postgres,mysql.
Using go tool (Go 1.27+)
Pin sql-http-proxy to a project rather than the machine:
# Add to go.mod as a tool dependency go get -tool github.com/mpyw/sql-http-proxy/cmd/sql-http-proxy@latest # Run via go tool go tool sql-http-proxy -c .sql-http-proxy.yaml -l :8080
Building from source
For platforms without pre-built packages, or to run an unreleased revision.
Requires Go 1.27+ — the project uses encoding/json/v2, encoding/json/jsontext, and generic methods.
git clone https://github.com/mpyw/sql-http-proxy.git cd sql-http-proxy go build ./cmd/sql-http-proxy # all drivers go build -tags postgres,mysql ./cmd/sql-http-proxy # selected drivers only
- YAML-defined endpoints: one entry per endpoint,
sqlormock, nothing to compile - Named parameters:
:nameplaceholders bound from query string, request body, or path - Path parameters: chi routing with regex shorthands like
{id:*uuid_v7*} - Transform pipeline: JavaScript
pre/posthooks to validate input, rewrite SQL, and reshape output - Mock mode: serve YAML, JSON, JSONL, CSV, or JavaScript fixtures with no database connection
- Multi-database: PostgreSQL, MySQL, SQLite, and SQL Server, selectable at build time
- Strict JSON: request bodies are parsed per RFC 7493 — duplicate keys and invalid UTF-8 are rejected rather than silently binding
The driver is chosen from the DSN scheme, so nothing else needs configuring:
| Database | DSN scheme | Build tag |
|---|---|---|
| PostgreSQL | postgres://, postgresql:// |
postgres |
| MySQL | mysql:// |
mysql |
| SQLite | file:, sqlite: — or a bare :memory:, *.db, *.sqlite path |
sqlite |
| SQL Server | sqlserver:// |
mssql |
Omit the build tag to get every driver; use -tags mock for a binary with none of them.
Create .sql-http-proxy.yaml:
database: dsn: postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST:-localhost}:${DB_PORT:-5432}/mydb queries: - type: many path: /users sql: SELECT * FROM users - type: one path: /users/{id:*uuid_v7*} sql: SELECT * FROM users WHERE id = :id
sql-http-proxy -l :8080
curl http://localhost:8080/users # List all users curl http://localhost:8080/users/019411a5-3d7f-7000-8000-000000000001 # Get user by ID (UUIDv7)
Run without a database using mock data. Use mock instead of sql:
queries: # Array source for type: many - type: many path: /users mock: array: - { id: 1, name: Alice } - { id: 2, name: Bob } # JavaScript for dynamic data - type: one path: /user mock: object_js: | if (input.id === '404') return null; return { id: parseInt(input.id), name: 'User ' + input.id }; # Array with filter for type: one - type: one path: /user-by-email mock: array: - { id: 1, name: Alice, email: [email protected] } - { id: 2, name: Bob, email: [email protected] } filter: return row.email === input.email
Note
Returning null or undefined from mock JS results in 404 Not Found for type: one.
See SCHEMA.md for the complete reference and sql-http-proxy.example.yaml for examples.
Database connection with ${VAR} environment variable expansion:
database: dsn: postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST:-localhost}:${DB_PORT:-5432}/mydb http: cors: true # or: { allowed_origins: [...], allow_credentials: true, max_age: 86400 }
Each endpoint uses either sql OR mock, not both:
queries: - type: one|many path: /endpoint sql: SELECT * FROM table WHERE id = :id # OR mock: { ... } transform: { ... } mutations: - type: one|many|none method: POST path: /endpoint sql: INSERT INTO table (...) RETURNING * # OR mock: { ... } transform: { ... }
Use {param} syntax in paths to capture URL segments (chi router syntax):
queries: - type: one path: /users/{id:*uuid_v7*} sql: SELECT * FROM users WHERE id = :id - type: many path: /users/{user_id:*uuid_v7*}/posts sql: SELECT * FROM posts WHERE user_id = :user_id mutations: - type: one method: PUT path: /users/{id:*uuid_v7*} sql: UPDATE users SET name = :name WHERE id = :id RETURNING *
Path parameters take priority over query string and body parameters.
Regex shorthands for common validation patterns:
| Shorthand | Description |
|---|---|
*uuid* |
Any UUID (lowercase) |
*uuid_v4* |
UUIDv4 only |
*uuid_v7* |
UUIDv7 only |
Custom regex (returns 404 if not matched):
# Numeric ID only path: /posts/{id:[0-9]+} # Slug format path: /articles/{slug:[a-z0-9-]+}
See SCHEMA.md for full regex syntax.
transform: pre: | # Validate input, modify SQL return { id: parseInt(input.id) }; post: | # Transform output return { ...output, formatted: true };
Reusable JavaScript functions for all transforms:
global_helpers: | function requireInt(val, name) { const n = parseInt(val); if (isNaN(n)) throw { status: 400, body: { error: name + ' required' } }; return n; } queries: - type: one path: /user sql: SELECT * FROM users WHERE id = :id transform: pre: | return { id: requireInt(input.id, 'id') };
Mock sources are divided into two categories based on the endpoint type.
Return a single object directly:
mock: object: { id: 1, name: Alice } # Inline YAML object # or object_json: '{"id": 1, "name": "Alice"}' # JSON string # or object_json_file: ./data/user.json # External JSON file # or object_js: | # JavaScript (dynamic) return { id: parseInt(input.id), name: 'User ' + input.id };
Return multiple rows:
mock: array: # Inline YAML array - { id: 1, name: Alice } - { id: 2, name: Bob } # or array_json: '[{"id": 1}, {"id": 2}]' # JSON string array_json_file: ./data/users.json # External JSON file # or csv: | # CSV with header id,name 1,Alice 2,Bob csv_file: ./data/users.csv # External CSV file # or jsonl: | # JSON Lines {"id": 1, "name": "Alice"} {"id": 2, "name": "Bob"} jsonl_file: ./data/users.jsonl # External JSONL file # or array_js: | # JavaScript (dynamic) return [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
Use filter to select a single row from array data:
- type: one path: /user mock: array: - { id: 1, name: Alice } - { id: 2, name: Bob } filter: return row.id === parseInt(input.id)
The filter receives row and input, returns true to include. First matching row is returned (404 if none match).
See SCHEMA.md for the complete mock reference.
Requires Go 1.27+.
# Build (all drivers) go build ./cmd/sql-http-proxy # Build with specific drivers go build -tags postgres,mysql ./cmd/sql-http-proxy # Run ./sql-http-proxy -c .sql-http-proxy.yaml -l :8080 # Unit tests go test ./internal/... # E2E tests go test ./e2e/... # Everything, with the race detector go test -race ./... # Lint (needs golangci-lint v2.13.0+ to parse generic methods) golangci-lint run
MIT License