GolangでRestAPIを作る-軽量フレームワークgorilla利用

Go初心者(A Tour of Go を2週目くらい)の私ですが、
そろそろ何か作ってみないと実力がつかないと思い、できるだけ短時間で学べる学習素材が無いか探してみました。ありました勉強できそうな動画がyoutubeに!(Golang REST API With Mux)英語ですがナレーションあり。ただし日本語字幕はありません。
実際コードを書く手順が目で追えますので多少英語のヒアリングできなくても心配ありません。
便利ですね。youtube.

近頃はyoutubeで参考になる動画が増えてきているようです。
参考にしたサイトは下段の関連リンクへまとめましたので、ご興味ある方は是非本記事とあわせてご確認いただければ幸いです。
職種は問わず、プログラミングに興味を持ち日々プログラミング言語学習に取り組まれている方へ、できるだけ手短にわかりやすくまとめてみました。
(結局は長くなりそう。)

動作環境準備

開発するためには最低でもPC1台は必要です。MAC、Windouwsまたはlinuxの各ディストリビューションのいずれかで構いません。

Goのインストール

Goのインストールについては各OS別にバイナリ版でインストールするか、ソースからコンパイルする方法等いくつかあります。
詳細についてはGoの公式サイトでご確認ください。
私の環境はMAC OS XのHigh Sierra(10.13.6)で少々古いです。
GoのインストールはHomebrewを使い行いました。
Homebrewがインストール済なら下記のコマンドをターミナルから実行すればOKです。

$ brew install go

Mac環境に関しては下記のURLに簡潔にまとめられておりましたので、もしインストールに困ったら参考に。
初めてのGo!インストールまで(qiita)
今後どこかのタイミングでGolang環境構築については別記事にまとめたいと思います。

Gopathとディレクトリ(参考例)

$GOPATH = /home/[UserName]/go
$GOPATH/bin
     |
     +-/pkg
     |
     +-/src
          |
          +-/github.com
                 |
                 +-- /gorilla  下記のgo getでパッケージのインストールをするとここへgorillaが配置されます。
                 |        |
                 |        +-- /mux
                 |        |
                 |        +-- /websoket
                 |
                 +-- /[Github UserName]
                                |
                                +--/restapi
                                       このディレクトリにmain.goファイルを作成しソースを書く

パッケージGorillaのインストール

gorillaはGoのウェブ開発用ツールキットです。
ツール毎にリポジトリがわかれている。
今回練習で実装するRestApiでは
ルーティング機能を使います。
インストールはターミナルから下記のgoコマンドを実行します。
GOPATHの直下に移動して実行するといい感じにパッケージがインストールされるはず。

$ go get -u github.com/gorilla/mux

コードについての説明

設計

作るものRestAPI
特定のURIにアクセスするとJson形式の情報がbodyに出力される。
実際はフロントエンド側でアプリを別途作成し、アプリ側からこのAPIをコール。
扱う情報は書籍(本)の情報とし、
項目は
 ID, ISBN, TITLE, AUTHOR
 AuthorはFirstname, Lastnameで構成する。

実際はデータベース等の接続と更新処理を必要とするが、これは練習なので
データベース接続や処理はモックとする。

APIのMETHODとエンドポイントは以下の構成とする。

http://localhost:8000/api/books
 期待値:複数の書籍情報を返す。
 メソッド GET
 パラメータ なし
 戻り値
 DBから取得した複数の書籍情報を返す。

http://localhost:8000/api/books/[id]
 期待値:IDに一致した書籍情報を返す。
 メソッド GET
 パラメータ ID
 戻り値:該当があれば1冊分の書籍情報を返す。

http://localhost:8000/api/books
 期待値:1冊の書籍情報を追加する
 メソッド POST
 パラメータ なし
 戻り値:追加後の1冊分のデータ
 POSTデータ:ISBN, TITLE, AUTHOR

http://localhost:8000/api/books/[id]
 期待値:1冊の書籍情報の更新する
 メソッド PUT
 パラメータ ID
 戻り値:更新後の1冊分のデータ
 PUTデータ:ID, ISBN, TITLE, AUTHOR

http://localhost:8000/api/books/[id]
 期待値:1冊の書籍情報の削除する
 メソッド DELETE
 パラメータ ID
 戻り値:削除後残った全書籍データ

実装コード

動画の中で書きすすめられたコードを写経してみた。

package main

import(
  "encoding/json"
  "log"
  "net/http"
  "math/rand"
  "strconv"
  "github.com/gorilla/mux"
)

// Book Struct
type Book struct {
  ID	string	`json:"id"`
  Isbn	string	`json:"isbn"`
  Title	string	`json:"title"`
  Author	*Author	`json:"author"`
}

// Author Struct
type Author struct {
  Firstname	string	`json:"firstname"`
  Lastname	string	`json:"lastname"`
}

// Init books var as a slice Book struct
var books []Book 

// Get All Books
func getBooks(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Type", "application/json")
  json.NewEncoder(w).Encode(books)
}

// Get Single Book
func getBook(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Type", "application/json")
  params := mux.Vars(r) // Get params
  // Loop through books and find with id
  for _, item := range books {
    if item.ID == params["id"] {
      json.NewEncoder(w).Encode(item)
      return
    }
  }
  json.NewEncoder(w).Encode(&Book{})
}

// Create a new Book
func createBook(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Type", "application/json")
  var book Book
  _ = json.NewDecoder(r.Body).Decode(&book)
  book.ID = strconv.Itoa(rand.Intn(10000000)) // Mock ID - not safe
  books = append(books, book)
  json.NewEncoder(w).Encode(book)
}

// Update a Book
func updateBook(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Type", "application/json")
  params := mux.Vars(r)
  for index, item := range books {
    if item.ID == params["id"] {
      books = append(books[:index], books[index+1:]...)
      var book Book
      _ = json.NewDecoder(r.Body).Decode(&book)
      book.ID = params["id"]
      books = append(books, book)
      json.NewEncoder(w).Encode(book)		
      return
    }
  }
  json.NewEncoder(w).Encode(books)
}

// Delete a Book
func deleteBook(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Type", "application/json")
  params := mux.Vars(r)
  for index, item := range books {
    if item.ID == params["id"] {
      books = append(books[:index], books[index+1:]...)
      break
    }
  }
  json.NewEncoder(w).Encode(books)
}

func main() {
  // init Router
  r := mux.NewRouter()

  // Mock Data - implemant DB
  books = append(books, Book{ID: "1", Isbn: "448743", Title: "Book One", Author: &Author{Firstname: "John", Lastname: "Doe"}})
  books = append(books, Book{ID: "2", Isbn: "847564", Title: "Book Two", Author: &Author{Firstname: "Steve", Lastname: "Smith"}})

  // Route Handler / Endpoints
  r.HandleFunc("/api/books", getBooks).Methods("GET")
  r.HandleFunc("/api/books/{id}", getBook).Methods("GET")
  r.HandleFunc("/api/books", createBook).Methods("POST")
  r.HandleFunc("/api/books/{id}", updateBook).Methods("PUT")
  r.HandleFunc("/api/books/{id}", deleteBook).Methods("DELETE")
  log.Fatal(http.ListenAndServe(":8000", r))

}

実行

とりあえず、簡略な方法としてターミナル内でrestapiの直下へ移動し

$ go build && ./restapi

テスト

restapi以下に作成したmain.goをビルド&実行した後に
各エンドポイント毎にjson形式でGET,POST,PUT,DELETEする。
Postman(アプリ)や「Talend API Tester – free edition」(chrome拡張機能)のツールを使う。
詳細は別記事でまとめます。

 

関連リンク

参考資料

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です