GolangでAPIを開発する方法まとめ【Docker・Gin・Nginx・MySQL・Postman・Actions】


 

こんにちは。Tomoyuki(@tomoyuki65)です。

最近ようやくGolangでAPIを開発する方法について調べ始めましたが、Railsなどに比べると「圧倒的に情報が少ない!!」という感じで、なかなか自分が欲しい情報に辿り着かなかったりするので、試行錯誤しながら取り組んでいます。

特に初心者向けにいい感じで情報がまとまっているのがなかったりするので、この記事では基礎的なところを中心にまとめていければと思っています。

これからGolangでAPI開発について学び始めるような方がいたら、ぜひ参考にしてみて下さい。

 



GolangでAPIを開発する方法まとめ【Docker・Gin・Nginx・MySQL・Postman・Actions】

  •  Docker・Nginx・Golang・Ginで簡単なAPIを構築
  •  ホットリロード用に「Air」を追加
  •  routerとcontrollerを追加
  •  config設定を追加
  •  database(MySQL)とマイグレーション(usersテーブル)の追加
  •  modelの追加およびusersテーブルの全レコードをJSON形式で返すAPIの作成
  •  seedとcmdの追加およびAPIの確認
  •  userモデルにCRUDを作る
  •  PostmanでAPIを検証
  •  PostmanでAPIのテストコードを作成
  •  GitHub ActionsでCIの構築(テストの自動化)

 

Docker・Nginx・Golang・Ginで簡単なAPIを構築

まずはDocker環境において、WebサーバーにはNginx、APIにはGolangおよびフレームワークの「Gin」を利用し、ブラウザにJSON形式のデータを出力するような簡単なAPIを構築していきます。

以下のコマンドを実行し、必要なファイルを作成します。(今回の例では最初に作るディレクトリ名を「go_sample」としています。)

$ mkdir go_sample
$ cd go_sample
$ touch docker-compose.yml
$ touch .env
$ mkdir nginx
$ cd nginx
$ touch Dockerfile
$ touch nginx.conf
$ mkdir log
$ cd log
$ touch .keep
$ cd ../..
$ mkdir src
$ cd src
$ touch main.go
$ mkdir docker
$ cd docker
$ mkdir dev
$ cd dev
$ touch Dockerfile
$ cd ../../..

※nginx/logの.keepは、GitHubで管理する際にlogディレクトリを保持させるための工夫です。管理不要なファイルがある場合は「.gitignore」を作成して記述して下さい。

 

次に各種ファイルの中身はそれぞれ以下のようにします。

# Nginxのバージョンは軽量版の「alpine」
FROM nginx:alpine

# インクルード用のディレクトリ内を削除
RUN rm -f /etc/nginx/conf.d/*

# Nginxの設定ファイルをコンテナにコピー
ADD ./nginx/nginx.conf /etc/nginx/conf.d/default.conf

# ビルド完了後にNginxを起動
CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf

 

server {
  # ポート番号
  listen 80;

  location / {
    # apiの部分はdocker-compose.ymlのサービス名
    proxy_pass http://api:3001;
  }
}

 

# 2023年3月時点の最新版goの軽量版「alpine」
FROM golang:1.20.2-alpine

# インストール可能なパッケージ一覧の更新
RUN apk update && \
    apk upgrade && \
    # パッケージのインストール(--no-cacheでキャッシュ削除)
    apk add --no-cache \
            git \
            gcc \
            musl-dev

# 作業ディレクトリの設定
WORKDIR /go/src

 

package main

import (
  // Ginをインポート
  "github.com/gin-gonic/gin"
)

funcmain() {
  router := gin.Default()

  // JSON形式で「"message": "Hello World"」を出力するAPI
  router.GET("/", func(c *gin.Context) {
    c.JSON(200, gin.H{
      "message": "Hello World",
    })
  })

  router.Run(":3001")
}

 

version: '3'
  services:
    # APIに関する設定
    api:
      container_name: go_sample_api
      build:
        context: .
        dockerfile: ./src/docker/dev/Dockerfile
      env_file:
        - ./.env
      command: go run main.go
      volumes:
        - ./src:/go/src
      tty: true
      stdin_open: true
      ports:
        - "3001:3001"
    # Webサーバーに関する設定
    web:
      container_name: go_sample_web
      build:
        context: .
        dockerfile: ./nginx/Dockerfile
      volumes:
        - ./nginx/log:/var/log/nginx
      ports:
        - "80:80"
      depends_on:
        - api

 

次に以下のコマンドを実行し、Golangのコンテナ(api)だけを起動させてコンテナ内に入ります。

$ docker compose run api ash

 

次にapiコンテナ内で以下のコマンドを実行し、go.modの初期化を行います。

$ go mod init go_sample
$ go mod tidy
$ exit

※go modのモジュール名は「go_sample」にしたため、別のディレクトリに作成したgoのパッケージをインポートする際などは、import “go_sample/パッケージ名”で可能です。また、go.modはパッケージ管理用のファイルなので、利用するパッケージが増減したら「go mod tidy」を実行して管理していきます。尚、コンテナから抜けるため、最後に「exit」を実行しています。

 

これで最低限の準備が整ったので、以下のコマンドを実行し、コンテナを再ビルドした後にコンテナを起動させます。

$ docker compose build --no-cache
$ docker compose up -d

 

コマンド「docker compose ps」を実行し、STATUSが「running」になればコンテナ起動中です。

 

ブラウザから「localhost」にアクセスし、以下のようにメッセージが出力されればOKです。

 



ホットリロード用に「Air」を追加

上記までで基本的なAPIが構築できましたが、このままだと機能追加などの開発を進めるのに不便(ファイルを更新後、反映するのにコンテナの再起動が必要)なので、次にホットリロード機能として「Air」を追加します。

まず上記でコンテナを起動中の場合は、以下のコマンドを実行してコンテナを削除して下さい。

$ docker compose down

 

次に以下のコマンドを実行し、ディレクトリ「src」配下に設定用のファイル「.air.toml」を作成します。

$ cd src
$ touch .air.toml
$ cd ..

そして、ファイル「.air.toml」の中身については、「Air」公式のGitHubにあるファイル「air_example.toml」の中身をそのままコピペして使えます。

 

次に「docker/dev/Dockerfile」と「docker-compose.yml」を以下のように修正します。

# 2023年3月時点の最新版goの軽量版「alpine」
FROM golang:1.20.2-alpine

# ホットリロード用にAirをインストール
RUN go install github.com/cosmtrek/air@latest

# インストール可能なパッケージ一覧の更新
RUN apk update && \
    apk upgrade && \
    # パッケージのインストール(--no-cacheでキャッシュ削除)
    apk add --no-cache \
            git \
            gcc \
            musl-dev

# 作業ディレクトリの設定
WORKDIR /go/src

 

version: '3'
  services:
    # APIに関する設定
    api:
      container_name: go_sample_api
      build:
        context: .
        dockerfile: ./src/docker/dev/Dockerfile
      env_file:
        - ./.env
      command: air -c .air.toml
      volumes:
        - ./src:/go/src
      tty: true
      stdin_open: true
      ports:
        - "3001:3001"
    # Webサーバーに関する設定
    web:
      container_name: go_sample_web
      build:
        context: .
        dockerfile: ./nginx/Dockerfile
      volumes:
        - ./nginx/log:/var/log/nginx
      ports:
        - "80:80"
      depends_on:
        - api

 

これで準備が整ったので、以下のコマンドを実行し、コンテナを再ビルドして起動させます。

$ docker compose build --no-cahce
$ docker compose up -d

 

ブラウザから「localhost」にアクセスし、起動したのを確認します。

 

次に試しにmain.goのファイルを以下のように修正し、ファイルを保存します。

package main

import (
  // Ginをインポート
  "github.com/gin-gonic/gin"
)

funcmain() {
  router := gin.Default()

  // JSON形式で「"message": "Hello World"」を出力するAPI
  router.GET("/", func(c *gin.Context) {
    c.JSON(200, gin.H{
      "message": "Hello World !!",
    })
  })

  router.Run(":3001")
}

 

もう一度ブラウザを更新して「localhost」にアクセスし、ファイルの修正が反映されればOKです。

 

尚、gitを導入している場合は、「.gitignore」を以下のようにしておきます。

/.env
/nginx/log/*
!/nginx/log/.keep
/tmp/db/*
!/tmp/db/.keep
/src/tmp

 

その他、Air導入後はコンテナの停止や再起動について、「docker compose stop」や「docker compose start」が上手く機能しなくなるため、「docker compose down」や「docker compose up -d」を使うようにして下さい。

 



routerとcontrollerを追加

次にRailsのようなディレクトリ構成を参考にしたいので、routerとcontrollerを追加し、それらにAPIの設定を集約できるようにします。

まずは以下のコマンドを実行し、routerとcontrollerを追加します。

$ cd src
$ mkdir router
$ mkdir controller
$ cd router
$ touch router.go
$ cd ..
$ cd controller
$ touch index_controller.go
$ cd ../..

 

次に「index_controller.go」と「router.go」の中身を次のようにします。

package controller

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

func Index(c *gin.Context) {
  c.JSON(200, gin.H{
    "message": "Hello World Index !!",
  })
}

 

package router

import (
  "github.com/gin-gonic/gin"
  "go_sample/controller"
)

func Init() {
  // routerの初期化
  router := gin.Default()

  // routerの設定
  router.GET("/", controller.Index)

  // routerを起動
  router.Run(":3001")
}

 

次に「main.go」を次のように修正します。

package main

import (
  "go_sample/router"
)

func main() {
  // routerの初期化
  router.Init()
}

 

次に以下のコマンドを実行し、goのパッケージ管理ファイルを更新します。

$ docker compose exec api go mod tidy

 

次にファイルの追加等をしたのでコンテナを再起動させます。

$ docker compose down
$ docker compose build --no-cahce
$ docker compose up -d

 

次にブラウザから「localhost」にアクセスし、以下のようにメッセージが出力されればOKです。

 

config設定を追加

次に環境変数に設定した値など、各種設定値をまとめて取得できるようにするため、config設定を追加します。

まずは以下のコマンドを実行し、各種ディレクトリやファイルを追加します。

$ cd src
$ mkdir config
$ cd config
$ touch config.go
$ touch config_dev.yml

※今回の例では、config関連の設定をするのにviperというパッケージを利用します。そのため、環境ごとの設定値を記載する「config_dev.yml」というymlファイルを作成しています。

 

次に最初に事前に追加しておいた「.env」ファイルに、この後に使うDB(MySQL)用の設定値などを以下のように設定しておきます。

ENV=dev
TZ=Asia/Tokyo
MYSQL_DATABASE=go_sample_db
MYSQL_USER=go_sample_user
MYSQL_PASSWORD=go_sample_password
MYSQL_ROOT_PASSWORD=go_sample_root_password

 

次に上記で作成した環境別(開発環境用ならdev、本番環境用ならprodなどのようにファイルを分けることが可能)の設定ファイル「config_dev.yml」を以下のように設定しておきます。

※このファイルにはパスワードなどのセキュリティに関わらない項目に関して設定値を記載します。セキュリティに関わるものは「.env」などを利用した環境変数に設定して下さい。

db:
  type: "mysql"
  host: "db"
  port: 3306
  charset: "utf8mb4"
  parseTime: true
  loc: "Local"
migrate:
  filePath: "file://database/migrate"

 

次に上記で作成したconfig用のパッケージ「config.go」を以下のように修正します。

※外部からこのパッケージを利用するため、変数名などの1文字目は大文字にする必要があるのでご注意下さい。(例えばtype Config structだったり、Envだったり)

package config

import (
  "fmt"
  "os"
  "github.com/spf13/viper"
  "github.com/fsnotify/fsnotify"
)

// config設定の構造体
type Config struct {
  Env string
  Tz string
  Db Db `yml:db`
  Migrate Migrate `yml:migrate`
}

type Db struct {
  Type string `yml:type`
  Host string `yml:host`
  Port int `yml:port`
  Charset string `yml:charset`
  ParseTime bool `yml:parseTime`
  Loc string `yml:loc`
  Database string
  User string
  Password string
}

type Migrate struct {
  FilePath string `yml:filePath`
}

var cfg *Config

func Init() {
  // viperの初期設定
  viper.SetConfigName("config_" + fmt.Sprintf("%s", os.Getenv("ENV")))
  viper.SetConfigType("yml")
  viper.AddConfigPath("config/")

  // configファイル更新時に再読み込み
  viper.WatchConfig()
  viper.OnConfigChange(func(e fsnotify.Event) {
    fmt.Println("Config file changed:", e.Name)
    viper.Unmarshal(&cfg)
  })

  // configファイルの読み込み
  err := viper.ReadInConfig()
  if err != nil {
    panic(err)
  }

  // 読み込んだデータを変数cfgに設定
  err = viper.Unmarshal(&cfg)
  if err != nil {
    panic(err)
  }

  // 環境変数の値を変数cfgに設定
  cfg.Env = os.Getenv("ENV")
  cfg.Tz = os.Getenv("TZ")
  cfg.Db.Database = os.Getenv("MYSQL_DATABASE")
  cfg.Db.User = os.Getenv("MYSQL_USER")
  cfg.Db.Password = os.Getenv("MYSQL_PASSWORD")
}

func GetConfig() *Config {
  return cfg
}

 

次にmain.goを以下のように修正して上記で作成したconfigファイルの初期化を実行し、「config.GetConfig()」で各種設定値を取得して利用できるようにします。

package main

import (
  "go_sample/config"
  "go_sample/router"
)

func main() {
  // configの初期化
  config.Init()

  // routerの初期化
  router.Init()
}

 

これでconfig設定の追加が完了したので、試しにconfig設定で取得した値を画面に表示するAPIを作成してみます。

まずは以下のコマンドを実行し、config用のコントローラーを作成します。

$ cd src/controller
$ touch config_controller.go

 

そして、config_controller.goについては以下のように修正します。

package controller

import (
  "github.com/gin-gonic/gin"
  "go_sample/config"
)

func ConfigIndex(c *gin.Context) {
  // config設定を取得
  cfg := config.GetConfig()

  c.JSON(200, gin.H{
    "ENV": cfg.Env,
    "Tz": cfg.Tz,
    "DB.Type": cfg.Db.Type,
    "DB.Host": cfg.Db.Host,
    "DB.Port": cfg.Db.Port,
    "DB.Charset": cfg.Db.Charset,
    "DB.ParseTime": cfg.Db.ParseTime,
    "DB.Loc": cfg.Db.Loc,
    "DB.Database": cfg.Db.Database,
    "DB.User": cfg.Db.User,
  })
}

 

次にrouter.goを以下のように修正します。

package router

import (
  "github.com/gin-gonic/gin"
  "go_sample/controller"
)

func Init() {
  // routerの初期化
  router := gin.Default()

  // routerの設定
  router.GET("/", controller.Index)

  // APIの設定
  apiV1 := router.Group("/api/v1")
  apiV1.GET("/config", controller.ConfigIndex)

  // routerを起動
  router.Run(":3001")
}

 

これで準備が整ったので、以下のコマンドを実行し、modファイルの修正およびコンテナの再起動を行います。

$ docker compose exec api go mod tidy
$ docker compose down
$ docker compose build --no-cache
$ docker compsoe up -d

 

次にブラウザから「localhost/api/v1/config」にアクセスし、以下のようにconfig設定から取得した値が表示されればOKです。

 



database(MySQL)とマイグレーション(usersテーブル)の追加

次にdatabase(MySQL)に関する部分を追加するため、以下のコマンドを実行してディレクトリやファイルを作成します。

$ cd src
$ mkdir database
$ cd database
$ touch database.go
$ mkdir migrate
$ cd ../..

 

そして、作成した「database.go」の中身は以下のように修正します。

package database

import (
  "fmt"
  "log"
  "go_sample/config"
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
  "github.com/golang-migrate/migrate/v4"
  _ "github.com/golang-migrate/migrate/v4/database/mysql"
  _ "github.com/golang-migrate/migrate/v4/source/file"
)

var db *gorm.DB
var m *migrate.Migrate

func Init() {
  // config設定を取得
  cfg := config.GetConfig()

  // DBの接続先設定
  dsn :=  fmt.Sprintf(
            "%s:%s@tcp(%s:%d)/%s",
            cfg.Db.User,
            cfg.Db.Password,
            cfg.Db.Host,
            cfg.Db.Port,
            cfg.Db.Database,
          )

  dsn_option := fmt.Sprintf(
                  "?charset=%s&parseTime=%t&loc=%s",
                  cfg.Db.Charset,
                  cfg.Db.ParseTime,
                  cfg.Db.Loc,
                )

  dsn_mysql := dsn + dsn_option

  // DBに接続
  var err error
  db, err = gorm.Open(mysql.Open(dsn_mysql), &gorm.Config{})
  if err != nil {
    panic(err)
  }

  // マイグレーション設定
  dsn_m :=  fmt.Sprintf(
              "%s://%s",
              cfg.Db.Type,
              dsn,
            )
  m, err =  migrate.New(
              cfg.Migrate.FilePath,
              dsn_m,
            )
  if err != nil {
    panic(err)
  }

  // マイグレーションの実行
  err = m.Up()
  // エラーメッセージが「no change」の場合もスキップ
  if err != nil && err.Error() != "no change" {
    log.Printf("m.Up() Error Message: %s\n", err)
  }
}

func GetDB() *gorm.DB {
  return db
}

func GetM() *migrate.Migrate {
  return m
}

func Close() {
  getDB, err := db.DB()
  if err != nil {
    panic(err)
  }
  getDB.Close()
}

 

次にコンテナを起動中の場合は、以下のコマンドを実行して停止させておきます。

$ docker compose down

 

次に「docker/dev/Dockerfile」、「docker-compose.yml」、「main.go」を以下のように修正します。

# 2023年3月時点の最新版goの軽量版「alpine」
FROM golang:1.20.2-alpine

# ホットリロード用にAirをインストール
RUN go install github.com/cosmtrek/air@latest

# マイグレーション用にglang-migrate(MySQL)をインストール
RUN go install -tags mysql github.com/golang-migrate/migrate/v4/cmd/migrate@latest

# インストール可能なパッケージ一覧の更新
RUN apk update && \
    apk upgrade && \
    # パッケージのインストール(--no-cacheでキャッシュ削除)
    apk add --no-cache \
            git \
            gcc \
            musl-dev

# 作業ディレクトリの設定
WORKDIR /go/src

※マイグレーション用として、今回は「golang-migrate」を使用します。

 

version: '3'
  services:
    # APIに関する設定
    api:
      container_name: go_sample_api
      build:
        context: .
        dockerfile: ./src/docker/dev/Dockerfile
      env_file:
        - ./.env
      command: air -c .air.toml
      volumes:
        - ./src:/go/src
      tty: true
      stdin_open: true
      ports:
        - "3001:3001"
      depends_on:
        - db
    # Webサーバーに関する設定
    web:
      container_name: go_sample_web
      build:
        context: .
        dockerfile: ./nginx/Dockerfile
      volumes:
        - ./nginx/log:/var/log/nginx
      ports:
        - "80:80"
      depends_on:
        - api
    # DBに関する設定
    db:
      container_name: go_sample_db
      image: mysql:8.0.32
      env_file:
        - ./.env
      ports:
        - 3306:3306
      volumes:
        - ./tmp/db:/var/lib/mysql

※DBには、今回はMySQLを使用します。

 

package main

import (
  "go_sample/config"
  "go_sample/database"
  "go_sample/router"
)

func main() {
  // configの初期化
  config.Init()

  // DBの初期化
  database.Init()
  defer database.Close()

  // routerの初期化
  router.Init()
}

 

次に以下のコマンドを実行し、コンテナを再ビルドします。

$ docker compose build --no-cache

 

次に以下のコマンドを実行し、一時的にapiコンテナを起動させてgo.modファイルの修正および、マイグレーション用のファイルを作成します。

$ docker compose run --rm api go mod tidy
$ docker compose run --rm api migrate create -ext sql -dir database/migrate -seq create_users_table

※今回の例では、ユーザー情報用のusersテーブルのマイグレーションファイルを作成します。

 

次に上記のmigrateコマンドでディレクトリ「src/database/migrate」配下にファイル「000001_create_users_table.down.sql」、「000001_create_users_table.up.sql」が作成されるので、それぞれを以下のように修正します。

DROP TABLE IF EXISTS users;

 

CREATE TABLE IF NOT EXISTS `users` (
  `id`         int(11) NOT NULL AUTO_INCREMENT,
  `uid`        VARCHAR(191) NOT NULL,
  `name`       VARCHAR(191) NULL,
  `email`      VARCHAR(191) NULL,
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `deleted_at` DATETIME NULL,
  UNIQUE INDEX `users_email_key`(`email`),
  UNIQUE INDEX `users_uid_key`(`uid`),
  PRIMARY KEY (`id`)
);

 

これでdatabaseとマイグレーションの準備が整ったので、以下のコマンドを実行してコンテナを起動させます。

$ docker compose up -d

 

下図のように追加したDBのコンテナも追加されて起動すればOKです。

 

次に以下のコマンドを実行し、マイグレーションによりDB(MySQL)にusersテーブルが追加されていることも確認してみます。

$ docker compose exec db bash
$ mysql -u root --password=go_sample_root_password
$ use go_sample_db
$ show tables;

 

下図のようにDB(MySQL)にusersテーブルが追加されていればOKです。

 

ついでに以下のコマンドを実行し、usersテーブルのカラムも確認しておきます。

$ show columns from users;

 

下図のようにusersテーブルのカラムが確認できればOKです。

 

最後にMySQLとコンテナから抜けるには以下のコマンドを実行します。

$ exit
$ exit

 

modelの追加およびusersテーブルの全レコードをJSON形式で返すAPIの作成

次に上記で追加したusersテーブル用のmodel(DBの対象テーブルと情報をやり取りするためのクラス的なもの)を追加するため、以下のコマンドを実行してディレクトリやファイルを作成します。

$ cd src
$ mkdir model
$ cd model
$ touch user.go
$ cd ../..

 

そして、作成した「user.go」の中身は以下のように修正します。

package model

import (
  "time"
  "go_sample/database"
  "errors"
)

// ユーザー情報の構造体
// usersテーブルのカラム名でjson出力するため、各項目に「`json:""`」を設定します。
type User struct {
  Id        int        `json:"id"`
  Uid       string     `json:"uid"`
  Name      string     `json:"name"`
  Email     string     `json:"email"`
  CreatedAt time.Time  `json:"created_at"`
  UpdatedAt time.Time  `json:"updated_at"`
  DeletedAt *time.Time `json:"deleted_at"`
}

// DBからusersテーブルの全レコードを取得
func GetUsersAll() (*[]User, error) {
  db := database.GetDB()

  var users *[]User
  result := db.Find(&users)
  if result.Error != nil {
    return nil, result.Error
  } else if result.RowsAffected == 0 {
    return nil, errors.New("users not registerd")
  }

  return users, nil
}

 

次にusersテーブルの全レコードをJSON形式で返すAPIを作成するため、まずは以下のコマンドを実行してコントローラーを作成します。

$ cd src/controller
$ touch users_controller.go
$ cd ../..

 

そして、「users_controller.go」の中身は以下のように修正します。

package controller

import (
  "github.com/gin-gonic/gin"
  "go_sample/model"
  "net/http"
)

// ユーザー一覧をJSON形式で出力
func UsersIndex(c *gin.Context) {
  users, err := model.GetUsersAll()
  if err != nil {
    c.JSON(http.StatusBadRequest, gin.H{
      "error": err.Error(),
    })
    return
  }

  c.JSON(http.StatusOK, users)
}

 

次にusersテーブルの全レコードをJSON形式で返すAPIを追加するため、「router.go」を以下のように修正します。

package router

import (
  "github.com/gin-gonic/gin"
  "go_sample/controller"
)

func Init() {
  // routerの初期化
  router := gin.Default()

  // routerの設定
  router.GET("/", controller.Index)

  // APIの設定
  apiV1 := router.Group("/api/v1")
  apiV1.GET("/config", controller.ConfigIndex)
  apiV1.GET("/users", controller.UsersIndex)

  // routerを起動
  router.Run(":3001")
}

 

次に以下のコマンドを実行し、go.modファイルを修正します。

$ docker compose exec api go mod tidy

 

次に以下のコマンドを実行し、コンテナの再ビルドして起動させます。

$ docker compose down
$ docker compose build --no-cache
$ docker compose up -d

 

次にブラウザで「localhost/api/v1/users」にアクセスし、下図のようになればOKです。

 

seedとcmdの追加およびAPIの確認

次にseed(テーブルにテストデータを追加するためのファイル)とcmd(seedを実行したりするためのコマンド用のファイル)を追加するため、以下のコマンドを実行してディレクトリとファイルを作成します。

$ cd src
$ mkdir seed
$ cd seed
$ touch user_seeds.go
$ cd ..
$ mkdir cmd
$ cd cmd
$ touch seeder.go
$ touch down_migrate.go
$ cd ../..

 

そして「user_seeds.go」、「seeder.go」、「down_migrate.go」はそれぞれ以下のように修正します。

package seed

import (
  "time"
  "go_sample/model"
  "gorm.io/gorm"
)

func UserSeeds(db *gorm.DB) error {
  // 1件目のデータ
  user1 :=  model.User{
              Uid:       "uid:go_sample_1",
              Name:      "Sample_User_1",
              Email:     "sample1@example.com",
              CreatedAt: time.Now(),
              UpdatedAt: time.Now(),
            }
  if err := db.Create(&user1).Error; err != nil {
    return err
  }

  // 2件目のデータ
  user2 :=  model.User{
              Uid:       "uid:go_sample_2",
              Name:      "Sample_User_2",
              Email:     "sample2@example.com",
              CreatedAt: time.Now(),
              UpdatedAt: time.Now(),
            }
  if err := db.Create(&user2).Error; err != nil {
    return err
  }
  return nil
}

 

package main

import (
  "fmt"
  "go_sample/config"
  "go_sample/database"
  "go_sample/seed"
)

func main() {
  // Configの初期化
  config.Init()

  // DBの初期化
  database.Init()
  defer database.Close()

  // DBの取得
  db := database.GetDB()

  // seedの実行
  err := seed.UserSeeds(db)
  if err != nil {
    panic(err)
  }

  fmt.Println("Execution user_seeds.go !!")
}

 

package main

import (
  "fmt"
  "go_sample/config"
  "go_sample/database"
)

func main() {
  // Configの初期化
  config.Init()

  // DBの初期化
  database.Init()
  defer database.Close()

  // mの取得
  m := database.GetM()

  // マイグレーションのDownコマンド実行
  err := m.Down()
  if err != nil {
    panic(err)
  }

  fmt.Println("Execution m.Down() !!")
}

 

次に以下のコマンドを実行し、go.modファイルを修正します。

$ docker compose exec api go mod tidy

 

次に以下のコマンドを実行し、user_seeds.goを実行させてDBのusersテーブルにデータを作成します。

$ docker compose exec api go run cmd/seeder.go

 

次にブラウザで「localhost/api/v1/users」にアクセスし、下図のようにDBのusersテーブルから取得したデータがJSON形式で出力されればOKです。

 



userモデルにCRUDを作る

次にuserモデルにCRUD(作成「Create」、読み出し「Read」、更新「Update」、削除「Delete」)の処理を作り、各種DB操作を出来るようにします。

まずは「model/user.go」を以下のように修正します。

package model

import (
  "time"
  "go_sample/database"
  "errors"
)

// ユーザー情報の構造体
// usersテーブルのカラム名でjson出力するため、各項目に「`json:""`」を設定します。
type User struct {
  Id        int        `json:"id"`
  Uid       string     `json:"uid"`
  Name      string     `json:"name"`
  Email     string     `json:"email"`
  CreatedAt time.Time  `json:"created_at"`
  UpdatedAt time.Time  `json:"updated_at"`
  DeletedAt *time.Time `json:"deleted_at"`
}

// DBからusersテーブルの全レコードを取得
func GetUsersAll() (*[]User, error) {
  db := database.GetDB()

  var users *[]User
  result := db.Find(&users)
  if result.Error != nil {
    return nil, result.Error
  } else if result.RowsAffected == 0 {
    return nil, errors.New("users not registerd")
  }

  return users, nil
}

// ユーザーを作成
func CreateUser(r User) error {
  db := database.GetDB()

  // トランザクションの開始
  tx := db.Begin()

  user := User{
            Uid:       r.Uid,
            Name:      r.Name,
            Email:     r.Email,
            CreatedAt: time.Now(),
            UpdatedAt: time.Now(),
          }

  if err := tx.Create(&user).Error; err != nil {
    // エラーの場合はロールバック
    tx.Rollback()
    return err
  }
  // コミットしてトランザクションを終了
  tx.Commit()

  return nil
}

// 対象のユーザーを1件取得
func GetUser(id int) (*User, error) {
  db := database.GetDB()

  var user *User
  result := db.First(&user, id)
  if result.Error != nil {
    return nil, result.Error
  }

  return user, nil
}

// ユーザー情報を更新
func UpdateUser(id int, r User) error {
  db := database.GetDB()

  // トランザクションの開始
  tx := db.Begin()

  // nil値項目の更新なし、updated_atの更新あり
  err := tx.Updates(&User{
              Id: id,
              Name: r.Name,
              Email: r.Email,
            }).Error
  if err != nil {
    // エラーの場合はロールバック
    tx.Rollback()
    return err
  }
  // コミットしてトランザクションを終了
  tx.Commit()

  return nil
}

// ユーザー削除
func DeleteUser(id int) error {
  db := database.GetDB()

  // トランザクションの開始
  tx := db.Begin()

  // 対象ユーザーを物理削除
  if err := tx.Delete(&User{}, id).Error; err != nil {
    // エラーの場合はロールバック
    tx.Rollback()
    return err
  }
  // コミットしてトランザクションを終了
  tx.Commit()

  return nil
}

 

次に「controller/users_controller.go」を以下のように修正します。

package controller

import (
  "github.com/gin-gonic/gin"
  "go_sample/model"
  "net/http"
  "strconv"
)

// ユーザー一覧をJSON形式で出力
func UsersIndex(c *gin.Context) {
  users, err := model.GetUsersAll()
  if err != nil {
    c.JSON(http.StatusBadRequest, gin.H{
      "error": err.Error(),
    })
    return
  }

  c.JSON(http.StatusOK, users)
}

// ユーザーを作成
func UserCreate(c *gin.Context) {
  // リクエストボディをバインド
  var req model.User
  if err := c.ShouldBindJSON(&req); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{
      "error": err.Error(),
    })
    return
  }

  if err := model.CreateUser(req); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{
      "error": err.Error(),
    })
    return
  }

  c.JSON(http.StatusOK, req)
}

// ユーザー1件をJSON形式で出力
func UserShow(c *gin.Context) {
  // パラメータ「:id」を数値変換
  id, err := strconv.Atoi(c.Param("id"))
  if err != nil {
    c.JSON(http.StatusBadRequest, gin.H{
      "error": err.Error(),
    })
    return
  }

  user, err := model.GetUser(id)
  if err != nil {
    c.JSON(http.StatusNotFound, gin.H{
      "message": err.Error(),
    })
    return
  }

  c.JSON(http.StatusOK, user)
}

// ユーザー情報を更新
func UserUpdate(c *gin.Context) {
  // パラメータ「:id」を数値変換
  id, err := strconv.Atoi(c.Param("id"))
  if err != nil {
    c.JSON(http.StatusBadRequest, gin.H{
      "error": err.Error(),
    })
    return
  }

  // リクエストボディをバインド
  var req model.User
  if err := c.ShouldBindJSON(&req); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{
      "error": err.Error(),
    })
    return
  }

  if err := model.UpdateUser(id, req); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{
      "error": err.Error(),
    })
    return
  }

  c.JSON(http.StatusOK, req)
}

// ユーザーを削除
func UserDelete(c *gin.Context) {
  // パラメータ「:id」を数値変換
  id, err := strconv.Atoi(c.Param("id"))
  if err != nil {
    c.JSON(http.StatusBadRequest, gin.H{
      "error": err.Error(),
    })
    return
  }

  if err := model.DeleteUser(id); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{
      "error": err.Error(),
    })
    return
  }

  c.JSON(http.StatusOK, gin.H{
    "message": "user(" + strconv.Itoa(id) + ") is deleted",
  })
}

 

次に「router/router.go」を以下のように修正します。

package router

import (
  "github.com/gin-gonic/gin"
  "go_sample/controller"
)

func Init() {
  // routerの初期化
  router := gin.Default()

  // routerの設定
  router.GET("/", controller.Index)

  // APIの設定
  apiV1 := router.Group("/api/v1")
  apiV1.GET("/config", controller.ConfigIndex)
  apiV1.GET("/users", controller.UsersIndex)

  // ユーザーのCRUD用APIを追加
  apiV1.POST("/users", controller.UserCreate)
  apiV1.GET("/users/:id", controller.UserShow)
  apiV1.PATCH("/users/:id", controller.UserUpdate)
  apiV1.DELETE("/users/:id", controller.UserDelete)

  // routerを起動
  router.Run(":3001")
}

 

次に以下のコマンドを実行し、go.modファイルを修正します。

$ docker compose exec api go mod tidy

 



PostmanでAPIを検証

これでuserモデルにCRUDを追加できたので、Postman(APIテストを行うためのツール)を使ってAPIを実行して試してみます。

尚、Postmanについてここでは詳しく解説しませんが、以下の関連記事にある「17.5 Postmanの登録方法」の部分で解説しているので、まだ使ったことがない方は参考にしてみて下さい。

 

関連記事👇

SPA構成のWebアプリケーションを開発する方法まとめ【Docker・NextJS(React)・Vercel・Rails7(APIモード)・AWS ECS(Fargate)】

2022年11月22日

 

Createの検証

では最初にCreateのAPIを検証しますが、Postmanでワークスペースを開き、画面左上の「+」をクリックして新しいコレクションを作成します。

 

次にコレクション名を付けますが、今回は「go_sample Collection」とします。 半角スペースが含まれていると後でファイルをエクスポートして使用する際に困るので、「go_sample_collection」のようにして下さい。

 

次に作成したコレクション名の左にある「>」をクリックしてフォルダを開き、「Add a request」をクリックします。

 

次にリクエスト名を「Create User Request」、メソッドを「POST」、エンドポイントを「http://localhost/api/v1/users」に設定します。

 

次に画面中央のタブ「Body」をクリック後、「raw」と「JSON」を選択し、入力欄には登録したいユーザー情報をJSON形式で記述します。

 

そして、画面右上の「Send」をクリックするとAPIが実行され、画面中央のStatusが「200 OK」となれば正常終了したのでOKです。

 

最後にブラウザで「http://localhost/api/v1/users」にアクセスしてユーザー一覧を確認し、上記で指定した条件でユーザーが新規作成されていればOKです。

 

Readの検証

次にReadのAPIを検証しますが、まずはコレクション名の右にある「◦◦◦」からメニューを開き、「Add request」をクリックして新しいリクエストを作成します。

 

次にリクエスト名を「Show User Request」、メソッドを「GET」、エンドポイントを「http://localhost/api/v1/users/:id」に設定後、画面下にパラメータ「id」の項目が表示されるので、Valueには上記Create APIで作成したデータを取得できるように「3」を設定します。

 

リクエストの設定完了後、画面右上の「Send」をクリックするとAPIが実行され、画面中央のStatusが「200 OK」、画面下のフィールドに先ほどCreate APIで作成したデータがJSON形式で表示されればOKです。

 

Updateを検証

次にUpdateのAPIを検証しますが、先ほどと同様に新しいリクエストを作成後、リクエスト名に「Update User Request」、メソッドに「PATCH」、エンドポイントに「http://localhost/api/v1/users/:id」、パラメータの値に「3」を設定します。

 

次に画面中央のタブ「Body」をクリック後、「raw」と「JSON」を選択し、入力欄には更新したい項目についてJSON形式で記述しますが、今回の例では、Create APIで作成したユーザーの名前を「Sample_User_3」に更新します。

 

リクエストの設定完了後、画面右上の「Send」をクリックするとAPIが実行され、画面中央のStatusが「200 OK」になればOKです。

 

最後にブラウザで「http://localhost/api/v1/users」にアクセスしてユーザー一覧を確認し、ユーザーの名前と更新日が更新されていればOKです。

 

Deleteを検証

次にDeleteのAPIを検証しますが、先ほどと同様に新しいリクエストを作成後、リクエスト名に「Delete User Request」、メソッドに「DELETE」、エンドポイントに「http://localhost/api/v1/users/:id」、パラメータの値に「3」を設定します。

 

リクエストの設定完了後、画面右上の「Send」をクリックするとAPIが実行され、画面中央のStatusが「200 OK」、画面下のフィールドに削除時のメッセージが表示されればOKです。

 

最後にブラウザで「http://localhost/api/v1/users」にアクセスしてユーザー一覧を確認し、Create APIで作成したユーザーが削除されて表示されなければOKです。

 



PostmanでAPIのテストコードを作成

次にPostmanでAPIのテストコードを作成します。

 

Create User Requestのテスト

まずはCreate User Requestのタブ「Pre-request Script」をクリックし、リクエスト実行前に実行しておきたい処理を記述します。

今回は例として、リクエスト実行後にユーザーのデータ件数が1件増えることをテストするため、事前にユーザーのデータ件数を取得し、変数「beforeDataCount」に格納しておきます。

尚、ユーザーのデータ件数の取得方法については、ユーザー一覧を取得するAPI「http://localhost/api/v1/users」を実行し、リクエスト結果のデータからユーザーの件数を取得しています。

 

・Pre-request Scriptに記述するコード

// リクエスト実行前にユーザー一覧からデータ件数を取得
pm.sendRequest("http://localhost/api/v1/users", function (err, response) {
    const beforeDataCount = response.json().length
    pm.variables.set("beforeDataCount", beforeDataCount);
});

※コードはjavascriptで記述します。  また、データ件数を変数に格納するには、「pm.variables.set(変数名, 変数にセットする値)」を使います。

 

次にタブ「Tests」をクリックし、リクエスト実行後に検証したいテスト内容を記述します。

今回の例では、リクエスト結果のステータスコードが200であることと、リクエスト実行後のユーザーのデータ件数が1件増えていることを検証します。

 

・Testsに記述するコード

pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
});

// リクエスト実行後にユーザー一覧からデータ件数を取得し、リクエスト実行前のデータ件数と比較
pm.sendRequest("http://localhost/api/v1/users", function (err, response) {
    const beforeDataCount = pm.variables.get("beforeDataCount");
    const afterDataCount = response.json().length;

    pm.test("ユーザーのデータ件数が1件増加", function () {
        pm.expect(afterDataCount).to.eql(beforeDataCount + 1);
    });
});

※事前に設定しておいた変数から値を取り出すには「pm.variables.get(変数名)」を使います。

 

次に画面右上の「Send」をクリックし、APIとテストを実行します。

実行後、画面下のタブ「Test Results」をクリックし、テスト結果を確認できますが、テスト項目のステータスが「PASS」なら正常終了なのでOKです。

 

Show User Requestのテスト

次にShow User Requestではエンドポイントに「:id」が含まれていて何か値を指定する必要がありますが、APIの実行でデータの作成や削除を繰り返すと、対象データのidの値は毎回変わってしまうので注意が必要です。

そのため、リクエスト実行前の処理でidに指定する値を取得して変数に格納後、その変数の値をパラメータの「:id」に指定することで、idの値が変わる問題に対処します。

ではShow User Requestのタブ「Params」をクリックし、idのValueには変数「paramId」を設定しますが、変数の値を利用する場合は「{{}}」で囲んで「{{paramId}}」を設定します。

 

次にタブ「Pre-request Script」をクリック後、リクエスト実行前の処理として、ユーザー一覧を取得するAPIを実行し、レスポンス結果から取得したデータの最後のデータのid(直近で作成されたデータのid)を変数「paramId」に格納します。

 

・Pre-request Scriptに記述するコード

// リクエスト実行前にユーザー一覧から取得した最後のデータのidを変数「paramId」に設定
pm.sendRequest("http://localhost/api/v1/users", function (err, response) {
    const lastUserData = response.json().pop()
    pm.variables.set("paramId", lastUserData.id);
});

 

次にタブ「Tests」をクリックし、今回の例ではリクエスト結果のステータスコードが200であることと、リクエスト結果のデータがCreate User Requestで作成したデータの値と一致していることを検証します。

 

・Testsに記述するコード

pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
});

// パラメータ「id」が「paramId」のユーザーのレスポンスの結果を確認
pm.test("id == paramId", function () {
    const paramId = pm.variables.get("paramId");
    pm.expect(pm.response.json().id).to.eql(paramId);
});
pm.test("uid == uid:test3", function () {
    pm.expect(pm.response.json().uid).to.eql("uid:test3");
});
pm.test("name == test3", function () {
    pm.expect(pm.response.json().name).to.eql("test3");
});
pm.test("email == test3@example.com", function () {
    pm.expect(pm.response.json().email).to.eql("test3@example.com");
});
pm.test("created_at is not null", function () {
    pm.expect(pm.response.json().created_at).not.eql(null);
});
pm.test("updated_at is not null", function () {
    pm.expect(pm.response.json().updated_at).not.eql(null);
});
pm.test("deleted_at is null", function () {
    pm.expect(pm.response.json().deleted_at).to.eql(null);
});

 

次に画面右上の「Send」をクリックしてAPIとテストを実行後、画面下のタブ「Test Results」からテスト結果を確認し、全てのテスト項目が「PASS」ならOKです。

 

Update User Requestのテスト

次にUpdate User Requestも上記と同様にまずはタブ「Params」からidのValueに「paramId」を設定します。

 

次にタブ「Pre-request Script」をクリック後、リクエスト実行前の処理として、パラメータに設定するidの値と、idに紐付くユーザーのデータを取得し、それぞれを変数に格納します。

 

・Pre-request Scriptに記述するコード

// リクエスト実行前にユーザー一覧から取得した最後のデータを変数に設定
pm.sendRequest("http://localhost/api/v1/users", function (err, response) {
    const lastUserData = response.json().pop()
    pm.variables.set("paramId", lastUserData.id);
    pm.variables.set("beforeUser", lastUserData);

    // updated_atの更新を確認するため1秒だけ待機させる
    const sleep = waitTime => new Promise( resolve => setTimeout(resolve, waitTime) );
    sleep(1000);
});

※データ更新時にupdated_atの値も更新されることを検証するため、sleep関数を定義して1秒間だけ待機処理をさせ、リクエストが実行されるまでの時間を少しずらしています。(これをしないと処理が即時実行されてcreated_atとupdated_atが同じ値になることがあるので注意)

 

次にタブ「Tests」をクリックし、今回の例ではリクエスト結果のステータスコードが200であることと、リクエスト実行後に対象の項目だけ指定した値で更新されていることを検証します。

 

・Testsに記述するコード

pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
});

// リクエスト実行後にユーザー一覧から取得した最後のデータとリクエスト実行前のデータを比較
pm.sendRequest("http://localhost/api/v1/users", function (err, response) {
    const beforeUser = pm.variables.get("beforeUser");
    const afterUser = response.json().pop()

    pm.test("リクエスト実行前後でidが更新されていないこと", function () {
        pm.expect(afterUser.id).to.eql(beforeUser.id);
    });
    pm.test("リクエスト実行前後でuidが更新されていないこと", function () {
        pm.expect(afterUser.uid).to.eql(beforeUser.uid);
    });
    pm.test("リクエスト実行後にnameが「Sample_User_3」更新されていること", function () {
        pm.expect(afterUser.name).to.eql("Sample_User_3");
    });
    pm.test("リクエスト実行前後でemailが更新されていないこと", function () {
        pm.expect(afterUser.email).to.eql(beforeUser.email);
    });
    pm.test("リクエスト実行後はupdated_atが更新されていること", function () {
        pm.expect(afterUser.updated_at > beforeUser.updated_at).to.eql(true);
    });
    pm.test("リクエスト実行後にdeleted_atがnullであること", function () {
        pm.expect(afterUser.deleted_at).to.eql(null);
    });
});

 

次に画面右上の「Send」をクリックしてAPIとテストを実行後、画面下のタブ「Test Results」からテスト結果を確認し、全てのテスト項目が「PASS」ならOKです。

 

Delete User Requestのテスト

次にDelete User Requestも上記と同様にまずはタブ「Params」からidのValueに「paramId」を設定します。

 

次にタブ「Pre-request Script」をクリック後、リクエスト実行前の処理として、ユーザー一覧のデータ件数とパラメータに設定するidの値を取得し、それぞれを変数に格納します。

 

・Pre-request Scriptに記述するコード

// リクエスト実行前にユーザー一覧からデータ件数と最後のデータを取得し、変数に設定
pm.sendRequest("http://localhost/api/v1/users", function (err, response) {
    const beforeDataCount = response.json().length
    const lastUserData = response.json().pop()

    pm.variables.set("beforeDataCount", beforeDataCount);
    pm.variables.set("paramId", lastUserData.id);
});

 

次にタブ「Tests」をクリックし、今回の例ではリクエスト結果のステータスコードが200であることと、リクエスト実行後のユーザーのデータ件数が1件減っていることを検証します。

 

・Testsに記述するコード

pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
});

// リクエスト実行後にユーザー一覧からデータ件数を取得し、リクエスト実行前のデータ件数と比較
pm.sendRequest("http://localhost/api/v1/users", function (err, response) {
    const beforeDataCount = pm.variables.get("beforeDataCount");
    const afterDataCount = response.json().length;

    pm.test("ユーザーのデータ件数が1件減少", function () {
        pm.expect(afterDataCount).to.eql(beforeDataCount - 1);
    });
});

 

次に画面右上の「Send」をクリックしてAPIとテストを実行後、画面下のタブ「Test Results」からテスト結果を確認し、全てのテスト項目が「PASS」ならOKです。

 

コレクション内の全てのAPIとテストをまとめて実行

上記で各APIのテストコードが書けたので、次はコレクション内の全てのテストをまとめて実行させて確認します。

まずは画面左にあるコレクション名をクリックします。

 

次に画面右上にある「Run」をクリックします。

 

次に新しいタブ「Runner」が開くので、画面下にある「Run [コレクション名]」をクリックして処理を実行します。

 

処理実行後、コレクション内の全てのAPIとテストが実行されるので、実行結果が全てPASSすればOKです。

 

Postman CLIからコレクションのテストを実行

次にコマンドから上記のコレクションのテストを実行できるようにするため、Postman CLIをインストールします。

※PostmanのCLIには「Newman」というのもありますが、私の環境では上手くいかなかった(http://localhostへのリクエストが失敗する)ので、Postman CLIをインストールして使用することにしました。

Postman CLIをインストールするには、公式サイトにあるPostman CLIのページを開き、使用しているデバイスに応じて、対応するコマンドを実行すればインストール可能です。

 

私の場合は現在Intel版のMacBookを利用しているので、以下のコマンドを実行してインストールしました。

$ curl -o- "https://dl-cli.pstmn.io/install/osx_64.sh" | sh

 

インストール後、以下のコマンドを実行してバージョンを確認します。

$ postman -v

 

コマンド実行後、以下のようにバージョンが表示されればpostmanコマンドが使用可能です。

 

次に上記で作成したコレクションのデータをエクスポートするため、コレクション画面の右上にある「◦◦◦」からメニューを開き、「Export」をクリックします。

 

次にポップアップ画面が表示されるので、画面右下の「Export」をクリックします。

 

これでコレクションのjsonファイルがダウンロードできます。

 

次に以下のコマンドを実行し、上記で取得したファイルを配置するディレクトリを作成します。

$ cd src
$ mkdir postman
$ cd ..

 

次に上記でダウンロードしたコレクションのファイルをコピーし、ディレクトリ「go_sample/src/postman」配下に格納します。

 

これで準備ができたので、以下のコマンドを実行し、コレクションのテストを実行してみます。

$ postman collection run src/postman/go_sample_collection.postman_collection.json

 

コマンド実行後、以下のようにコレクションが実行され、全てのテストが正常終了すればOKです。

 



GitHub ActionsでCIの構築(テストの自動化)

ここまででコマンドからPostmanのテストを実行できるようになりましたが、最後にCI(Continuous Integration)を構築し、テストの自動化を行います。

CIを構築するツールは色々ありますが、現在はGitHub Actionsで構築するのがトレンドなので、今回はGitHubにコードをプッシュした際に、GitHub ActionsからPostmanのテストを自動的に実行できるようにします。

※以下ではGitHubを既に利用している前提で話を進めます。

 

まずは以下のコマンドを実行し、GitHub Actions用のディレクトリとファイルを作成します。

$ mkdir -p .github/workflows
$ cd .github/workflows
$ touch actions.yml
$ cd ../..

 

次にファイル「actions.yml」を以下のように修正します。

name: go_sample github actions

on:
  push:

jobs:
  test:
    name: Run Postman Api Tests
    runs-on: ubuntu-latest
    # 使用する環境変数定義の指定
    environment: dev
    # 処理の実行
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Postman CLI install
        run: curl -o- "https://dl-cli.pstmn.io/install/linux64.sh" | sh

      # .envの作成
      - name: Create .env
        run: touch .env

      - name: Add environment variable to .env
        run: |
          echo ENV=${{secrets.ENV}} >> .env
          echo TZ=${{secrets.TZ}} >> .env
          echo MYSQL_DATABASE=${{secrets.MYSQL_DATABASE}} >> .env
          echo MYSQL_USER=${{secrets.MYSQL_USER}} >> .env
          echo MYSQL_PASSWORD=${{secrets.MYSQL_PASSWORD}} >> .env
          echo MYSQL_ROOT_PASSWORD=${{secrets.MYSQL_ROOT_PASSWORD}} >> .env

      # コンテナのビルドと起動
      - name: docker compose build --no-cache
        run: docker compose build --no-cache

      - name: docker compose up -d
        run: docker compose up -d

      # コンテナが立ち上がり切るまでの待機処理
      - name: sleeep 30
        run: sleep 30

      # seederの実行
      - name: docker compose exec api go run cmd/seeder.go
        run: docker compose exec api go run cmd/seeder.go

      # テストの実行
      - name: postman collection run
        run: postman collection run src/postman/go_sample_collection.postman_collection.json

 

次にGitHubの対象リポジトリに環境変数を設定するため、リポジトリ画面上のメニューから「Settings」をクリックします。

 

次に画面左のメニューから「Environments」をクリック後、画面右上の「New environment」をクリックします。

 

次に環境変数定義の名前を入力し、「Configure environment」をクリックします。

※今回は例として「dev」としています。

 

これで環境変数の定義が作成されたので、画面下にあるEnvironment secrets(機密情報用の環境変数)の「+ Add secret」をクリックします。

 

次にポップアップが表示されるので、環境変数の名前と値を入力後、右下の「Add secret」をクリックすると環境変数の登録が可能です。

 

上記の方法にて、「.env」ファイルに定義していた環境変数を全て登録します。

※環境変数を登録後、ymlファイルの「environment:」で環境変数の定義を指定(今回の例だとdevを指定)すると、「${{secrets.ENV}}」のようにして登録した環境変数にアクセス可能です。

 

これで準備が整ったので、GitHubにコードをプッシュしてみて下さい。

プッシュ後、GtiHubのリポジトリ画面上のメニューから「Actions」をクリックすると、実行されたワークフローを確認できます。

さらにワークフローの詳細を確認したい場合は、画面中央にある対象のコミットメッセージをクリックします。

 

次に画面中央のジョブ名をクリックします。

 

これでジョブの実行中の処理を確認可能です。

 

ジョブの処理が終了後、画面左のステータスのマークに緑色のチェックがつけば正常終了なのでOKです。(エラーの場合は赤色の×マークになります)

 



最後に

今回はGolangでAPIを開発する方法についてまとめました。

Railsと比べると圧倒的に情報が少ないため、Golangの基礎的な部分を学ぶのも一苦労しましたが、基本的な部分はこの記事にてある程度まとめれたと思います。

これからGolangでAPIの開発を始めてみたいという方は、ぜひ参考にしてみて下さい。

 

各種SNSなど

各種SNSなど、チャンネル登録やフォローをしていただけると励みになるので、よければぜひお願いします!

 

The following two tabs change content below.

Tomoyuki

SEを5年経験後、全くの未経験ながら思い切ってブロガーに転身し、月間13万PVを達成。その後コロナの影響も受け、以前から興味があったWeb系エンジニアへのキャリアチェンジを決意。現在はWeb系エンジニアとして働きながら、プロゲーマーとしても活躍できるように活動中。








シェアはこちらから


おすすめのゲーミングPCまとめ

近年、eスポーツという言葉が浸透し、ゲーミングPCの需要も増えましたが、「ゲーミングPCはどう選べばいいの?」という人も多いのではないでしょうか。そんなPC初心者でも迷わず選べるよう、おすすめのゲーミングPCをご紹介します!


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です