GoでシンプルなWebAPIを作成

Golangで簡単なWebAPIを作りました。Frameworkを使うか悩みましたが、まだ玉石混交でデファクトスタンダードが決まっていないようなので、ルーターを使っています。

基本となるAPIサーバーを作ってみる

まずはじめに、HTTPリクエストを処理できるシンプルなAPIサーバーを用意。下記のように main.go ファイルを作り、3つのファンクションを定義しました。

package main

import (
  "fmt"
  "log"
  "net/http"
)

func homePage(w http.ResponseWriter, r *http.Request){
  fmt.Fprintf(w, "Welcome to the HomePage!")
  fmt.Println("Endpoint Hit: homePage")
}

func handleRequests() {
  http.HandleFunc("/", homePage)
  log.Fatal(http.ListenAndServe(":8081", nil))
}

func main() {
    handleRequests()
}
  • homePage…ルートURLへのすべてのリクエストを処理する関数として定義
  • handleRequests…叩かれたURLのパスと関数を結びつける(ルーティング)
  • main…APIを起動する関数

ポート8081が開かれた状態であれば、ブラウザで http://localhost:8081/ へアクセスすることで、レスポンスが返ってきます。

構造体の定義

下記コードでArticleという構造体と、複数形のArticlesを定義しています。Articlesは単数のArticleの配列です。

type Article struct {
    Title string `json:"Title"`
    Desc string `json:"desc"`
    Content string `json:"content"`
}

type Articles []Article

“encoding/json"というパッケージが必要になるので、importのリストに追記しておきます。

import (
  "fmt"
  "log"
  "net/http"
  "encoding/json"
)

全ての記事を取得する関数の定義

returnAllArticles という名前の関数を定義して、全ての記事を返すAPIのエンドポイントを用意します。

func returnAllArticles(w http.ResponseWriter, r *http.Request){
  articles := Articles{}
  for i := 0; i < 10; i++ {
    title := "Hello_%!d(MISSING)"
    articles = append(
      articles,
      Article{Title: fmt.Sprintf(title, i), Desc: "Article Description", Content: "Article Content"})
  }
  fmt.Println("Endpoint Hit: returnAllArticles")
  json.NewEncoder(w).Encode(articles)
}

このコードでは、 articles という配列を用意、for文で記事データを作成して代入しています。アクセスがあれば、エンドポイントが叩かれたというログと記事データをJSON形式で返します。次は、handleRequestsにルートを追加します。

func handleRequests() {
  http.HandleFunc("/", homePage)
  http.HandleFunc("/articles", returnAllArticles)
  log.Fatal(http.ListenAndServe(":8081", nil))
}

これで、 http://localhost:8081/all を叩くと。。!

[
  {
    "Title":"Hello_0",
    "desc":"Article Description",
    "content":"Article Content"
  },
  {
    "Title":"Hello_1",
    "desc":"Article Description",
    "content":"Article Content"
  }
  ...省略
]

↑のようなJSONが返ってきます。

gorilla/muxルーターの導入

とりあえず標準のライブラリだけで実装できることがわかりましたが、実際の構築は時間短縮のためサードパーティ製のルーターを使うことが多いかと思います。というわけで今回はgorilla/muxというルーターを使うことにしました↓

gorilla/mux router

強そうな名前ですよね。ゴリラマーックス(ง°̀ロ°́)ง!!

ちなみにmuxはmultiplexer(マルチプレクサ)。多重器とか、多重装置のことだそうです。複数の通信を束ねるゴリラ的な。gorilla/muxを使うには、適当なディレクトリに git clone します。Golangではgithub.comというディレクトリの中に置くのが通例のようです。

/go/src
├── github.com
│   └── gorilla
│       └── mux
└── main.go

ルーターの構築

importに github.com/gorilla/mux を追記。handleRequestsを下記のように書き換えます。

import (
  "fmt"
  "log"
  "net/http"
  "encoding/json"
  "github.com/gorilla/mux"
)

...省略

func handleRequests() {
  myRouter := mux.NewRouter().StrictSlash(true)
  myRouter.HandleFunc("/", homePage)
  myRouter.HandleFunc("/articles", returnAllArticles)
  log.Fatal(http.ListenAndServe(":8081", myRouter))
}
...省略

変数を含むパスの定義

ゴリラmuxのおかげで、変数をパスに追加することができるようになっています。

returnSingleArticleという関数を作り、個別記事のデータを返すエンドポイントを作成してみます。

↓下記をhandleRequestsに追記。{id}が変数です。

myRouter.HandleFunc("/article/{id}", returnSingleArticle)

↓Article構造体にIdプロパティを追記。

type Article struct {
  Id int `json:"Id"`
  Title string `json:"Title"`
  Desc string `json:"desc"`
  Content string `json:"content"`
}

↓returnSingleArticleを定義。

func returnSingleArticle(w http.ResponseWriter, r *http.Request){
    vars := mux.Vars(r)
    key := vars["id"]
    fmt.Fprintf(w, "Key: " + key + "\n")
}

これで http://localhost:1000/article/1 を叩くと、 Key: 1 が返ります。あとはMySQLやPostgresなどでデータを保存しておいて、URLのkeyにヒットするレコードを拾うようにすればORM的なものができるはず!

まとめ

Golangでもなんとかできそうですね。でもDjangoに比べるととても大変そう。

↓ソースコードまとめ

package main

import (
  "fmt"
  "log"
  "net/http"
  "encoding/json"
  "github.com/gorilla/mux"
)

type Article struct {
  Id int `json:"Id"`
  Title string `json:"Title"`
  Desc string `json:"desc"`
  Content string `json:"content"`
}

type Articles []Article

func homePage(w http.ResponseWriter, r *http.Request){
  fmt.Fprintf(w, "Welcome to the HomePage!")
  fmt.Println("Endpoint Hit: homePage")
}

func returnAllArticles(w http.ResponseWriter, r *http.Request){
  articles := Articles{}
  for i := 0; i < 10; i++ {
    title := "Hello_%!d(MISSING)"
    articles = append(
      articles,
      Article{Id: i, Title: fmt.Sprintf(title, i), Desc: "Article Description", Content: "Article Content"})
  }
  fmt.Println("Endpoint Hit: returnAllArticles")
  json.NewEncoder(w).Encode(articles)
}

func returnSingleArticle(w http.ResponseWriter, r *http.Request){
  vars := mux.Vars(r)
  key := vars["id"]
  fmt.Fprintf(w, "Key: " + key + "\n")
}

func handleRequests() {
  myRouter := mux.NewRouter().StrictSlash(true)
  myRouter.HandleFunc("/", homePage)
  myRouter.HandleFunc("/articles", returnAllArticles)
  myRouter.HandleFunc("/article/{key}", returnSingleArticle)
  log.Fatal(http.ListenAndServe(":8081", myRouter))
}

func main() {
  fmt.Println("Rest API v2.0 - Mux Routers")
  handleRequests()
}