🍈

Go+Gin × React(Typescript)でシンプルなチャットAPIを作る

に公開

はじめに

今回は、GoとReact(Typescript)を使って、フロントエンドからバックエンドAPIを呼び出すシンプルなチャットアプリの基盤を構築します。この記事では、API通信までの一連の流れを紹介します。

これは、将来的にVertex AIなどと組み合わせたチャット形式のAIエージェントを構築していくプロジェクトの最初のステップです。まずはフロントエンドで入力されたメッセージを取得し、サーバーサイドでAPIを叩いてレスポンスを返すという、チャットエージェントにおける基本機能を構築します。

開発環境

OS: macOS Sequoia 15.5
Go: 1.24.2
Gin: 1.10.1
node: 23.11.0
npm: 10.9.2

実装する流れ

  1. Go + Ginで/chatPOST APIを構築
  2. React + TypeScriptのフロントエンドをcreate-react-appで生成
  3. Reactからfetch POSTでGo APIを呼び出し
  4. Goが得たメッセージを文字列加工して応答
  5. Reactでの応答を受け取り表示

backend: Go + Gin

1. Ginプロジェクトを初期化

go mod init badkend
go get github.com/gin-gonic/gin

2. 各フォルダ・ファイルの作成

backendフォルダのディレクトリ構造は以下の通りです。

backend/
 ├── main.go
 ├── go.mod
 ├── go.sum
 ├── controllers/
 │   └── chat_controller.go
 ├── routes/
 │   └── routes.go  
 └── models/
     └── chat.go            

今後の拡張性を考慮して、上記のように用途部でフォルダを分けています。

3. /chatエンドポイント実装

まずはmain.goのみで記述すると以下のようになります。

main.go
package main
 
 import (
 	"net/http"
 	"github.com/gin-gonic/gin"
 )

 func main() {
 	r := gin.Default()
 
 	r.POST("/chat", func(c *gin.Context) {
 		var req struct {
 			Message string `json:"message"`
 		}
 
 		if err := c.ShouldBindJSON(&req); err != nil {
 			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input"})
 			return
 		}
 
 		reply := "受け取ったメッセージ: " + req.Message
 		c.JSON(http.StatusOK, gin.H{"reply": reply})
 	})
 
 	r.Run(":8080")
 }

今後のために、上記のコードを機能ごとに分割します。

main.go
package main

import (
	"github/k-kanke/backend/routes"
)

func main() {
	r := routes.SetupRoutes()
	r.Run(":8080")
}
chat_controller.go
package controllers

import (
	"github/k-kanke/backend/models"
	"net/http"

	"github.com/gin-gonic/gin"
)

func ChatHandler(c *gin.Context) {
	var req models.ChatRequest

	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input"})
		return
	}

	resp := models.ChatResponse{
		Reply: "受け取ったメッセージ: " + req.Message,
	}
	c.JSON(http.StatusOK, resp)
}
routes.go
package routes

import (
	"github/k-kanke/backend/controllers"

	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
)

func SetupRoutes() *gin.Engine {
	r := gin.Default()

	r.Use(cors.New(cors.Config{
		AllowOrigins:     []string{"http://localhost:3000"},
		AllowMethods:     []string{"GET", "POST"},
		AllowHeaders:     []string{"Content-Type"},
		AllowCredentials: true,
	}))

        // `/ping` エンドポイントは、サーバーが正常に起動しているかを確認するための
	// 一般的なヘルスチェック用のエンドポイントであり、本チャットアプリケーションの
	// 主要な機能とは直接関係ありません。
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{"message": "pong"})
	})

	r.POST("/chat", controllers.ChatHandler)

	return r
}

上記のroutes.goファイルではCORS設定をしています。React(ポート:3000)からGo(ポート:8080)への異なるドメイン間のリクエストを許可するために必要です。

chat.go
package models

type ChatRequest struct {
	Message string `json:"message"`
}

type ChatResponse struct {
	Reply string `json:"reply"`
}

frontend: React + TypeScript

  1. プロジェクト生成
npx create-react-app frontend --template typescript
  1. Chatコンポーネントの作成src/components/Chat.tsx
Chat.tsx
import React, { useState } from "react";

const Chat: React.FC = () => {
    const [message, setMessage] = useState('');
    const [reply, setReply] = useState('');

    const handleSend = async () => {
        if (!message.trim()) return;

        try {
            const res = await fetch('http://localhost:8080/chat', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ message }),
            });

            const data = await res.json();
            setReply(data.reply);
        } catch (err) {
            console.error('送信エラー: ', err);
            setReply('エラーが発生しました');
        }
    };

    return (
        <div>
            <h1>Zenith</h1><input 
                type="text"
                value={message}
                onChange={(e) => setMessage(e.target.value)}
                placeholder="メッセージを入力"
                style={{ padding: '0.5rem', width: '300px' }}
            />
            <button onClick={handleSend} style={{ marginLeft: '1rem' }}>
                送信
            </button>
            <div style={{ marginTop: '1rem' }}>
                <strong>応答:</strong> {reply}
            </div>
        </div>
    );
};

export default Chat;
  1. App.tsx
    ChatコンポーネントはApp.tsxで以下のように呼び出されています。
App.tsx
import './App.css';
import Chat from './components/Chat';

function App() {
  return (
    <div className="App">
      <Chat />
    </div>
  );
}

export default App;

動作確認

Goサーバー起動

go run main.go

Reactサーバー起動

npm start

ブラウザでlocalhost:3000にアクセスすると以下のような画面になります。

テストとして"Hello World!!"というメッセージを入力し、送信すると以下のような挙動が見られました。

正常に動いていることが確認できました。

おわりに

今回は以下のフローを実装しました。
[1] ユーザーがブラウザでメッセージを入力して「送信」ボタンを押す
[2] ReactがfetchでJSONデータをGoサーバーにPOST(/chat)
[3] Gin (Go) がPOSTリクエストを受け取り、メッセージをJSONから取得
[4] Ginが加工した応答をJSONで返す
[5] Reactがレスポンスを受け取り、画面に表示

API通信の基礎が確立できたことで、今後はこの上に様々な機能を追加していく予定です。

Discussion