病みつきエンジニアブログ

機械学習、Python、Scala、JavaScript、などなど

API と SPA からなるプロジェクト(JAMstack)を作るベストプラクティス

背景

サーバーサイドの人がフロントエンドを結構こだわったプロジェクトをやるときに、混乱することが多そうなので、(社内向けに) 自分がいろいろ作ったなかで思ったベストプラクティス(?)を共有する

用語

もう少しちゃんとした説明は、各キーワードでググって下さい。

サンプルリポジトリ

https://github.com/yamitzky/nuxt-api-example

実装は分割してプルリクエストにした

前提

プロジェクト全体の前提としては、

  • 「SPA と API」という構成にする
  • フロントエンドは Nuxt などのレールに乗せる
  • (ほぼ)同じオリジンとして扱う

そもそもフロントエンドは独立したコードベース (≒ SPA) にしたが良いのか?

例えば管理画面を作るときなど、例えば DjangoRails などで SPA ではない構成を取ることが多いと思う。ケースバイケースだが、フロントエンドを独立したコードベースの SPA にすると下記のようなメリットがある。

  • フロントエンドのコードが複雑になったとき、破綻しづらい
    • コンポーネント指向な設計になる
    • Flux なり Vuex なり、状態管理の設計が強制される
  • フロントエンドのエコシステムに乗っかれる
    • ホットリロード
    • エディタサポート
  • JAMStackのメリットが得られる
  • ようするに餅は餅屋

ただし、 qrunch.io は Rails と turbolinks ではあるが、SPA のようなサクサクしたユーザー体験を提供できている、という例もある。ので、結局好みでは。

ディレクトリ構成

ディレクトリは次のような構成にする。api 以下は Python とかの世界。frontend 以下は Nuxt とかの世界。「frontendプロジェクト」と「apiプロジェクト」の2つのリポジトリがあるモノレポみたいなもの。

こうすると、Docker のイメージが効率良く作りやすくなる、それぞれのホットリロードが正しく効くとかのメリットがある。

project
├── api
└── frontend

配信構成

nginx、api の2つのコンテナから成り立たせる。

nginx (example.com)
  -> /api/* -----> api のコンテナ
  -> それ以外 --> 静的ファイルとしてフロントエンドを配信

あるいは、静的ファイルは CDN にデプロイする

example.com ------> CDN 上で配信
api.example.com --> api のコンテナ

JAMstack としては、静的ファイルは CDN に乗せるのが良さそうなんだけど、SSR ができないというデメリットもあるので、どちらでも良さそう。(パフォーマンス要件などに応じて適当に考える)

ここで大事なのは、*Django などの APIフレームワークは一切の静的ファイルを配信しないことJSONを吐き出すのが責務

開発時の配信構成

docker-compose などで、 frontend のコンテナと api のコンテナがある想定

frontend (localhost:3000)
  -> /api/* -----> http://api:8080/* をプロキシ
  -> それ以外 --> frontend のプロジェクトとして普通に配信

あるいは、docker-compose を使いたくないなら

frontend (localhost:3000)
  -> /api/* -----> http://localhost:8080/* をプロキシ
  -> それ以外 --> frontend のプロジェクトとして普通に配信

frontend のプロジェクトには、たいてい、開発用にプロキシする機能がついている(nuxt, vue-cli)。

デバッグするときに見るのは、 localhost:3000 だけ。そうすると CORS とか Cookie の問題が解消するので、ハッピーです。

Nuxt 使おうぜ話

概ね、 Nuxt のレール乗っかっておくと非常に楽。

  • 設計が強制される
  • SPA のために必要な機能が揃ってる
  • store のモジュール化なども自動で対応してくれて便利
  • ~/hoge でアクセスできたりして便利
  • SSR も対応
  • ビルドパイプラインのメンテが簡易

もちろん Nuxt 以外のレールでももちろん良いが、 Vue は easy と言われることもあり、学習コストが低めで良いのではないか、というスタンス。

認証

Nuxt の認証はだいたい3パターンぐらいあって、

セッションや JWT を使うのは、Nuxt じゃない技術選定をしても、だいたいそれらをどう保持するかという話になりそう。localstorage かセッション、Cookie に保持する。

TODO

  • PWA、キャッシュ

おまけ:環境変数とか

  • yarn
  • pipenv
  • .env
  • direnv

を使っている。

サンプルプロジェクトだと、あえて .env と .envrc をリポジトリーに追加している。

.env と direnv の使い分けは、

  • .env: 環境変数を定義する。 JetBrains 系、VSCode などのエディターが .env をサポートしているため
  • direnv: .env に定義された環境変数や、pipenv --venv で得られる環境変数をロードする。.envrc には、環境変数の定義自体はしない
# .envrc

source `pipenv --venv`/bin/activate
export `cat .env`