Skip to content

用 PostgREST + PgBouncer 打造可水平擴充的 PostgreSQL API 架構

Published: at 下午05:26

介紹

PostgRest 是一款直接無痛把 Postgresql db 直接轉成 可以用來當作 api 服務的 工具

不用在特別在另外開一個 Api Service

從 controller , service 一路刻到 repository 最後才能拿到資料

然後才要設想如何 回傳資料給 前端

對需要快速 POC 或是 驗證心中想法

的事項 都能提升 一大段時間

安裝

我就直接使用 docker 安裝 postgresql + postgrest

讓這兩者在同一 docker-compose.yml 網路下 也就可以不用解那些奇奇怪怪的網路問題

version: '3'
services:
  postgres:
    image: postgres:18
    restart: always
    environment:
      POSTGRES_USER: {{YOUR_DB_USER_NAME}}
      POSTGRES_PASSWORD: {{YOUT_DB_PASSWORD}}
      POSTGRES_DB: postgres
      POSTGRES_MAX_CONNECTIONS: 100
    ports:
      - "5432:5432"
    volumes:
      - ./data:/var/lib/postgresql
  pgbouncer:
    image: edoburu/pgbouncer:1.22.1
    restart: always
    depends_on:
      - postgres
    ports:
      - "6432:6432"
    environment:
      POOL_MODE: transaction
      MAX_CLIENT_CONN: 2000
      DEFAULT_POOL_SIZE: 40
      MIN_POOL_SIZE: 10
      RESERVE_POOL_SIZE: 10
      SERVER_RESET_QUERY: DISCARD ALL
      IGNORE_STARTUP_PARAMETERS: extra_float_digits
    volumes:
      - ./pgbouncer:/etc/pgbouncer
  postgrest:
    image: postgrest/postgrest:latest
    container_name: postgrest
    environment:
      - PGRST_DB_URI=postgres://{{YOUR_DB_USER_NAME}}:{{YOUT_DB_PASSWORD}}@pgbouncer:6432/postgres
      - PGRST_DB_SCHEMA=public
      - PGRST_DB_ANON_ROLE=anon
    ports:
      - "3000:3000"
    depends_on:
      - postgres

將以上 docker-compose.yml 放在一個好地方

補充 pgbouncer 需要再相對位置 增加兩個file

需要開一個資料夾

mkdir ./pgbouncer
cd pgbouncer

新增以下檔案

[databases]
postgres = host=postgres port=5432 dbname=postgres

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432

auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt

pool_mode = transaction

max_client_conn = 2000
default_pool_size = 40
min_pool_size = 10
reserve_pool_size = 10

server_reset_query = DISCARD ALL
ignore_startup_parameters = extra_float_digits

admin_users = {{your_db_user}}

log_connections = 1
log_disconnections = 1
"your_db_account" "your_db_password" 

密碼可以使用md5 但我沒有試成功 要怎麼做 以後再來看

然後執行

docker-compose up -d 

就可以將服務執行在背景了

此次範例的 sql ddl 為

table name = flower_prod_day_trans

CREATE TABLE public.flower_prod_day_trans (
	trans_date date NOT NULL,
	trans_date_roc text NULL,
	query_time timestamp NULL,
	query_time_roc text NULL,
	market text NOT NULL,
	product text NOT NULL,
	max_price numeric NULL,
	high_price numeric NULL,
	mid_price numeric NULL,
	low_price numeric NULL,
	avg_price numeric NULL,
	price_change_pct numeric NULL,
	volume int4 NULL,
	volume_change_pct numeric NULL,
	residual_volume int4 NULL,
	result_title text NULL,
	source_url text NULL,
	raw jsonb NULL,
	inserted_at timestamptz DEFAULT now() NOT NULL,
	updated_at timestamptz DEFAULT now() NOT NULL,
	CONSTRAINT flower_prod_day_trans_pkey PRIMARY KEY (trans_date, market, product)
);

配置 Postgrest 的匿名使用者

進入到 docker postgres db

docker exec -it postresql-docker_postgres_1 psql -U {{YOUR_USER_NAME}} -d postgres

然後輸入以下

CREATE ROLE anon nologin;
GRANT anon TO postgres;

GRANT SELECT ON schema public TO anon;
GRANT SELECT ON public.flower_prod_day_trans TO anon;

可以嘗試呼叫

curl http://127.0.0.1:3000

就可以看到 swagger 的doc了

curl http://127.0.0.1:3000/flower_prod_day_trans?limit=1

就可以取到一筆資料了

新增表

現在如果我有一張新的表 叫做 cache 要加入 anon 的可select 權限

則需要再進入到 postgresql docker 中

docker exec -it postresql-docker_postgres_1 psql -U {{YOUR_USER_NAME}} -d postgres

接著輸入

GRANT SELECT ON public.cache TO anon;

再到cmd 測試

curl http://127.0.0.1:3000/cache

就可以查到資料了

當你需要 group by 的話

會出現以下錯誤

{"code":"PGRST123","details":null,"hint":null,"message":"Use of aggregate functions is not allowed"}

因為預設 是不支持 group by 的

因為這樣有可能 讓使用者 可以自己 group by 他可以group幾百萬行 容易打爆資料庫

但如果真的需要使用的話

需要於 docker-compose 中增加

PGRST_DB_AGGREGATES_ENABLED=true

就可以使用

http://xxxxxx/yout_table?select=volume.sum(),name

http://xxxxxx/yout_table?select=volume.count(),name

但就會有另外的問題 如果要group by sum()然後 order by sum() 呢

http://xxxxxx/yout_table?select=volume.sum(),trans_date&order=volume.sum().asc ?

很抱歉 目前不支持

如果真的要做 寫成一個view 吧

然後記得放對資料表位置 也要確認是否有授權給 驗證的帳號

條件查詢

就直接把欄位打上去就好 比如需要 查詢 “700 台南市場” 就會是

market=eq.700%20台南市場

%20 = 空白號

為 URL encodeing

in

查「700 台南市場」或「800 高雄市場」

market=in.(700 台南市場,800 高雄市場)

market=in.(700%20%E5%8F%B0%E5%8D%97%E5%B8%82%E5%A0%B4,800%20%E9%AB%98%E9%9B%84%E5%B8%82%E5%A0%B4)

like

market 包含「台南」

market=like.%25台南%25

等同於 SQL

market LIKE ‘%台南%‘

%25 = 百分比符號

查日期區間

查 2026-01-18 ~ 2026-01-19

trans_date=gte.2026-01-18&trans_date=lte.2026-01-19

補充 我的欄位型別是

trans_date          → date
query_time          → timestamp
inserted_at         → timestamptz
updated_at          → timestamptz

某時間之後

/query_time=gte.2026-01-19T11:00:00

對照表

運算子意義
eq=
neq!=
gt>
gte>=
lt<
lte<=
likeLIKE
ilikeILIKE
inIN
isIS (null / true / false)

如果需要做新增?

如果需要做新增資料 需要再想以下事項

  1. 權限管理 token 相關 (註冊,登入,jwt驗證)
  2. 資料列層級安全性(Row-Level Security)
  3. 萬一 寫爆資料庫? (雖然讀也有一樣問題)

有稍微看了看 可以無腦直接 開insert 給 某user 但這樣做會有很大的風險

於是就不探討了

問了 下gemini 有以下回饋

方案 A:使用 Supabase (目前最紅)
Supabase 本質上就是 Postgres + PostgREST + GoTrue (認證模組)。

它不讓你手寫 login function。

它提供一個專門處理登入、註冊、第三方登入的服務(GoTrue)。

登入後,它會發放 JWT,這個 JWT 直接給 PostgREST 用,觸發你寫在資料庫裡的 RLS。

結論: 這是目前「既想用 RLS 威力,又不想手寫 SQL 登入」的最佳實務。

方案 B:自建小型 Auth API
你自己寫一個簡單的 Node.js/Express,只負責 /login 和 /signup。

驗證成功後,用同樣的 JWT_SECRET 簽發 Token 給前端。

剩下的所有資料操作(CRUD)全部直接交給 PostgREST。

就這樣吧 感謝