🌐

RestfullAPIをGo言語で学ぶ

に公開

はじめに

※新米による執筆した内容ですので、どうぞ温かくご覧いただけますと幸いです。

設計上の特徴
インメモリデータストア:データベースではなく、メモリ上のスライスでデータ管理
Mutex による排他制御:並行アクセス時のデータ競合を防止
線形検索:シンプルな実装
RESTful設計:HTTP動詞とURLパスが適切に設計

API設計は業務フローから?

API設計は、業務フローに基づいて行うのが理想的です。業務フローを理解し、それに合わせてAPIのエンドポイントやデータのやり取りを設計することで、システム間のスムーズな連携が可能になります。業務フローの把握から始め、リソース、データ形式、エラー処理、認証など、具体的な要件に基づいて設計を進めます。

RESTful APIの特徴

リソース指向

RESTful APIでは、エンドポイント(URL)をリソース(データやサービス)の識別子として使用します。リソースは通常、名詞で表され、URLがそのリソースの「位置」を示します。

例: /users(ユーザーのリスト)、/users/{id}(特定のユーザー)

HTTPメソッドの活用

RESTful APIは、HTTPメソッド(GET, POST, PUT, DELETE)をリソースの操作に使います。これにより、操作が直感的に理解でき、HTTP標準に従った設計が可能です。

HTTPメソッド URL 操作
GET /users ユーザー一覧を取得
POST /users 新しいユーザーを作成
GET /users/{id} 特定のユーザーの詳細を取得
PUT /users/{id} 特定のユーザーの情報を更新
DELETE /users/{id} 特定のユーザーを削除

RESTful APIの設計時に注意するポイント

リソースの命名

リソース名は単数形や複数形を統一するなど、命名規則を定めることが重要です。例えば、「/users」や「/orders」などのように、複数形を使うことが一般的です。

. ステータスコードの使用:
レスポンスには適切なHTTPステータスコードを返すことが重要です。これにより、クライアントがリクエストの結果を理解しやすくなります。

ステータスコード 意味 用途
200 OK 成功 リクエストが正常に処理された
201 Created 新規作成 POSTリクエストでリソースが正常に作成された
400 Bad Request リクエスト不正 リクエストの形式に問題がある
404 Not Found リソースなし 指定されたリソースが見つからない
500 Internal Server Error サーバーエラー サーバー側で予期せぬエラーが発生した

ページネーションとフィルタリング

大量のデータを扱う場合、ページネーション(データの分割)やフィルタリングをAPIでサポートすることが重要です。要は、大量のリソースデータを複数ページに分割して取得する方法であり。これにより、クライアントは必要なデータのみを効率的に取得できます。

  • 例: /users?page=1&size=20(1ページ目の20件)
  • 例: /users?age=25(年齢が25歳のユーザー)

バージョニング

APIの変更に備えてバージョン管理を行うことが重要です。バージョン管理を行わないと、APIが変更されるたびにクライアントが動作しなくなってしまう恐れがあります。

  • 例: /api/v1/users
  • 例: /v1/users

GETとPOSTの処理の流れ 例:動画管理システムのAPI開発

目的: 動画データ(動画リスト、個別動画、字幕、翻訳など)を管理し、クライアントがアクセスできるRESTful APIを構築。

全体の流れ

クライアント → サーバー → クライアント

クライアント (JavaScript): ユーザーの操作 → リクエスト送信 → レスポンス受信 → UI更新
サーバー (Go, Gin): リクエスト受信 → データ処理 → レスポンス返却

HTTPメソッド URL 操作
GET /videos 動画リスト取得
POST /videos 新規動画作成
GET /videos/{id} 特定動画取得
PUT /videos/{id}/status ステータス更新
GET /videos/{id}/transcript 字幕データ取得
GET /videos/{id}/translation 翻訳データ取得

バックエンド側

//GET /videos - 全動画取得
gofunc getVideos(c *gin.Context) {
    mu.Lock()
    defer mu.Unlock()
    c.JSON(http.StatusOK, videos)
}

排他制御:mu.Lock()でデータの同時アクセスを防止
全件取得:メモリ上のvideosスライス全体をJSON形式で返却
HTTPステータス200で成功レスポンス

//GET /videos/:id - 特定動画取得
gofunc getVideo(c *gin.Context) {
    id := c.Param("id")
    mu.Lock()
    defer mu.Unlock()
    for _, video := range videos {
        if video.ID == id {
            c.JSON(http.StatusOK, video)
            return
        }
    }
    c.JSON(http.StatusNotFound, gin.H{"error": "Video not found"})
}

URLパラメータ取得:c.Param("id")でパスからIDを抽出
線形検索:videosスライスを順次検索(O(n)の時間計算量)
条件分岐:見つかれば200、見つからなければ404エラー

//PUT /videos/:id/status - 動画ステータス更新
gofunc updateVideoStatusHandler(c *gin.Context) {
    id := c.Param("id")
    var req struct {
        Status string `json:"status" binding:"required"`
    }
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    updateVideoStatus(id, req.Status)
    c.JSON(http.StatusOK, gin.H{"message": "Status updated"})
}

JSONバインディング:リクエストボディからstatusフィールドを抽出
バリデーション:binding:"required"で必須チェック
別関数呼び出し:実際の更新処理はupdateVideoStatus()に委譲

//GET /videos/:id/transcript - 字幕取得
gofunc getTranscript(c *gin.Context) {
    id := c.Param("id")
    mu.Lock()
    defer mu.Unlock()
    for _, transcript := range transcripts {
        if transcript.VideoId == id {
            c.JSON(http.StatusOK, transcript)
            return
        }
    }
    c.JSON(http.StatusNotFound, gin.H{"error": "Transcript not found"})
}

関連データ検索:transcriptsスライスから該当する字幕データを検索
外部キー検索:transcript.VideoIdで動画IDとの関連付け

//GET /videos/:id/translation - 翻訳取得
gofunc getTranslation(c *gin.Context) {
    id := c.Param("id")
    mu.Lock()
    defer mu.Unlock()
    for _, translation := range translations {
        if translation.TranscriptId == id {
            c.JSON(http.StatusOK, translation)
            return
        }
    }
    c.JSON(http.StatusNotFound, gin.H{"error": "Translation not found"})
}

翻訳データ検索:translationsスライスから該当データを検索
注意点:TranscriptId == idという条件は、動画IDではなく字幕IDで検索している

クライアント側

// ボタンを押した時の処理
function handleButtonClick() {
  if (url === '') {
    setMessage('URLを入力してください');
    return;
  }
  setLoading(true);
  setMessage('処理中です...');
  sendToServer();
}

入力値チェック: url変数が空文字列でないかを検証
状態管理: setLoading(true)でUI上のローディング表示を開始
ユーザーフィードバック: 処理開始をメッセージで通知
関数委譲: 実際のAPI通信はsendToServer()に委譲

// POST /videos - 新規動画作成リクエスト
javascriptfunction sendToServer() {
  // ① エンドポイント設定
  const serverUrl = 'http://localhost:8080/videos';
  // ② リクエストボディ構築
  const data = { youtube_url: url };

  // ③ HTTPリクエスト送信
  fetch(serverUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  })

エンドポイント指定: RESTful設計に従い/videosリソースを指定
HTTPメソッド: POSTで新しいリソース作成を要求
Content-Type設定: JSON形式でのデータ送信を明示
データシリアライゼーション: JavaScript オブジェクトをJSON文字列に変換

// レスポンス応答を順番に処理
 .then(response => {
    if (response.ok) { 
      return response.json(); 
    }
    throw new Error('サーバーエラー');
  })
  .then(data => {
    setMessage('翻訳が開始されましたID: ' + data.id);
    setUrl('');
    loadVideos();
  })
  .catch(error => {
    setMessage('エラーが発生しました: ' + error.message);
  })
  .finally(() => { 
    setLoading(false); 
  });
}

レスポンス妥当性チェック: response.okでHTTPステータスコード200番台を確認
JSONパース: 成功レスポンスをJavaScriptオブジェクトに変換
成功時処理: サーバーから返されたIDを使用してユーザーに通知
UI状態リセット: 入力フィールドをクリアし、動画リストを再取得
エラーハンドリング: ネットワークエラーやサーバーエラーをキャッチ
後処理保証: finally()でローディング状態を必ず解除

function loadVideos() {
  fetch('http://localhost:8080/videos', {
    method: 'GET',
    headers: { 'Content-Type': 'application/json' }
  })
  .then(response => response.json())
  .then(videos => {
    setVideoList(videos); // 取得した動画リストをUIに反映
  })
  .catch(error => {
    console.error('動画リスト取得エラー:', error);
  });
}

GETリクエスト: 全動画リソースの一覧取得
レスポンス処理: JSON配列をJavaScriptオブジェクトに変換
UI更新: 取得したデータでビューを更新

まとめ

GoとGinで動画管理RESTful APIを構築。動画登録・一覧取得・字幕/翻訳取得・ステータス更新を実装。JSのFetch APIでUIをスムーズに更新。RESTful設計を遵守し、Mutexで安全性を確保。今後、DBや認証を追加し、スケーラブルなAPIを検討。

参考記事

https://speakerdeck.com/nagix/xian-chang-deyi-li-tuapidezain

Discussion