フロントエンドとバックエンドの連携とデプロイ
フロントエンドとバックエンドを連携する
Reactによって作成されたブラウザ上で動くアプリケーションと、Node.jsによって作成されたサーバー上で動くアプリケーションを接続する方法について学びましょう。
データベースの節で作成した掲示板サービスの処理の流れは、次の図のようになっていました。
今回作成する掲示板サービスでは、フロントエンドとバックエンドでそれぞれ別のサーバーを起動します。バックエンドはExpressを用いてWebサーバーを起動し、フロントエンドはViteを用いて開発用サーバーを起動します。処理の流れは、次の図のようになります。
バックエンドを作成する
次の手順に従って、バックエンドを作成しましょう。
-
新しいプロジェクト用のディレクトリを作成し、その中に
backendディレクトリを作成して開きます。 -
バックエンドでTypeScriptを使用するためのセットアップを行います。
ここに動画を埋め込む
npm initコマンドでpackage.jsonファイルを作成した後、typeフィールドの値を"module"にします。npm install -D typescriptコマンドを実行してTypeScriptをインストールし、npx tsc --initコマンドを実行してTypeScriptの設定を記述するためのtsconfig.jsonファイルを作成します。- ここでは、TypeScriptファイルを直接実行するために、
npm install -D tsxコマンドを実行してtsxをインストールします。tsx 実行するファイルのパスとすることで、TypeScriptファイルを直接実行できます。
-
データーベースの節と同じように、データベースを作成し、Expressを用いてWebサーバーを作成します。
-
Supabaseで新しいデータベースを作成します。
-
npm install @prisma/client dotenvコマンドとnpm install -D prismaコマンドを実行して、Prismaのセットアップに必要なパッケージをインストールします。 -
npx prisma initコマンドを実行してPrismaのセットアップに必要なファイルを作成します。作成されたprisma.config.tsファイルを編集し、.envファイルの内容が読み込まれるようにします。 -
.envファイルを編集し、Prismaがデータベースに接続できるようにします。 -
作成された
schema.prismaファイルを編集し、掲示板への投稿を保存するためのテーブルとそのカラムの定義を次のように記述します。/backend/prisma/schema.prisma// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client"
output = "../generated/prisma"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Post {
id Int @id @default(autoincrement())
message String
} -
npx prisma db pushコマンドを実行して、テーブルとカラムの定義をデータベースに反映します。 -
掲示板への投稿のサンプルデータをデータベースに登録します。
-
npm install expressコマンドとnpm install -D @types/expressコマンドを実行して、Expressとその型定義をインストールします。 -
main.tsファイルを作成し、次のように記述します。/backend/main.tsimport express from "express";
import { PrismaClient } from "./generated/prisma/client.js";
const app = express();
const client = new PrismaClient();
app.use(express.json());
app.get("/posts", async (request, response) => {
response.json(await client.post.findMany());
});
app.post("/send", async (request, response) => {
await client.post.create({ data: { message: request.body.message } });
response.send();
});
app.listen(3000); -
package.jsonのscriptsプロパティに開発によく使うコマンドを登録します。次のように記載して、npm run devコマンドが使えるようにしましょう。tsxでも、Node.jsと同様に--env-fileオプションが利用できます。これにより、npm run devコマンドを実行することでWebサーバーを起動できるようになります。/backend/package.jsonの抜粋{
"scripts": {
"dev": "tsx --env-file=.env main.ts"
}
}
-
-
フロントエンドとバックエンドで別のサーバーを起動しており、これらのオリジンが異なることから、CORS(Cross-Origin Resource Sharing)を設定する必要があります。
ここに動画を埋め込む
-
npm install corsコマンドとnpm install -D @types/corsコマンドを実行して、corsパッケージとその型定義である@types/corsパッケージをインストールします。 -
.envファイルを編集し、環境変数WEB_ORIGINの値をViteの開発用サーバーのオリジンである"http://localhost:5173"に設定します。 -
main.tsファイルを編集し、CORSに関する設定を行います。8行目のapp.use(cors({ origin: process.env.WEB_ORIGIN }));により、WEB_ORIGINに設定したオリジンからのリクエストのみを許可するようにします。/backend/main.tsimport express from "express";
import cors from "cors";
import { PrismaClient } from "./generated/prisma/client.js";
const app = express();
const client = new PrismaClient();
app.use(cors({ origin: process.env.WEB_ORIGIN }));
app.use(express.json());
app.get("/posts", async (request, response) => {
response.json(await client.post.findMany());
});
app.post("/send", async (request, response) => {
await client.post.create({ data: { message: request.body.message } });
response.send();
});
app.listen(3000);
オリジンCORS -
-
npm run devコマンドを実行してWebサーバーを起動し、http://localhost:3000/postsにアクセスして、掲示板への投稿の一覧が取得できることを確認します。
フロントエンドを作成する
次の手順に従って、フロントエンドを作成しましょう。
ここに動画を埋め込む
-
npm create vite@latestコマンドを実行してfrontendという名前でReactのプロジェクトを作成し、作成されたディレクトリ内でnpm installコマンドを実行して必要なパッケージをインストールします。 -
.envファイルを作成し、環境変数VITE_API_ENDPOINTの値をバックエンドのURLである"http://localhost:3000"に設定します。 -
App.tsxファイルを編集し、次のように記述します。掲示板への投稿の取得や、新しい投稿の送信には、fetch関数を使用します。fetch関数の第1引数は今までは/postsのように記述していましたが、ここではimport.meta.env.VITE_API_ENDPOINTにより環境変数VITE_API_ENDPOINTに設定したバックエンドのURLを参照して、${import.meta.env.VITE_API_ENDPOINT}/postsと記述しています。/frontend/src/App.tsximport { useEffect, useState } from "react";
type Post = { id: number; message: string };
function App() {
const [posts, setPosts] = useState<Post[]>([]);
const [newPostContent, setNewPostContent] = useState("");
useEffect(() => {
async function fetchPosts() {
const response = await fetch(
`${import.meta.env.VITE_API_ENDPOINT}/posts`,
);
setPosts(await response.json());
}
fetchPosts();
}, []);
return (
<>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.message}</li>
))}
</ul>
<input
value={newPostContent}
onChange={(e) => {
setNewPostContent(e.target.value);
}}
/>
<button
type="button"
onClick={async () => {
await fetch(`${import.meta.env.VITE_API_ENDPOINT}/send`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: newPostContent }),
});
}}
>
送信
</button>
</>
);
}
export default App;Viteでの環境変数の利用Viteでは
VITE_で始まる環境変数をimport.meta.envオブジェクトのプロパティとして利用できます。例えば、VITE_API_ENDPOINTという環境変数がある場合、import.meta.env.VITE_API_ENDPOINTとして参照できます。なお、Viteは起動時に
.envファイルの内容を自動的に読み込みます。 -
npm run devコマンドを実行してViteの開発用サーバーを起動し、http://localhost:5173/にアクセスして、正しく動作することを確認します。
デプロイする
今までは、バックエンドとフロントエンドのそれぞれでnpm run devコマンドを実行することで、バックエンドの場合にはTypeScriptファイルを直接実行してWebサーバーを起動し、フロントエンドの場合にはViteの開発用サーバーを起動していました。しかしながら、デプロイする際には、これとは異なる方法を用いる必要があります。バックエンドの場合には、TypeScriptファイルをJavaScriptファイルにトランスパイルしてから、そのJavaScriptファイルを実行します。フロントエンドの場合には、Viteにより出力されたファイルを配信します。
| 環境 | バックエンド | フロントエンド |
|---|---|---|
| 開発環境 | TypeScriptファイルを直接実行してWebサーバーを起動 | Viteの開発用サーバーを起動 |
| 本番環境 | TypeScriptファイルをJavaScriptファイルにトランスパイルして実行 | Viteにより出力されたファイルを配信 |
ビルドの設定をする
フロントエンドの場合には、npm run buildコマンドを実行することで、Viteによりトランスパイルとバンドルの結果がdistディレクトリに格納されるのでした。デプロイの際には、このdistディレクトリを配信すればよいです。
次に、バックエンドのTypeScriptファイルをトランスパイルするための設定を行いましょう。
ここに動画を埋め込む
-
tsconfig.jsonのoutDirオプションの値を"./dist"にして、トランスパイル結果がdistディレクトリに入るようにします。また、.gitignoreファイルに/distを追加して、distディレクトリがGitの管理下に入らないようにします。 -
package.jsonに次のように記載して、npm run buildコマンドとnpm startコマンドが使えるようにしましょう。npm run buildコマンドでTypeScriptファイルをJavaScriptファイルにトランスパイルし、npm startコマンドで出力されたJavaScriptファイルを実行できるようになります。/backend/package.jsonの抜粋{
"scripts": {
"dev": "tsx --env-file=.env main.ts",
"build": "prisma generate && tsc",
"start": "node --env-file=.env dist/main.js"
}
}prisma generateコマンドprisma generateコマンドは、schema.prismaファイルの内容に基づいてPrisma Clientクラスを生成します。通常、schema.prismaファイルを編集するたびにこのコマンドを再度実行してPrisma Clientクラスを再生成する必要があります。ただし、prisma db pushコマンドを実行する際にもPrisma Clientクラスは生成されるため、prisma db pushコマンドを実行した場合にはprisma generateコマンドを実行する必要はありません。
Renderにデプロイする
次の手順に従って、作成したアプリケーションをRenderにデプロイしましょう。Renderにデプロイするには、作成したアプリケーションをGitHubのリポジトリに保存しておく必要があります。
ここに動画を埋め込む
-
バックエンドをデプロイするため、Renderにログインした直後の画面から
Web Serviceを作成します。
-
デプロイするGitHubのリポジトリを選択した後、次のように設定します。

-
フロントエンドをデプロイするため、
Static Siteを作成します。
-
次のように設定します。

-
環境変数
VITE_API_ENDPOINTに先ほどデプロイしたバックエンドのURLを設定します。
-
バックエンドの環境変数の設定を再度開き、環境変数
DATABASE_URLを設定し、環境変数WEB_ORIGINに先ほどデプロイしたフロントエンドのオリジンを設定します。