Edited at

Golangで郵便番号から住所を取得するAPIを作成する


1. Google Maps API の登録と設定をする

https://cloud.google.com/maps-platform/?hl=ja

※ 今回使用するのは Geocoding API


2. Google Maps API を直接叩いてみる

以下のような URL でアクセスして、JSON形式で値が返ってきていれば OK.

https://maps.googleapis.com/maps/api/geocode/json?address=105-0003&language=ja&sensor=false&key=APIキー


参考


3. Go で上述のAPIを呼ぶ

基本的には以下のサンプルをもとに進めれば良い。

https://github.com/googlemaps/google-maps-services-go/blob/master/examples/geocoding/cmdline/main.go

ただ、はじめはなるべくミニマムで動かしたいので最低限の記述に変更する。


main.go

package main

import (
"context"
"flag"
"fmt"
"log"

"googlemaps.github.io/maps"
)

type Address struct {
Prefecture string `json:"prefecture"`
Locality string `json:"locality"`
}

var (
apiKey = flag.String("key", "APIキー", "API Key for using Google Maps API.")
language = flag.String("language", "ja", "The language in which to return results.")
region = flag.String("region", "JP", "The region code, specified as a ccTLD two-character value.")
)

func main() {
flag.Parse()

var client *maps.Client
var err error

client, err = maps.NewClient(maps.WithAPIKey(*apiKey))
outputFatalLog(err)

req := &maps.GeocodingRequest{
Address: "105-0003",
Language: *language,
Region: *region,
}
resp, err := client.Geocode(context.Background(), req)
outputFatalLog(err)

address := Address{
Prefecture: resp[0].AddressComponents[2].LongName,
Locality: resp[0].AddressComponents[1].LongName,
}

fmt.Println(address)
}

func outputFatalLog(err error) {
if err != nil {
log.Fatalf("fatal error: %s", err)
}
}


go run main.go を実行して {東京都 港区} と出力されれば OK.


4. API っぽくする

3 の状態では、固定された住所(東京都港区)しか返却できないので、受け取った郵便番号を元に住所を取得して、その結果を返却するAPIを作成する。


main.go

package main

import (
"context"
"encoding/json"
"flag"
"log"
"net/http"

"github.com/julienschmidt/httprouter"
"googlemaps.github.io/maps"
)

type Address struct {
Prefecture string `json:"prefecture"`
Locality string `json:"locality"`
}

type Error struct {
Message string `json:"message"`
}

var (
apiKey = flag.String("key", "APIキー", "API Key for using Google Maps API.")
language = flag.String("language", "ja", "The language in which to return results.")
region = flag.String("region", "JP", "The region code, specified as a ccTLD two-character value.")
)

func main() {
router := httprouter.New()
router.GET("/v1/:postalCode", SearchAddressByPostalCode)

log.Fatal(http.ListenAndServe(":8080", router))
}

func SearchAddressByPostalCode(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
flag.Parse()

w.Header().Set("Content-Type", "application/json; charset=UTF-8")

var client *maps.Client
var err error
if *apiKey != "" {
client, err = maps.NewClient(maps.WithAPIKey(*apiKey))
} else {
createErrorResponse("APIキーを指定してください", w, http.StatusBadRequest)
return
}
outputFatalLog(err)

req := &maps.GeocodingRequest{
Address: ps.ByName("postalCode"),
Language: *language,
Region: *region,
}
resp, err := client.Geocode(context.Background(), req)
outputFatalLog(err)

if len(resp) == 0 {
createErrorResponse("住所が見つかりませんでした", w, http.StatusNotFound)
return
}

address := Address{
Prefecture: resp[0].AddressComponents[2].LongName,
Locality: resp[0].AddressComponents[1].LongName,
}

w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(address)
}

func createErrorResponse(msg string, w http.ResponseWriter, statusCode int) {
errorResponse := Error{
Message: msg,
}
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(errorResponse)
}

func outputFatalLog(err error) {
if err != nil {
log.Fatalf("fatal error: %s", err)
}
}


go run main.go で起動し、別タブで curl -v --silent localhost:8080/v1/105-0003 | jq のようにアクセスして、以下のようなレスポンスが返ってきていれば OK.

> GET /v1/105-0003 HTTP/1.1

> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=UTF-8
< Date: Tue, 30 Jul 2019 07:16:31 GMT
< Content-Length: 47
<
{ [47 bytes data]
* Connection #0 to host localhost left intact
{
"prefecture": "東京都",
"locality": "港区"
}


参考

http://sgykfjsm.github.io/blog/2016/03/13/golang-json-api-tutorial/


おまけ

apiKey = flag.String("key", "APIキー", "API Key for using Google Maps API.")

手元でテストする分には良いが、APIキーをこのままコミットしてしまうと大変なことになるので、環境変数から取得できるように改修する。


.env

GOOGLE_MAP_API_KEY=hogefuga


やり方は色々あるかもしれないが、自分は以下のように書き換えた。


main.go

apiKey := os.Getenv("GOOGLE_MAP_API_KEY")

if apiKey == "" {
panic("APIキーを指定してください")
return
}
client, err = maps.NewClient(maps.WithAPIKey(apiKey))



あとがき

今回は Google Maps API をラップしたAPIを自作したことになる。

直接クライアントから Google Maps API を呼んでも問題ないが、わざわざラップするメリットはいくつかある。


  • APIキーの管理が楽


    • 呼び出し元のクライアントが複数になると、APIキーをクライアントそれぞれで管理することになる



  • レスポンスを改造できる



    • {"prefecture_id": "13", "prefecture": "東京都", "locality": "港区" } みたいなことができる