1
0

More than 1 year has passed since last update.

Go(echo)とMySQLでAPIサーバー構築してみた

Posted at

はじめに

Goを使ってみたいな〜と思っていた矢先、就職先の新人研修でAPIサーバーを構築する機会があったので、練習として簡単に作ってみました。

概要としては、以下の流れを実行するAPIサーバーを作ってみました。

  1. POSTメソッドで送られてくるデータを受け取る
  2. 受け取ったデータを元に、SQL側にリアクションを求める
  3. 求めたリアクションによる結果を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)です。

api.go
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に関する設定が主です。

main.go
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のモジュールとかの環境を整理してくれるファイルです。

go.mod
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が少数派の文法だと知りました..。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0