はじめに
Goを使ってみたいな〜と思っていた矢先、就職先の新人研修でAPIサーバーを構築する機会があったので、練習として簡単に作ってみました。
概要としては、以下の流れを実行するAPIサーバーを作ってみました。
- POSTメソッドで送られてくるデータを受け取る
- 受け取ったデータを元に、SQL側にリアクションを求める
- 求めたリアクションによる結果をjson形式で返す
ディレクトリ構造
以下の通り。同じディレクトリ内で作りました。
.
├─ api.go
├─ main.go
├─ go.mod
└─ go.sum
データベース構造
データベース名はsample
で、テーブル名はemployee
にしました。
// データベース作成
CREATE DATABASE sample;
// 使用するデータベースの選択
USE sample;
// テーブル作成
CREATE TABLE employee (id integer PRIMARY KEY, name varchar(10));
ソースコード
api.go
api.go
では、POSTメソッドで送られてきたjson情報を元に、データベースに対して特定の処理を実行する様になっています。今回は、データベースへデータの追加(INSERT
)、削除(DELETE
)、検索(SELECT
)です。
package main
import (
"fmt"
"database/sql"
"net/http"
"github.com/labstack/echo"
_ "github.com/lib/pq"
"github.com/go-sql-driver/mysql"
)
/* 受け取る用のjson */
type Employee struct {
Id int `json:"id"`
Name string `json:"name"`
}
/* 受け取ったjson情報を判定するフラグ */
type ReturnJson struct {
Flag int `json:"flag"`
}
var db *sql.DB
func init() {
// 使用するデータベースの情報
config := mysql.Config{
DBName: "sample",
User: "root",
}
var err error
db, err = sql.Open("mysql", config.FormatDSN())
if err != nil {
panic(err)
}
}
func Post(c echo.Context, exec_type string) error {
post := new(Employee)
if err := c.Bind(post); err != nil {
return err
}
var where string
if post.Id != 0 && post.Name != "" {
where = fmt.Sprintf("WHERE id=%d AND name='%s'", post.Id, post.Name)
} else if post.Id != 0 {
where = fmt.Sprintf("WHERE id=%d", post.Id)
} else if post.Name != "" {
where = fmt.Sprintf("WHERE name='%s'", post.Name)
} else {
panic("Not enough data.")
}
table_name := "employee" // 使用するテーブル名
var exec_sentence string
switch exec_type{
case "add":
exec_sentence = fmt.Sprintf("INSERT INTO %s VALUES (%d, '%s');", table_name, post.Id, post.Name)
case "delete":
exec_sentence = fmt.Sprintf("DELETE FROM %s %s;", table_name, where)
case "select":
exec_sentence = fmt.Sprintf("SELECT id, name FROM %s %s;", table_name, where)
default:
panic("Maybe you have the wrong URL?")
}
fmt.Println(exec_sentence)
resJson := ReturnJson{}
rows, err := db.Query(exec_sentence)
if err != nil {
resJson.Flag = 0
} else {
resJson.Flag = 1
}
if exec_type == "select" {
count_sentence := fmt.Sprintf("SELECT COUNT(*) FROM %s %s;", table_name, where)
var cnt int
err = db.QueryRow(count_sentence).Scan(&cnt)
resJsonSelect := make([]Employee, cnt)
for rows.Next() {
cnt --
rows.Scan(&resJsonSelect[cnt].Id, &resJsonSelect[cnt].Name)
}
fmt.Println(resJsonSelect)
return c.JSON(http.StatusCreated, &resJsonSelect)
}
return c.JSON(http.StatusCreated, resJson)
}
/* POSTメソッドで送られてきた情報をデータベースに追加する */
func PostAddMember(c echo.Context) error {
return Post(c, "add")
}
/* POSTメソッドで送られてきた情報を元に、データベースから削除する */
func PostDeleteMember(c echo.Context) error {
return Post(c, "delete")
}
/* POSTメソッドで送られてきた情報をデータベースから探索する */
func PostSelectMember(c echo.Context) error {
return Post(c, "select")
}
個人的に苦戦した箇所は、SELECT
によって複数の結果が返ってくる場面(同じデータのときとか)に対して、どうやってjsonファイルを送り返そうか、という点でした。
色々調べてみたところ、make関数を使う方法が良さそうだと思ったのですが、どうやらSQL側で取得した結果が何個であるかを調べる術はGo側にないらしく、SQL側でSELECT COUNT(*) ~
することによって解消しました。
途中でswitch文が出現していますが、これは気分です。普段Pythonばっかり触っているので、switch文が新鮮で使ってみたくなった。ただそれだけです。
main.go
main.go
では、APIサーバーの制限やどういった情報を受け取るか、といったAPIに関する設定が主です。
package main
import (
"net/http"
"github.com/labstack/echo/middleware"
"github.com/labstack/echo"
)
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"*"},
AllowMethods: []string{
http.MethodPost,
},
}))
e.POST("/add", PostAddMember)
e.POST("/delete", PostDeleteMember)
e.POST("/select", PostSelectMember)
e.Logger.Fatal(e.Start(":8080"))
}
go.mod
Goのモジュールとかの環境を整理してくれるファイルです。
module main
go 1.20
require (
github.com/go-sql-driver/mysql v1.7.1
github.com/labstack/echo v3.3.10+incompatible
github.com/lib/pq v1.10.9
)
require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/mattn/go-colorable v0.1.11 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
golang.org/x/crypto v0.11.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
)
go.sum
気づいたら勝手に作成されています。
実際に実行してみた
一方のターミナルで、今回のGoファイルがある階層まで移動して、以下を実行。
go run *.go
これで、カレントディレクトリ直下にあるGoファイルが全て実行できるみたいです。
そして、他方のターミナルを用意して、そこからcurl
コマンドを叩いて、実行確認。
データを追加
curl -X POST http://localhost:8080/add -d "id=1" -d "name=taro"
curl -X POST http://localhost:8080/add -d "id=2" -d "name=hanako"
curl -X POST http://localhost:8080/add -d "id=3" -d "name=kenta"
curl -X POST http://localhost:8080/add -d "id=4" -d "name=hanako"
SQL側を確認してみる。
mysql> SELECT * FROM employee;
+----+--------+
| id | name |
+----+--------+
| 1 | taro |
| 2 | hanako |
| 3 | kenta |
| 4 | hanako |
+----+--------+
4 rows in set (0.00 sec)
データを削除
curl -X POST http://localhost:8080/delete -d "id=3"
SQL側を確認してみる。
mysql> SELECT * FROM employee;
+----+--------+
| id | name |
+----+--------+
| 1 | taro |
| 2 | hanako |
| 4 | hanako |
+----+--------+
データを検索
curl -X POST http://localhost:8080/select -d "id=1" -d "name=taro"
[{"id":1,"name":"taro"}]
curl -X POST http://localhost:8080/select -d "name=hanako"
[{"id":4,"name":"hanako"},{"id":2,"name":"hanako"}]
URLを変えたり、データを変えたりして、SQL側に反映されていることが確認できました!
おわりに
どうやら、curl
コマンドだとAPIリクエストのヘッダー情報とかを細かく指定せずとも、APIサーバーへアクセスできるのですが、実際にwebサーバーとかからアクセスする際には、ヘッダー情報をきちんと設定しないといけないらしいです。その辺は、追って勉強します。
また、今回はMySQLをSQL文を使って操作しましたが、余裕があれば、GoのORMであるGORMも使ってみたいです。
個人的には、SQLの中ではMySQLが慣れているので、ORMを使わずとも苦労はしませんでしたが、他のSQLを使うとなると、ちょっとMySQLと文法が変わるので、そういった場面でGORMを試してみたいです。
こちらの記事を読んでみて、MySQLが少数派の文法だと知りました..。