React × SupabaseプロジェクトをVercelでデプロイ(webpack + TypeScript + React)
まえがき
「フロントエンドの処理は整った!でもデータベースもサーバーも設定しないとアプリとして公開できない...」「バックエンドは簡単に構築したい...」そんな願いをかなえるのが、BaaS( = Backend as a Service)のひとつ、「Supabase」 です。
本記事は、そんなSupabaseの入門記事として、また 「React×Supabaseプロジェクトをいかにして公開するのか?」 を解決する記事としてお届けします。
本記事で解説すること
- 技術を予習する
- Supabaseとは?
- Vercelとは?
- 手順
- GitHubリポジトリとローカルのプロジェクトを紐づける
- webpack + TypeScript + React のプロジェクトを用意
- Supabaseでデータベースを構築
- Supabaseでバックエンドを構築
- Vercelでデプロイ
技術を予習する
Supabaseとは?
バックエンド機能はこれ1つで
Supabaseは、これ1つでアプリに必要な様々なバックエンドの機能を提供するサービス。開発工数を大幅カットしてくれます。データベース・認証・ストレージ・リアルタイム更新など、全てがここに。
PostgreSQLベースな、Firebaseの代替
そんなSupabaseは2020年から提供されており、同じくアプリケーション開発プラットフォームである 「Firebase」の代替として注目が集まっています。
その一番の違いは、FirebaseがNoSQL(SQLを用いない)であるのに対して、SupabaseはRDBMSであるPostgreSQLを選択している点です。
確かに、慣れてしまえばFirebaseも便利ではありますが、独自の作法を学ばなければならず、扱えるようになるには一定の学習コストが必要です。
一方で、SQLを扱ったことがあれば親しみやすく、何よりSQL文によって複雑な検索や処理も可能となります。
Vercelとは?
Webアプリケーションを簡単にデプロイ
Vercelは、Webアプリケーションを簡単にデプロイできるプラットフォームです。
アプリケーションをデプロイ(= Web上に配置)するためには、サーバーが必要になります。マンションを建てるのに土地が必要なのと似ています。といっても、自力でサーバーを立てる(≒ 土地を整備する)のは大変なので、AWSなどのクラウドサーバーを借りるのが一般的です。
しかし、それでもWebアプリを公開する工程は簡単ではありません。200を超えるAWSのサービスから適切なものを選択し、正しく設定する必要があります。セキュリティや料金も考慮しなくてはなりません。
そんな 「デプロイ」を一瞬で済ませてしまうのがVercelです。
GitHubとのシームレスな統合
VercelはGitHubリポジトリと連携しており、ソースコードに変更があるたびに自動的に更新されます。そのため、開発者は継続的なデプロイを手動で管理する必要がありません。
手順
さっそく作業に移っていきましょう。
1. GitHubリポジトリとローカルのプロジェクトを紐づける
ローカル環境でプロジェクトディレクトリを作成
まずはご自身のローカル環境でプロジェクトディレクトリreact-supabase-todo-app
を作成し、コードエディタ(VSCode)で開きましょう。
GitHubで新規リポジトリを作成・紐づけ
次にGitHubを開き、右上の「NEW」と書かれたボタンをクリックし、リポジトリ名を記入して新規リポジトリを作成します。
作成後、上記のような画面になるため、...or create a new repository on the command line
にある以下のコマンドをコピーし、ローカル環境でターミナルを開き、プロジェクトディレクトリ上(...\react-supabase-todo-app>
)で実行します。
echo "# リポジトリ名" >> README.md
git init
git add README.md
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/アカウント名/リポジトリ名.git
git push -u origin main
これで紐づけは完了です。コードを編集した際はコミットしましょう。
2. webpack + TypeScript + React のプロジェクトを用意
僕はwebpackを採用していますが、Create-React-AppでもViteでも以下の手順に大きな影響はありません。(※Vercelのビルド設定が異なります。)任意のプロジェクトをご用意ください。
今回、僕は以下のようなディレクトリ構成から開始します。
react-supabase-todo-app
└── src/
├── components/
│ ├── Header.tsx
│ ├── InputTodo.tsx
│ ├── TodoApp.tsx
│ └── TodoItem.tsx
├── css/
│ ├── reset.css
│ └── style.css
├── App.tsx
├── index.html
└── main.tsx
① .gitignoreファイルの作成
プロジェクトディレクトリに.gitignore
を作成します。
.gitignore
は、Gitで管理されているプロジェクトの中で特定のディレクトリやファイルを除外する役割を持ちます。
touch .gitignore
or
ni .gitignore
.gitignore
に以下のコードを記述します。
# dependencies
/node_modules
# production
/dist
# env files
.env
.gitignoreの記述の詳細
- 「#」以降の記述はコメントアウトされます。
- ローカル環境で作成した
node_modules
やbuild
ファイルはあくまで開発環境のもので、あらためてデプロイされた環境でnpm install``npm start
が実行されることで生成されます。そのためgitにコミットする際は無視する必要があります。 -
.env
は環境変数をローカル環境でシークレットに管理するファイルのため、gitにコミットしません。
② Node.js(npm)が導入されているか確認
node -v
vX.X.X
のような実行結果が反映されれば既にインストール済みです。
npm -v
X.X.X
のような実行結果が反映されれば既にインストール済みです。
③ package.jsonを作成
以下のコマンドで初期化処理を行います。
npm init
今回は詳細な設定は不要です。何も入力せずEnterキーを押し続けましょう。
package.json
が作成されます。
④ Reactのインストール
npm i react react-dom
コマンドの詳細
-
npm i
:npm install
のこと。node_modules
ディレクトリが作成され、インストールされたパッケージはpackage.json
のdependencies
に記載されます。
⑤ Bootstrapのインストール(任意)
CSSフレームワークです。プロジェクトで使用する場合はインストールしましょう。
npm i bootstrap bootstrap-icons react-bootstrap
⑥ TypeScriptを使用するために必要なパッケージのインストール
JavaScriptではなくTypeScriptを使用する場合はインストールする必要があります。
開発環境に限定してインストールします。
npm i -D typescript @types/react @types/react-dom @babel/preset-typescript ts-loader
コマンドの詳細
-
npm i -D
:npm install --save-dev
のこと。node_modules
にインストールされたパッケージはpackage.json
のdevDependencies
に記載され、開発環境のみでの使用が可能となります。
⑦ webpack関連パッケージのインストール
開発環境に限定してインストールします。
npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin dotenv-webpack @babel/core @babel/preset-env @babel/preset-react babel-loader css-loader style-loader
⑧ webpackを設定
プロジェクトディレクトリにwebpack.config.js
を作成します。
touch webpack.config.js
or
ni webpack.config.js
webpack.config.jsに以下のコードを記述します。
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require("webpack");
const env = require("dotenv").config().parsed;
module.exports = {
mode: 'development',
entry: {
main: __dirname + '/src/main.tsx',
},
output: {
path: __dirname + '/dist',
filename: '[name].js',
},
module: {
rules: [
{
test: [/\.ts$/, /\.tsx$/],
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'],
},
},
{
loader: 'ts-loader',
},
],
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
],
},
resolve: {
modules: [__dirname + '/node_modules'],
extensions: [".ts", ".tsx", ".js"],
},
plugins: [
new HtmlWebpackPlugin({
template: __dirname + '/src/index.html',
}),
env !== undefined
? new webpack.DefinePlugin({
"process.env": JSON.stringify(process.env),
})
: new webpack.DefinePlugin({
"process.env.REACT_PUBLIC_SUPABASE_URL": JSON.stringify(
process.env.REACT_PUBLIC_SUPABASE_URL
),
"process.env.REACT_PUBLIC_SUPABASE_ANON_KEY": JSON.stringify(
process.env.REACT_PUBLIC_SUPABASE_ANON_KEY
),
}),
],
devServer: {
static: {
directory: __dirname + '/dist',
},
port: 8080,
},
};
⑨ tsconfig.jsonの作成と変更
プロジェクトディレクトリの直下で以下のコマンドを実行するとtsconfig.json
が作成されます。
tsc --init
tsconfig.json
に以下のコードを記述します。
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noUnusedParameters": true,
"noUnusedLocals": true,
"noImplicitReturns": true,
"jsx": "react",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules"]
}
tsconfig.jsonの項目の詳細
-
"target": "esnext"
: 最新のJSに準拠したコードを出力させる。 -
"module": "commonjs"
: 出力したJavaScriptがモジュールを"Common.js"に準拠して読み込ませる。
などの項目がある。
⑩ 開発サーバーの起動準備
scripts
プロパティにstart
・dev
プロパティを追加します。
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "npx webpack --config webpack.config.js",
"dev": "npx webpack serve --config webpack.config.js"
},
以下のようなディレクトリ構成になっているか確認します。
frontend
├── node_modules/
├── src/
│ ├── components/
│ │ ├── Header.tsx
│ │ ├── InputTodo.tsx
│ │ ├── TodoApp.tsx
│ │ └── TodoItem.tsx
│ ├── css/
│ │ ├── reset.css
│ │ └── style.css
│ ├── App.jsx
│ ├── index.html
│ └── main.jsx
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
├── tsconfig.json
└── webpack.config.js
3. Supabaseでデータベースを構築
Supabaseにサインアップし、プロジェクトを作成
GitHubアカウントでSupabaseにログインします。
プロジェクト名、パスワード、地域を入力し、プロジェクトを作成します。
するとダッシュボードの表示に切り替わります。
.envファイルの作成
プロジェクトディレクトリ直下に、.env
ファイルを作成しましょう。
touch .env
or
ni .env
そして、以下の通りに記述しましょう。
SUPABASE_URL=
SUPABASE_ANON_KEY=
そして、Supabaseのプロジェクトダッシュボードの「Project API」セクションから「Project URL」「API Key」をコピーし、それぞれ「SUPABASE_URL」「SUPABASE_ANON_KEY」に代入しましょう。
テーブルの作成
左のタブから 「Table Editor」 を選択し、「Create a new table」でテーブルを作成しましょう。
今回は、テーブル名を「todo_items」、columnsを添付画像の通りにして作成します。
↓
「Insert row」 を選択し、2つほどデータを追加しましょう。
4. Supabaseでバックエンドを構築
supabase-jsをインストール
Supabase Documentationを開きます。スクロールすると「Client Libraries」というセクションがあるので、JavaScriptを選択して閲覧しましょう。
↓
インストールコマンドをコピーしてターミナルで実行しましょう。
npm install @supabase/supabase-js
Supabaseクライアントの実装
プロジェクトディレクトリ直下に utils
ディレクトリを作成します。この中に、これから作成するファイルを格納していきましょう。
mkdir utils
まずプロジェクトディレクトリ上で以下のコマンドを実行し、utils
ディレクトリ直下に supabase.ts
ファイルを作成します。
touch utils/supabase.ts
or
ni utils/supabase.ts
そして、Supabase DOCS > JavaScript Client Library > Initializing を参考に以下のコードを記述します。
import { createClient } from '@supabase/supabase-js';
// Create a single supabase client for interacting with your database
export const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!
);
データのCRUDを実装
プロジェクトディレクトリ上で以下のコマンドを実行し、utils
ディレクトリ直下に supabaseFunction.ts
ファイルを作成します。ここに関数を記述していきます。
touch utils/supabaseFunction.ts
or
ni utils/supabaseFunction.ts
Supabase DOCS > JavaScript Client Library > Fetch data ~ Update data を参考に以下のコードを記述します。本来は1つずつ実装するのが良いですが、説明の簡略化のためにまとめて記述してしまいます。
import { supabase } from './supabase';
export const fetchTodoList = async () => {
const todoItems = await supabase.from('todo_items').select('*');
return todoItems.data;
};
export const addTodoItem = async (title: string) => {
await supabase.from('todo_items').insert({ title: title });
};
export const deleteTodoItem = async (id: number) => {
await supabase.from('todo_items').delete().eq('id', id);
};
export const checkTodoItem = async (id: number, status: boolean) => {
await supabase.from('todo_items').update({ status: !status }).eq('id', id);
};
つづいて、TodoApp
コンポーネントにsupabaseFunctions.ts
に記述した関数を呼び出し、CRUDを実行したデータをstateに渡す処理を記述していきます。
// import文省略
// supabaseFunctionsの呼び出し
import {
fetchTodoList,
addTodoItem,
deleteTodoItem,
checkTodoItem,
} from '../../utils/supabaseFunctions';
// JSONサーバーの記述を削除
// const API_URL = 'http://localhost:3000/todo/';
interface Todo {
id: number;
title: string;
status: boolean;
}
const TodoApp = () => {
const [todoList, setTodoList] = useState<Todo[]>([]);
useEffect(() => {
fetchTodo();
}, []);
// const fetchTodo = () => {
// fetch(API_URL)
// .then((responseData) => {
// return responseData.json();
// })
// .then((result) => {
// setTodoList(result);
// });
// };
// SupabaseによるfetchTodo関数を定義
const fetchTodo = async () => {
const todoList = (await fetchTodoList()) as Todo[];
setTodoList(todoList);
};
// const addTodo = (inputTitle: string) => {
// const addData = {
// title: inputTitle,
// status: false,
// };
// fetch(API_URL, {
// body: JSON.stringify(addData),
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// },
// }).then(fetchTodo);
// };
// SupabaseによるaddTodo関数を定義
const addTodo = async (inputTitle: string) => {
if (!inputTitle) return;
await addTodoItem(inputTitle);
fetchTodo();
};
// const deleteTodo = (id: number) => {
// const targetUrl = API_URL + id;
// fetch(targetUrl, {
// method: 'DELETE',
// }).then(fetchTodo);
// };
// SupabaseによるdeleteTodo関数を定義
const deleteTodo = async (id: number) => {
await deleteTodoItem(id);
fetchTodo();
};
// const checkTodo = (id: number, title: string, status: boolean) => {
// const targetUrl = API_URL + id;
// const editData = {
// id: id,
// title: title,
// status: !status,
// };
// fetch(targetUrl, {
// body: JSON.stringify(editData),
// method: 'PUT',
// headers: {
// 'Content-Type': 'application/json',
// },
// }).then(fetchTodo);
// };
// SupabaseによるcheckTodo関数を定義
const checkTodo = async (id: number, status: boolean) => {
await checkTodoItem(id, status);
fetchTodo();
};
return (
// 記述省略
);
};
export default TodoApp;
なんとこれで完成です。ここではまとめて記述してもらいましたが、Supabase DOCSをもとに自分で考えながら実装すると力がつきます。
5. Vercelでデプロイ
GitHubからプロジェクトをインポート
ビルド設定を変更
VercelはデフォルトではNext.jsのビルド設定になっているため、以下のように変更します。
Build Command: "npm run start"
Output Directory: "dist"
.envファイルからEnvironment Variablesへの転記
Environment Variablesを選択して、環境変数を設定します。
.env
で設定したREACT_PUBLIC_SUPABASE_URL
とREACT_PUBLIC_SUPABASE_API_KEY
を設定しましょう。
さぁ、あとは 「Deploy」 ボタンを押すだけです!
準備完了!
デプロイしたURLを開き、動作を確認してみましょう。反映されていたらこれで完成です!
総括
おそらく、最後まで走り切ってしまえば今後のフルスタックアプリケーション開発は圧倒的に楽ができ、フロントエンド開発に注力することができるでしょう。お財布事情にも優しく、非エンジニアの方に簡単に自分の成果物を見せるのにも最適です。
ぜひ皆さん、React × SupabaseプロジェクトをVercelでデプロイする方法を身に着けて自作のアプリをどんどん公開していきましょう!
Discussion