GoのフロントエンドにReactを使う#
ReactはVue,Angularに並ぶ3大フロントエンドフレームワークの一つで、Facebookが開発・メンテナンスしているJavaScriptライブラリです。
Reactの役目はHTMLをユーザの操作に応じて適切に書き換えることです。
早速、Reactを始めていきましょう。
前提#
Goプロジェクト(こちらの手順で作成したもの)をお持ちでなければ、最初からセットアップしておいてください。
- Node.jsをインストールしておいてください。
- 必須ではないですが、Visual Studio Codeをインストールして、ファイルの編集に使うと便利です。
プロジェクト構造#
このチュートリアルでは、GoのバックエンドとReactのフロントエンドを別々のディレクトリで管理します。これは実際の開発でよく使われるアプローチで、フロントエンドとバックエンドを独立して開発できる利点があります。
go_web_app/
├── go-web-server/ (バックエンドのGoプロジェクト)
└── react-frontend/ (フロントエンドのReactプロジェクト)
Reactプロジェクトの作成#
まず、Reactプロジェクトを作成します。go_web_app
ディレクトリで以下のコマンドを実行してください:
npx create-react-app react-frontend
cd react-frontend
これでreact-frontend
ディレクトリにReactプロジェクトが作成されました。
開発サーバーの起動#
まずはReactの開発サーバーを起動して、正常に動作するか確認しましょう:
npm start
ブラウザが自動的に開き、Reactのウェルカムページが表示されれば成功です。
APIと通信するためのプロキシ設定#
Reactの開発サーバーはデフォルトでlocalhost:3000
で動作します。一方、Goのバックエンドサーバーはlocalhost:8080
で動作します。クロスオリジンリソース共有(CORS)の問題を避けるため、開発時はプロキシを設定します。
react-frontend/package.json
に以下の行を追加してください:
{
// ...既存のコンテンツ
"proxy": "http://localhost:8080"
}
コンポーネントの作成#
Reactでは、UIの各部分をコンポーネントとして定義します。まずは簡単なコンポーネントを作成しましょう。
react-frontend/src/App.js
を以下のように編集します:
import React, { useState, useEffect } from 'react';
import './App.css';
function App() {
const [message, setMessage] = useState('');
useEffect(() => {
// コンポーネントがマウントされたらAPIからメッセージを取得
fetch('/hello')
.then(response => response.json())
.then(data => setMessage(data.text))
.catch(error => console.error('Error fetching message:', error));
}, []);
return (
<div className="App">
<header className="App-header">
<p>{message || 'ロード中...'}</p>
</header>
</div>
);
}
export default App;
この例では、useEffect
フックを使って、コンポーネントがマウントされたときにGoバックエンドの/hello
エンドポイントからデータを取得しています。
バックエンドとフロントエンドの連携#
完全なアプリケーションをテストするには、両方のサーバーを起動する必要があります。
-
一つのターミナルでGoバックエンドを起動:
bash cd go-web-server go run main.go
-
別のターミナルでReactフロントエンドを起動:
bash cd react-frontend npm start
ブラウザでhttp://localhost:3000
にアクセスすると、Reactアプリケーションが表示され、「こんにちは!」というメッセージがGoバックエンドから取得されて表示されるはずです。
本格的なユーザー管理アプリケーションを作成#
次に、前回作成したユーザーAPIを使った本格的なユーザー管理アプリケーションを作成しましょう。
ユーザーリストコンポーネントの作成#
react-frontend/src/components
ディレクトリを作成し、その中にUserList.js
ファイルを作成します:
mkdir -p react-frontend/src/components
react-frontend/src/components/UserList.js
の内容:
import React, { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// ユーザーリストを取得
fetchUsers();
}, []);
const fetchUsers = () => {
setLoading(true);
fetch('/api/users')
.then(response => {
if (!response.ok) {
throw new Error('ユーザーデータの取得に失敗しました');
}
return response.json();
})
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
};
if (loading) return <div>読み込み中...</div>;
if (error) return <div>エラー: {error}</div>;
return (
<div className="user-list">
<h2>ユーザー一覧</h2>
{users.length === 0 ? (
<p>ユーザーがいません</p>
) : (
<ul>
{users.map(user => (
<li key={user.id}>
{user.id}: {user.account_name} - {user.name} - {user.tel}
</li>
))}
</ul>
)}
</div>
);
}
export default UserList;
ユーザー作成コンポーネントの作成#
react-frontend/src/components/UserInput.js
を作成します:
import React, { useState } from 'react';
function UserInput({ onUserCreated }) {
const [user, setUser] = useState({
name: '',
account_name: '',
tel: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setUser(prevUser => ({
...prevUser,
[name]: value
}));
};
const isFormValid = () => {
return user.name && user.account_name && user.tel;
};
const createUser = async () => {
if (!isFormValid()) return;
try {
const queryParams = new URLSearchParams({
name: user.name,
account_name: user.account_name,
tel: user.tel
}).toString();
const response = await fetch(`/api/users/create?${queryParams}`);
if (!response.ok) {
throw new Error('ユーザー作成に失敗しました');
}
const newUser = await response.json();
// 親コンポーネントに通知
if (onUserCreated) {
onUserCreated(newUser);
}
// フォームをリセット
setUser({
name: '',
account_name: '',
tel: ''
});
} catch (error) {
console.error('ユーザー作成エラー:', error);
alert(error.message);
}
};
return (
<div className="user-input">
<h2>新規ユーザー作成</h2>
<div>
<input
type="text"
name="name"
placeholder="名前"
value={user.name}
onChange={handleChange}
/>
</div>
<div>
<input
type="text"
name="account_name"
placeholder="アカウント名"
value={user.account_name}
onChange={handleChange}
/>
</div>
<div>
<input
type="text"
name="tel"
placeholder="電話番号"
value={user.tel}
onChange={handleChange}
/>
</div>
<button
onClick={createUser}
disabled={!isFormValid()}
>
新規作成
</button>
</div>
);
}
export default UserInput;
メインアプリケーションの更新#
react-frontend/src/App.js
を更新して、作成したコンポーネントを統合します:
import React, { useState } from 'react';
import './App.css';
import UserList from './components/UserList';
import UserInput from './components/UserInput';
function App() {
const [refreshList, setRefreshList] = useState(false);
// ユーザーが作成されたら、リストを更新
const handleUserCreated = () => {
setRefreshList(prev => !prev);
};
return (
<div className="App">
<header className="App-header">
<h1>ユーザー管理アプリケーション</h1>
</header>
<main className="App-content">
<UserInput onUserCreated={handleUserCreated} />
<UserList key={refreshList} />
</main>
</div>
);
}
export default App;
スタイルの追加(オプション)#
react-frontend/src/App.css
を更新して、アプリケーションに基本的なスタイルを追加します:
.App {
text-align: center;
}
.App-header {
background-color: #282c34;
padding: 20px;
color: white;
}
.App-content {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.user-input {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
}
.user-input input {
margin: 10px;
padding: 8px;
width: 300px;
}
.user-input button {
margin-top: 10px;
padding: 8px 15px;
background-color: #61dafb;
border: none;
border-radius: 4px;
cursor: pointer;
}
.user-input button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.user-list {
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
}
.user-list ul {
list-style-type: none;
padding: 0;
}
.user-list li {
padding: 10px;
border-bottom: 1px solid #eee;
text-align: left;
}
Goバックエンドの準備:CORSの設定#
リアルワールドの開発環境では、フロントエンドとバックエンドの開発サーバーが異なるポートで動作することがあります。その場合、CORSの設定が必要になります。Ginを使用する場合、cors middlewareを追加してください:
go get -u github.com/gin-contrib/cors
そして、main.go
に以下のように設定を追加します:
package main
import (
"fmt"
"log"
"time"
"go-web-server/database"
"go-web-server/handlers"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func main() {
// データベースを初期化
database.InitDatabase()
// Ginルーターを初期化
r := gin.Default()
// CORSミドルウェアを設定
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"http://localhost:3000"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))
// APIエンドポイントを設定
r.GET("/api/users", handlers.GetAllUsers)
r.GET("/api/users/:id", handlers.GetUser)
r.GET("/api/users/create", handlers.CreateUser)
r.GET("/api/users/:id/update", handlers.UpdateUser)
r.GET("/api/users/:id/delete", handlers.DeleteUser)
// 基本的なヘルスチェックエンドポイント
r.GET("/hello", func(c *gin.Context) {
c.JSON(200, gin.H{"text": "こんにちは!"})
})
// サーバーを起動
fmt.Println("サーバーを起動します: http://localhost:8080")
log.Fatal(r.Run(":8080"))
}
この設定により、localhost:3000
からのリクエストを許可するようになります。
アプリケーションの実行#
両方のサーバーを起動していることを確認してください:
-
Goバックエンド(ポート8080):
bash cd go-web-server go run main.go
-
Reactフロントエンド(ポート3000):
bash cd react-frontend npm start
ブラウザでhttp://localhost:3000
にアクセスすると、ユーザー管理アプリケーションが表示されます。
ユーザーを作成すると、リストに表示されるはずです。これは、ReactフロントエンドがGoバックエンドのAPIと正常に通信していることを示しています。
次はAxiosを使ってサーバーと通信する方法を学びましょう!