ゲーム開発
Unity
UnrealEngine
C++
Blender
ゲーム数学
ゲームAI
グラフィックス
サウンド
アニメーション
GBDK
制作日記
IT関連
ツール開発
フロントエンド関連
サーバサイド関連
WordPress関連
ソフトウェア設計
おすすめ技術書
音楽
DTM
楽器・機材
ピアノ
ラーメン日記
四コマ漫画
その他
おすすめアイテム
おもしろコラム
  • ゲーム開発
    • Unity
    • UnrealEngine
    • C++
    • Blender
    • ゲーム数学
    • ゲームAI
    • グラフィックス
    • サウンド
    • アニメーション
    • GBDK
    • 制作日記
  • IT関連
    • ツール開発
    • フロントエンド関連
    • サーバサイド関連
    • WordPress関連
    • ソフトウェア設計
    • おすすめ技術書
  • 音楽
    • DTM
    • 楽器・機材
    • ピアノ
  • ラーメン日記
    • 四コマ漫画
      • その他
        • おすすめアイテム
        • おもしろコラム
      1. ホーム
      2. 20240327_01_go_api_crud

      【Go言語】Gin、GORMでシンプルなREST APIを作成する【CRUD】

      サーバサイド関連GoGinGORM
      2024-03-26

      マイケル
      マイケル
      みなさんこんにちは! マイケルです!
      エレキベア
      エレキベア
      こんにちクマ~~
      マイケル
      マイケル
      今回はGo言語を使用して簡単なREST APIを作ってみます! そして最後は下記のUnityプロジェクトと実際に繋げて動作確認してみます。
      【Unity】UnityWebRequestを使ってCRUD機能を実装する【Ruby on Rails】
      2021-12-12
      エレキベア
      エレキベア
      Go言語いいクマね~~ いろんな企業で使われるようになってきている印象クマ
      マイケル
      マイケル
      今回作ったサンプルは下記GitHubリポジトリにもあげているので、こちらもよければご参照ください! それではさっそく見ていきましょう!

      GitHub - go-book-simple-api

      ▲今回作成したサンプル

      作成したAPIの仕様

      マイケル
      マイケル
      今回作成するAPIは、GinGORMライブラリを使用して作成しました。 データベースはMySQLでDocker環境で構築しています。 フレームワーク無しでもいいかなと思いつつ、実装を少しでも簡略化するために選定しました。
      • フレームワーク
        • Gin
      • ORM
        • GORM
      • データベース
        • MySQL
      エレキベア
      エレキベア
      定番の構成クマね
      マイケル
      マイケル
      テーブルとAPI定義は下記になっていて、シンプルなCRUD機能になります。
      booksテーブル
      Id
      Name
      Price
      integer
      string
      integer
      API定義
      URL
      メソッド
      概要
      /books
      GET
      book情報取得
      /books
      POST
      book情報作成
      /books/:id
      PUT
      book情報更新
      /books/:id
      DELETE
      book情報削除
      エレキベア
      エレキベア
      これ以上ないくらいシンプルクマね

      Docker環境の構築

      マイケル
      マイケル
      まずはDocker環境を構築します。 GoアプリのDockerfileについては、golangイメージのマニュアルをベースに作成しました。

      Docker - golang

      FROM golang:1.22
      
      ENV ROOT=/usr/src/app
      ENV LOCAL_DIR=./app
      
      WORKDIR ${ROOT}
      
      # pre-copy/cache go.mod for pre-downloading dependencies and only redownloading them in subsequent builds if they change
      COPY ${LOCAL_DIR}/go.mod ${ROOT}
      RUN go mod download && go mod verify
      
      # copy local resources
      COPY ${LOCAL_DIR} ${ROOT}
      
      # make go.som
      RUN go mod tidy
      
      # build and run
      CMD ["go", "run", "main.go"]
      
      
      ▲GoアプリのDockerfile
      エレキベア
      エレキベア
      modをダウンロードして実行するだけのシンプルな処理クマね
      マイケル
      マイケル
      そしてMySQL用のコンテナも別途用意して、docker-compose.ymlに記載して繋げます。 DBの環境変数については、Go側でも使用するため.envファイルとして用意して参照するようにしました。
      version: "3"
      services:
        app:
          build:
            context: .
            dockerfile: build/app/Dockerfile
          tty: true
          ports:
            - "8080:8080"
          volumes:
            - ./app:/usr/src/app
          depends_on:
            - mysql
          links:
            - mysql
          environment:
            MYSQL_DATABASE: ${MYSQL_DATABASE}
            MYSQL_USER: ${MYSQL_USER}
            MYSQL_PASSWORD: ${MYSQL_PASSWORD}
            MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
        mysql:
          image: mysql:8.3.0
          ports:
            - "3306:3306"
          volumes:
            - ./db/data:/var/lib/mysql
          environment:
            MYSQL_DATABASE: ${MYSQL_DATABASE}
            MYSQL_USER: ${MYSQL_USER}
            MYSQL_PASSWORD: ${MYSQL_PASSWORD}
            MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      
      
      ▲Go、SQLコンテナの記述
      MYSQL_DATABASE=sample
      MYSQL_USER=user
      MYSQL_PASSWORD=password
      MYSQL_ROOT_PASSWORD=password
      
      
      ▲データベース定義
      マイケル
      マイケル
      これでDocker環境の構築は完了です!
      エレキベア
      エレキベア
      非常にシンプルな構成クマ

      CRUD処理の実装

      マイケル
      マイケル
      ここから実際にAPIを実装します。 設計については悩みましたが、下記のような構成で実装してみました。
      /app
      ├── /controller
      ├── /model
      ├── /repository
      ├── /server
      └── main.go
      
      フォルダ名
      概要
      controller
      各API処理の定義
      model
      モデル定義
      repository
      DB操作関連
      server
      サーバ関連
      20240327_01_go_api_crud_01
      ▲依存関係の図

      エレキベア
      エレキベア
      最低限の分割は行っているクマね

      modelの定義

      マイケル
      マイケル
      modelの定義は単純で、テーブル項目をGORMの形式で指定しています。
      package model
      
      import "time"
      
      type Book struct {
      	Id        uint      `gorm:"primary_key"`
      	Name      string    `gorm:"size:255"`
      	Price     uint      `gorm:default:0`
      	CreatedAt time.Time `gorm:default:CURRENT_TIMESTAMP`
      	UpdatedAt time.Time `gorm:default:CURRENT_TIMESTAMP`
      }
      
      
      ▲book項目の定義

      repositoryの定義

      マイケル
      マイケル
      そしてrepositoryに関してはDB関連の処理を定義しています。 DB接続やマイグレーションも含めて実装しています。
      package repository
      
      import (
      	"book-simple-api/model"
      	"fmt"
      	"gorm.io/driver/mysql"
      	"gorm.io/gorm"
      	"os"
      )
      
      var (
      	Db  *gorm.DB
      	err error
      )
      
      func Init() {
      	// Get DB settings.
      	dbUser := os.Getenv("MYSQL_USER")
      	dbPassword := os.Getenv("MYSQL_PASSWORD")
      	dbName := os.Getenv("MYSQL_DATABASE")
      	dbHost := "mysql" // Db container name
      	dbPort := "3306"
      
      	// Connect DB.
      	dsn := fmt.Sprintf(
      		"%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
      		dbUser, dbPassword, dbHost, dbPort, dbName)
      	Db, err = gorm.Open(mysql.Open(dsn))
      	if err != nil {
      		panic(err)
      	}
      
      	// Migration.
      	err = AutoMigrate()
      	if err != nil {
      		panic(err)
      	}
      }
      
      func AutoMigrate() error {
      	err := Db.AutoMigrate(&model.Book{})
      	if err != nil {
      		return err
      	}
      	return nil
      }
      
      
      ▲DB接続、マイグレーション
      package repository
      
      import (
      	"book-simple-api/model"
      	"github.com/gin-gonic/gin"
      	"strconv"
      )
      
      type BookRepository struct{}
      type Book model.Book
      
      func (r BookRepository) GetAll() ([]Book, error) {
      	var books []Book
      	err := Db.Find(&books).Error
      	if err != nil {
      		return nil, err
      	}
      	return books, nil
      }
      
      func (r BookRepository) Create(c *gin.Context) (Book, error) {
      	var book Book
      	err := c.ShouldBindJSON(&book)
      	if err != nil {
      		return book, err
      	}
      	err = Db.Create(&book).Error
      	if err != nil {
      		return book, err
      	}
      	return book, nil
      }
      
      func (r BookRepository) UpdateById(id string, c *gin.Context) (Book, error) {
      	var book Book
      	err := Db.First(&book, id).Error
      	if err != nil {
      		return book, err
      	}
      	err = c.ShouldBindJSON(&book)
      	if err != nil {
      		return book, err
      	}
      	parseId, _ := strconv.ParseUint(id, 10, 64)
      	book.Id = uint(parseId)
      	err = Db.Save(&book).Error
      	if err != nil {
      		return book, err
      	}
      	return book, nil
      }
      
      func (r BookRepository) DeleteById(id string, c *gin.Context) error {
      	var book Book
      	err := Db.First(&book, id).Error
      	if err != nil {
      		return err
      	}
      	err = Db.Delete(&book).Error
      	if err != nil {
      		return err
      	}
      	return nil
      }
      
      
      ▲book関連のDB処理
      エレキベア
      エレキベア
      実際のCRUD処理もここに記述するクマね
      マイケル
      マイケル
      GORMを使用しているため、FindやSelectといった呼び出しで操作することができています。

      controllerの定義

      マイケル
      マイケル
      controllerにはAPI定義を実装しています。 「/books」「/books/:id」といったURLもこちらに設定しました。
      package controller
      
      import (
      	"book-simple-api/repository"
      	"fmt"
      	"github.com/gin-gonic/gin"
      	"net/http"
      )
      
      type BookController struct{}
      
      func (ctrl BookController) Routes() []Route {
      	return Routes{
      		Route{
      			MethodType:  GET,
      			Path:        "/books",
      			HandlerFunc: ctrl.Index,
      		},
      		Route{
      			MethodType:  POST,
      			Path:        "/books",
      			HandlerFunc: ctrl.Create,
      		},
      		Route{
      			MethodType:  PUT,
      			Path:        "/books:id",
      			HandlerFunc: ctrl.Update,
      		},
      		Route{
      			MethodType:  DELETE,
      			Path:        "/books:id",
      			HandlerFunc: ctrl.Delete,
      		},
      	}
      }
      
      func (ctrl BookController) Index(c *gin.Context) {
      	var r repository.BookRepository
      	books, err := r.GetAll()
      	if err != nil {
      		c.AbortWithStatus(http.StatusBadRequest)
      		fmt.Println(err)
      	} else {
      		c.JSON(http.StatusOK, gin.H{"books": books})
      	}
      }
      
      func (ctrl BookController) Create(c *gin.Context) {
      	var r repository.BookRepository
      	book, err := r.Create(c)
      	if err != nil {
      		c.AbortWithStatus(http.StatusBadRequest)
      		fmt.Println(err)
      	} else {
      		c.JSON(http.StatusOK, book)
      	}
      }
      
      func (ctrl BookController) Update(c *gin.Context) {
      	var r repository.BookRepository
      	id := c.Param("id")
      	book, err := r.UpdateById(id, c)
      	if err != nil {
      		c.AbortWithStatus(http.StatusBadRequest)
      		fmt.Println(err)
      	} else {
      		c.JSON(http.StatusOK, book)
      	}
      }
      
      func (ctrl BookController) Delete(c *gin.Context) {
      	var r repository.BookRepository
      	id := c.Param("id")
      	err := r.DeleteById(id, c)
      	if err != nil {
      		c.AbortWithStatus(http.StatusBadRequest)
      		fmt.Println(err)
      	} else {
      		c.JSON(http.StatusOK, gin.H{"message": "success deleted."})
      	}
      }
      
      
      ▲book関連のAPI定義
      マイケル
      マイケル
      定義したルーティングはcontroller.go内でまとめて返却しておきます。
      package controller
      
      import (
      	"github.com/gin-gonic/gin"
      )
      
      type Controller struct{}
      
      type MethodType int
      
      const (
      	GET MethodType = iota
      	POST
      	PUT
      	DELETE
      )
      
      type Route struct {
      	MethodType  MethodType
      	Path        string
      	HandlerFunc gin.HandlerFunc
      }
      type Routes []Route
      
      // GetAllRoutes all controller routes.
      func GetAllRoutes() Routes {
      	var routes Routes
      	routes = append(routes, BookController{}.Routes()...)
      	return routes
      }
      
      
      ▲ルーティング定義を返却する
      エレキベア
      エレキベア
      API定義はcontroller内で完結させるクマね

      ルーティングの登録

      マイケル
      マイケル
      controllerから返却したルーティングはserver.go内で登録します。
      package server
      
      import (
      	"book-simple-api/controller"
      	"github.com/gin-gonic/gin"
      )
      
      var router *gin.Engine
      
      func Init() {
      	router = CreateRouter(controller.GetAllRoutes())
      }
      
      func CreateRouter(routes controller.Routes) *gin.Engine {
      	r := gin.Default()
      	for _, route := range routes {
      		switch route.MethodType {
      		case controller.GET:
      			r.GET(route.Path, route.HandlerFunc)
      		case controller.POST:
      			r.POST(route.Path, route.HandlerFunc)
      		case controller.PUT:
      			r.PUT(route.Path, route.HandlerFunc)
      		case controller.DELETE:
      			r.DELETE(route.Path, route.HandlerFunc)
      		}
      	}
      	return r
      }
      
      func Listen() {
      	err := router.Run(":8080")
      	if err != nil {
      		panic(err)
      	}
      }
      
      
      ▲ルーティング定義の登録
      マイケル
      マイケル
      最後にrepositoryとserverを初期化してListenを開始すれば実装は完了です!
      package main
      
      import (
      	"book-simple-api/repository"
      	"book-simple-api/server"
      )
      
      func main() {
      	// Initialize.
      	repository.Init()
      	server.Init()
      
      	// Start listen server.
      	server.Listen()
      }
      
      
      エレキベア
      エレキベア
      流れが掴めたクマ~~~

      動作確認

      マイケル
      マイケル
      作成したAPIの動作確認を行ってみます。 一番簡単な方法はVSCodeの拡張機能であるThunderClientを使用する方法です。
      20240327_01_go_api_crud_02
      ▲ThunderClientによる確認

      エレキベア
      エレキベア
      VSCode上で確認できるのは便利クマね
      マイケル
      マイケル
      あとは以前実装したUnityプロジェクトでも動かしてみましたが、 こちらでも問題なく動作していそうでした!
      20240327_01_go_api_crud_03
      ▲Unityからも問題なく接続できている

      【Unity】UnityWebRequestを使ってCRUD機能を実装する【Ruby on Rails】
      2021-12-12
      エレキベア
      エレキベア
      これで専用のAPIサーバが作れそうクマね

      おわりに

      マイケル
      マイケル
      というわけで今回はGo言語でAPIサーバを実装してみました! どうだったかな?
      エレキベア
      エレキベア
      Goはシンプルで読みやすくて中々使いやすそうだと思ったクマ〜〜
      マイケル
      マイケル
      後方互換性もあるようだから、メンテも考えるとAPIに採用するメリットは大きそうだね 今後も積極的に使っていこう!
      マイケル
      マイケル
      それでは今日はこの辺で! アデューー!!
      エレキベア
      エレキベア
      クマ〜〜〜〜〜

      【Go言語】Gin、GORMでシンプルなREST APIを作成する【CRUD】~完~


      サーバサイド関連GoGinGORM
      2024-03-26

      関連記事
      【Unity】GoでのランキングAPI実装とVPSへのデプロイ方法についてまとめる【Go言語】
      2024-04-14
      【Rails7】Ruby on Rails7で主要な機能をtodoアプリで学ぶ
      2022-11-10
      【Rails7】Ruby on Rails7のローカル環境構築とScaffoldでのアプリ作成
      2022-11-06
      【ブログ改造計画】公開中のブログをGit管理して開発環境を構築する【WordPress】
      2022-04-27
      【ブログ改造計画】WordPressの基本機能とDocker環境の構築について【WordPress】
      2022-04-24
      【NAS】QNAPのNASで最低限しておくべきセキュリティ設定
      2022-02-21
      【Unity】UnityWebRequestを使ってCRUD機能を実装する【Ruby on Rails】
      2021-12-12
      【Docker】Dockerの基本的な使い方についてまとめる
      2021-05-02