75
70

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JSL (日本システム技研)Advent Calendar 2018

Day 20

golangでREST APIをやってみた①

Last updated at Posted at 2018-12-19

はじめに

この記事は JSL (日本システム技研) Advent Calendar 2018 - Qiita 20日目の記事です。

昨年同様、一人アニバーサリー記念記事です。色々ありましたが、なんとか本厄も乗り切れそうです!

golangでREST APIをやってみた話を書きます。最近ちらほらPythonからgolangへ移行した
話を聞くので、お試し的なノリでやってみたいと思います。

今回は、「ORMで取得したデータをGETメソッドで返すところまで」を目標にします。
弊社のプロダクトでDjango restframeworkを使用してバックエンドを実装しているものがいくつかあるので、今後は、そちらのAPIとのパフォーマンス比較をしたりしてみたいです。

※golangデビューなので、手探り感満載なので、お気づきの点あればフォローもらえるとありがたいです!!

環境周り

まずはHello world!

まずは、go-json-restの公式にある「Hello world!」を試してみます。

package main

import (
    "github.com/ant0ine/go-json-rest/rest"
    "log"
    "net/http"
)

func main() {
    api := rest.NewApi()
    api.Use(rest.DefaultDevStack...)
    api.SetApp(rest.AppSimple(func(w rest.ResponseWriter, r *rest.Request) {
		w.WriteJson(map[string]string{"Body": "Hello World!"})
	}))
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler())) 
}

実行して、ブラウザから 「http://127.0.0.1:8080」 を叩いて見ます。

$ go run helloworld.go

"Body": "Hello World!"が返されたことを書くに出来ました。

REST APIを試す

同じく、go-json-restの公式にある「Countries」を試してみます。
サンプルコードは、DBで永続化はせずに、メモリ上にデータを置く感じです。

package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
	"sync"
)

func main() {
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	router, err := rest.MakeRouter(
		rest.Get("/countries", GetAllCountries),
		rest.Post("/countries", PostCountry),
		rest.Get("/countries/:code", GetCountry),
		rest.Delete("/countries/:code", DeleteCountry),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}


type Country struct {
	Code string
	Name string
}

var store = map[string]*Country{}

var lock = sync.RWMutex{}


func GetCountry(w rest.ResponseWriter, r *rest.Request) {
	code := r.PathParam("code")

	lock.RLock()
	var country *Country
	if store[code] != nil {
		country = &Country{}
		*country = *store[code]
	}
	lock.RUnlock()

	if country == nil {
		rest.NotFound(w, r)
		return
	}
	w.WriteJson(country)
}

func GetAllCountries(w rest.ResponseWriter, r *rest.Request) {
	lock.RLock()
	countries := make([]Country, len(store))
	i := 0
	for _, country := range store {
		countries[i] = *country
		i++
	}
	lock.RUnlock()
	w.WriteJson(&countries)
}

func PostCountry(w rest.ResponseWriter, r *rest.Request) {
	country := Country{}
	err := r.DecodeJsonPayload(&country)
	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if country.Code == "" {
		rest.Error(w, "country code required", 400)
		return
	}
	if country.Name == "" {
		rest.Error(w, "country name required", 400)
		return
	}
	lock.Lock()
	store[country.Code] = &country
	lock.Unlock()
	w.WriteJson(&country)
}

func DeleteCountry(w rest.ResponseWriter, r *rest.Request) {
	code := r.PathParam("code")
	lock.Lock()
	delete(store, code)
	lock.Unlock()
	w.WriteHeader(http.StatusOK)
}

curlコマンドで以下のような感じで叩きます。

# POST
$ curl -i -H 'Content-Type: application/json' \
 -d '{"Code":"JP","Name":"Japan"}' http://127.0.0.1:8080/countries

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Powered-By: go-json-rest
Date: Tue, 18 Dec 2018 05:52:20 GMT
Content-Length: 37

{
  "Code": "JP",
  "Name": "Japan"
}

# GET
$ curl -i http://127.0.0.1:8080/countries/JP

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Powered-By: go-json-rest
Date: Tue, 18 Dec 2018 05:53:45 GMT
Content-Length: 37

{
  "Code": "JP",
  "Name": "Japan"
}


$ curl -i http://127.0.0.1:8080/countries

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Powered-By: go-json-rest
Date: Tue, 18 Dec 2018 05:56:39 GMT
Content-Length: 104

[
  {
    "Code": "JP",
    "Name": "Japan"
  },
  {
    "Code": "US",
    "Name": "United States"
  }
]⏎                                                       ⏎                                                                                                                

ORMを試す

なんとなくRESTぽいことが出来ることが分かったの、実際に既存のMySQLよりでORMでデータ取得してみます。まずはお試しなので、Django標準で作成されるテーブル
auth_user より全件取得してみました。

注意点として、GORM は、デフォルトだとModel名に対して、「s」を付与したテーブル名を探しにいくため(例:UserであればUsersを探しにいく)、今回のように既存のテーブルを読む場合は、別途 **TableName()**というメソッドを用意してあげる必要があります。

package main

import (
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
	"fmt"
)

type User struct {
	ID      int64 `gorm:"primary_key"`
	Username  string
}

func (s *User) TableName() string {
	return "auth_user"
}

func gormConnect() *gorm.DB {
	DBMS     := "mysql"
	USER     := "user"
	PASS     := "password"
	PROTOCOL := "tcp(127.0.0.1:3306)"
	DBNAME   := "dbname"
  
	CONNECT := USER+":"+PASS+"@"+PROTOCOL+"/"+DBNAME
	db,err := gorm.Open(DBMS, CONNECT)
  
	if err != nil {
	  panic(err.Error())
	}
	return db
}
  
func main() {
	db := gormConnect()

	// 全件取得
	var allUsers []User
	db.Find(&allUsers)
	fmt.Println(allUsers)

	defer db.Close()
}

実行すると以下のようなフォーマットで取得することが出来ました。

$ go run gorm.go
[{1 admin} {2 katekichi} {3 nakazawa} {4 hogefuga} {6 fugahoge}]

ORMで取得したデータをGETで返す

ORMから取得する箇所は、先ほどのものと同じです。

package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
	"fmt"
)

type User struct {
	ID      int64 `gorm:"primary_key"`
	Username  string
}

func (s *User) TableName() string {
	return "auth_user"
}

func gormConnect() *gorm.DB {
	DBMS     := "mysql"
	USER     := "user"
	PASS     := "password"
	PROTOCOL := "tcp(127.0.0.1:3306)"
	DBNAME   := "dbname"
  
	CONNECT := USER+":"+PASS+"@"+PROTOCOL+"/"+DBNAME
	db,err := gorm.Open(DBMS, CONNECT)
  
	if err != nil {
	  panic(err.Error())
	}
	return db
}

func main() {
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	router, err := rest.MakeRouter(
		rest.Get("/users", GetAllUsers),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

func GetAllUsers(w rest.ResponseWriter, r *rest.Request) {
	db := gormConnect()
	defer db.Close()

	// 全件取得
	var allUsers []User
	db.Find(&allUsers)
	fmt.Println(allUsers)

	w.WriteHeader(http.StatusOK)
	w.WriteJson(&allUsers)
}

curlコマンドで叩きます。

$ curl -i http://127.0.0.1:8080/users

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Powered-By: go-json-rest
Date: Wed, 19 Dec 2018 04:47:35 GMT
Content-Length: 245

[
  {
    "ID": 1,
    "Username": "admin"
  },
  {
    "ID": 2,
    "Username": "katekichi"
  },
  {
    "ID": 3,
    "Username": "nakazawa"
  },
  {
    "ID": 4,
    "Username": "hogefuga"
  },
  {
    "ID": 6,
    "Username": "fugahoge"
  }
]⏎                                                   

まとめ

今回は、時間の都合上、GETの実装のみで終わってしまいましたが、引き続き、その他のメソッドについても実装したいです。また、ページネーションが非対応のためこの辺りも次回は、触れてみたいと思います。

75
70
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
75
70

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?