12
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Go x Gin x GORMでToDoアプリを作成してみる

Last updated at Posted at 2019-11-04

はじめに

今年(2019年)の5月からチーム内の勉強会が週1〜2ペースで開催されるようになり、
モダンな言語を新たに習得したかったため積極的に参加して学習を進めてきました。
基本的なコードを読めるようになり、更なる理解を深めようと次のステップとして、
お決まりですがToDoアプリを作成したので手順などをまとめておきます。

今回GinとGORMを採用した理由は参考にできる記事や公式のドキュメントが充実しているからです。

勉強会について

半年で進めていた内容は以下の通りです。

  • A Tour of Go
    →読書会形式で皆で内容を読み進め、playgrond上でコードをし挙動を確認し進めました。
  • Writing Web Applications
    → 講義形式(ハンズオン)で実装を進めました。
  • GCPのチュートリアル
    →講義形式(ハンズオン)で実装を進めました。
  • AtCoder
    →低難度の問題を繰り返し解き、基本構文(変数宣言、関数宣言、slice、for文など)の記述方法に慣れました。

今回の勉強会の参加者は全員Golang未経験だったため、ファシリテーター(進行役)が説明する講義・発表会形式ではなく、皆で資料の内容を読みながら疑問点を洗い出し、Googleで検索して疑問を解消するような流れが多かったです。
これは勉強会が講義・発表会形式だとファシリテーターが資料や教材の準備などが負担になり勉強会が開催されなくなるケースを防ぐためです。
とにかく一週間に一度はGoに触れようというコンセプトで今も勉強会を継続しています。

開発環境

  • MacBook Pro
  • go version : 1.12.9
version
 $ go version
go version go1.12.9 darwin/amd64

事前情報

Webフレームワーク : Gin
ORM : GORM
DB : sqlite3
依存関係管理ツールdep : dep

プロジェクト構成
├── Gopkg.lock
├── Gopkg.toml
├── README.md
├── controllers
│   └── task_controller.go
├── db
│   └── db.go
├── main.go
├── models
│   └── task.go
├── router
│   └── router.go
├── task.db
├── templates
│   ├── edit.html
│   └── index.html
└── vendor

ソースコードはGitHubにアップロードしています。

プロジェクトフォルダの作成

プロジェクトフォルダの作成
# $GOPATH + github.com + user名の配下にプロジェクトを作成
# ~/go/src/github.com/hanadaUG/go-gin-gorm-todo-app

$ cd go/src/github.com/hanadaUG/
$ mkdir go-gin-gorm-todo-app
$ cd go-gin-gorm-todo-app

depで外部パッケージを管理する

dep
# macにdepをインストール
$ brew install dep

# 初期化
$ dep init

# 外部パッケージをimportした時、vendor配下にインストールする
$ dep ensure

main.go

Webアプリの起動起点となるmain.goではDBの初期化+Open処理、
ルーティングの設定のみを行うシンプルな構造になっています。

main.go
package main

import (
	"github.com/hanadaUG/go-gin-gorm-todo-app/db"
	"github.com/hanadaUG/go-gin-gorm-todo-app/router"
)

func main() {
	// DBのOpen & Close処理の遅延実行
	db.Initialize()
	defer db.Close()

	// ルーティング設定
	router.Router()
}

db/db.go

DBのOpen/Close処理の制御はmain.goから行うことを想定しているため
publicな関数(関数名を大文字から始める)として定義しています。
Get関数はDBのインスタンスを外部から取得するために用意しました。

db/db.go
package db

import (
	"github.com/hanadaUG/go-gin-gorm-todo-app/models"
	"github.com/jinzhu/gorm"
	_ "github.com/mattn/go-sqlite3"
)

var db *gorm.DB

func Initialize() {
	// 宣言済みのグローバル変数dbをshort variable declaration(:=)で初期化しようとすると
	// ローカル変数dbを初期化することになるので注意する

	// DBのコネクションを接続する
	db, _ = gorm.Open("sqlite3", "task.db")
	//if err != nil {
	//	panic("failed to connect database\n")
	//}

	// ログを有効にする
	db.LogMode(true)

	// Task構造体(Model)を元にマイグレーションを実行する
	db.AutoMigrate(&models.Task{})
}

func Get() *gorm.DB {
	return db
}

// DBのコネクションを切断する
func Close() {
	db.Close()
}

models/task.go

ToDoアプリに登録する1タスクの定義のみ記述しています。
gorm.Model構造体を埋め込み(embedded)、
Textというフィールド変数を追加しています。
※ タスクの状態を保持するStateというフィールド変数を後日追加します。

models/task.go
package models

import "github.com/jinzhu/gorm"

type Task struct {
	gorm.Model
	Text string
}

controllers/task_controller.go

CRUDの挙動を記述しています。
templateからの入力を受け、出力内容を制御します。

controllers/task_controller.go
package controllers

import (
	"github.com/gin-gonic/gin"
	"github.com/hanadaUG/go-gin-gorm-todo-app/models"
	"github.com/jinzhu/gorm"
	"net/http"
)

type TaskHandler struct {
	Db *gorm.DB
}

// 一覧表示
func (handler *TaskHandler) GetAll(c *gin.Context) {
	var tasks []models.Task                                    // レコード一覧を格納するため、Task構造体のスライスを変数宣言
	handler.Db.Find(&tasks)                                    // DBから全てのレコードを取得する
	c.HTML(http.StatusOK, "index.html", gin.H{"tasks": tasks}) // index.htmlに全てのレコードを渡す
}

// 新規作成
func (handler *TaskHandler) Create(c *gin.Context) {
	text, _ := c.GetPostForm("text")            // index.htmlからtextを取得
	handler.Db.Create(&models.Task{Text: text}) // レコードを挿入する
	c.Redirect(http.StatusMovedPermanently, "/")
}

// 編集画面
func (handler *TaskHandler) Edit(c *gin.Context) {
	task := models.Task{}                                   // Task構造体の変数宣言
	id := c.Param("id")                                     // index.htmlからidを取得
	handler.Db.First(&task, id)                             // idに一致するレコードを取得する
	c.HTML(http.StatusOK, "edit.html", gin.H{"task": task}) // edit.htmlに編集対象のレコードを渡す
}

// 更新
func (handler *TaskHandler) Update(c *gin.Context) {
	task := models.Task{}            // Task構造体の変数宣言
	id := c.Param("id")              // edit.htmlからidを取得
	text, _ := c.GetPostForm("text") // edit.htmlからtextを取得
	handler.Db.First(&task, id)      // idに一致するレコードを取得する
	task.Text = text                 // textを上書きする
	handler.Db.Save(&task)           // 指定のレコードを更新する
	c.Redirect(http.StatusMovedPermanently, "/")
}

// 削除
func (handler *TaskHandler) Delete(c *gin.Context) {
	task := models.Task{}       // Task構造体の変数宣言
	id := c.Param("id")         // index.htmlからidを取得
	handler.Db.First(&task, id) // idに一致するレコードを取得する
	handler.Db.Delete(&task)    // 指定のレコードを削除する
	c.Redirect(http.StatusMovedPermanently, "/")
}

router/router.go

ルーティングの設定を行います。
リクエストに対して、controllers/task_controller.goで定義した振る舞いを割り当てます。

router/router.go
package router

import (
	"github.com/gin-gonic/gin"
	"github.com/hanadaUG/go-gin-gorm-todo-app/controllers"
	"github.com/hanadaUG/go-gin-gorm-todo-app/db"
)

func Router() {
	// gin内で定義されているEngine構造体インスタンスを取得
	// Router、HTML Rendererの機能を内包している
	router := gin.Default()

	// globパターンに一致するHTMLファイルをロードしHTML Rendererに関連付ける
	// 今回のケースではtemplatesディレクトリ配下のhtmlファイルを関連付けている
	router.LoadHTMLGlob("templates/*.html")

	// TaskHandler構造体に紐付けたCRUDメソッドを呼び出す
	handler := controllers.TaskHandler{
		db.Get(),
	}

	// 各パスにGET/POSTメソッドでリクエストされた時のレスポンスを登録する
	// 第一引数にパス、第二引数にHandlerを登録する
	router.GET("/", handler.GetAll)            // 一覧表示
	router.POST("/", handler.Create)           // 新規作成
	router.GET("/:id", handler.Edit)           // 編集画面
	router.POST("/update/:id", handler.Update) // 更新
	router.POST("/delete/:id", handler.Delete) // 削除

	// Routerをhttp.Serverに接続し、HTTPリクエストのリスニングとサービスを開始する
	router.Run()
}

templates/index.html

templates/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>TODOアプリ(Golang * gin * gorm)</title>
</head>
<body>
<form method="POST" action="/">
    Task : <input type="text" name="text" value="">
    <input type="submit" value="登録">
</form>

<br>

<ul>
    {{ range .tasks }}
        <li>
            <form method="POST" action="/delete/{{.ID}}">
                {{.ID}} : {{ .Text }} [<a href="/{{.ID}}">編集</a>]
                <input type="submit" value="削除">
            </form>
        </li>
    {{end}}
</ul>

</body>
</html>

templates/edit.html

templates/edit.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>TODOアプリ(Golang * gin * gorm)</title>
</head>
<body>
<form action="/update/{{.task.ID}}" method="POST">
    <input type="text" name="text" value="{{.task.Text}}"><br>
    <input type="submit" value="更新">
</form>
</body>
</html>

動作確認

起動
$ go run main.go 
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] Loaded HTML Templates (3): 
	- edit.html
	- index.html
	- 

[GIN-debug] GET    /                         --> github.com/hanadaUG/go-gin-gorm-todo-app/controllers.(*TaskHandler).GetAll-fm (3 handlers)
[GIN-debug] POST   /                         --> github.com/hanadaUG/go-gin-gorm-todo-app/controllers.(*TaskHandler).Create-fm (3 handlers)
[GIN-debug] GET    /:id                      --> github.com/hanadaUG/go-gin-gorm-todo-app/controllers.(*TaskHandler).Edit-fm (3 handlers)
[GIN-debug] POST   /update/:id               --> github.com/hanadaUG/go-gin-gorm-todo-app/controllers.(*TaskHandler).Update-fm (3 handlers)
[GIN-debug] POST   /delete/:id               --> github.com/hanadaUG/go-gin-gorm-todo-app/controllers.(*TaskHandler).Delete-fm (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080

http://localhost:8080 にブラウザでアクセスして動作を確認できます。

todo.png

ソースコードはGitHubにアップロードしています。

参考

下記サイト、記事を参考にさせていただきました。

Next

Goで作成したToDoアプリにRESTful APIを追加してみるに続きます。

12
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?