visit
PostgREST is a standalone web server that turns your PostgreSQL database directly into a RESTful API. The structural constraints and permissions in the database determine the API endpoints and operations. --
Let's apply it to a simple use case. Here's a product
table that I want to expose via a CRUD API:
Note that you can find the whole source code on to follow along.
FROM debian:bookworm-slim #1
ARG POSTGREST_VERSION=v10.1.1 #2
ARG POSTGREST_FILE=postgrest-$POSTGREST_VERSION-linux-static-x64.tar.xz #2
RUN mkdir postgrest
WORKDIR postgrest
ADD //github.com/PostgREST/postgrest/releases/download/$POSTGREST_VERSION/$POSTGREST_FILE \
. #3
RUN apt-get update && \
apt-get install -y libpq-dev xz-utils && \
tar xvf $POSTGREST_FILE && \
rm $POSTGREST_FILE #4
The Docker image contains a postgrest
executable in the /postgrest
folder. We can "deploy" the architecture via Docker Compose:
version: "3"
services:
postgrest:
build: ./postgrest #1
volumes:
- ./postgrest/product.conf:/etc/product.conf:ro #2
ports:
- "3000:3000"
entrypoint: ["/postgrest/postgrest"] #3
command: ["/etc/product.conf"] #4
depends_on:
- postgres
postgres:
image: postgres:15-alpine
environment:
POSTGRES_PASSWORD: "root"
volumes:
- ./postgres:/docker-entrypoint-initdb.d:ro #5
Dockerfile
postgrest
executable
At this point, we can query the product
table:
curl localhost:3000/product
[{"id":1,"name":"Stickers pack","description":"A pack of rad stickers to display on your laptop or wherever you feel like. Show your love for Apache APISIX","price":0.49,"hero":false},
{"id":2,"name":"Lapel pin","description":"With this \"Powered by Apache APISIX\" lapel pin, support your favorite API Gateway and let everybody know about it.","price":1.49,"hero":false},
{"id":3,"name":"Tee-Shirt","description":"The classic geek product! At a conference, at home, at work, this tee-shirt will be your best friend.","price":9.99,"hero":true}]
PostgREST is a fast way to construct a RESTful API. Its default behavior is great for scaffolding in development. When it’s time to go to production it works great too, as long as you take precautions. PostgREST is a small sharp tool that focuses on performing the API-to-database mapping. We rely on a reverse proxy like Nginx for additional safeguards. --
version: "3"
services:
apisix:
image: apache/apisix:2.15.0-alpine #1
volumes:
- ./apisix/config.yml:/usr/local/apisix/conf/config.yaml:ro
ports:
- "9080:9080"
restart: always
depends_on:
- etcd
- postgrest
etcd:
image: bitnami/etcd:3.5.2 #2
environment:
ETCD_ENABLE_V2: "true"
ALLOW_NONE_AUTHENTICATION: "yes"
ETCD_ADVERTISE_CLIENT_URLS: "//0.0.0.0:2397"
ETCD_LISTEN_CLIENT_URLS: "//0.0.0.0:2397"
We shall first configure Apache APISIX to proxy calls to postgrest
:
curl //apisix:9080/apisix/admin/upstreams/1 -H 'X-API-KEY: 123xyz' -X PUT -d ' #1-2
{
"type": "roundrobin",
"nodes": {
"postgrest:3000": 1 #1-3
}
}'
curl //apisix:9080/apisix/admin/routes/1 -H 'X-API-KEY: 123xyz' -X PUT -d ' #4
{
"uri": "/*",
"upstream_id": 1
}'
localhost
but be sure to expose the ports
curl localhost:9080/product
We haven't added anything, but we're ready to start the work. Let's first protect our API from Distributed Denial of Service attacks. Apache APISIX is designed around a plugin architecture. To protect from DDoS, we shall use a plugin. We can set plugins on a specific route when it's created or on every route; in the latter case, it's a global rule. We want to protect every route by default, so we shall use one.
curl //apisix:9080/apisix/admin/global_rules/1 -H 'X-API-KEY: 123xyz' -X PUT -d '
{
"plugins": {
"limit-count": { #1
"count": 1, #2
"time_window": 5, #2
"rejected_code": 429 #3
}
}
}'
limit-count
limits the number of calls in a time window429 Too Many Requests
; the default is 503
curl localhost:9080/product
<html>
<head><title>429 Too Many Requests</title></head>
<body>
<center><h1>429 Too Many Requests</h1></center>
<hr><center>openresty</center>
</body>
</html>
PostgREST also offers an Open API endpoint at the root. We thus have two routes: /
for the Open API spec and /product
for the products. Suppose we want to disallow unauthorized people to access our data: Regular users can access products, while admin users can access both the Open API spec and products.
APISIX offers several . We will use the simplest one possible, . It relies on the abstraction. key-auth
requires a specific header: the plugin does a reverse lookup on the value and finds the consumer whose key corresponds.
curl //apisix:9080/apisix/admin/consumers -H 'X-API-KEY: 123xyz' -X PUT -d ' #1
{
"username": "admin", #2
"plugins": {
"key-auth": {
"key": "admin" #3
}
}
}'
We do the same with consumer user
and key user
. Now, we can create a dedicated route and configure it so that only requests from admin
pass through:
curl //apisix:9080/apisix/admin/routes -H 'X-API-KEY: 123xyz' -X POST -d ' #1
{
"uri": "/",
"upstream_id": 1,
"plugins": {
"key-auth": {}, #2
"consumer-restriction": { #2
"whitelist": [ "admin" ] #3
}
}
}'
key-auth
and consumer-restriction
pluginsadmin
-authenticated requests can call the route
curl localhost:9080
{"message":"Missing API key found in request"}
curl -H "apikey: user" localhost:9080
It doesn't work as we are authenticated as user
, but the route is not authorized for user
but for admin
.
{"message":"The consumer_name is forbidden."}
curl -H "apikey: admin" localhost:9080
version: "3"
services:
prometheus:
image: prom/prometheus:v2.40.1 #1
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml #2
depends_on:
- apisix
grafana:
image: grafana/grafana:8.5.15 #3
volumes:
- ./grafana/provisioning:/etc/grafana/provisioning #4
- ./grafana/dashboards:/var/lib/grafana/dashboards #4
- ./grafana/config/grafana.ini:/etc/grafana/grafana.ini #4-5
ports:
- "3001:3001"
depends_on:
- prometheus
3000
to 3001
to avoid conflict with the PostgREST service
plugin_attr:
prometheus:
export_addr:
ip: "0.0.0.0" #1
port: 9091 #2
9091
. Prometheus metrics are available on //apisix:9091/apisix/prometheus/metrics
on the Docker network
curl //apisix:9080/apisix/admin/global_rules/2 -H 'X-API-KEY: 123xyz' -X PUT -d '
{
"plugins": {
"prometheus": {}
}
}'
To go further:
Originally published at on November 20th, 2022