本文書ではフロントエンドにReact、バックエンドにNode.js(Express)を使って構築したアプリケーションをHerokuにデプロイするまでの方法を説明しています。

問題なく構築したアプリケーションをHerokuにデプロイできれば気にすることはありませんが必ずしもうまくいくわけではなくエラーが発生した場合は各自でデプロイの問題を解決しなければならない時もあります。その際どうやってログを見ればいいのか、デプロイのキャンセルはどうやって行うかも説明を行っています。

node.js, npm, gitがインストールされているmacOS環境で動作確認を行っています。手順通りに進めればHerokuにReact+Node.jsのアプリケーションをインターネット上に公開することができます。

環境の構築

任意の名前のプロジェクトフォルダを作成してください。ここではfront_react_back_nodeとしています。作成したfront_react_back_nodeに移動してnpm init -yコマンドを実行してください。


 % mkdir front_react_back_node
 % cd front_react_back_node 
 % npm init -y

npm init -yコマンドを実行したfront_react_back_nodeフォルダには、package.jsonファイルが作成されます。

package.jsonにはインストールを行うJavaScriptのライブラリの情報だけではなくプロジェクトに必要な情報が保存されます。
fukidashi

バックエンドExpressサーバの構築

最初にExpressサーバの構築を行ってきます。front_react_back_nodeのフォルダにbackendフォルダを作成してください。

Expressライブラリのインストールを行います。


 % npm install express

インストール完了後にbackendフォルダを作成します。作成後backendフォルダに移動してindex.jsファイルを作成してください。


 % mkdir backend
 % cd backend

index.jsファイルにはExpressサーバ稼働に必要なコードを記述します。PORTについてはデプロイするHeroku側でポートが設定できるようにprocess.env.PORTを設定します。環境変数が取得できない場合は指定した3000で起動します。


const express = require('express')
const app = express()
const port = process.env.PORT || 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`listening on *:${port}`);
})

front_react_back_nodeフォルダからExpressサーバを起動します。nodeコマンドには作成したbackend/index.jsファイルを指定してくてください。


 % node backend/index.js
listening on *:3000

ブラウザからlocalhost:3000にアクセスするとHello World!が表示されます。Expressサーバが稼働していることが確認できます。

Hello Worldが表示
Hello Worldが表示

package.jsonファイルを開いて、scriptsにstartをを追加します。


  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node backend/index.js"
  },

追加後はnpm startでExpressサーバを起動することができます。


 % npm start

APIのエンドポイントの作成

フロントエンドReactからバックエンドExpress.jsサーバにfetch(またはaxios)を利用してアクセスするためアクセス先のエンドポイントをExpressサーバ側に準備します。/(ルート)にブラウザからアクセスすると文字列を戻していましたが、JSONで戻すように設定を行います。エンドポイントのURLは/apiとしています。


const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.get("/api", (req, res) => {
  res.json({ message: "Hello World!" });
});

app.listen(port, () => {
  console.log(`listening on *:${port}`);
})

ブラウザから/apiにアクセスするとJSONで戻されていることが確認できます。

/apiにブラウザからアクセスした結果
/apiにブラウザからアクセスした結果

エンドポイントの動作確認ができたのでReact側からこのエンドポイントにアクセスすることができるはずなので次はフロントエンドのReactの設定を行っていきます。

フロントエンドReactの設定

front_react_back_nodeフォルダにReactプロジェクトを作成します。プロジェクトフォルダの名前にはfrontendとつけています。これでfront_react_back_nodeフォルダにはExpressサーバのbackendとReactのfrontendのフォルダが存在することになります。


 % npx create-react-app frontend

作成したfrontendに移動してReactを起動します。Reactを起動するためにnpm startコマンドを実行するとport3000が使用済みであるというメッセージが表示されます。これはExpressサーバで同じポート3000を利用しているためです。

npm startコマンドはExpressサーバを起動している状態で実行してください。
fukidashi

%cd frontend 
%npm start
 ・
 ・
? Something is already running on port 3000. Probably:
  /usr/local/bin/node backend/index.js (pid 9200)
  in /Users/mac/Desktop/front_react_back_node

他のポートで起動するか聞かれまずが”n”を選択します。もし”Y”を選択して場合は3000ではなく3001のポートでReactが起動します。

ReactではなくExpressサーバのポートを変更します。ここでは3001に設定します。


const express = require('express')
const app = express()
const port = process.env.PORT || 3001
//略

Expressサーバのポートを3001に変更して、再度npm startを実行すると下記の画面が表示されます。

React初期画面
React初期画面

Reactの初期画面を変更するためにfrontend¥src¥App.jsを以下のように更新します。


import './App.css';

function App() {
  return (
    <div className="App">
      <h1>フロントエンド</h1>
    </div>
  );
}

export default App;

ブラウザで確認すると以下の画面が表示されます。

App.jsファイルの更新
App.jsファイルの更新

ReactからExpressサーバへのアクセス

useState, useEffectを利用

バックエンドのExpressサーバからデータを取得するためにfetchメソッドを利用しますが、fetchメソッドを利用する前にReactのuseState, useEffectを確認します。

useStateは変数を定義し値を保存することができるHookです。useEffectはコンポーネントをレンダリングする際にuseEffect内で設定した関数を実行することができるHookです。この2つを利用してExpressサーバからデータを取得します。

useEffectを利用しているのでレンダリング時に一度useEffectの中身が実行されます。fetchメソッドではURLにlocalhost:3001を指定しています(Expressサーバのポート)。取得したデータをuseStateで定義したmessageに設定します。Reactのプロジェクトフォルダであるfontend/srcのApp.jsを更新します。


import './App.css';
import { useState,useEffect } from 'react'

function App() {
  const [message, setMessage] = useState('');
  useEffect(() =>{
    fetch('http://localhost:3001/api')
      .then((res) => res.json())
      .then((data) => setMessage(data.message));
  },[])
  return (
    <div className="App">
      <h1>フロントエンド</h1>
      <p>{ message }</p>
    </div>
  );
}

export default App;

実際にブラウザでReactにアクセスしてもmessageは表示されません。デベロッパーツールのコンソールを見るとエラーを確認することができます。

エラーの内容ではlocalhost:3000からlocalhost:3001/apiへのfetchはCORSポリシーによってブロックされていると表示されています。

エラーが発生
エラーが発生

この問題を回避するためにReactのpackage.jsonファイルにproxyを設定します。


{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "proxy": "http://localhost:3001",
  //略

proxyを設定するのでApp.jsのfetchメソッドのURLもApp.jsに設定したhttp://localhost:3001/apiから/apiに変更することができます。


useEffect(() =>{
  fetch('/api')
    .then((res) => res.json())
    .then((data) => setMessage(data.message));
},[])

再度ブラウザでアクセスするとバックエンドサーバのエンドポイント/apiにアクセスして取得したHello Worldを確認することができます。

/apiから取得したデータを取得
/apiから取得したデータを取得

proxyを設定することによって何が行っているのか確認しておきます。app.jsファイルではfecth先を/apiを設定しているのでlocalhost:3000/apiへのアクセスが行われます。デベロッパーツールのネットワークタブでRequest URLを確認してもhttp://localhost:3000/apiにGETメソッドが送信されていることが確認できます。

http://localhost:3000/apiにアクセスしているように見えるので表面的にはわかりませんが、裏側ではReact側のproxyの設定によってhttp://localhost:3001/apiにアクセスが行われています。

ここまでの設定でフロントエンドのReactからバックエンドのExpressサーバに接続を行いデータを取得できることが確認できました。

Herokuへのデプロイの準備

Expressサーバにブラウザからアクセスがあった場合にReactの内容を表示できるように設定を行っていきます。

指定したパス内の静的ファイルをExpressサーバで扱えるようにexpress.staticを利用します。Reactのindex.htmlファイルはReactのビルドを行うとfrontend/buildの下に作成されるのでindex.jsファイルから存在するfront_react_back_nodeのパスを設定します。


app.use(express.static(path.join(__dirname, '../frontend/build')));

Expressサーバの/api以外のアクセスはすべてReactに渡せるように*(アスタリスク)設定も追加します。


const express = require('express')
const app = express()
const path = require('path');
const port = process.env.PORT || 3001;

app.use(express.static(path.join(__dirname, '../frontend/build')));

app.get("/api", (req, res) => {
  res.json({ message: "Hello World!" });
});

app.get('*', (req, res) => {
    res.sendFile(path.join(__dirname,'../frontend/build/index.html'));
});

app.listen(port, () => {
  console.log(`listening on *:${port}`);
})
app.get(“/api”,..)とapp.get(“*”,..)を順番を逆にするとapp.get(“*”,..)が優先されてapp.get(“/api”,..)に記述した処理は実行されません。/apiにアクセスしてもReactのindex.htmlファイルにアクセスすることになります。
fukidashi

frontendフォルダに移動して、Reactのビルドを行います。


% npm run build

ビルドが完了するとfrontendフォルダにbuildフォルダが作成されその中にindex.htmlファイルが作成されていることが確認できます。

buildが完了したら、front_react_back_nodeでnpm startコマンドを実行してください。


 % npm start

> front_react_back_node@1.0.0 start
> node backend/index.js

listening on *:3001

ブラウザでlocalhost:3001にアクセスすると下記の画面が表示されます。

Reactの内容が表示
Reactの内容が表示

最後にHeroku上でビルドを行えるようにfront_react_back_nodeのpackage.jsonファイルのscriptsに以下を追加します。


"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "start": "node backend/index.js",
  "heroku-postbuild": "cd frontend && npm install && npm run build"
},

Herokuのデプロイ

Herokuのアカウント作成

Herokuへのデプロイを行うためにはHerokuのアカウントの作成とHeroku CLIのインストールが必要となります。

Herokuのアカウントの作成とHeoku CLIのインストールについてはこちらで解説を行っています。

Heroku CLIとgitを使ってデプロイ

Heroku CLIとgitコマンドを利用してHerokuのデプロイを行います。

frontendフォルダに移動して、.gitフォルダを削除してください。.gitフォルダが存在するとHerokuでReactのビルドが行われません。


 % rm -rf .git

git initコマンドをfront_react_back_nodeフォルダで実行してgitリポジトリの初期化を行います。実行すると.gitフォルダが作成されます。


 % git init

git add .コマンドで実行したフォルダ下のすべてのファイルをステージングエリアに移動しcommitの対象とした後、commitでメッセージをつけてリポジトリに登録します。


 % git add . && git commit -m "init"

Herokuへのログインを行います。


 % heroku login
heroku: Press any key to open up the browser to login or q to exit:

ログインが完了したら、任意の名前のプロジェクトをHeroku上に作成します。


 % heroku create front-react-back-node
Creating ⬢ front-react-back-node... done
https://front-react-back-node.herokuapp.com/ | https://git.heroku.com/front-react-back-node.git

git pushコマンドを利用してHerokuのリモートリポジトリにローカルのファイルをプッシュします。remote: Verifying deploy… done.が表示されることを確認します。


 % git push heroku master
 //略
 remote: Verifying deploy... done.
To https://git.heroku.com/front-react-back-node.git
 * [new branch]      master -> master
git push heroku masterの実行に失敗した場合(デプロイの失敗ではなくコマンドが実行できない)はheroku git:clone -a front-react-back-nodeを行い再度実行してください
fukidashi

デプロイが成功するとローカルで動作確認したようにフロントエンドの文字列の下にバックエンドのExpress.jsの/apiにfetchでアクセスして取得したHello World!が表示されます。

Reactのコンテンツが表示
Reactのコンテンツが表示

URLはlocalではなくfront-end-back-node.herokuapp.comという名前になっており、インターネット上に公開されています。公開が完了しているのでどこからでもアクセスすることが可能となります。

ブラウザにNot Foundが出た場合

デプロイが成功したのにも関わらずブラウザでアクセスするとNot Foundが表示された場合はエラーログを確認します。コマンドラインであればherokuにロウインした状態でheroku logsで確認することができます。ログが出力されるのでメッセージを確認してください。問題の原因となるヒントがあるはずです。


 % heroku logs

ダッシュボードであれば右側のOpen appボタンの右にあるMoreをクリックするとメニューが表示されるので、View logsを選択します。

ダッシュボードからログを確認
ダッシュボードからログを確認

エラーメッセージの中にError: ENOENT: no such file or directory, stat ‘/app/frontend/build/index.html’が出力されReactで作成したコンテンツが表示されない場合はindex.jsのexpress.staticで設定したパスが間違っていないかまたReactのfrontendフォルダのビルドが行われていない可能性があります。その場合はHerokuに接続しコマンドラインを利用して指定しているファイルが存在するか確認することができます。このようにコマンドラインを利用してHerokuにアクセスすることも可能です。


% heroku login
% heroku run bash -a front-react-back-node
Running bash on ⬢ front-react-back-node... up, run.7617 (Free)
%ls 
backend  frontend  node_modules  package-lock.json  package.json
%cd frontend
$ ls
README.md  build  node_modules  package-lock.json  package.json  public  src
frontendのフォルダが空の場合はローカルのfrontendの.gitフォルダを削除して再度gitを使ってpushを行ってみてください。
fukidashi

Herokuのビルドの停止

途中何かの記述ミスがによりビルドが終わらない場合はコマンドラインを利用してビルドを停止することができます。コマンドラインで停止するためにはheroku-buildsをインストールします。


 % heroku plugins:install heroku-builds
Installing plugin heroku-builds... installed v0.0.29

下記のコマンドで現在のビルドの状況を確認することができます。オプション-aの後ろにプロジェクト名を指定します。


% heroku builds:info -a front-react-back-node
=== Build 71154f89-3ab8-4830-8932-ce2a9adfff19
By:         orangesky2021@gmail.com
Status:     pending
When:       2021-02-13T00:54:59Z

ビルドのキャンセルは以下のコマンドで実行します。こちらも-aの後ろにプロジェクト名を指定します。


 % heroku builds:cancel -a front-react-back-node
Stopping build 71154f89-3ab8-4830-8932-ce2a9adfff19... done

再度heroku builds:infoコマンドでステータスを確認するかブラウザからHerokuにアクセスし、latest ActivityをみてBuild failedが表示されていればキャンセルは完了しています。