A secure, read-only SQL sandbox API service for running ad-hoc queries against a sample PostgreSQL database. It enforces timeouts, rate limits, and query safety, and returns JSON-formatted results and query plans.
- REST API:
POST /api/query,GET /api/health - Read-only sandbox over PostgreSQL with schema isolation via
search_path - Query validation (whitelist SELECT/WITH/EXPLAIN, forbid dangerous keywords)
- Statement timeout (5s), result row limit (100), query length limit (1000 chars)
- Connection pooling and context timeouts
- EXPLAIN (ANALYZE, FORMAT JSON) parsing for planning/execution metrics
- Nginx reverse proxy with rate limiting, CORS, request logging
- Docker Compose for one-command startup
# From repo root
docker compose up --build
# API on http://localhost:80 via Nginx (or http://localhost:8080 directly to app)The API reads the following env vars (see docker-compose.yml):
DB_HOST(default: postgres)DB_PORT(default: 5432)DB_USER(default: sandbox_ro)DB_PASSWORD(default: sandbox_ro)DB_NAME(default: goto)DB_SSLMODE(default: disable)API_PORT(default: 8080)
GET /api/health
Response: { "status": "ok" }
POST /api/query
Content-Type: application/json
{ "query": "SELECT * FROM users LIMIT 10" }
Response:
{
"execution_time_ms": 12,
"rows_affected": 10,
"query_plan": { "Plan": { "Node Type": "Limit", "Total Cost": 123.45 }},
"plan_metrics": {"planning_time_ms": 0.12, "execution_time_ms": 1.23, "total_cost": 123.45},
"results": [{"id":1, "name":"User 1", "email":"user1@example.com"}]
}Errors return with HTTP 4xx/5xx and { "error": "message" }.
# Health
curl -s http://localhost/api/health | jq
# Simple SELECT
curl -s -X POST http://localhost/api/query \
-H 'Content-Type: application/json' \
-d '{"query":"SELECT id, name, email FROM users ORDER BY id LIMIT 10"}' | jq
# Join query
curl -s -X POST http://localhost/api/query \
-H 'Content-Type: application/json' \
-d '{"query":"SELECT o.id, u.email, o.amount FROM orders o JOIN users u ON u.id=o.user_id ORDER BY o.id LIMIT 10"}' | jq- Go 1.21+
- Chi router, pq driver, godotenv
Run locally without Docker:
export DB_HOST=localhost DB_USER=sandbox_ro DB_PASSWORD=sandbox_ro DB_NAME=goto DB_SSLMODE=disable
go run ./cmd/api- Read-only DB user with RLS; no write grants
- Server-side
statement_timeoutset per transaction - Query whitelist and blocklist enforced in API
- Nginx rate limiting at 10 req/sec per IP
MIT