LoginSignup
2

More than 3 years have passed since last update.

Goのginとgormで簡単なWebアプリケーションを作る。

Last updated at Posted at 2020-12-13

はじめに

どもです。
最近、Webアプリケーションを久しぶりに作るってなって、GoでのCRUDの書き方等を忘れたので、メモといった感じで記していきます。

ルーティングとCRUDについて詳しく書いていきます。

githubに今回作成したソースコードを載せておきます

使用する環境

実際の環境

項目 使用したもの
OS CentOS8
DB MySQL
バックエンド Go

Goで使用したパッケージ

用途 パッケージ名
ルーティング gin
ORM gorm

開発環境に関しては、すでに終わっていることを仮定します。

実装する内容

今回作成するものは、元から存在するアプリケーションのユーザの管理をするアプリケーションです。
本来、gormのマイグレーションというのもできますが、今回はすでにテーブル等が作成されているものとします。

  1. ユーザの一覧(Select)
  2. ユーザの詳細(Select)
  3. ユーザの削除(Delete)
  4. ユーザの編集(Update)

といったものを実装していきます。

ディレクトリ構造

.
|- handler.go   //DBにCRUDする関数を書く
|- main.go      //GETとPOSTの処理を書く
|- routing.go   //ルーティングの関数を書く
|- views/
    |- css/
    |      |- main.css
    |- html/
    |      |- delete.html
    |      |- detail.html
    |      |- index.html
    |      |- update.html
    |- js/
           |- main.js

gin

ginのGoDocはこちらから

ginというのは、GoのWebフレームワークで、とりあえず早い。ライバルとしてはechoがあります。
自分的には、どっちでもいいのかなと思います。ただ、ginのほうが簡潔に書けるという印象があります

実装

main.go
package main

import (
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    r.LoadHTMLGlob("./views/html/*.html")
    r.Static("/css", "./views/css")

    r.GET("/", index)
    r.GET("/detail/:id", detail)
    r.GET("/delete/:id", delete)
    r.GET("/update/:id", update)

    r.POST("/do_delete/:id", do_delete)
    r.POST("/do_update/:id", do_update)

    r.Run(":80")
}
routing.go
package main

import (
    "fmt"
    "net/http"
    "strconv"

    "github.com/gin-gonic/gin"
)

func index(c *gin.Context) {
    users, err := UserGetAll()
    if err != nil {
        fmt.Println(err)
        c.AbortWithStatus(http.StatusInternalServerError)
        return
    }
    c.HTML(http.StatusOK, "index.html", gin.H{"users": users})
}

~~~~

func do_update(c *gin.Context) {
    n := c.Param("id")
    name := c.PostForm("name")
    email := c.PostForm("email")
    id, err := strconv.Atoi(n)
    if err != nil {
        fmt.Println(err)
        c.AbortWithStatus(http.StatusInternalServerError)
        return
    }
    err = UserUpdate(id, name, email)
    if err != nil {
        fmt.Println(err)
        c.AbortWithStatus(http.StatusInternalServerError)
        return
    }
    c.Redirect(302, "/")
}

解説

主に使っている関数をピックアップして、紹介します。

main.go

まずはここ。main関数の

r := gin.Default()

こいつで、r ってのがginパッケージ使いまっせーの宣言をする。r変数は好きにつけてぐださい

r.LoadHTMLGlob("./views/html/*.html")
r.Static("/css", "./views/css")
r.GET("/", index)
r.POST("/do_delete/:id", do_delete)
r.Run(":80")

rを用いて、このような関数を呼び出してます。

LoadHTMLGlob:htmlファイルの場所の指定
Static:css等の静的ファイルの指定(jsでも同様)
GET:GETの処理(第一引数がドメイン以降のパス指定、第二引数がそのパスに対応した関数の呼び出し)
POST:POSTの処理(第一引数がドメイン以降のパス指定、第二引数がそのパスに対応した関数の呼び出し)
Run:サーバの起動(:80は80番ポートで起動してます)

GETとPOSTで呼び出す関数は、次のrouting.goに書いてます。

routing.go

GETPOSTという処理があって、とりあえずGETだったらHTMLを出力してPOSTだったらリダイレクトする的なイメージがあると思います。

とりあえず、各関数の引数にc *gin.Contextとありますが、先ほどのrみたいな感じです。

関数の解説

c.AbortWithStatus(http.StatusInternalServerError)
c.HTML(http.StatusOK, "index.html", gin.H{"users": users})
c.Redirect(302, "/")

cを用いて、このような関数を呼び出してます。

AbortWithStatus(http.StatusInternalServerError):リクエストの認証に失敗したら、401を返す
HTML:htmlファイルを返す。(第一引数は置いておいて、第二引数はhtmlファイルの指定、第三引数はhtmlに値を渡す変数の定義)
Redirect:リダイレクトをする。(第一引数で302で返す宣言、第二引数でリダイレクト先の指定)

HTMLに変数を渡す場合の処理は、Go Templateとかで検索すると、いろいろ出てくるので、そちらをどうぞ。

main.gorouting.goは以上。これで、ページ遷移くらいはできると思います。単にHTMLを出力するサンプルコードを載せて、次に行きます。

サンプルコード
package main

import (
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    r.LoadHTMLGlob("./views/html/*.html")

    r.GET("/", index)

    r.Run(":80")
}

func index(c *gin.Context) {
    c.HTML(http.StatusOK, "index.html", gin.H{})
}

これで、index.htmlは、出力するはず。(エラーだったらコメントください)

gorm

gormの公式ドキュメントはこちらから

gormというのは、Go用のORMライブラリで、DB操作を簡単にできるすげーいいやつ。
公式ドキュメントが超優秀なので、ぜひ見てみてください

実装

handler.go
package main

import (
    "fmt"

    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

type Users struct {
    Id       int
    Name     string
    Email    string
    Password string
}

type Posts struct {
    Id       int
    Author   int
    Title    string
    Content  string
    Comments int
}

const connect = "root:root@tcp(127.0.0.1:3306)/laravel?charset=utf8mb4&parseTime=True&loc=Local"

func UserGetAll() ([]Users, error) {
    db, err := gorm.Open(mysql.Open(connect), &gorm.Config{
        DisableForeignKeyConstraintWhenMigrating: true,
    })
    if err != nil {
        return nil, fmt.Errorf("UserGetAll失敗: %w", err)
    }
    var users []Users
    err = db.Select("id", "name", "email", "password").Find(&users).Error
    if err != nil {
        return nil, fmt.Errorf("UserGetAll_Select失敗: %w", err)
    }
    return users, nil
}

func UserGetOne(id int) (Users, error) {
    db, err := gorm.Open(mysql.Open(connect), &gorm.Config{
        DisableForeignKeyConstraintWhenMigrating: true,
    })
    var user Users
    if err != nil {
        return Users{}, fmt.Errorf("UserGetOne失敗: %w", err)
    }
    err = db.First(&user, id).Error
    if err != nil {
        return Users{}, fmt.Errorf("UserGetOne_First失敗: %w", err)
    }
    return user, nil
}

func PostGetAll(author int) ([]Posts, error) {
    db, err := gorm.Open(mysql.Open(connect), &gorm.Config{
        DisableForeignKeyConstraintWhenMigrating: true,
    })
    if err != nil {
        return nil, fmt.Errorf("PostGetAll失敗: %w", err)
    }
    var posts []Posts
    err = db.Where("author = ?", author).Find(&posts).Error
    if err != nil {
        return nil, fmt.Errorf("PostGetAll_Select失敗: %w", err)
    }
    return posts, nil
}

func UserDelete(id int) error {
    db, err := gorm.Open(mysql.Open(connect), &gorm.Config{
        DisableForeignKeyConstraintWhenMigrating: true,
    })
    if err != nil {
        return fmt.Errorf("UserDelete失敗: %w", err)
    }
    var users Users
    err = db.Delete(&users, id).Error
    if err != nil {
        return fmt.Errorf("UserDelete_Delete失敗: %w", err)
    }
    return nil
}

func UserUpdate(id int, name string, email string) error {
    db, err := gorm.Open(mysql.Open(connect), &gorm.Config{
        DisableForeignKeyConstraintWhenMigrating: true,
    })
    if err != nil {
        return fmt.Errorf("UserUpdate失敗: %w", err)
    }
    var users Users
    err = db.First(&users, id).Error
    if err != nil {
        return fmt.Errorf("UserUpdate_First失敗: %w", err)
    }
    users.Name = name
    users.Email = email
    db.Save(&users)
    return nil
}

解説

handler.go

各関数の最初に書かれている、gorm.Openが最重要で、DB(MySQL)に接続する関数で、countで定義したDB情報を呼び出して使ってます。

自分の関数の命名についてですが、〇〇GetAllだと要素の全取得、〇〇GetOneだと一つだけ取得、〇〇Deleteだと削除、〇〇Updateだと更新、みたいな感じです

流れとしては、UsersPostsのように、必要な情報を構造体として扱って処理をします。
ここでは、gormの関数が多いので、よく使うものだけピックアップします。

関数の解説

First:なにか指定したものの最初にマッチした要素を取得する
Where:データの特定をするために、カラムを指定する
Find:Firstと異なり、特定の一つのデータを取得する

と、公式ドキュメントにもありますが、関数とSQL文がいい具合に処理できるので、すごく便利。
自分の場合、errとしてエラーハンドリングをしつつ処理をしています。
(確かGoのすごい人からおしえてもらった書き方だった気がする…)

終わり

かなり長くなってしまいましたが、これで解説は終わりです。

Goを用いたWebアプリケーション開発というのは、これからもっとはやっていくと思います。そこで、この記事が参考になることを祈ります。

疲れた…

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
2