Chapter 13

APIバックエンド連携の例

NoboNobo
NoboNobo
2020.10.28に更新

バックエンド連携

バックエンド連携の戦略は大きく二つ。

  • バックエンドとフロントエンドを同じドメインからサーブ
  • CORS対応バックエンドとフロントエンドを別ドメインからサーブ

前者の場合はspago server-p path=urlオプションでバックエンドへ転送をかけることで開発を進めることができます。後者の場合はバックエンドのエンドポイントさえわかればそれでフロントの開発ができますが、httpとhttpsアクセスの混在が問題になる場合があります。さらに最終的なデプロイにはフロントエンドもバックエンドもhttpsでアクセス可能にする必要があります(そうしない場合は気をつけることが増えてしまいます)。

ここでは前者のスタイルの例を解説します。

シンプルな例

コードツリー

  • ルート/
    • go.mod
    • assets/ 後述します
    • backend/
      • api.go
    • frontend/
      • index.go
    • server.go

ルートでモジュール初期化

> go mod init simple-api

結果以下のファイルができます。
go.mod

module simple-api

go 1.15

バックエンドAPI実装
api.go

package backend

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

func now(w http.ResponseWriter, r *http.Request) {
	if err := json.NewEncoder(w).Encode(time.Now().Unix()); err != nil {
		log.Print(err)
		http.Error(w, "get now failed", http.StatusBadGateway)
	}
}

func init() {
	http.HandleFunc("/api/now", now)
}

フロントエンドビュー実装
index.go

package main

import (
	"syscall/js"

	"github.com/nobonobo/spago"
	"github.com/nobonobo/spago/jsutil"
)

var Now string = "unknown"

type Index struct{ spago.Core }

func (c *Index) Render() spago.HTML {
	return spago.Tag("body",
		spago.Tag("label",
			spago.T(spago.S(Now)),
		),
		spago.Tag("button",
			spago.Event("click", c.OnClick),
			spago.T("Get Now"),
		),
	)
}

func (c *Index) OnClick(ev js.Value) {
	go func() {
		resp, err := jsutil.Fetch("/api/now", nil)
		if err != nil {
			js.Global().Call("alert", err.Error())
			return
		}
		value, err := jsutil.Await(resp.Call("json"))
		if err != nil {
			js.Global().Call("alert", err.Error())
			return
		}
		Now = js.Global().Get("JSON").Call("stringify", value).String()
		spago.Rerender(c)
	}()
}

func main() {
	spago.RenderBody(&Index{})
	select {}
}

バックエンドサーバー起動実装
server.go

package main

import (
	"log"
	"net/http"

	_ "simple-api/backend"
)

func main() {
	if err := http.ListenAndServe(":8888", nil); err != nil {
		log.Fatal(err)
	}
}

バックエンドサーバー起動

> go run .

フロントエンドサーバー起動

> cd frontend
> spago server -p /api/=http://localhost:8888/api/
2020/10/28 10:43:11 listen and serve: :8080
2020/10/28 10:43:11 proxy: "/api/" => "http://localhost:8888/api/"

これでパスプレフィックスが/api/のものはバックエンドサーバーに転送されます。
それ以外はフロントエンドサーバーが応答します。

ブラウザで表示

http://localhost:8080/ を開きましょう。

Get Nowをクリックすると?うまく動くと以下のような表示になります。

何かエラーがあるとアラート表示が出ます。

開発の進め方

二つのサーバーを並行して起動した状態で開発を進めると良いです。
修正作業を行った後は以下のように操作することで最新の状態になります。

  • バックエンドの修正をしてバックエンドを再起動
  • フロントエンドの修正をしてブラウザをリロード

サーバーをまとめる

フロントエンドをspago deployすると静的ファイル群に出力できますので、バックエンドサーバーにそのファイル群をサーブする機能を追加します。

> cd frontend
> spago deploy ../assets

このあと、server.goのmain関数の先頭に一行追加します。
server.go

package main

import (
	"log"
	"net/http"

	_ "simple-api/backend"
)

func main() {
	http.Handle("/", http.FileServer(http.Dir("./assets")))
	if err := http.ListenAndServe(":8888", nil); err != nil {
		log.Fatal(err)
	}
}

これでバックエンドサーバーだけでフロントエンドもバックエンドも動作する状態になります。
バックエンドサーバーを再起動して http://localhost:8888/ を開けば開発中と同様に動作することが確認できるはずです。

また、このままフロントエンド用サーバーを起動して http://localhost:8080/ を開けばフロントエンドの開発作業を再開できます。(この場合、フロントエンドリソースはフロントエンドサーバーが返すのでバックエンドサーバーへのリクエストは/api/以下だけになります。)