LoginSignup
92
93

More than 5 years have passed since last update.

nginx + golangでWeb API開発

Posted at

プログラミング言語goを使ってWeb API開発する記事はいろいろとありますが、できるだけ外部パッケージを使わない方針で開発しましたので報告します。goを使ったWeb API開発する際の参考になれば幸いです。
また、Web APIを公開する場合、Webサーバー(nginx) + アプリケーションサーバー(go)という構成が一般的(?)かと思いましたので、設定についての記載を最後に追加しました。

ソースコードはGithubにあります。
https://github.com/unokun/go-todo-api

環境

項目 バージョン
OS Centos7
go 1.11.4
mysql(maria DB) 5.5.60

TODO管理 Web API

baseurl(/todo/api/v1)にPathを加えたURLで各機能を実装しています。

Path HTTPメソッド 機能
/tasks GET タスク一覧取得
/tasks POST タスク登録
/tasks/:id PATCH タスク更新
/tasks/:id DELETE タスク削除

インストール

goのインストールは、以下の記事を、
CentOSにGo言語のインストール - Qiita

また、Web APIの実装については以下の記事を参考にさせていただきました。
GoでWebアプリを作ろう 第一回 : Goで簡単なCRUD - Studio Andy

使った外部パッケージは、以下の二つです。
* gin
* mysql

ginは、go製のWebフレームワークです。jsonレスポンス処理機能も持っている優れものです。
mysqlはDBに合わせて変更してください。

$ go get github.com/gin-gonic/gin
$ go get github.com/go-sql-driver/mysql

ファイル構成

$ tree go-todo-api
go-todo-api
├── db
│   └── createTask.sql
└── src
    ├── controller
    │   └── task.go
    ├── main.go
    └── model
        ├── model.go
        └── task.go

DB

データベース(tododb)作成
専用のユーザー(todo)を作成し権限を付与します。

$ mysql -u root -p
MariaDB [(none)]> create database tododb default charset utf8;
MariaDB [(none)]> create user 'todo'@'localhost' identified by 'todo';
MariaDB [(none)]> grant all on `tododb`.* to 'todo'@'localhost';

テーブルはタイトルのみ持つシンプルな構成です。

USE tododb
DROP TABLE IF EXISTS task;
CREATE TABLE IF NOT EXISTS task (
    id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    created_at TIMESTAMP NOT NULL,
    updated_at TIMESTAMP NOT NULL,
    title VARCHAR(255) NOT NULL,
    PRIMARY KEY(id)
);

model

DBアクセス処理を作成します。
task.goにJson返却用のstructを定義しています。

package model

import (
    "time"
)

type Task struct {
    ID        uint      `json:"id"`         // id
    CreatedAt time.Time `json:"created_at"` // created_at
    UpdatedAt time.Time `json:"updated_at"` // updated_at
    Title     string    `json:"title"`      // title
}

model.goにDB接続用関数を作成しています。

package model

import (
    "database/sql"
    "log"
    "os"

    // mysql driver
    _ "github.com/go-sql-driver/mysql"
)

// DBConnect returns *sql.DB
func DBConnect() (db *sql.DB) {
    dbDriver := "mysql"
    dbUser := "todo"
    dbPass := os.Getenv("MYSQL_TODO_PASSWORD") // 環境変数から取得
    dbName := "tododb"
    dbOption := "?parseTime=true"
    db, err := sql.Open(dbDriver, dbUser+":"+dbPass+"@/"+dbName+dbOption)
    if err != nil {
        log.Fatal(err)
    }
    return db
}

controller

mainから呼び出すタスク処理を記述します。

package controller

import (
    "fmt"
    "net/http"
    "strconv"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/unokun/go-todo-api/src/model"
)

// タスク一覧
func TasksGET(c *gin.Context) {
    db := model.DBConnect()
    result, err := db.Query("SELECT * FROM task ORDER BY id DESC")
    if err != nil {
        panic(err.Error())
    }

    // json返却用
    tasks := []model.Task{}
    for result.Next() {
        task := model.Task{}
        var id uint
        var createdAt, updatedAt time.Time
        var title string

        err = result.Scan(&id, &createdAt, &updatedAt, &title)
        if err != nil {
            panic(err.Error())
       }

        task.ID = id
        task.CreatedAt = createdAt
        task.UpdatedAt = updatedAt
        task.Title = title
        tasks = append(tasks, task)
    }
    c.JSON(http.StatusOK, gin.H{"tasks": tasks})
}
// タスク検索
func FindByID(id uint) model.Task {
    db := model.DBConnect()
    result, err := db.Query("SELECT * FROM task WHERE id = ?", id)
    if err != nil {
        panic(err.Error())
    }
    task := model.Task{}
    for result.Next() {
        var createdAt, updatedAt time.Time
        var title string

        err = result.Scan(&id, &createdAt, &updatedAt, &title)
        if err != nil {
            panic(err.Error())
        }

        task.ID = id
        task.CreatedAt = createdAt
        task.UpdatedAt = updatedAt
        task.Title = title
    }
    return task
}
// タスク登録
func TaskPOST(c *gin.Context) {
    db := model.DBConnect()

    title := c.PostForm("title")
    now := time.Now()

    _, err := db.Exec("INSERT INTO task (title, created_at, updated_at) VALUES(?, ?, ?)", title, now, now)
    if err != nil {
        panic(err.Error())
    }

    fmt.Printf("post sent. title: %s", title)
}
// タスク更新
func TaskPATCH(c *gin.Context) {
    db := model.DBConnect()

    id, _ := strconv.Atoi(c.Param("id"))
    title := c.PostForm("title")
    now := time.Now()

    _, err := db.Exec("UPDATE task SET title = ?, updated_at=? WHERE id = ?", title, now, id)
    if err != nil {
        panic(err.Error())
    }

    task := FindByID(uint(id))

    fmt.Println(task)
    c.JSON(http.StatusOK, gin.H{"task": task})
}
// タスク削除
func TaskDELETE(c *gin.Context) {
    db := model.DBConnect()

    id, _ := strconv.Atoi(c.Param("id"))

    _, err := db.Query("DELETE FROM task WHERE id = ?", id)
    if err != nil {
        panic(err.Error())
    }

    c.JSON(http.StatusOK, "deleted")
}

main

メイン処理を記述します。

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/unokun/go-todo-api/src/controller"
)

func main() {
    router := gin.Default()

    v1 := router.Group("/todo/api/v1")
    {
        v1.GET("/tasks", controller.TasksGET)
        v1.POST("/tasks", controller.TaskPOST)
        v1.PATCH("/tasks/:id", controller.TaskPATCH)
        v1.DELETE("/tasks/:id", controller.TaskDELETE)
    }
    // nginxのreverse proxy設定
    router.Run(":9000")
}

実行

バックグランドジョブとして実行します。

$ MYSQL_TODO_PASSWORD=xxx go run main.go &

API呼び出し

API呼び出しを実行する方法はいろいろありますが、Google Chromeの拡張機能である「Restlet Client」が使いやすいです。
Restlet Client - REST API Testing - Chrome ウェブストア

タスク一覧
go_todo_list_tasks.png

タスク登録
go_todo_add_task.png

ginのログ
gin_log.png

nginxの設定

Webサーバー(nginx) + アプリケーションサーバー(go webapi)という構成にする場合、リバースプロキシーとして動作させます。locationの記述についてはここを参考にしてください。

$ cat /etc/nginx/conf.d/https.conf
        location ^~ /todo/ {
                proxy_pass   http://127.0.0.1:9000;
        }

リンク

92
93
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
92
93