Technical note
Discovering PostgREST
In this article we discover together how to expose tables from your Postgres database through a REST API.
Postgres is an open-source database management system that I love to use in my personal projects.
Starting the Postgres and PostgREST containers
For the demonstration I will create a Postgres container, but if you are following this demo you probably already have a Postgres database.
I create a Docker compose file in which I declare two services: db for Postgres and postgrest for PostgREST.
services:
db:
image: postgres:16
container_name: postgres
restart: always
environment:
POSTGRES_USER: postgrest_user
POSTGRES_PASSWORD: secretpassword
POSTGRES_DB: ordersDB
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
postgrest:
image: postgrest/postgrest
container_name: postgrest
restart: always
depends_on:
- db
ports:
- "3000:3000"
environment:
PGRST_DB_URI: postgres://postgrest_user:secretpassword@db:5432/ordersDB
PGRST_DB_ANON_ROLE: web_anon
PGRST_SERVER_PORT: 3000
volumes:
- ./postgrest.conf:/etc/postgrest.conf
volumes:
pgdata:
Start the containers with the docker compose up command.
docker compose up -d
Project setup
This project sets up a simple yet functional REST API for an e-commerce company specialized in selling computer hardware. The database relies on three main tables: users, products and orders.
Table creation script
-- Create the anonymous role (used by PostgREST)
CREATE ROLE web_anon NOLOGIN;
-- Users table
CREATE TABLE users (
id SERIAL PRIMARY KEY,
full_name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- Products table
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
price NUMERIC(10, 2) NOT NULL,
stock INT DEFAULT 0
);
-- Orders table
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(id),
product_id INT REFERENCES products(id),
quantity INT NOT NULL CHECK (quantity > 0),
order_date TIMESTAMP DEFAULT NOW()
);
Inserting the data
-- Users
INSERT INTO users (full_name, email) VALUES
('Alice Dupont', '[email protected]'),
('Bob Martin', '[email protected]'),
('Claire Morel', '[email protected]'),
('David Lefevre', '[email protected]'),
('Eva Bernard', '[email protected]'),
('Florian Dubois', '[email protected]'),
('Gisèle Laurent', '[email protected]'),
('Henri Petit', '[email protected]'),
('Inès Renault', '[email protected]'),
('Julien Mercier', '[email protected]');
-- Products
INSERT INTO products (name, description, price, stock) VALUES
('Dell XPS 13 laptop', 'Powerful and lightweight ultrabook', 1299.99, 15),
('Logitech mechanical keyboard', 'RGB keyboard with Cherry MX switches', 149.99, 30),
('Logitech MX Master 3 mouse', 'Ergonomic wireless mouse', 99.99, 40),
('27" LG 4K monitor', 'UHD monitor for designers and developers', 399.99, 20),
('Samsung 1TB SSD', 'High performance NVMe SSD drive', 159.99, 25),
('USB-C docking station', 'To connect multiple peripherals', 89.99, 35),
('NVIDIA RTX 4070 graphics card', 'GPU for gaming and creation', 649.99, 10),
('Bose QC45 headphones', 'Bluetooth noise-cancelling headphones', 329.99, 18),
('Logitech C920 webcam', 'HD webcam for video conferences', 89.99, 50),
('TP-Link Wi-Fi 6 router', 'Powerful router for the connected home', 129.99, 22);
-- Orders
INSERT INTO orders (user_id, product_id, quantity) VALUES
(1, 1, 1),
(2, 3, 2),
(3, 2, 1),
(4, 5, 1),
(5, 4, 1),
(6, 1, 1),
(7, 7, 1),
(8, 6, 1),
(9, 8, 1),
(10, 9, 2);
Creating a view to display the orders
CREATE VIEW public_orders AS
SELECT
o.id AS order_id,
u.full_name AS customer,
p.name AS product,
o.quantity,
p.price,
o.quantity * p.price AS total_price,
o.order_date
FROM orders o
JOIN users u ON o.user_id = u.id
JOIN products p ON o.product_id = p.id;
Accessing the data through the PostgREST endpoint
PostgREST can only access our database objects if we have granted it the rights.
GRANT USAGE ON SCHEMA public TO web_anon;
GRANT SELECT ON public_orders TO web_anon;
Returning all the data from the public_orders view
curl GET http://localhost:3000/public_orders
[
{
"order_id": 1,
"customer": "Alice Dupont",
"product": "Dell XPS 13 laptop",
"quantity": 1,
"price": 1299.99,
"total_price": 1299.99,
"order_date": "2025-05-02T12:51:32.03124"
},
{
"order_id": 2,
"customer": "Bob Martin",
"product": "Logitech MX Master 3 mouse",
"quantity": 2,
"price": 99.99,
"total_price": 199.98,
"order_date": "2025-05-02T12:51:32.03124"
}
]
Filtering the data from the public_orders view
http://localhost:3000/public_orders?customer=eq.Alice%20Dupont
[
{
"order_id": 1,
"customer": "Alice Dupont",
"product": "Dell XPS 13 laptop",
"quantity": 1,
"price": 1299.99,
"total_price": 1299.99,
"order_date": "2025-05-02T12:51:32.03124"
}
]
Conclusion and next steps
To keep this article manageable, I will stop at the GET request to the PostgREST API. In future articles we will see together how to send POST, DELETE, PUT … requests with PostgREST.
I created a GitHub repository to follow the project’s progress: https://github.com/agailloty/postgrest-demo