50
56

More than 5 years have passed since last update.

golangでCRUDアプリを実装する

Last updated at Posted at 2019-03-31

概要

golangのWebフレームワークであるginでCRUDアプリケーションを実装したので、これからgolangを勉強したいといった方の参考になるように簡単にまとめたいと思います。

作成したWebアプリケーション

自分が今まで読んできた本のタイトル、値段、合計冊数、合計金額を記録していけるWebアプリです。読んできた本の合計金額や合計冊数を数値化して記録することで、読書に対するモチベーションを高めたいという個人的な思いから作りました!
golang-web1.gif

ソースコード

main.go
package main

import (
    "strconv"

    "github.com/gin-gonic/gin"
    "github.com/jinzhu/gorm"

    _ "github.com/mattn/go-sqlite3"
)

type Book struct {
    gorm.Model
    Title string
    Price int
}

type Result struct {
    Total int
}

// DB migration
func dbInit() {
    db, err := gorm.Open("sqlite3", "book.sqlite3")
    if err != nil {
        panic("You can't open DB (dbInit())")
    }
    defer db.Close()
    db.AutoMigrate(&Book{})
}

// DB Create
func dbInsert(title string, price int) {
    db, err := gorm.Open("sqlite3", "book.sqlite3")
    if err != nil {
        panic("You can't open DB (dbInsert())")
    }
    defer db.Close()
    db.Create(&Book{Title: title, Price: price})
}

// DB Update
func dbUpdate(id int, title string, price int) {
    db, err := gorm.Open("sqlite3", "book.sqlite3")
    if err != nil {
        panic("You can't open DB (dbUpdate())")
    }
    defer db.Close()
    var book Book
    db.First(&book, id)
    book.Title = title
    book.Price = price
    db.Save(&book)
}

// DB Delete
func dbDelete(id int) {
    db, err := gorm.Open("sqlite3", "book.sqlite3")
    if err != nil {
        panic("You can't open DB (dbDelete())")
    }
    defer db.Close()
    var book Book
    db.First(&book, id)
    db.Delete(&book)
}

// DB All Get
func dbGetAll() []Book {
    db, err := gorm.Open("sqlite3", "book.sqlite3")
    if err != nil {
        panic("You can't open DB (dbGetAll())")
    }
    defer db.Close()
    var books []Book
    db.Order("created_at desc").Find(&books)
    return books
}

// DB One Get
func dbGetOne(id int) Book {
    db, err := gorm.Open("sqlite3", "book.sqlite3")
    if err != nil {
        panic("You can't open DB (dbGetOne())")
    }
    defer db.Close()
    var book Book
    db.First(&book, id)
    return book
}

func dbGetNum() int {
    db, err := gorm.Open("sqlite3", "book.sqlite3")
    if err != nil {
        panic("You can't open DB (dbGetNum())")
    }
    defer db.Close()
    var num int
    db.Table("books").Count(&num)
    return num
}

func dbGetPrice() Result {
    db, err := gorm.Open("sqlite3", "book.sqlite3")
    if err != nil {
        panic("You can't open DB (dbGetPrice())")
    }
    defer db.Close()
    var result Result
    db.Table("books").Select("sum(price) as total").Scan(&result)
    return result
}

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

    dbInit()

    //Index
    router.GET("/", func(c *gin.Context) {
        books := dbGetAll()
        num := dbGetNum()
        sumPrice := dbGetPrice()
        c.HTML(200, "index.html", gin.H{"books": books, "num": num, "sumPrice": sumPrice.Total})
    })

    //Create
    router.POST("/new", func(c *gin.Context) {
        title := c.PostForm("title")
        p := c.PostForm("price")
        price, err := strconv.Atoi(p)
        if err != nil {
            panic(err)
        }
        dbInsert(title, price)
        c.Redirect(302, "/")
    })

    //Edit
    router.GET("/edit/:id", func(c *gin.Context) {
        n := c.Param("id")
        id, err := strconv.Atoi(n)
        if err != nil {
            panic(err)
        }
        book := dbGetOne(id)
        c.HTML(200, "edit.html", gin.H{"book": book})
    })

    //Update
    router.POST("/update/:id", func(c *gin.Context) {
        n := c.Param("id")
        id, err := strconv.Atoi(n)
        if err != nil {
            panic(err)
        }
        title := c.PostForm("title")
        p := c.PostForm("price")
        price, errPrice := strconv.Atoi(p)
        if errPrice != nil {
            panic(errPrice)
        }
        dbUpdate(id, title, price)
        c.Redirect(302, "/")
    })

    //delete
    router.POST("/delete/:id", func(c *gin.Context) {
        n := c.Param("id")
        id, err := strconv.Atoi(n)
        if err != nil {
            panic(err)
        }
        dbDelete(id)
        c.Redirect(302, "/")
    })

    //delete_confirm
    router.GET("/delete_confirm/:id", func(c *gin.Context) {
        n := c.Param("id")
        id, err := strconv.Atoi(n)
        if err != nil {
            panic(err)
        }
        book := dbGetOne(id)
        c.HTML(200, "delete.html", gin.H{"book": book})
    })

    router.Run()
}
index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>一覧ページ</title>
  </head>
  <body>
    <header>
      <h1>Bookshelf</h1>
    </header>
    <div class="wrap">
      <div class="input">
        <form action="/new" method="post">
          <p>Title :<input type="text" name="title" size="30"placeholder="タイトル名"/></p>
          <p>Price :<input type="text" name="price" size="30" placeholder="値段" /></p>
          <p><input type="submit" value="Send" /></p>
        </form>
      </div>
      <div class="indexGet">
        <p>本の数 : {{.num}}</p>
        <p>合計金額 : {{.sumPrice}}</p>
        <ul>
          {{range.books}}
          <li>
            Title:{{.Title}}、Price:{{.Price}}
            <label><a href="/edit/{{.ID}}">編集</a></label>
            <label><a href="/delete_confirm/{{.ID}}">削除</a></label>
          </li>
          {{end}}
        </ul>
      </div>
    </div>
  </body>
</html>
edit.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>編集ページ</title>
  </head>
  <body>
    <header>
      <h1>編集</h1>
    </header>
    <div class="wrap">
      <form action="/update/{{.book.ID}}" method="post">
        <p>Title:<input type="text" name="title" value="{{.book.Title}}" /></p>
        <p>Price: <input type="text" name="price" value="{{.book.Price}}" /></p>
        <p><input type="submit" value="Send"></p>
      </form>
    </div>
  </body>
</html>
delete.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>削除ページ</title>
  </head>
  <body>
    <h1>削除確認</h1>
    <p>本当に削除しますか?</p>
    <ul>
      <li>Title : {{.book.Title}}</li>
      <li>Price : {{.book.Price}}</li>
    </ul>

    <form action="/delete/{{.book.ID}}" method="post">
      <p><input type="submit" value="削除"></p>
      <p><a href="/">戻る</a></p>
    </form>
  </body>
</html>

【Go / Gin で超簡単なWebアプリを作る】を参考にさせていただきました。

今回は自分が新しく加えた箇所に関してご説明させていただきます。

本の合計冊数を取得する

main.go
func dbGetNum() int {
    db, err := gorm.Open("sqlite3", "book.sqlite3")
    defer db.Close()
    if err != nil {
        panic("You can't open DB (dbGetNum())")
    }
    var num int
    db.Table("books").Count(&num)
    return num
}

新しくdbGetNum()を加えました。
int型のnumを宣言し、db.Table("books").Count(&num)でbooksテーブルにあるデータの数を取得し、取得した値をreturn numで返します。詳しくはこちら

本の合計金額を取得する

main.go
type Result struct {
    Total int
}
main.go
func dbGetPrice() Result {
    db, err := gorm.Open("sqlite3", "book.sqlite3")
    defer db.Close()
    if err != nil {
        panic("You can't open DB (dbGetPrice())")
    }
    var result Result
    db.Table("books").Select("sum(price) as total").Scan(&result)
    return result
}

まず新しく構造体を宣言します。
そして、db.Table("books").Select("sum(price) as total").Scan(&result)でbooksテーブルのpriceカラムの合計値を、新しく宣言した構造体のフィールドに保存しています。詳しくはこちら

main関数

main.go
//Index
    router.GET("/", func(c *gin.Context) {
        books := dbGetAll()
        num := dbGetNum()
        sumPrice := dbGetPrice()
        c.HTML(200, "index.html", gin.H{"books": books, "num": num, "sumPrice": sumPrice.Total})
    })

numsumPriceにそれぞれ上述した関数を用いて値を代入しています。
そしてその値を、c.HTML(200, "index.html", gin.H{"books": books, "num": num, "sumPrice": sumPrice.Total})としてHTMLに渡しています。ちなみにc.HTML(200...の200はHTTPステータスコードといい、処理が成功して正常にレスポンスができている状態を意味します。詳しくはこちら

みなさんお気づきでしょうか...

スクリーンショット 2019-03-31 21.02.25.png
削除処理をした後に画面上から本の表示は消えているのに、合計冊数と合計金額はデータベース上に残ったままというバグが発生しています...。何度ググっても、何度コードを見返しても解決できませんでした...。もし、golangやgormに詳しい方がいらっしゃいましたら、何卒アドバイスをよろしくお願い致します。
無事に解決することができました。
解決法はコメント欄の方に載っております。
x-colorさんありがとうございます。

リファレンス

50
56
2

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
50
56