A YAML-driven HTTP server that maps endpoints to SQL queries. Define your API in YAML, run the server, and get a working REST API.
Note
This project was written by AI (Claude Code).
brew install mpyw/tap/sql-http-proxyscoop bucket add mpyw https://github.com/mpyw/scoop-bucket
scoop install sql-http-proxyexport VERSION=1.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 dpkg -i "sql-http-proxy_${VERSION}-1_${ARCH}.deb"export VERSION=1.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 rpm -i "sql-http-proxy-${VERSION}-1.${ARCH}.rpm"# Set version and architecture
export VERSION=1.0.0
export ARCH=amd64 # or arm64
# Linux
curl -L "https://github.com/mpyw/sql-http-proxy/releases/download/v${VERSION}/sql-http-proxy_${VERSION}_linux_${ARCH}.tar.gz" | tar xz
sudo mv sql-http-proxy /usr/local/bin/
# macOS
curl -L "https://github.com/mpyw/sql-http-proxy/releases/download/v${VERSION}/sql-http-proxy_${VERSION}_darwin_${ARCH}.tar.gz" | tar xz
sudo mv sql-http-proxy /usr/local/bin/Or download manually from GitHub Releases.
go install github.com/mpyw/sql-http-proxy/cmd/sql-http-proxy@latestUse build tags for smaller binaries:
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)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 = :idsql-http-proxy -l :8080curl 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: alice@example.com }
- { id: 2, name: Bob, email: bob@example.com }
filter: return row.email === input.emailReturning
nullorundefinedfrom mock JS results in 404 Not Found fortype: one.
See SCHEMA.md for 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 complete mock reference.
MIT