📶

SPA (Vite) から PocketBase に接続するときのちょっとしたテクニック

に公開

普通の方法

フロントエンドの JavaScript から PocketBase に接続する際,PocketBase の URL を指定する必要があります.例えば,Vite の開発サーバーが http://localhost:5173 で,PocketBase が http://localhost:8090 で起動している場合,フロントエンドからは以下のように接続します.

import PocketBase from 'pocketbase';

// PocketBase の URL を指定して接続する
const pb = new PocketBase('http://localhost:8090');

本番環境へデプロイした際には,本番環境用の PocketBase サーバーに接続できるよう,接続先 URL を環境変数で管理するのが一般的です.

import PocketBase from 'pocketbase';

// 環境変数の値を接続先URLとして使う
const pb = new PocketBase(import.meta.env.VITE_POCKETBASE_URL);

開発環境では VITE_POCKETBASE_URL=http://localhost:8090,本番環境では VITE_POCKETBASE_URL=https://pocketbase.example.com のように,環境に応じて環境変数の値を変更することで,接続先を切り替えられます.

実は,以下で説明する方法を使うと,環境変数の切り替えすらも不要になります.環境ごとに異なる環境変数を正しく設定するのは地味に面倒な作業ですが,その面倒を1つ減らすことができます.

開発環境の設定 (Vite Server Proxy)

Vite の Server Proxy を利用すると,開発環境においてはコードや環境変数を変更せずに PocketBase へ接続できるようになります.

まずは,開発環境での設定を見ていきましょう.
Vite の設定ファイル vite.config.ts に,以下のようにプロキシ設定を追加します.

vite.config.ts
...
export default defineConfig({
  plugins: [react()],
+  server: {
+    proxy: {
+      "/api": {
+        target: "http://localhost:8090",
+        changeOrigin: true,
+      },
+    },
+  },
...

これは Vite の Server Proxy 機能を利用した設定です.この設定により,Vite の開発サーバー (http://localhost:5173) へのリクエストのうち,パスが /api から始まるものは PocketBase (http://localhost:8090) へ転送されます.それ以外のリクエストは,従来通り Vite 開発サーバーが処理します.

PocketBase の Web API は,デフォルトですべて /api から始まるパスを持ちます.Server Proxy を設定することで,ブラウザは Vite 開発サーバーを介して PocketBase と通信できるようになります.

ブラウザから見ると,フロントエンドを配信しているサーバーと API を提供しているサーバーが同一オリジンに見えるため,フロントエンドのコード内で HTTP リクエストのホスト名を省略できます.

fetch("http://localhost:5173/api/hello")

fetch("/api/hello") // 上と同じ意味になる

これにより,PocketBase クライアントの初期化時にも,接続先 URL を省略できます.

import PocketBase from 'pocketbase';

// 同じオリジンで PocketBase と通信できるので URL を省略できる
const pb = new PocketBase();

本番環境の設定

ここまでは開発環境における設定でした.本番環境では Vite の開発サーバーは使用しないため,このプロキシ設定は直接利用できません.

しかし,PocketBase でフロントエンドの静的ファイルも配信し,結果的にフロントエンドと API が同一オリジンになるように構成すれば,開発環境と同様に URL を省略した形で PocketBase に接続できます.PocketBase は,ルートディレクトリに pb_public というディレクトリが存在する場合,その中のファイルを静的に配信します.ビルドして生成されたフロントエンドの静的ファイルをこの pb_public ディレクトリに配置することで,PocketBase とフロントエンドを同一オリジンで配信できます.

この配置処理は,ビルドやデプロイのプロセスに組み込むことになります.例えば,PocketBase を Docker でデプロイする場合の Dockerfile は以下のようになります.

# フロントエンドをビルドするステージ
FROM node:18-alpine AS frontend-builder

WORKDIR /app/frontend

COPY ./package.json ./package-lock.json* ./
RUN npm ci
COPY . .
RUN npm run build

# PocketBase のステージ
FROM alpine:latest

ARG PB_VERSION=0.28.1

RUN apk add --no-cache \
    unzip \
    ca-certificates

ADD https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_linux_amd64.zip /tmp/pb.zip
RUN unzip /tmp/pb.zip -d /pb/

# ビルドしたフロントエンドの静的ファイルを pb_public に配置する
COPY --from=frontend-builder /app/frontend/dist /pb/pb_public

COPY ./pb_migrations /pb/pb_migrations
# COPY ./pb_hooks /pb/pb_hooks

EXPOSE 8080

CMD ["/pb/pocketbase", "serve", "--http=0.0.0.0:8080"]

この設定により,/api から始まるリクエストには PocketBase の Web API が応答し,それ以外のリクエストには pb_public ディレクトリ内の静的ファイルが配信されます.

結果として,フロントエンドのオリジンと Web API のオリジンが同一になるため,PocketBase への接続コードは開発環境と同じ記述のまま動作します.

import PocketBase from 'pocketbase';

// 開発環境でも本番環境でも変わらない
const pb = new PocketBase();

地味ですが,結構便利です.

Discussion