Go言語でWeb APIを作成してWijmo×Reactのフロントエンドアプリと連携する

Go言語は、Googleが開発したオープンソースのプログラミング言語です。コマンドラインツールやWebアプリケーション、クラウドサービスの開発、DevOpsやSREなど幅広い用途に使用可能な言語です。

今回はこのGo言語を使用してMySQLのデータベースと連携するWeb APIを作成し、さらにReactとJavaScript開発ライブラリ「Wijmo(ウィジモ)」で作成したフロントエンドアプリケーションと連携して、データの生成(Create)、読込(Read)、更新(Update)、削除(Delete)を行う方法をご紹介します。

バックエンド

データベースの作成

今回はデータベースにMySQLを使用します。以下の記事を参考に予めMySQLの環境の構築を行ってください。

Web APIの作成

まずは任意の場所に「go-api」フォルダを作成し、「go mod init」コマンドを使用してモジュールの初期化を行います。

go mod init example.com/go-api

また、APIのルーティングの設定の為に「gorilla/mux」、MySQLを利用するためのドライバーとして「Go-MySQL-Driver」をそれぞれインストールします。

go get github.com/gorilla/mux
go get github.com/go-sql-driver/mysql

インストールが完了したら、「server.go」ファイルを作成し、APIの処理を追加していきます。まずはDBのカラムを扱うためにOrder構造体とOrders構造体の配列を定義します。

package main

import (
	"database/sql"
	"encoding/json"
	"io"
	"log"
	"net/http"
	"strconv"

	_ "github.com/go-sql-driver/mysql"
	"github.com/gorilla/mux"
)

type Order struct {
    Id      int    `json:"id"`
    Product string `json:"product"`
    Date    string `json:"date"`
    Amount  int    `json:"amount"`
}

type Orders []Order

次にDBにアクセスする処理を設定します。

・・・(中略)・・・
func dbConn() (db *sql.DB) {
	// DB名やユーザー、パスワードなどは環境に応じて適切な値を設定してください
	db, err := sql.Open("mysql", "root:xxxx@/test")
	if err != nil {
		panic(err.Error())
	}
	return db
}

次にmain関数内にmuxモジュールを利用してルーティングの設定を行います。内部で実行している各メソッドはこの後それぞれ定義していきます。

・・・(中略)・・・
func main() {
	r := mux.NewRouter()
	r.HandleFunc("/api/orders", getOrders).Methods("GET")
	r.HandleFunc("/api/orders/{id}", getOrder).Methods("GET")
	r.HandleFunc("/api/orders", createOrder).Methods("POST")
	r.HandleFunc("/api/orders/{id}", updateOrder).Methods("PATCH")
	r.HandleFunc("/api/orders/{id}", deleteOrder).Methods("DELETE")

	http.ListenAndServe(":8000", setHeaders(r))
}

次にリクエストから各パラメータを取得するメソッドを定義します。getParameterメソッドではリクエストパラメータをOrder構造体に変換し、getIdメソッドはリクエストからIDを取得します。

・・・(中略)・・・
func getParameter(r *http.Request) (item Order) {
	// リクエストパラメータを読み取ります
	bodyBytes, err := io.ReadAll(r.Body)
	if err != nil {
		log.Panic(err)
		return
	}
	var param Order
	// Order構造体に変換します
	err = json.Unmarshal(bodyBytes, ¶m)
	if err != nil {
		log.Panic(err)
		return
	}
	return param
}
func getId(r *http.Request) (id int) {
	params := mux.Vars(r)
	//パラメータのidをint型に変換します
	id, err := strconv.Atoi(params["id"])
	if err != nil {
		log.Panic(err.Error())
	}
	return id
}

次にGET、POST、PATCH、DELETEの処理を定義していきます。GET(全件参照)ではDBから取得したレコードを1件ずつOrder構造体に変換した後にOrders配列に追加し、最終的にOrders配列を返却しています。POSTでは現在のDB内のIDカラムの最大値を取得し、IDの最大値+1の値をリクエストパラメータと共にDBへINSERTしています。

・・・(中略)・・・
// GET(全件参照)
func getOrders(w http.ResponseWriter, r *http.Request) {
	db := dbConn()
	defer db.Close()

	rows, err := db.Query("SELECT * FROM orders")

	var orders Orders
	if err != nil {
		return
	}
	for rows.Next() {
		m := Order{}
		rows.Scan(&m.Id, &m.Product, &m.Date, &m.Price)
		orders = append(orders, m)
	}
	//ステータスコード200を返却します
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(orders)
}

// GET(1件参照)
func getOrder(w http.ResponseWriter, r *http.Request) {
	db := dbConn()
	defer db.Close()

	m := Order{}
	id := getId(r)
	err := db.QueryRow("SELECT * FROM orders WHERE id=?", id).Scan(&m.Id, &m.Product, &m.Date, &m.Price)

	if err != nil {
		log.Panic(err)
	}
	//ステータスコード200を返却します
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(m)
}

// POST
func createOrder(w http.ResponseWriter, r *http.Request) {
	db := dbConn()
	defer db.Close()

	ins, err := db.Prepare("INSERT INTO orders(id,product,date,price) VALUES(?,?,?,?)")
	if err != nil {
		log.Panic(err)
	}

	var id int
	//登録されているIDの最大値を取得します
	err = db.QueryRow("SELECT id FROM orders WHERE id = (SELECT MAX(id) FROM orders)").Scan(&id)
	if err != nil {
		log.Panic(err)
	}

	param := getParameter(r)

	//idを加算します
	id += 1
	_, er := ins.Exec(id, param.Product, param.Date, param.Price)
	if er == nil {
		//ステータスコード201を返却します
		w.WriteHeader(http.StatusCreated)
	}
}

// PATCH
func updateOrder(w http.ResponseWriter, r *http.Request) {
	db := dbConn()
	defer db.Close()

	upd, err := db.Prepare("UPDATE orders SET product=?,date=?,price=? WHERE id=?")
	if err != nil {
		log.Panic(err)
	}

	id := getId(r)
	param := getParameter(r)

	_, er := upd.Exec(param.Product, param.Date, param.Price, id)
	if er == nil {
		//ステータスコード200を返却します
		w.WriteHeader(http.StatusOK)
	}
}

// DELETE
func deleteOrder(w http.ResponseWriter, r *http.Request) {
	db := dbConn()
	defer db.Close()

	order, err := db.Prepare("DELETE FROM orders WHERE id=?")
	if err != nil {
		log.Panic(err)
	}

	id := getId(r)
	_, er := order.Exec(id)
	if er == nil {
		//ステータスコード204を返却します
		w.WriteHeader(http.StatusNoContent)
	}
}

最後にCORSの設定を行います。オリジンにはこれから作成するReactアプリのものを設定します。

・・・(中略)・・・
func setHeaders(h http.Handler) http.Handler {
	//CORSを有効にします
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		//オリジンはAPIを実行するアプリケーションに応じて設定してください
		w.Header().Set("Access-Control-Allow-Origin", "http://localhost:5173")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE, OPTIONS")
		w.Header().Set("Content-Type", "application/json")
		w.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type")
		if r.Method == "OPTIONS" {
			return
		}

		h.ServeHTTP(w, r)
	})
}

Web APIの実行

以上でWeb APIの作成が完了したので実行してみます。以下のコマンドを実行してAPIを起動します。

go run server.go

APIを起動したら、Postmanなどのツールを使用してリクエストを送信して動作を確認してみます。

GET(全件参照)

「http://localhost:8000/api/orders」に対してGETリクエストを送信すると、DBから取得したデータが一覧で表示されます。

GET(1件参照)

「http://localhost:8000/api/orders/15」のようにIDを指定して1件だけ参照することもできます。

POST(登録)

「http://localhost:8000/api/orders」に対して以下のパラメータを追加してPOSTリクエストを送信すると、データの登録が可能です。

{
    "product": "果汁100% グレープ",
    "date": "2023-01-30",
    "price": 12000
}

PATCH(更新)

「http://localhost:8000/api/orders/85」とIDを指定して以下のパラメータと共にPATCHリクエストを送信し、先ほど登録したレコードを更新します。

{
    "product": "果汁100% グレープ",
    "date": "2023-01-30",
    "price": 25000
}

DELETE(削除)

最後に「http://localhost:8000/api/orders/85」にDELETEリクエストを送信し、登録したデータを削除します。

フロントエンド

Go言語でWeb APIが作成できましたので、そのAPIと連携するフロントエンドのアプリをReactとWijmoで作成していきます。

ViteでReactアプリケーションの作成

Reactのアプリを作成する方法はいくつかありますが、今回はビルドツール「Vite」を使用します。

以下のコマンドを実行して、ベースとなるReactアプリケーションを作成します。今回は「react-ts」のオプションを指定してReactとTypeScriptのアプリケーションをスキャフォールドします。
※ 使用可能なテンプレートはこちらをご覧ください。

npm create vite wijmo-react-app -- --template react-ts

次に以下のコマンドを実行してアプリケーションプロジェクトのフォルダに移動し、必要なパッケージのインストールを行います。

cd wijmo-react-crud
npm install

パッケージがインストールされたら、以下のコマンドを実行してReactアプリケーションを起動します。

npm run dev

ブラウザで「http://localhost:5173/」を参照すると以下のように開発サーバーでReactアプリケーションの実行を確認できます。

Reactアプリケーションの実行

アプリケーションの起動を確認したらCtrl+Cキーを押下して終了しておきます。

Wijmoのインストールとアプリへの組み込み

「npm install」コマンドを実行して、WijmoのReact用パッケージをアプリケーションにインストールします。

npm install @grapecity/wijmo.react.all

「src\App.tsx」を以下のように修正し、Wijmoのデータグリッドコントロール「FlexGrid」を組み込んでCRUD処理を行います。

コードの中では、WijmoのReactモジュールやCSS、日本語カルチャファイルをインポートし、WijmoのhttpRequestメソッドを使用してWeb APIからデータを取得しています。また、グリッド上のデータ管理を行うCollectionViewはstateを使用して管理します。

[更新]押下時に実行される「update」関数では登録、更新、削除の処理を行っており、FlexGrid上で行われた変更内容が格納される「customer.itemsEdited」「customer.itemsAdded」「customer.itemsRemoved」の3つの配列を参照してリクエストをそれぞれ送信します。
※ ライセンスキーを設定しない場合トライアル版を示すメッセージが表示されます。ライセンスキーの入手や設定方法についてはこちらをご覧ください。

import * as React from "react";
import "@grapecity/wijmo.styles/wijmo.css";
import * as wjCore from "@grapecity/wijmo";
import * as wjGrid from "@grapecity/wijmo.react.grid";
import { FlexGridFilter } from "@grapecity/wijmo.react.grid.filter";
import "@grapecity/wijmo.cultures/wijmo.culture.ja";

wjCore.setLicenseKey('ここにライセンスキーの文字列を設定します');
const url = "http://localhost:8000/api/orders";

function App() {
  // CollectionViewをstateで管理
  const [order, setOrder] = React.useState(new wjCore.CollectionView());

  //GET
  React.useEffect(() => {
    wjCore.httpRequest(url, {
      success: (xhr) => {
        setOrder(
          new wjCore.CollectionView(JSON.parse(xhr.response), {
            trackChanges: true,
          })
        );
      },
    });
  }, []);


  const update = () => {
    //PATCH
    for (const item of order.itemsEdited) {
      wjCore.httpRequest(url + "/" + item.id, {
        method: "PATCH",
        data: item,
      });
    }

    //POST
    for (const item of order.itemsAdded) {
      wjCore.httpRequest(url, {
        method: "POST",
        data: item,
      });
    }

    //DELETE
    for (const item of order.itemsRemoved) {
      wjCore.httpRequest(url + "/" + item.id, {
        method: "DELETE",
      });
    }

    order.itemsEdited.length > 0 &&
      alert(order.itemsEdited.length + "件のデータを更新しました。");
    order.itemsAdded.length > 0 &&
      alert(order.itemsAdded.length + "件のデータを登録しました。");
    order.itemsRemoved.length > 0 &&
      alert(order.itemsRemoved.length + "件のデータを削除しました。");
  };

  return (
    <>
      <button onClick={update} className="button">
        更新
      </button>
      <wjGrid.FlexGrid itemsSource={order} allowAddNew={true} allowDelete={true}>
        <FlexGridFilter />
        <wjGrid.FlexGridColumn header="商品名" binding="product" width={250} />
        <wjGrid.FlexGridColumn header="受注日" binding="date" width={150} />
        <wjGrid.FlexGridColumn header="受注金額" binding="price" width={150} format="c"/>
      </wjGrid.FlexGrid>
    </>
  )
}

export default App

最後に仕上げとして「src\index.css」に設定されているデフォルトのスタイルを削除し、代わりにボタンのスタイルを追加します。

.button {
  font-size: 18px;
  margin-bottom: 10px;
  padding: 10px 40px;
}

データの取得(READ)

以上の手順で、Wijmoの組み込みは完了です。再び「npm run dev」コマンドを実行して「http://localhost:5173/」に接続すると、FlexGrid上にAPIから取得したデータが表示されていることを確認できます。
※ 事前に冒頭で作成したGo言語のWeb APIを起動しておいてください。

APIからデータの取得

ソートやExcelライクなフィルタも使用可能です。

データの登録(CREATE)

FlexGridの一番下の行にデータを入力することで新規データの登録が可能です。データ入力後[更新]ボタンを押下するとAPIに登録のリクエストを送信できます。

データの更新(UPDATE)

FlexGrid上で任意のデータを更新し、[更新]ボタンを押下するとAPIに更新のリクエストを送信できます。

データの削除(DELETE)

FlexGrid上で削除したい行を選択しDeleteキーを押下すると対象の行を削除できます。その後、[更新]ボタンを押下するとAPIに削除のリクエストを送信できます。

さいごに

以上がGo言語を使用してWeb APIを作成し、ReactとWijmoを使ったフロントエンドアプリと連携する方法でした。

Wijmoに関する詳細は以下のWebサイトをご覧ください。

Wijmoの機能を手軽に体験できるデモアプリケーションやトライアル版も公開しておりますので、こちらもご確認ください。

また、ご導入前の製品に関するご相談、ご導入後の各種サービスに関するご質問など、お気軽にお問合せください。

\  この記事をシェアする  /