ゲーム開発
Unity
UnrealEngine
C++
ゲーム数学
ゲームAI
サウンド
アニメーション
GBDK
制作日記
3DCG
Houdini
Blender
USD
グラフィックス
テクノロジ
ツール開発
フロントエンド関連
サーバサイド関連
ソフトウェア設計
ハードウェア関連
おすすめ技術書
音楽
DTM
楽器・機材
ピアノ
その他
都会のエレキベア
ラーメン日記
四コマ漫画
おすすめアイテム
おもしろコラム
  • ゲーム開発
    • Unity
    • UnrealEngine
    • C++
    • ゲーム数学
    • ゲームAI
    • サウンド
    • アニメーション
    • GBDK
    • 制作日記
  • 3DCG
    • Houdini
    • Blender
    • USD
    • グラフィックス
  • テクノロジ
    • ツール開発
    • フロントエンド関連
    • サーバサイド関連
    • ソフトウェア設計
    • ハードウェア関連
    • おすすめ技術書
  • 音楽
    • 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