【Go実践】フォームで入力された情報をデータベースに登録する

シェアする

前回の記事で、入力フォームからPOSTされた値を受け取って画面に出力するプログラムを作成しました。

今回は、ここにデータベースへの登録処理を追加し、フォームへの入力値をテーブルへ保存する一連の流れを実装しながら解説していきます。

前提

今回のサンプルは、前回の記事で作成した入力フォームをベースに、データベースへの登録処理を追加していきます。

【Go実践】POSTされたデータを受け取って処理する ― 入力フォームと確認画面の作成

また、データベースへの接続・テーブル操作を行うにあたり、以下の記事で作成したqueryパッケージとqueryパッケージが存在することが前提となります。
また、動作確認までを行う場合、MySQLデータベース環境が構築されており、ユーザ情報マスタテーブルが作成済である必要があります。

【Go実践】GoでMySQLを使おう(1) – ドライバのインストールからデータベース接続まで
【Go実践】GoでMySQLを使おう(2) – 基本的なテーブル操作と構造体へのマッピング

当該記事を未読の方は、まずはそちらをご覧になってください。

前回のおさらいとハンドラ関数の分離

今回の追加で画面が3つになり、ともなってハンドリングが必要なURLも3つになります。
すべてのリクエストハンドラをメイン処理のファイルに記述すると、コードが煩雑になってしまいます。
そこで、新しくreq_handlerディレクトリを作成し、HTTPリクエストハンドラの関数を保有するパッケージを格納することにしましょう。

req_handlerディレクトリにh_UserForm.goファイルとh_UserRegistered.goファイルを作成し、前回作成したmain.goファイルからリクエストハンドラ関数をそれぞれ転記します。

package req_handler // 独自のHTTPリクエストハンドラパッケージ

import (
    "fmt"
    "html/template"
    "net/http"
)

/// 入力フォーム画面
func HandlerUserForm(w http.ResponseWriter, r *http.Request) {
    // テンプレートをパースする
    tpl := template.Must(template.ParseFiles("templates/user-form.gtpl"))

    // テンプレートに出力する値をマップにセット
    values := map[string]string{}

    // マップを展開してテンプレートを出力する
    if err := tpl.ExecuteTemplate(w, "user-form.gtpl", values); err != nil {
        fmt.Println(err)
    }
}
package req_handler // 独自のHTTPリクエストハンドラパッケージ

import (
    "fmt"
    "html/template"
    "net/http"
)

// 入力内容の確認画面
func HandlerUserConfirm(w http.ResponseWriter, req *http.Request) {
    // テンプレートをパースする
    tpl := template.Must(template.ParseFiles("templates/user-confirm.gtpl"))

    // テンプレートに出力する値をマップにセット
    values := map[string]string{
        "account": req.FormValue("account"),
        "name":    req.FormValue("name"),
        "passwd":  req.FormValue("passwd"),
    }

    // マップを展開してテンプレートを出力する
    if err := tpl.ExecuteTemplate(w, "user-confirm.gtpl", values); err != nil {
        fmt.Println(err)
    }
}

main.goファイルには、req_handlerパッケージのインポートを追加したうえで、リクエストハンドラ関数を削除します。

package main

import (
    "net/http"

    "./req_handler" // 実装したHTTPリクエストハンドラパッケージの読み込み
)

func main() {

    // "user-form"へのリクエストを関数で処理する
    http.HandleFunc("/user-form", req_handler.HandlerUserForm)

    // "user-confirm"へのリクエストを関数で処理する
    http.HandleFunc("/user-confirm", req_handler.HandlerUserConfirm)

    // サーバーを起動
    http.ListenAndServe(":8080", nil)
}

これで、前回のプログラムの機能はそのままに、リクエストハンドラ関数を専用パッケージに分離することができました。
念のため、いったんこの状態でmain.goを実行し、前回と同じ挙動をするか確認しておいてください。

データベース登録機能の実装

それでは、いよいよデータベースへの登録処理を追加していきましょう。
実装の仕方としては、入力内容の確認画面(user-confirm)からPOSTでデータを受け取り、受け取った情報をテーブルに登録させます。

確認画面の修正

まずは確認画面(user-confirm)を修正し、登録ボタンと、POST送信用のhiddenフィールドを追加します。
登録ボタン押下時の送信先はuser-registeredとしましょう。

<!DOCTYPE html>
<html>
<body>
  <h2>登録内容の確認</h2>
  <form action="user-registered" method="post">
    <table>
      <tr>
        <td>アカウント名:</td>
        <td>{{.account}}</td>
      </tr>
      <tr>
        <td>お名前:</td>
        <td>{{.name}}</td>
      </tr>
      <tr>
        <td>パスワード:</td>
        <td>{{.passwd}}</td>
      </tr>
    </table>
    <input type="hidden" name="account" value="{{.hid_account}}">
    <input type="hidden" name="name" value="{{.hid_name}}">
    <input type="hidden" name="passwd" value="{{.hid_passwd}}">
    <button type="button" onclick="history.back()">入力画面に戻る</button>
    <input type="submit" value="この内容で登録する">
  </form>
</body>
</html>

あわせて、追加したhiddenフィールドにもデータを流入するよう、ハンドラ関数も修正します。

package req_handler // 独自のHTTPリクエストハンドラパッケージ

import (
    "fmt"
    "html/template"
    "net/http"
)

// 入力内容の確認画面
func HandlerUserConfirm(w http.ResponseWriter, req *http.Request) {
    // テンプレートをパースする
    tpl := template.Must(template.ParseFiles("templates/user-confirm.gtpl"))

    // テンプレートに出力する値をマップにセット
    values := map[string]string{
        "account":     req.FormValue("account"),
        "name":        req.FormValue("name"),
        "passwd":      req.FormValue("passwd"),
        "hid_account": req.FormValue("account"),
        "hid_name":    req.FormValue("name"),
        "hid_passwd":  req.FormValue("passwd"),
    }

    // マップを展開してテンプレートを出力する
    if err := tpl.ExecuteTemplate(w, "user-confirm.gtpl", values); err != nil {
        fmt.Println(err)
    }
}
データベース登録画面のテンプレート

次に、登録ボタン押下時の送信先画面のテンプレートを作成します。
内容はひとまず、データベース登録処理の結果の成否を表示することとしましょう。

templatesディレクトリに、user-registered.gtplファイルを作成して、以下のように記述してください。

<!DOCTYPE html>
<html>
<body>
  <h2>登録結果の確認</h2>
  <span>{{.result}}</span><br>
  <button type=“button” onclick="location.href='./user-form'">入力画面に戻る</button>
</body>
</html>

{{.result}}フィールドに登録処理の結果を出力する想定です。

データベース登録処理の実装

それでは、いよいよデータベースへの登録を行う処理を追加します

req_handlerディレクトリに、h_UserRegistered.goファイルを作成して、以下のように記述してください。

package req_handler // 独自のHTTPリクエストハンドラパッケージ

import (
    "database/sql"
    "fmt"
    "html/template"
    "net/http"

    "../conf"    // 実装した設定パッケージの読み込み
    "../query"   // 実装したクエリパッケージの読み込み
    _ "github.com/go-sql-driver/mysql"
)

// 登録結果の確認画面
func HandlerUserRegistered(w http.ResponseWriter, req *http.Request) {

    // POSTデータINSERT関数を実行
    result := insertPostedUser(req)

    // テンプレートをパースする
    tpl := template.Must(template.ParseFiles("templates/user-registered.gtpl"))

    // テンプレートに出力する値をマップにセット
    values := map[string]string{
        "result": result,
    }

    // マップを展開してテンプレートを出力する
    if err := tpl.ExecuteTemplate(w, "user-registered.gtpl", values); err != nil {
        fmt.Println(err)
    }
}

// POSTデータINSERT関数
func insertPostedUser(req *http.Request) string {

    // 正常終了時のreturn値
    result := "ユーザ情報の登録に成功しました。"

    // 設定ファイルを読み込む
    confDB, err := conf.ReadConfDB()
    if err != nil {
        result = "設定ファイルの読み込みに失敗しました。"
    }

    // 設定値から接続文字列を生成
    conStr := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", confDB.User, confDB.Pass, confDB.Host, confDB.Port, confDB.DbName, confDB.Charset)

    // データベース接続
    db, err := sql.Open("mysql", conStr)
    if err != nil {
        result = "データベースへの接続に失敗しました。"
    }
    // deferで処理終了前に必ず接続をクローズする
    defer db.Close()

    // POST値を渡してINSERT処理を実行
    _, err = query.InsertUser(req.FormValue("account"), req.FormValue("name"), req.FormValue("passwd"), db)
    if err != nil {
        result = "ユーザ情報の登録に失敗しました。"
    }

    // 結果をreturnする
    return result
}

まず15行目で、登録ボタンクリック時のsubmit先であるuser-registeredへのリクエストハンドラ関数を定義しています。

POSTされた情報をテーブルへINSERTする処理は、35行目でinsertPostedUser関数として定義しています。
この処理の内容は、プレースホルダーにセットする値がPOST値であることを除き、以下の記事で実装した内容と同一です。
【Go実践】GoでMySQLを使おう(2) – 基本的なテーブル操作と構造体へのマッピング

insertPostedUserは、INSERT処理の結果をリターンしています。
リクエストハンドラ関数はこの結果を受け取り、テンプレートへ出力しています。

メイン処理への追記

最後に、main.gouser-registeredへのリクエストをハンドリングする処理を追記します。

package main

import (
    "net/http"

    "./req_handler" // 実装したHTTPリクエストハンドラパッケージの読み込み
)

func main() {

    // "user-form"へのリクエストを関数で処理する
    http.HandleFunc("/user-form", req_handler.HandlerUserForm)

    // "user-confirm"へのリクエストを関数で処理する
    http.HandleFunc("/user-confirm", req_handler.HandlerUserConfirm)

    // "user-registered"へのリクエストを関数で処理する
    http.HandleFunc("/user-registered", req_handler.HandlerUserRegistered)
    
    // サーバーを起動
    http.ListenAndServe(":8080", nil)
}

17-18行目のhttp.HandleFuncが追記箇所です。

実行結果

それではこのコードを実行し、ブラウザでhttp://localhost:8080/user-formにアクセスしましょう。
フォームに入力を行い、[確認画面へ]をクリックします。

フォームへ入力した値が確認画面に出力されたら、登録ボタンをクリックします。

実装に問題がなければ、以下のように登録処理が正常終了した旨が表示されます。

データベースのユーザマスタテーブルも確認してみましょう。
フォームに入力した内容が新規登録されているはずです。

終わりに

今回紹介した内容は、デザインやセッションの取り扱いなどは度外視しているものの、実践的なWEB開発においても骨子の一つとなる実装です。
GoでどのようにWEB開発を行っていくか、全体像がだんだんと解ってきたのではないでしょうか。

引き続き、データベースに登録されている情報をリスト表示したり、登録済み情報を修正する機能を実装していきたいと思います。

スポンサーリンク
スポンサーリンク
スポンサーリンク

シェアする

フォローする

スポンサーリンク
スポンサーリンク