🍛

フロントエンドとバックエンドを分離した際のWebアプリケーションの構成(冗長化なし)

2022/11/19に公開

はじめに

昨今、Web アプリケーションフレームワークが日々進化しており、誰でも Web アプリケーションが簡単に構築できるようになってきました。
また、この進化の過程で、少し前まではフロントエンドもバックエンドを全て一つのフレームワークの中で実装するのが主流だでしたが、最近はフロントエンドとバックエンドを分離して開発することが増えてきているように感じています。

現在、私はフロントエンドをNext.js、バックエンドをNest.jsで構築するようにしています。しかし、このように分けると、どのように組み合わせて一つの Web アプリケーションとしてデプロイすれば良いか最初は困ったものです。
Chrome だと CORS エラーにも引っかかるし...
意外に記事もあまり見かけないので、デプロイ方法は言わずもがななのかもしれないですね。

今回、私がどのようにして、これらを 1 つのアプリケーションとしてデプロイしているのかを紹介します。

※ あくまで冗長化なしのデモレベルの構成です。本番環境でのアーキテクチャは別途記事にまとめる想定である。

あえて、結論から

以下の構成で環境を構築。

想定読者

  • Next.jsを勉強していて、まだバックエンドとの連携の仕方がわからない方

構成図の説明

私が利用するデモ環境では、Nginxを Web サーバとして配置し、その後ろにフロントエンドアプリNext.jsとバックエンドアプリNest.jsを配置しています。
その上で Nginxに来たリクエストを以下のように各アプリケーションに振り分けています。

URL 振り分け先
http://<web-server>/ Next.js (http://<frontend>:3000/)
http://<web-server>/api/ Nest.js (http://<backend>:3000/api)

興味があれば実際の Nginx の設定を以下に載せてますので、ご参照ください。

Nginx の設定

以下の conf ファイルの中で、「location /」と「location /api」を設定している箇所が振り分け処理を設定している部分です。

/etc/nginx/conf.d/default.conf
server {
  listen 80;
  server_name localhost;

  client_max_body_size 100M;
  charset utf-8;
  access_log /var/log/nginx/default.access.log main;
  error_log /var/log/nginx/default.error.log;

  proxy_redirect off;
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-Host $host;
  proxy_set_header X-Forwarded-Server $host;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

  location / {
    proxy_pass http://frontend:3000;
  }

  location /api {
    proxy_pass http://backend:3000;
  }

  location /_next/webpack-hmr {
    proxy_pass http://frontend:3000/_next/webpack-hmr;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  }
}

また、おまけではありますが、「location /_next/webpack-hmr」の部分は、next.jsで"npm run dev"を実行して開発サーバを起動した際に、Next.js内のファイル更新に伴って Nginx経由でもブラウザの画面が更新されるようにするために入れています。
これがないと"npm run dev"の場合は、ブラウザのコンソールにエラーが出続けます。

その他の説明は今回は割愛します。

本構成におけるメリット

私がこのように構成する理由としては、以下のようなメリットがあるからです。

Next.js の API ルートのファイルをそのままにデプロイできる

Next.jsには、API ルートという機能があります。
これは、バックエンドを別で準備しなくてもNext.jsでバックエンドの API を提供できる仕組みです。

https://nextjs-ja-translation-docs.vercel.app/docs/api-routes/introduction

「じゃあ、バックエンドもNext.jsで実装すればいいじゃん」という方もいるかもしれませんね。
ただ、バックエンドは別のフレームワークを使ったほうが、楽に構築することができるので、私は分けてます。

ただ、フロントエンドとバックエンドを完全に分離して開発を進める上で、私はモック API を作成する手段としてこの機能を使っています。
これを使うことで、バックエンドの開発完了を待たずして、Next.jsだけで API 呼び出しからの画面表示までのチェックができるのです。

以下の記事が参考になるかもしれません。

https://qiita.com/roana0229/items/547437b6314fd283ddca

ただ、モック API をNext.jsで作成するのはいいですが、無駄なコードが本番環境にデプロイされて、このモック API がクライアント PC から呼び出されるのは防がないといけません。
この役割を担うのが、Nginxになるわけです。

Next.jsの API ルート機能では、/api配下に作成したファイルをそのまま API として提供してくれるのですが、Nginx/apiへのリクエストは全てNest.jsに割り振るようにします。
こうすると、わざわざ本番では使わないモック API を削除する必要もなく、そのままデプロイできますよね。

これが 1 つ目のメリットです。
無駄なコードが本番に置かれるのは嫌だという人もいると思うので、別のツールを使ってモック API を準備するでも問題ないと私は思います。

CORS エラーに引っかからない

最初にも記載しましたが、Next.jsNest.jsでアプリケーションサーバを起動して API リクエストをNest.jsに向けると、CORS エラーになって動作検証できません。
これはブラウザの機能なのですが、異なるドメインにブラウザから同時にアクセスしようとすると出る厄介なエラーです。(説明が正確ではないかも...)

Next.jsNest.jsに設定を追加して回避する方法はあるものの、別のバックエンドのアプリケーションサーバを作成するときに、また CORS 対策するのは面倒ですよね。

これは、ブラウザから複数のドメインにアクセスするのが問題なので、ブラウザからアクセスする先を一つにしてしまえば良いのです。もう察していると思いますが、ブラウザからのアクセス先をNginxだけにしているのが、今回の構成です。

これにより、面倒な CORS エラーに引っかからなくなります。

新しいアプリケーションサーバが増えたら Nginx の後ろに配置してあげるだけで良い

特にバックエンドの API サーバが増えた時に、Next.jsのリクエスト先のホスト名を適切に切り替えてあげるのはめんどくさいですよね。

今回の構成では、Nginxがクライアントから見た時の唯一のホストになるため、Next.jsでは「/api/...」へのリクエストを投げていればホスト名を意識する必要がなくなります。
あとは、URL のパスに応じてNginxがサーバを振り分けてくれればいいのです。

実際にパスによって切り替えるNginxの設定を以下に記載してますので、興味があればご参照ください。

Nginx の設定

locationの部分だけを記載します。それ以外は前の設定と変わりません。

/etc/nginx/conf.d/default.conf
server {
  ...

  location / {
    proxy_pass http://frontend:3000;
  }

  location /api {
    return 404;
  }

  location /api/a {
    proxy_pass http://backend-a:3000;
  }

  location /api/b {
    proxy_pass http://backend-b:3000;
  }

  location /_next/webpack-hmr {
    proxy_pass http://frontend:3000/_next/webpack-hmr;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  }
}

今回は、/api/a/api/bにそれぞれアクセスすると違うバックエンドが呼ばれるようになっています。
また、この両方にもマッチしない/apiから始まる URL がアクセスされた場合は、Nginx404を返すようにしています。

これは、想定していない API が叩かれた場合に、Next.jsに API リクエストが飛んでしまうことを避けています。
特段、モック API で想定していない API を実装していなければ問題はないのですが、念のためというところです。

さいごに

今回、フロントエンドとバックエンドを分離した際の Web アプリケーションの構成という題材でお話ししました。
主にデモ環境を想定しており、冗長化等は一切考慮していません。

ただ、AWS にデプロイする場合などは、Nginxの部分がALBになって、アプリケーションサーバをAuto ScalingEC2に実装してあげれば、いいのかなとか思ったりしてます。

ここら辺はこれから検証するので、問題なく実現できれば別途記事にまとめたいと思います。

また、今回はあくまで構成例としてあげさせてもらい、実装例はほとんど載せていません。
ここら辺については、Docker 環境での実装例を別途記事にしようと思います。

余談ですが、NginxNext.jsNest.jsも名前が似ていて文字だけだと区別つきにくいですね...

まだまだ新米のエンジニアなので、何でもコメントをいただけると幸いです。

Discussion