CRUDを書くのをやめよう:PostgRESTでPostgreSQLから直接REST APIを構築する

Database tutorial - IT technology blog
Database tutorial - IT technology blog

ボイラープレートの罠

何度も週末をループの中で無駄にしてきた。きれいなPostgreSQLスキーマを設計した後、Node.jsやPythonで冗長なCRUD(Create、Read、Update、Delete)ロジックを書くだけで次の3日間が吹き飛ぶ。コントローラーを作り、DTOを定義し、すべてのデータベースカラムをAPIエンドポイントに手作業でマッピングする。単調で、エラーが起きやすく、プロジェクトの勢いを完全に削いでしまう。

このような摩擦が生じるのは、私たちが巨大なアーキテクチャのギャップを当たり前として受け入れてきたからだ。データベースを受動的なストレージバケット、バックエンドを唯一の知性の源として扱っている。しかし、PostgreSQLはすでに驚くほど高度な機能を備えている。堅牢な型システム、複雑な制約、そして実績のある権限モデルを持っている。その同じロジックをミドル層で書き直すのは、多くの場合、冗長な作業に過ぎない。

PostgRESTはこの中間層を排除し、スキーマから直接RESTful APIを提供する。データベースの構造を検査し、エンドポイントをオンザフライで生成する。バックエンドコードを1行も書くことなく、本番環境対応のAPIが手に入る。

クイックスタート:5分でゼロからAPIへ

邪魔にならないツールが好きだ。PostgRESTはHaskellでコンパイルされた単一のバイナリだ。驚くほど軽量で、通常はRAMを30MB未満しか消費しないにもかかわらず、月5ドルの基本的なVPSで毎秒2,000以上のリクエストを処理できる。新しいマシンでライブAPIをセットアップする方法を紹介しよう。

1. データベースの準備

セキュリティは分離から始まる。publicスキーマは決して公開しない。代わりに、専用のapiスキーマを作成して、Webに公開するものを厳密にコントロールする。シンプルなタスクマネージャーを作ってみよう。

-- Postgresインスタンスに接続する
CREATE SCHEMA api;

CREATE TABLE api.todos (
  id SERIAL PRIMARY KEY,
  done BOOLEAN DEFAULT false,
  task TEXT NOT NULL,
  due TIMESTAMPTZ
);

-- 匿名Webリクエスト用のロールを作成する
CREATE ROLE web_anon NOLOGIN;
GRANT USAGE ON SCHEMA api TO web_anon;
GRANT SELECT ON api.todos TO web_anon;

2. PostgRESTのインストールと起動

インストールは簡単だ。Dockerを使うこともできるが、ローカル開発ではバイナリの方が速い。OS用の最新リリースをダウンロードして展開しよう。

# Linuxユーザー向けの例
wget https://github.com/PostgREST/postgrest/releases/download/v12.0.2/postgrest-v12.0.2-linux-static-x64.tar.xz
tar xf postgrest-v12.0.2-linux-static-x64.tar.xz

次に、PostgRESTがデータベースと通信する方法を設定するためのtutorial.confファイルを作成する:

db-uri = "postgres://authenticator:mysecretpassword@localhost:5432/postgres"
db-schema = "api"
db-anon-role = "web_anon"

./postgrest tutorial.confで起動しよう。APIは今すぐhttp://localhost:3000/todosで利用可能だ。シンプルなGETリクエストで、データがきれいなJSON配列として即座に返ってくる。

詳細解説:JWTとRLSによるセキュリティ

最もよく聞かれる懸念は「ユーザー権限はどう処理するのか?」というものだ。PostgRESTはカスタムログインロジックを必要としない。セキュリティをJSON Web Tokens(JWT)とPostgreSQLネイティブのRow Level Security(RLS)に委任する。このアプローチは従来のアプリケーションレベルのチェックよりも大幅に安全だ。

Authenticatorパターン

authenticatorロールは信頼されたプロキシとして考えよう。データベースに接続するが、それ自体には権限がない。その唯一の仕事は、JWTに含まれるクレームに基づいて特定のユーザーロールに切り替えることだ。

-- ログイン済みユーザー用のロールを作成する
CREATE ROLE todo_user NOLOGIN;
GRANT USAGE ON SCHEMA api TO todo_user;
GRANT ALL ON api.todos TO todo_user;
GRANT USAGE, SELECT ON SEQUENCE api.todos_id_seq TO todo_user;

-- authenticatorがtodo_userに切り替えられるようにする
GRANT todo_user TO authenticator;

JWTシークレットの設定

tutorial.confjwt-secretを追加しよう。このキーはブルートフォース攻撃を防ぐために、少なくとも32文字以上でなければならない。

jwt-secret = "a-very-secure-32-character-secret-key"

クライアントがAuthorization: Bearer <token>ヘッダーを送信すると、PostgRESTがそれを検証する。トークンに"role": "todo_user"が含まれていれば、データベースはそのユーザーとしてクエリを実行する。Node.jsコードでif (user.id === record.owner_id)を手動でチェックする必要はもうない。

応用:シンプルなCRUDを超えて

PostgRESTは単なる基本的なラッパーではない。通常なら何百行ものコードを必要とする複雑なデータ要件を処理できる。

強力なフィルタリング

検索エンジンを構築する必要はない。PostgRESTはURLパラメーターをSQL条件にマッピングする。「coffee」に関連する未完了のタスクで、期限が近いものを検索するには、次のように使うだけだ:

GET /todos?done=is.false&task=like.*coffee*&limit=10

リソースの埋め込み(JOIN)

関連データの取得は、よく「N+1クエリ」というパフォーマンスの罠につながる。PostgRESTはリソースの埋め込みでこの問題を解決する。usersテーブルとtodosテーブルが外部キーで紐付けられていれば、1つのリクエストで一緒に取得できる:

GET /users?select=name,todos(*)

効率的なデータインポート

大量データの処理は共通の悩みどころだ。レガシーなスプレッドシートを移行する際、toolcraft.app/ja/tools/data/csv-to-jsonを使ってローカルで行をJSON配列に変換している。そのあと、その配列をPostgRESTエンドポイントに直接POSTする。データベースが単一トランザクションで一括挿入を処理し、データの整合性を確保してくれる。

本番環境の強化

高トラフィックのプロジェクトにPostgRESTを何度かデプロイした経験から、次の3つのプラクティスが不可欠だとわかった:

  • SQLビューを使う:テーブルはプライベートとして扱おう。代わりにビューをAPIに公開する。これにより、フロントエンドのAPIコントラクトを壊さずにデータベーススキーマを変更できる
  • RLSを強制する:常にRow Level Securityを有効にしよう。これはセーフティネットとして機能し、APIエンドポイントが誤って設定された場合でも、ユーザーが他の人の行にアクセスできないようにする。
  • ロジックにはストアドプロシージャを:ウェルカムメールの送信などのアクションには、PostgreSQLトリガーまたは関数を使おう。POST /rpc/send_welcome_emailでこれらを呼び出せる。

ロジックをデータベースに移すことで、「信頼できる唯一の情報源」を作れる。ユーザーがWebアプリ、モバイルアプリ、CLIツールのどれで接続しても、ビジネスルールは一貫している。これはSQLをマスターすることを強いるが、それは今日のトレンドなバックエンドフレームワークが消えた後も長く役立つスキルだ。

Share: