Go言語によるREST APIの実装方法を紹介します。
今回は例としてシンプルなCRUD API(GET
、POST
、PUT
、DELETE
)を実装します。
Go言語は1.16.3、フレームワークはEcho v4.2.2、O/RマッパはGORM v1.21.9、データベースはMySQL 8.0.21を利用します。
以下のような手順でREST APIを実装します。
- Docker環境の構築
- EchoでHTTPサーバを実装する
- GORMでデータベースにアクセスする
- 『Echo + GORM』でCRUD APIを実装する
目次
Docker環境の構築
Dockerを利用して検証環境を構築します。
実装の詳細は『ホットリロード x Go言語 x MySQL』なHTTPサーバのDocker環境構築手順で解説していますのであわせてご覧になってください。
Dockerfile
FROM golang:1.16.3-buster
WORKDIR /app
COPY . /app
RUN go mod tidy
RUN go get github.com/cosmtrek/air
CMD ["air"]
docker-compose.yml
version: '3'
services:
app:
build: .
ports:
- '3030:3000'
volumes:
- .:/app
depends_on:
- db
command: ["./start.sh"]
db:
image: mysql:8.0.21
ports:
- '3306:3306'
volumes:
- go_mysql_data:/var/lib/mysql
- ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
command: --default-authentication-plugin=mysql_native_password
environment:
MYSQL_USER: 'webuser'
MYSQL_PASSWORD: 'webpass'
MYSQL_ROOT_PASSWORD: 'pass'
MYSQL_DATABASE: 'go_mysql8_development'
volumes:
go_mysql_data:
start.sh
#!/bin/bash -eu
go mod tidy
air
MySQLのDockerイメージはコンテナ起動時に/docker-entrypoint-initdb.d
配下のスクリプト(.sql
、.sh
、.sql.gz
)を実行します。1
この機能を利用してサンプルデータを作成します。
docker-entrypoint-initdb.d/00_create_users.sql
DROP TABLE IF EXISTS users;
CREATE TABLE users (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255),
`email` VARCHAR(255),
PRIMARY KEY (`id`)
);
INSERT INTO users (id, name, email) VALUES (1, "Yamada", "yamada@example.com");
INSERT INTO users (id, name, email) VALUES (2, "Tanaka", "tanaka@example.com");%
コンテナを起動する際にgo.mod
が必要になるため、go mod init
でGo Modulesの初期化をします。
$ mkdir echo-gorm-crud-api-example cd $_
$ go mod init `basename $PWD`
$ tree -L 2
.
├── Dockerfile
├── docker-compose.yml
├── docker-entrypoint-initdb.d
│ └── 00_create.sql
├── go.mod
└── start.sh
EchoでHTTPサーバを実装する
EchoとはGo言語の軽量フレームワークです。特にREST APIをGo言語で実装する際に人気です。
まずはEchoで実装したHTTPサーバへ正しくリクエストが届くのか確認をします。
Echoの公式ドキュメント#Guideを参考に実装します。
main.go
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
func hello(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
}
func main() {
e := echo.New()
e.GET("/", hello)
e.Logger.Fatal(e.Start(":3000")) // コンテナ側の開放ポートと一緒にすること
}
動作確認
コンテナを起動してレスポンスが確認できればOKです。
### 作業ディレクトリへ移動
$ cd /path/to/working_directory
### 現在のディレクトリの内容を確認
$ tree -L 2
.
├── Dockerfile
├── docker-compose.yml
├── docker-entrypoint-initdb.d
│ └── 00_create_users.sql
├── go.mod
├── main.go
└── start.sh
### バックグラウンドで起動
$ docker-compose up -d
### レスポンスが返ってくればOK
$ curl localhost:3030
Hello, World!
GORMでデータベースにアクセスする
GORMとはGo言語のO/Rマッパです。Ruby on RailsのActive Recordに似た使い勝手のため、特にRuby経験者に人気です。
GORMのドキュメントを参考に、データベース接続の設定を追加します。
今回はデータベース接続に関するロジックを別パッケージで用意しました。
データベース接続の実装の詳細はGo言語でデータベース(MySQL)に接続する方法で紹介していますので併せてご覧になってください。
なお、GORMはV2.0とV1.0でデータベースの接続設定方法が異なるので注意が必要です。2
main.go
package main
import (
"echo-gorm-crud-api-example/database"
"net/http"
"github.com/labstack/echo/v4"
)
func hello(c echo.Context) error {
database.Connect()
sqlDB, _ := database.DB.DB()
defer sqlDB.Close()
err := sqlDB.Ping()
if err != nil {
return c.String(http.StatusInternalServerError, "データベース接続失敗")
} else {
return c.String(http.StatusOK, "Hello, World!")
}
}
func main() {
e := echo.New()
e.GET("/", hello)
e.Logger.Fatal(e.Start(":3000"))
}
connect.go
package database
import (
"fmt"
"os"
"github.com/joho/godotenv"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// DBを使い回すことで、DBへのConnectとCloseを毎回しないようにする
var DB *gorm.DB
func Connect() {
err := godotenv.Load()
if err != nil {
panic(err.Error())
}
user := os.Getenv("DB_USER")
password := os.Getenv("DB_PASSWORD")
host := os.Getenv("DB_HOST")
port := os.Getenv("DB_PORT")
database_name := os.Getenv("DB_DATABASE_NAME")
dsn := user + ":" + password + "@tcp(" + host + ":" + port + ")/" + database_name + "?charset=utf8mb4"
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Println(err.Error())
}
}
動作確認
データベースへ正常にアクセスできることを確認します。
### 作業ディレクトリへ移動
$ cd /path/to/working_directory
### 現在のディレクトリの内容を確認
$ tree -L 2
.
├── Dockerfile
├── database
│ └── connect.go
├── docker-compose.yml
├── docker-entrypoint-initdb.d
│ └── 00_create_users.sql
├── go.mod
├── main.go
└── start.sh
### バックグラウンドで起動
$ docker-compose up -d
### レスポンスが返ってくればOK
$ curl localhost:3030
Hello, World!
『Echo + GORM』でCRUD APIを実装する
ここからはEchoとGORMを利用してREST APIを実装する手順について紹介します。
今回はCRUD API(GET
, PUT
, POST
, DELETE
)を実装します。
GET API(全件取得)を実装する
レコードの構造体(User
)を作成し、JSON形式でレスポンスを返します。
JSONのプロパティ名は構造体のタグ(json: "xxx"
)で定義します。
main.go
package main
import (
"echo-gorm-crud-api-example/database"
"net/http"
"github.com/labstack/echo/v4"
)
type User struct {
Id int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func getUsers(c echo.Context) error {
users := []User{}
database.DB.Find(&users)
return c.JSON(http.StatusOK, users)
}
func main() {
e := echo.New()
database.Connect()
sqlDB, _ := database.DB.DB()
defer sqlDB.Close()
e.GET("/users", getUsers)
e.Logger.Fatal(e.Start(":3000"))
}
コンテナ起動後、curl
でJSONレスポンスが取得できればOKです。
$ curl localhost:3030/users
[{"id":1,"Name":"Yamada","email":"yamada@example.com"},{"id":2,"Name":"Tanaka","email":"tanaka@example.com"}]
GET API(1件取得)を実装する
Echoには、構造体にタグを付与することでリクエストデータとテーブルのレコードをバインドする機能があります。3
この機能によって、たとえば『/users/:id
というエンドポイントに/users/2
というリクエストがきた場合、ID = 2
のレコードを取得する』といったことが可能になります。
具体的なソースコードは以下の通りです。
main.go
package main
import (
"echo-gorm-crud-api-example/database"
"net/http"
"github.com/labstack/echo/v4"
)
type User struct {
Id int `json:"id" param:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func getUsers(c echo.Context) error {
users := []User{}
database.DB.Find(&users)
return c.JSON(http.StatusOK, users)
}
func getUser(c echo.Context) error {
user := User{}
if err := c.Bind(&user); err != nil {
return err
}
database.DB.Take(&user)
return c.JSON(http.StatusOK, user)
}
func main() {
e := echo.New()
database.Connect()
sqlDB, _ := database.DB.DB()
defer sqlDB.Close()
e.GET("/users", getUsers)
e.GET("/users/:id", getUser)
e.Logger.Fatal(e.Start(":3000"))
}
コンテナ起動後、curl
でJSONレスポンスが取得できればOKです。
$ curl localhost:3030/users/2
{"id":2,"name":"Tanaka","email":"tanaka@example.com"}
最終的なソースコード
PUT
・POST
・DELETE
を追加した最終的なソースコードは以下の通りです。
main.go
package main
import (
"go-docker-example/database"
"net/http"
"github.com/labstack/echo/v4"
)
type User struct {
Id int `json:"id" param:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func getUsers(c echo.Context) error {
users := []User{}
database.DB.Find(&users)
return c.JSON(http.StatusOK, users)
}
func getUser(c echo.Context) error {
user := User{}
if err := c.Bind(&user); err != nil {
return err
}
database.DB.Take(&user)
return c.JSON(http.StatusOK, user)
}
func updateUser(c echo.Context) error {
user := User{}
if err := c.Bind(&user); err != nil {
return err
}
database.DB.Save(&user)
return c.JSON(http.StatusOK, user)
}
func createUser(c echo.Context) error {
user := User{}
if err := c.Bind(&user); err != nil {
return err
}
database.DB.Create(&user)
return c.JSON(http.StatusCreated, user)
}
func deleteUser(c echo.Context) error {
id := c.Param("id")
database.DB.Delete(&User{}, id)
return c.NoContent(http.StatusNoContent)
}
func main() {
e := echo.New()
database.Connect()
sqlDB, _ := database.DB.DB()
defer sqlDB.Close()
e.GET("/users", getUsers)
e.GET("/users/:id", getUser)
e.PUT("/users/:id", updateUser)
e.POST("/users", createUser)
e.DELETE("/users/:id", deleteUser)
e.Logger.Fatal(e.Start(":3000"))
}
さいごに
最終的なソースコードはnishina555/echo-gorm-crud-api-exampleに公開しています。
Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!