この記事では以下のようなWebアプリを作る手順を解説します。
- バックエンドはNode.js 
- フロントエンドはReact 
- バックエンドもフロントエンドもTypeScript使用 
完成するWebアプリは、画像と文字だけを表示する簡単なものです。
その代わりに、より確実なコードを書いていくことを重視する内容となっております。
解説の流れは以下です。
- Node.jsをインストールする 
- プロジェクトフォルダを作る 
- バックエンドを作る準備 
- tsconfig.jsonを作る 
- Expressを導入する 
- Expressを使ってみる 
- ts-nodeでサーバーを起動 
- nodemonでサーバーをリアルタイムで更新 
- バックエンドをデータの置き場にする 
- Reactプロジェクトを作る 
- Expressサーバーにアクセスする 
- proxyを設定する 
では、順番にやっていきます。
Node.jsをインストールする
Node.jsやReactを使って開発するには「Node.js」をパソコンにインストールします。
Windowsの場合は、Node.jsの公式サイトからパッケージをダウンロードしてインストールしてください。
ダウンロードするのは「推奨版」でいいです。
Macの場合も、Windowsのやり方でNode.jsをインストールできます。
ですが、Macならもっと便利な方法でNode.jsをインストールできます。
もしMacをお使いなら、以下の記事を参考にされてください。

プロジェクトフォルダを作る
では、好きな場所にフォルダを作ってください。
そのフォルダの中に2つのフォルダを作ります。
名前は何でもいいです。
今回は例として「nodejs_react_app」というフォルダを作り、その中に「frontend」と「backend」という名前のフォルダを作りました。
そして「nodejs_react_app」をエディタで開きます。
この記事では、エディタとしてVSCodeを使っていきます。

VSCodeだと、上の画像のような感じになっているかと思います。
バックエンドを作る準備
まずはNode.jsでバックエンドを作っていきます。
Windowsならコマンドプロンプト、Macならターミナルを開いてください。
そして、「backend」に移動します。
cd nodejs_react_app/backend移動したら、以下を実行します。
npm init -yyarnを使う場合は以下です。
yarn init -y
すると、「backend」の中に「package.json」が作られます。
tsconfig.jsonを作る
次にTypeScriptを使う準備をします。
2つのパッケージをインストールします。
- typescript 
- @types/node 
npm install -D typescript @types/nodeyarnなら以下。
yarn add -D typescript @types/node次に、以下のコマンドを実行します。
npx tsc --inityarnなら以下。
yarn tsc --initすると、「backend」の中に「tsconfig.json」が作られます。
「tsconfig.json」はTypeScriptに関する設定を書くところです。
「tsconfig.json」の中身を見てみると、すでにいくつか記述があるかと思います。
しかし、コメントアウトばかりでごちゃごちゃしてますよね。
「tsconfig.json」の中身をいったん全部消してください。
そして、今回は以下のように書きます。
{
  "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}デフォルトのままほとんど変えていません。
変えたのは「"target": "esnext"」の部分だけです。
tsconfig.jsonの中身をどう書くべきかは、色々な考え方があります。
今回は必要最低限な設定だけにしました。
Expressを導入する
「Express」はNode.jsのフレームワークです。
Expressがあれば、Node.jsとReactの接続が簡単です。
では、Expressをインストールします。
ターミナルで以下のコマンドを実行しましょう。
npm install expressyarnの場合は以下。
yarn add express今回はTypeScriptを使うため、以下も実行します。
npm install -D @types/expressyarnの場合は以下。
yarn add -D @types/expressそして、「backend」の中に「server.ts」というファイルを作ります。

VSCodeだと、上の画像のような状態になります。
別に名前は「server.ts」じゃなくても構いません。
ただ、Expressを使った開発では「server.ts(server.js)」という名前のファイルをメインに使うことが多いような気がします。
「server.ts」がメインのファイルとなるので、「package.json」にもそれを明示しておきましょう。

「package.json」の中に、「"main": "index.js"」と書かれているところがあると思います。
そこを、「"main": "server.ts"」に書き換えます。
これを書かないことで何かエラーが出るわけではないです。
具体的には、「パッケージとしてnpmに公開するとき」に必要になってくる記述です。
今回はあまり気にしなくていいです。
では、「server.ts」にコードを書いていきます。
/* server.ts */
import express from "express";
const app: express.Express = express();これで、Expressを使う準備が整いました。
Expressを使ってみる
では、Expressを使って簡単な文字列を出力するアプリを作ってみます。
「server.ts」にコードを追加しましょう。
/* server.ts */
import express from "express";
const app: express.Express = express();
const port = 8000;
app.get("/", (req: express.Request, res: express.Response) => {
  res.send("Hello, world!");
});
app.listen(port, () => {
  console.log(`port ${port} でサーバー起動中`);
});このコードはExpressの公式サイトで紹介されているものをアレンジしました。
portは「3000」でも「4000」でも「5000」でも何でもいいです。
ただ、「3000」にすると話がややこしくなります。
できれば「3000」以外がいいです。
ここでは「8000」にしました。
では、サーバーを起動してみます。
ターミナルで以下のコマンドを実行します。
node server.tsこれでサーバーが起動するはずです。
しかし、エラーが出たかと思います。
import express from "express";
^^^^^^
SyntaxError: Cannot use import statement outside a module上記のような内容のエラーです。
「import express from "express";」の部分がエラーになっているようです。
これは、「package.json」に記述を追加することでとりあえず解消できます。

追加するのは「"type": "module"」という記述です。
これでもう一度サーバーを起動してみます。
先ほどのエラーは出なくなるはずです。
ですが、またエラーになったかと思います。
以下のような内容のエラーです。
node:internal/errors:477
    ErrorCaptureStackTrace(err);
    ^
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for ...普通のJavaScriptであれば上手くはずなんです。
ですが、今回はTypeScriptを使っています。
そのため「サーバーが起動しない」という状態です。
そこで「ts-node」を使います。
ts-nodeでサーバーを起動
「ts-node」は、TypeScriptでNode.jsを動かすための実行エンジンです。
というわけで、ts-nodeをインストールしましょう。
npm install -D ts-nodeyarnの場合は以下。
yarn add -D ts-nodeでは、ts-nodeを使ってサーバーを起動してみます。
以下のコマンドを実行してください。
npx ts-node server.tsyarnの場合は以下。
yarn ts-node server.tsどうでしょうか...。
...またまたエラーですね。
そこで、再び「package.json」を書き換えます。

先ほど「"type": "module"」と書いていた部分を「"type": "commonjs"」に書き換えてください。
これでようやくサーバーを起動できます。
「じゃあなんでさっき"type": "module"って書いたんだよ!」
って感じですよね...。
ややこしくてすみません。
「ts-nodeを使うので、さっきとは状況が変わった」と考えてください。
ではサーバーを起動します。
以下のコマンドを実行してください。
npx ts-node server.tsyarnの場合は以下。
yarn ts-node server.ts「ts-node」の前には「npx」や「yarn」をつけないといけません。
サーバーが起動すると、以下のように表示されると思います。
port 8000 でサーバー起動中では、ブラウザで「localhost:8000」にアクセスしてみてください。
「Hello, world!」という文字が表示されたかと思います。
Expressで作ったアプリが上手く動いたということです。
今度は試しに「Hello, world!」という文字列を書き換えてみましょう。
以下のように書き換えてみます。
/* server.ts */
import express from "express";
const app: express.Express = express();
const port = 8000;
app.get("/", (req: express.Request, res: express.Response) => {
  res.send("Hello, Node.js!"); /* ←「"Hello, Node.js!"」に書き換える */
});
app.listen(port, () => {
  console.log(`port ${port} でサーバー起動中`);
});「Hello, world!」と「Hello, Node.js!」に書き換えてみました。
しかし、ブラウザの表示は「Hello, world!」のままになっているはずです。
そこでサーバーを一度終了させます。
Windowsの場合はキーボードで「Ctrl + "c"」、Macの場合は「control + "c"」でサーバーを終了できます。
そして、またサーバーを起動します。
すると、文字が「Hello, Node.js!」に変わっているはずです。
このままでもいいんですが、いちいちサーバーを終了させて、また起動して...ってのが面倒ですよね。
そんなときに便利なのが「nodemon」です。
nodemonでサーバーをリアルタイムで更新
「nodemon」を使えば、先ほどの「サーバーを終了させて、また起動して...」の作業が不要です。
さっそくインストールします。
npm install -D nodemonyarnの場合は以下。
yarn add -D nodemonそして、nodemonを使ってサーバーを起動します。
npx nodemon server.tsyarnの場合は以下。
yarn nodemon server.tsこれでサーバーが起動します。
軽く説明すると、
「nodemonがts-nodeを使ってサーバーを起動してくれる」
という状態です。
ts-nodeがインストールされていれば、nodemonが勝手にts-nodeを使ってくれるみたいですね。
これで、また「localhost:8000」にアクセスしてみると「Hello, Node.js!」という文字が表示されているかと思います。
ここで、一度サーバーは終了させましょう。
もうひと手間加えます。
サーバーを起動するときに、いちいち「npx nodemon server.ts」って入力するの面倒ですよね。
そこで、「package.json」に記述を加えます。

上の画像のように、「"start": "nodemon server.ts"」という記述を加えてください。
これにより、以下のコマンドでサーバーを起動できるようになります。
npm startyarnの場合は以下。
yarn startでは、また「server.ts」を編集して「Hello, Node.js!」を「Hello, world!」に書き換えて保存してみましょう。
すると、ターミナルに以下のような表示が出ると思います。
[nodemon] restarting due to changes...
[nodemon] starting `ts-node server.ts`
port 8000 でサーバー起動中「server.ts」が書き換えられたことで、nodemonが自動でサーバーを再起動してくれました。
ブラウザを更新してみると、「Hello, world!」と表示されるはずです。
バックエンドをデータの置き場所にする
色々回り道をしましたが、ようやくバックエンドが整いそうです。
今はバックエンドで「Hello, world!」という文字列を表示しているだけです。
ここからは、以下の流れとなります。
- バックエンド(Express)をデータの置き場所にする 
- そのデータをフロントエンド(React)で表示する 
「どんなデータを置くの?」って感じですよね。
ここでは「果物屋さんのサイトを作る」を想定します。
思ったよりショボくなるかもですが、許してください...。
ここでいう「データ」は以下とします。
- 果物の番号 
- 果物の名前 
- 果物の値段 
- 果物の画像 
上記のデータを「Reactで受け取って、CSSで飾って表示させる」というイメージです。
では進めていきましょう。
server.tsを書き換えます。
/* server.ts */
import express from "express";
const app: express.Express = express();
const port = 8000;
app.get("/", (req: express.Request, res: express.Response) => {
  res.send("Hello, world!");
});
/* ↓これ追加 */
app.get("/api", (req: express.Request, res: express.Response) => {
  res.json([
    {
      id:1,
      name:"りんご",
      price:200,
      image:"https://source.unsplash.com/gDPaDDy6_WE",
    },
    {
      id:2,
      name:"バナナ",
      price:300,
      image:"https://source.unsplash.com/zrF6ACPLhPM",
    },
    {
      id:3,
      name:"みかん",
      price:"150",
      image:"https://source.unsplash.com/bogrLtEaJ2Q",
    },
    {
      id:4,
      name:"メロン",
      price:"2000",
      image:"https://source.unsplash.com/8keUtGmy0xo",
    },
  ]);
});
app.listen(port, () => {
  console.log(`port ${port} でサーバー起動中`);
});少し長めですが、上記のようにコードを追加します。
果物は以下の4つです。
- りんご 
- バナナ 
- みかん 
- メロン 
今回はURLを「"/api"」としました。
画像は「Unsplash」を利用しております。

これで、バックエンドは完成です。
次はフロントエンドを作っていきます。
次からは「バックエンド用のターミナル」と「フロントエンド用のターミナル」が必要です。
Windowsの場合は、コマンドプロンプトで以下を実行します。
startこれで、コマンドプロンプトがもうひとつ開きます。
Macでは、キーボードの「command + "n"」でターミナルがもうひとつ開きます。
2つのコマンドプロンプト(ターミナル)を使い分けて進めてください。
Reactプロジェクトを作る
ここからはフロントエンドを作っていきます。
フロントエンド用のターミナルで、「nodejs_react_app」の中の「frontend」というフォルダに移動してください。
cd nodejs_react_app/frontend移動したら、以下のコマンドを実行します。
npx create-react-app . --template typescriptyarnの場合は以下。
yarn create react-app . --template typescriptこれで、Reactプロジェクトが作られます。
先にCSSを書いておきます。
「index.css」というファイルの中身を全て削除して、以下のように書いてください。
/* index.css */
body {
  margin: 0;
  color: #555555;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
  "Oxygen","Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
h1, h2, p, figure {
  margin: 0;
  padding: 0;
}
img {
  max-width: 100%;
  height: auto;
  vertical-align: bottom;
}
.container {
  width: 92%;
  max-width: 720px;
  margin: 0 auto;
}
.fruitsList {
  display: grid;
  gap: 30px;
  padding-block: 30px;
}
.fruitsList h1 {
  justify-self: center;
  grid-column: 1 / -1;
}
.fruitsList img {
  aspect-ratio: 4 / 3;
  object-fit: cover;
}
.text {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 10px;
}
.text p {
  font-size: 36px;
}
@media (min-width: 768px) {
  .fruitsList {
    grid-template-columns: 1fr 1fr;
  }
}あくまで一例ですので、CSSはお好みで変えてもらってもいいです。
「App.tsx」も開いて中身を削除し、以下のように書いてください。
/* App.tsx */
import * as React from "react";
const App: React.FC = () => {
  return (
    <div className="container fruitsList">
      <h1>Fruits Store</h1>
    </div>
  );
};
export default App;では、Reactの開発サーバーを起動してみましょう。
フロントエンド用のターミナルで以下のコマンドを実行します。
npm startyarnの場合は以下。
yarn startブラウザで「localhost:3000」が自動で開くと思います。
「Fruits Store」という文字が真ん中の上あたりに表示されていればOKです。
Expressサーバーにアクセスする
では、Expressで作ったバックエンドのデータをReactで表示していきます。
/* App.tsx */
import * as React from "react";
type Fruit = {
  id: number;
  name: string;
  price: number;
  image: string;
};
const App: React.FC = () => {
  const [fruits, setFruits] = React.useState<Fruit[] | null>(null);
  React.useEffect(() => {
    const fetchData = async () => {
      try {
        const res = await fetch("http://localhost:8000/api");
        const json: React.SetStateAction<Fruit[] | null> = await res.json();
        setFruits(json);
      } catch (e: unknown) {
        if (e instance of Error) {
          console.error(e.message);
        }
      }
    };
    fetchData();
  }, []);
  return (
    <div className="container fruitsList">
      <h1>Fruits Store</h1>
      {fruits?.map((fruit) => (
        <div key={fruit.id}>
          <figure>
            <img src={fruit.image}alt={fruit.name}/>
          </figure>
          <div className="text">
            <h2>{fruit.name}</h2>
            <p>{`¥${fruit.price}`}</p>
          </div>
        </div>
      ))}
    </div>
  );
};
export default App;Reactで外部からデータを取得するので、「fetch関数」を使っています。
さらに、データ取得のときは「useState」と「useEffect」を使います。
「データがない状態」から「データがある状態」に変化させるため「useState」を使います。
さらに、データ取得の処理は「コンポーネントをマウントしたときだけ」行うので、「useEffect」を使います。
そうしないとデータ取得のループが起きて、バグ発生となります。
では、データが上手く表示できるか確認してみます。
まず、バックエンドの方でサーバーを起動します。
バックエンド用のターミナルで以下のコマンドを実行します。
npm startyarnの場合は以下。
yarn start同じように、フロントエンドの方もサーバーを起動してください。
ブラウザが開いて、「localhost:3000」に自動でアクセスするはずです。
上手く表示されたでしょうか。
...多分、「Fruits Store」の文字しか表示されないはずです。
何かしらのエラーが発生しているようです。
そこで、ブラウザのデベロッパーツールを開いてみます。

すると、上記のようなエラー表示が出ているかと思います。
簡単に要約すると以下です。
「CORSポリシーにより、"http://localhost:8000/api"にアクセスことはできません」
意味不明ですよね。
とりあえず、今のままでは上手くいかないわけです。
proxyを設定する
そこで、React側の「package.json」に記述を加えましょう。

上の画像のように、「"proxy": "http://localhost:8000"」という記述を加えます。
さらに、「App.tsx」も書き換えます。
/* App.tsx */
... 略 ...
  image: string;
};
const App: React.FC = () => {
  const [fruits, setFruits] = React.useState<Fruit[] | null>(null);
  React.useEffect(() => {
    const fetchData = async () => {
      try {
        /* ↓「http://localhost:8000」を消す */
        const res = await fetch("/api");
        const json: React.SetStateAction<Fruit[] | null> = await res.json();
        setFruits(json);
      } catch (e: unknown) {
        if (e instance of Error) {
          console.error(e.message);
        }
      }
    };
    fetchData();
  }, []);
  return (
    ... 略 ...
  );
};
export default App;fetch関数の中の「"http://localhost:8000/api"」を「"/api"」に書き換えます。
そして、フロントエンドのサーバーを一度終了させてもう一度起動してください。

上の画像のように表示されればOKです。
これでサンプルアプリの完成となります。
最後に
お疲れ様でした。
最後までお付き合いいただき、ありがとうございます。
記事のボリュームのわりには、サンプルアプリのクオリティがショボくて申し訳ないです。
ですが、「よりリアルな開発」をご紹介できたかなと思います。
始めから正解のコードを書くのではなく、わざとエラーを起こしてそれにどう対応するかを体感することが合理的なのかなと個人的には考えています。
そんな考え方を持った上で、ブログを執筆しております。
他の記事も読んでいただけると嬉しいです。
というわけで記事は以上です。
