Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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": "港区" } みたいなことができる
kamakura-net
株式会社鎌倉新書は、さまざまな情報を集めて提供する、情報加工会社です。人々の織りなす不思議な縁と、その美しく強い結びつきのお手伝いをすることにより、私たちの企業と私たちの社会の発展、繁栄に貢献することを経営理念としています。
https://www.kamakura-net.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away