Echo

2021年1月27日

【Go】EchoのTemplatesでForm送信サンプル

EchoのTemplates機能でForm送信を実装

前の記事でJSONレスポンスのサンプルを、HTMLのformタグから送信するように変更、ブラウザから登録・変更・削除できるようにします。

ディレクトリ構造(変更)

ディレクトリにHTMLテンプレートを追加して、構造は下記のように変更になります。

└── src
    └── sample
        ├── controller
        │   └─ user_controller.go
        ├── go.mod
        ├── go.sum
        ├── main.go
        ├── model
        │   ├── db.go
        │   └── user.go
        ├── public
        │   └── views
        │       ├── signup.html
        │       └── user.html
        ├── router
        │   └── router.go

追加変更したソース

前の記事から追加変更したソースのみ記載します。
main.go、そしてmodelフォルダ内のdb.goとuser.goは変更ありません。

controller/user_controller.go

ここにテンプレート機能の呼び出しを追加しています。
重要なポイントとして、現在のブラウザはmethodがGETかPOSTの送信方式にしか対応していません。
PUTやDELETEを指定しても非対応です。
これがPHPなどでは、<input type=”hidden” name=”_method” value=”PUT”>とformタグ内に入れることでPUT方式として受信することができますが、Goの場合はそのままで送信してもGET扱いになります。

Echoの公式ドキュメントにmethodの上書きについて記載があります。
Method OverrideするmiddleWareを追加して、PUTやDELETEに対応させるようにします。

https://echo.labstack.com/middleware/method-override

※本当はユーザー情報の登録や更新に際して、入力内容のバリデーションやサニタイズなどのセキュリティ対策をすべきです。ですのでこのサンプルソースをそのままプロダクトに利用しないでください。この記事ではあくまでもTemplates機能の説明のみに焦点を当てています。

package controller

import (
	"net/http"
	"strconv"

	"sample/model"

	"github.com/labstack/echo/v4"
)

type User struct {
	ID   uint   `json:"id" form:"id" query:"id"`
	Name string `json:"name" form:"name" query:"name"`
	Age  uint   `json:"age" form:"age" query:"age"`
}

func MethodOverride(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		if c.Request().Method == "POST" {
			method := c.Request().PostFormValue("_method")
			if method == "PUT" || method == "PATCH" || method == "DELETE" {
				c.Request().Method = method
			}
		}
		return next(c)
	}
}

func SignUp(c echo.Context) error {
	return c.Render(http.StatusOK, "signup.html", map[string]interface{}{
		"title": "Sign Up Form",
	})
}

func GetUser(c echo.Context) error {
	i, _ := strconv.Atoi(c.Param("id"))
	id := uint(i)
	user := model.User{}
	user.FirstById(id)

	return c.Render(http.StatusOK, "user.html", map[string]interface{}{
		"title": "Edit Your Account",
		"id":    user.ID,
		"name":  user.Name,
		"age":   user.Age,
	})
}

func CreateUser(c echo.Context) error {
	name := c.FormValue("name")
	a, _ := strconv.Atoi(c.FormValue("age"))
	age := uint(a)

	user := model.User{
		Name: name,
		Age:  age,
	}
	user.Create()

	return c.Render(http.StatusOK, "user.html", map[string]interface{}{
		"title": "Edit Your Account",
		"id":    user.ID,
		"name":  user.Name,
		"age":   user.Age,
	})
}

func UpdateUser(c echo.Context) error {
	i, _ := strconv.Atoi(c.Param("id"))
	id := uint(i)
	name := c.FormValue("name")
	a, _ := strconv.Atoi(c.FormValue("age"))
	age := uint(a)

	user := model.User{
		ID:   id,
		Name: name,
		Age:  age,
	}
	user.Updates()

	return c.Render(http.StatusOK, "user.html", map[string]interface{}{
		"title": "Updated Your Account",
		"id":    user.ID,
		"name":  user.Name,
		"age":   user.Age,
	})
}

func DeleteUser(c echo.Context) error {
	i, _ := strconv.Atoi(c.Param("id"))
	id := uint(i)
	user := model.User{}
	user.DeleteById(id)

	return c.Render(http.StatusOK, "signup.html", map[string]interface{}{
		"title": "Sign Up Form",
	})
}

router/router.go

ルーティングに先ほどのmiddleWareを事前に読み込む記述、e.Pre(controller.MethodOverride)を追記します。
そのほかにもテンプレートを読み込む記述を追記します。

package router

import (
	"io"
	"sample/controller"
	"text/template"

	"github.com/labstack/echo/v4"
)

// TemplateRenderer is a custom html/template renderer for Echo framework
type TemplateRenderer struct {
	templates *template.Template
}

// Render renders a template document
func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
	// Add global methods if data is a map
	if viewContext, isMap := data.(map[string]interface{}); isMap {
		viewContext["reverse"] = c.Echo().Reverse
	}

	return t.templates.ExecuteTemplate(w, name, data)
}

func Init() {
	t := &TemplateRenderer{
		templates: template.Must(template.ParseGlob("public/views/*.html")),
	}
	e := echo.New()
	e.Pre(controller.MethodOverride)
	e.Renderer = t
	e.GET("/", controller.SignUp)
	e.GET("/user/:id", controller.GetUser)
	e.POST("/user", controller.CreateUser)
	e.PUT("/user/:id", controller.UpdateUser)
	e.DELETE("/user/:id", controller.DeleteUser)

	e.Logger.Fatal(e.Start(":8080"))
}

public/views/signup.html

データ登録に使用するテンプレートになります。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{index . "title"}}</title>
</head>
<body>
    <h1>{{index . "title"}}</h1>
    <form method="POST" action="/user">
        <dl>
            <dt>Name:</dt>
            <dd><input type="text" name="name"></dd>
            <dt>Age:</dt>
            <dd><input type="number" name="age"></dd>
        </dl>
        <p><input type="submit" name="submit" value="Sign Up"></p>
    </form>
</body>
</html>

public/views/user.html

登録されたデータを表示するテンプレートになります。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{index . "title"}}</title>
</head>
<body>
    <h1>{{index . "title"}}</h1>

    <h2>Your Account</h2>
    <form method="POST" action="/user/{{index . "id"}}">
        <input type="hidden" name="_method" value="PUT">
        <dl>
            <dt>ID:</dt>
            <dd>{{index . "id"}}</dd>
            <dt>Name:</dt>
            <dd><input type="text" name="name" value="{{index . "name"}}"></dd>
            <dt>Age:</dt>
            <dd><input type="text" name="age" value="{{index . "age"}}"></dd>
        </dl>
        <p><input type="submit" name="submit" value="Submit"></p>
    </form>
    
    <h2>Delete</h2>
    <form method="POST" action="/user/{{index . "id"}}">
        <input type="hidden" name="_method" value="DELETE">
        <p><input type="submit" name="submit" value="Delete"></p>
    </form>
</body>
</html>

これでブラウザで http://localhost:8080/ にアクセスするとフォームが表示されます。

今回の記事は以上となります。
次回の記事では、JWT認証に挑戦します。

LINEで送る
Pocket

label

isaka

Written by
isaka

バックエンドエンジニア

CONTACT

お問い合わせ、ご依頼などは下記電話番号かメールアドレスまでご連絡ください。
※内容により回答までお時間をいただく場合がございます、予めご了承ください。

tel. 06-6534-9333

10:00-19:00(※土日祝を除く)