MERNフルスタックアプリをHerokuにデプロイする方法
はじめに
先日、MERNフルスタックのアプリケーションをローカルで開発したので、Heroku
にデプロイしようと思ったら、意外と参照する記事が少なかったので、備忘録的に記事にしようかと思います。
Herokuを選択した理由
HerokuはPaasの1つで無料でデプロイできるので選択しました。
AWSでもできますが、無料期間を過ぎたため、コストがかかるので、それは避けたいと思ったためです。
デプロイ
まずはMERNプロジェクトを編集します。
私の場合、プロジェクトの階層が以下のようになっています。
app
|
|_ _ client
| |_ src
|
|_ _ server
| |_ src
|
|_ _ package.json
|_ _ Procfile
後ほど、説明しますが、package.json
とProcfile
はHerokuのデプロイで必須となるので、作成しておきます。
サーバー側の編集
:::note info
npm run buildしたものをサーバーで読み込む
:::
デプロイではクライアント側で生成するbuild
フォルダをサーバーにインポートする必要があるので、そのための記述を加えます。
const path = require("path");
const app: express.Express = express();
app.use(express.static(path.resolve(__dirname, "../client/build")));
app.get("*", (request, response) => {
response.sendFile(path.resolve(__dirname, "../client/build", "index.html"));
});
上記を記述することで、アプリケーションがデプロイされた後、React Routerで定義されたルーティングにリクエストを送ることができます。
:::note info
ポートの設定
:::
次にポートの設定を行います。
const app: express.Express = express();
// Express FWによるサーバーの立ち上げ
const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
上記を追加します。
Herokuの場合、process.env.PORT
とすることでHerokuのアプリケーション立ち上げ時のポート番号を取得することができます。
ちなみに、ローカルでは4000番を使用していたので、このような記述になっています。
全体の記述としては以下のようになります。
import express from "express";
import mongoose from "mongoose";
import "dotenv/config";
const cors = require("cors");
const path = require("path");
const app: express.Express = express();
// CORS対応
app.use(
cors({
origin: "*",
exposedHeaders: [
"Content-Length",
"Authorization",
"Access-Control-Allow-Origin",
"Access-Control-Allow-Headers",
],
})
);
// Express FWによるローカルサーバーの立ち上げ
const PORT = process.env.PORT || 4000;
const url = process.env.MONGODB_URL as string;
// jsonオブジェクトを扱うため
app.use(express.json());
// エンドポイントからAPIを呼び出す
app.use("/api/v1", require("./src/v1/routes"));
// DB接続
try {
mongoose.set("strictQuery", true);
mongoose.connect(url);
console.log("DB接続");
} catch (e) {
console.log(e);
}
app.use(express.static(path.resolve(__dirname, "../client/build")));
app.get("*", (request, response) => {
response.sendFile(path.resolve(__dirname, "../client/build", "index.html"));
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Herokuではクロスオリジンの推奨としてワイルドカードを指定しますが、本来であれば、環境変数などで定義する必要があります。
以上が、サーバー側の設定です。
クライアント側の編集
こちらは記述を1行変更するだけで完了します。
:::note info
BaseURLの変更
:::
REACT_APP_API_BASE_URL='https://<app-name>.herokuapp.com/<自身のAPIエンドポイント>'
import axios from "axios";
const BASE_URL = process.env.REACT_APP_API_BASE_URL as string;
const axiosClient = axios.create({
// エンドポイントとなるURLのベース
baseURL: BASE_URL,
});
axiosClient.interceptors.response.use(
(response) => {
return response;
},
(err) => {
throw err.response;
}
);
export default axiosClient;
APIのエンドポイントとなるBASE_URL
をHerokuのものにするだけで、クライアント側の修正は終了です。
package.json
次に、先ほど少し登場したプロジェクトのルートディレクトリに作成したpackage.json
の中身を作成していきます。
内容としては、サーバー側のpackage.jsonとほとんど同じ大丈夫です。
{
// scriptsより前の記述はサーバー側のpackage.jsonをコピー
"scripts": {
"start": "node_modules/.bin/ts-node server/index.ts",
"start:dev": "nodemon --exec node_modules/.bin/ts-node server/index.ts",
"heroku-postbuild": "npm install --only=dev --no-shrinkwrap && npm install --prefix client && npm run build --prefix client"
}
// devDependenciesやdependencies等もサーバー側のpackage.jsonをコピー
}
scriptsの各内容を説明すると、以下のようになります。
- start・・・node_modulesのts-nodeを使用しindex.tsを起動(devDependenciesでts-nodeを記載しているため)
- start:dev・・・開発環境用の起動コマンド。Herokuでnodemonの起動をするとエラーが出力され、起動しない場合があるため(npm run start:dev で実行)
- heroku-postbuild・・・Herokuのデプロイで使用するスクリプト。
heroku-postbuild
の細かい内容は以下になります。
heroku-postbuildのコマンド内容
-
npm install --only=dev --no-shrinkwrap
・・・開発環境(devDependencies
)のみの依存関係をインストールする。--no-shrinkwrap
オプションはnpm-shrinkwrap.json
を無視して、package.json
から直接依存関係を読み取るように指示する -
npm install --prefix client
・・・clientディレクトリにある依存関係をインストールする。--prefix
オプションは、特定のディレクトリに対して、操作を実行するために使用する。 -
nnpm run build --prefix client
・・・clientディレクトrのbuildスクリプト(npm run build
)を実行する
Procfile
Procfileというのは、Herokuアプリを起動時に実行したいコマンドやタスク、Webサーバー等を定義するためのファイルです。
詳しくは以下、公式を参照ください。
内容ですが、1行のみで、アプリを起動するコマンドを記載します。
web: npm run start
上記を記述することでHerokuのデプロイプロセスが完了した際に、アプリケーションが起動します。
以上で、プログラムの設定は終了です。
Heroku CLI
次にターミナルでHerokuのコマンドをポチポチしていきます。
※Heroku CLIをインストール済という前提で進めます。
% heroku login
このコマンドでHerokuのログイン画面が立ち上がるので、ユーザー情報を入力して、ログインしてください。
次にHerokuのアプリケーションを作成していきます。
% cd your-project
% heroku create app-name
Creating ⬢ app-name... done
https://app-name.herokuapp.com/ | https://git.heroku.com/app-name.git
Creating・・・doneと表示されれば成功です。
表示されたURLにアプリケーションがデプロイされます。
:::note alert
環境変数を設定
※サーバーサイドプロジェクトで環境変数を設定している場合は必須
:::
私の場合、MongoDBにアクセスするURL等を環境変数としているため、以下のように設定しました。
% heroku config:set MONGODB_URL='mongodb+srv://<username>:<password>@clusterX.xxxxx.mongodb.net/?retryWrites=true&w=majority'
% heroku config:set SECRET_KEY='xxxxxxxxxxxxxxxx'
% heroku config:set TOKEN_SECRET_KEY='xxxxxxxxxxxxxxxx'
このようにして設定できます。
以下のようにして、値が出力されれば、正しく設定できています。
% heroku config:get MONGODB_URL
mongodb+srv://<username>:<password>@clusterX.xxxxx.mongodb.net/?retryWrites=true&w=majority
続いてここまでの変更をgitにプッシュします。
(HerokuのアプリケーションもHeroku上のmaster(main)ブランチにプッシュする形でデプロイするため)
そして、herokuのmaster(main)ブランチにもプッシュ(デプロイ)していきます。
% git add .
% git commit -m "Prepare for deployment to Heroku"
% git push heroku master
※自身のgitでmainブランチを使用している場合は以下のようにします。
% git push heroku main
以下のようにターミナルに出力されればデプロイ成功です。
emote: Verifying deploy... done.
To https://git.heroku.com/your-app.git
xxxxxxx..xxxxxxx main -> main
以下、コマンドでアプリケーションのWebページを開いてみましょう。
% heroku open
:::note warn
Application Errorとなった場合
:::
上記でデプロイ自体は完了ですが、APIのエンドポイントが違ったり、ポート周辺の記述が違うなど、デプロイ後に画面表示できずApplication Error
となる場合が多々あります。
その際は、以下のコマンドでログを出力してください。
% heroku logs --tail
これで、エラーの詳細を終えると思います。
以上で、MERNフルスタックアプリケーションのHerokuへのデプロイ方法についての解説を終了です。
参考になれば幸いです。
Discussion