PostgREST
PostgREST turns your PostgreSQL schema into a REST API. The Control Plane deploys and manages a PostgREST container alongside your database, handling connection strings, multi-host failover, and schema validation on every deploy.
Overview
The Control Plane provisions a PostgREST container on each host you
specify. The container connects to the database using
automatically-managed credentials and serves HTTP at your configured
port. Anonymous requests run as the configured db_anon_role.
JWT-authenticated requests switch to any role granted to the
authenticator.
See Managing Services for instructions on adding, updating, and removing services. The sections below cover PostgREST-specific configuration.
Configuration Reference
All configuration fields go in the config object of the service spec.
All fields are optional. The defaults work for a read-only API.
Database
| Field | Type | Default | Description |
|---|---|---|---|
db_schemas |
string | "public" |
Comma-separated schemas to expose. Each schema is checked at deploy time. Provisioning fails if any schema does not exist. |
db_anon_role |
string | "pgedge_application_read_only" |
PostgreSQL role for unauthenticated requests. The role must exist before deployment. |
db_pool |
integer | 10 |
Connection pool size. Range: 1-30. |
max_rows |
integer | 1000 |
Maximum rows returned per response. Range: 1-10000. |
JWT Authentication
JWT authentication lets clients switch PostgreSQL roles per request using a signed token. Omit these fields to run in anonymous-only mode.
| Field | Type | Description |
|---|---|---|
jwt_secret |
string | Signing key for validating incoming JWTs. Minimum 32 characters. Required to enable JWT auth. |
jwt_aud |
string | Expected audience claim. PostgREST rejects tokens without a matching aud claim. |
jwt_role_claim_key |
string | JSONPath to the role field in the JWT payload. Defaults to ".role". |
CORS
| Field | Type | Description |
|---|---|---|
server_cors_allowed_origins |
string | Comma-separated list of allowed CORS origins. Omit to disable CORS headers. |
Examples
Read-Only API (No JWT)
This example provisions a PostgREST service with default settings. All requests run as the anonymous role.
curl -X POST http://host-1:3000/v1/databases \
-H 'Content-Type: application/json' \
--data '{
"id": "storefront",
"spec": {
"database_name": "storefront",
"nodes": [
{ "name": "n1", "host_ids": ["host-1"] }
],
"services": [
{
"service_id": "api",
"service_type": "postgrest",
"version": "latest",
"host_ids": ["host-1"],
"port": 3100,
"config": {}
}
]
}
}'
JWT-Authenticated API
This example enables JWT authentication. Clients send a signed token to switch to a specific PostgreSQL role.
curl -X POST http://host-1:3000/v1/databases \
-H 'Content-Type: application/json' \
--data '{
"id": "storefront",
"spec": {
"database_name": "storefront",
"nodes": [
{ "name": "n1", "host_ids": ["host-1"] }
],
"services": [
{
"service_id": "api",
"service_type": "postgrest",
"version": "latest",
"host_ids": ["host-1"],
"port": 3100,
"config": {
"jwt_secret": "a-secret-key-of-at-least-32-characters"
}
}
]
}
}'
Multiple Schemas
This example exposes two schemas. The Control Plane checks both exist before deploying.
curl -X POST http://host-1:3000/v1/databases \
-H 'Content-Type: application/json' \
--data '{
"id": "storefront",
"spec": {
"database_name": "storefront",
"nodes": [
{ "name": "n1", "host_ids": ["host-1"] }
],
"services": [
{
"service_id": "api",
"service_type": "postgrest",
"version": "latest",
"host_ids": ["host-1"],
"port": 3100,
"config": {
"db_schemas": "public,api",
"jwt_secret": "a-secret-key-of-at-least-32-characters"
}
}
]
}
}'
Querying the API
PostgREST accepts requests once the service instance reaches the
running state. Send requests to:
http://{host}:{port}/{table_or_view}
Replace {host} with your host name, {port} with your service port,
and {table_or_view} with a table or view in an exposed schema.
Anonymous Request
curl http://host-1:3100/products
JWT-Authenticated Request
Generate a signed JWT and pass a Bearer token. The role claim sets
the PostgreSQL role PostgREST uses for the request.
TOKEN=$(python3 - <<'EOF'
import hmac, hashlib, base64, json, time
secret = b"a-secret-key-of-at-least-32-characters"
def b64url(data):
if isinstance(data, str):
data = data.encode()
return base64.urlsafe_b64encode(data).rstrip(b"=").decode()
header = b64url(json.dumps({"alg":"HS256","typ":"JWT"}))
payload = b64url(json.dumps({"role":"app","exp":int(time.time())+3600}))
sig = b64url(hmac.new(secret, f"{header}.{payload}".encode(), hashlib.sha256).digest())
print(f"{header}.{payload}.{sig}")
EOF
)
curl -X POST http://host-1:3100/products \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
--data '{"name": "Widget", "price": 9.99}'
The role claim must name a PostgreSQL role granted to the PostgREST
authenticator user. The authenticator username is visible in the
service_instances field of your database status response. Grant your
application roles to that user before sending authenticated requests.
Preflight Checks
At deploy time, the Control Plane connects to the primary Postgres node and checks:
- Every schema in
db_schemasexists. - The role in
db_anon_roleexists.
Deployment fails with a descriptive error if either check fails. No container starts until both checks pass.
Multi-Host Failover
The connection string in postgrest.conf includes every Postgres node
hostname with target_session_attrs=read-write. After a primary
switchover, PostgREST reconnects to the new primary automatically. No
configuration change or restart needed.
Schema Cache
PostgREST caches your database schema at startup. To expose a newly added table or view without restarting the container, send a POST request to the admin server:
curl -X POST http://host-1:3101/notify-reload
The admin port is always one higher than your main PostgREST port
({port} + 1).
To trigger a full redeploy with a fresh schema cache, update the
service spec (for example, change db_schemas) and submit an update
request.