1
1

More than 1 year has passed since last update.

beegoとgormでチャットapiを作成する

Last updated at Posted at 2021-12-03

flutterのチュートリアル?codelabs(https://codelabs.developers.google.com/codelabs/flutter)
でチャットアプリの作成をやっている中で、チャットのメッセージ情報を腹持ちしているのをapiから取得したいと思いbeegoでサクッとapiを作成しました。

コード(https://github.com/fu-yuta/go_friendly_chat/tree/create_chat_api)

環境

go: v1.17
beego: v1.12.1
gorm: v1.9.16
mysql: v8.0.27 for macos11.6

フォルダ構成

├── conf
│   └── app.conf
├── controllers
│   ├── chat.go
│   ├── requests
│   │   └── chat.go
│   └── responses
│       └── chat.go
├── go.mod
├── main.go
├── models
│   └── chat.go
└── routers
    └── router.go

bee api {アプリ名}で作成されるテンプレートの構成を使用しています。

DB操作

DBへの接続

接続情報をconf/app.confに記述する。

db_driver_name = mysql
db_name = chat_db
db_user_name = root
db_user_password = root
db_host = tcp(127.0.0.1:3306)

model/chat.goでDBに接続(初期化)する。

model/chat.go
import (
    "errors"
    "fmt"
    "go_friendly_chat/controllers/requests"
    "log"

    "github.com/astaxie/beego"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
)

var db *gorm.DB

func init() {
    db = setupDB()
}

func setupDB() *gorm.DB {
    DBDriverName := beego.AppConfig.String("db_driver_name")
    DBName := beego.AppConfig.String("db_name")
    DBUserName := beego.AppConfig.String("db_user_name")
    DBUserPassword := beego.AppConfig.String("db_user_password")
    DBHost := beego.AppConfig.String("db_host")

    connectionInfo := fmt.Sprintf("%s:%s@%s/%s", DBUserName, DBUserPassword, DBHost, DBName)
    db, err := gorm.Open(DBDriverName, connectionInfo)

    if err != nil {
        log.Println(err.Error())
    }

    return db

}

DBへの操作

models/chat.goにDBへの各操作を記述する

chatデータの定義

今回は、ユーザーの名前メッセージを持つデータを定義する。

models/chat.go
type Chat struct {
    Id       uint   `gorm:"primary_key"`
    UserName string `gorm:"size:255"`
    Message  string `gorm:"size:255"`
}

Id uint gorm:"primary_key"で定義した値は、データの保存の際などにgormがユニークでいい感じに連番(1からスタート)を振ってくれるようでした。

レコードの取得

全てのデータを検索する時はFindにchatデータのスライスを送る。

models/chat.go
func GetAllChats() []Chat {
    var allChats []Chat
    // SELECT * FROM chat
    db.Find(&allChats)
    return allChats
}

単一データの検索はFirstにchatデータとidの値を送る。
見つからなかった場合はidが0となる。

models/chat.go
func GetChat(id string) (*Chat, error) {
    var chat Chat
    // SELECT * FROM chat WHERE id = 10;(入力したidが10の場合)        
    db.First(&chat, id)
    if chat.Id == 0 {
        log.Println("not found chat error")
        return nil, errors.New("not found chat error")
    }

    return &chat, nil
}

レコードの保存

NewRecordでchatデータをDBに保存するレコードに変換して、Createで保存する

models/chat.go
func AddChat(c Chat) (*Chat, error) {
    if !db.NewRecord(c) {
        log.Println("NewRecord error")
        return nil, errors.New("NewRecord error")
    }

    // INSERT INTO `chat` (`id`,`user_name`,`message`) VALUES ("Id", "UserName", "Message")
    err := db.Create(&c).Error
    if err != nil {
        log.Println("Create error")
        return nil, err
    }

    return &c, nil
}

レコードの更新

更新するデータをFirstで取得してから、Saveで値を更新する。

models/chat.go
func UpdateChat(id string, updateChat requests.UpdateChat) (*Chat, error) {
    var chat Chat
    db.First(&chat, id)
    if chat.Id == 0 {
        log.Println("not found chat error")
        return nil, errors.New("not found chat error")
    }

    chat.Message = updateChat.Message

    // UPDATE chat SET user_name=UserName, message=Message WHERE id=Id;
    err := db.Save(&chat).Error
    if err != nil {
        log.Println("Update error")
        return nil, err
    }
    return &chat, nil
}

レコードの削除

更新するデータをFirstで取得してから、Deleteで値を削除する。

models/chat.go
func DeleteChat(id string) (*Chat, error) {
    var chat Chat
    db.First(&chat, id)
    if chat.Id == 0 {
        log.Println("not found chat error")
        return nil, errors.New("not found chat error")
    }
    // DELETE from chat where id = Id;
    err := db.Delete(&chat).Error
    if err != nil {
        log.Println("Delete error")
        return nil, err
    }

    return &chat, nil
}

Controllerの追加

apiのリクエストに合わせて、先程のmodel/chat.goのメソッドをそれぞれ呼び出す。

controllers/chat.go
package controllers

import (
    "encoding/json"
    "go_friendly_chat/controllers/requests"
    "go_friendly_chat/controllers/responses"
    "go_friendly_chat/models"
    "log"

    "github.com/astaxie/beego"
)

type ChatController struct {
    beego.Controller
}

// @Title CreateChatMessage
// @Description create message
// @Param   body        body    requests.Chat   true        "body for user content"
// @Success 200 {object} responses.Chat
// @Failure 403 : requests.Chat is empty
// @Failure 500 internal server error
// @router / [post]
func (c *ChatController) Post() {
    var chat requests.Chat

    err := json.Unmarshal(c.Ctx.Input.RequestBody, &chat)
    if err != nil {
        log.Println("Chat Post json.Unmarshal error")
        c.Ctx.Output.SetStatus(403)
        c.ServeJSON()
    }

    newChat, err := models.AddChat(models.Chat{
        Id:       0,
        UserName: chat.UserName,
        Message:  chat.Message,
    })
    if err != nil {
        log.Println("AddChat error")
        c.Ctx.Output.SetStatus(500)
        c.ServeJSON()
    }

    res := responses.Chat{
        Id:       int(newChat.Id),
        UserName: newChat.UserName,
        Message:  newChat.Message,
    }
    c.Data["json"] = res

    c.ServeJSON()
}

// @Title GetAll
// @Description get all Chats
// @Success 200 {object} responses.Chats
// @router / [get]
func (c *ChatController) GetAll() {
    chats := models.GetAllChats()
    var res responses.Chats
    for _, chat := range chats {
        res.Chats = append(res.Chats, responses.Chat{
            Id:       int(chat.Id),
            UserName: chat.UserName,
            Message:  chat.Message,
        })

    }
    c.Data["json"] = res
    c.ServeJSON()
}

// @Title Get
// @Description get chat by id
// @Param   id      path    string  true
// @Success 200 {object} responses.Chat
// @Failure 403 :id is empty
// @Failure 404 :chat is not found
// @router /:id [get]
func (c *ChatController) Get() {
    id := c.GetString(":id")
    if id != "" {
        chat, err := models.GetChat(id)
        if err != nil {
            log.Println("chat is not found")
            c.Ctx.Output.SetStatus(404)
            c.ServeJSON()
        } else {
            res := responses.Chat{
                Id:       int(chat.Id),
                UserName: chat.UserName,
                Message:  chat.Message,
            }
            c.Data["json"] = res
        }
        c.ServeJSON()
    } else {
        log.Println("id is empty error")
        c.Ctx.Output.SetStatus(403)
        c.ServeJSON()
    }
}

// @Title Update
// @Description update the chat
// @Param   id      path    string  true
// @Param   body        body    requests.UpdateChat true
// @Success 200 {object} responses.Chat
// @Failure 403 :id is not int
// @Failure 500 internal server error
// @router /:id [put]
func (c *ChatController) Put() {
    id := c.GetString(":id")
    if id != "" {
        var req requests.UpdateChat
        json.Unmarshal(c.Ctx.Input.RequestBody, &req)
        updateChat, err := models.UpdateChat(id, req)
        if err != nil {
            c.Ctx.Output.SetStatus(500)
            c.ServeJSON()
        }
        res := responses.Chat{
            Id:       int(updateChat.Id),
            UserName: updateChat.UserName,
            Message:  updateChat.Message,
        }
        c.Data["json"] = res
        c.ServeJSON()
    } else {
        log.Println("id is empty error")
        c.Ctx.Output.SetStatus(403)
        c.ServeJSON()
    }
}

// @Title Delete
// @Description delete the chat
// @Param   id      path    string  true
// @Success 200 {object} responses.Chat
// @Failure 403 id is empty
// @Failure 500 internal server error
// @router /:id [delete]
func (c *ChatController) Delete() {
    id := c.GetString(":id")
    if id != "" {
        deleteChat, err := models.DeleteChat(id)
        if err != nil {
            log.Println("Delete error")
            c.Ctx.Output.SetStatus(500)
            c.ServeJSON()
        }
        res := responses.Chat{
            Id:       int(deleteChat.Id),
            UserName: deleteChat.UserName,
            Message:  deleteChat.Message,
        }
        c.Data["json"] = res
        c.ServeJSON()
    } else {
        log.Println("id is empty error")
        c.Ctx.Output.SetStatus(403)
        c.ServeJSON()
    }
}

リクエスト/レスポンス用のデータを定義する。

リクエスト用

controllers/requests/chat.go
package requests

type Chat struct {
    UserName string `json:"user_name"`
    Message  string `json:"message"`
}

type UpdateChat struct {
    Message string `json:"message"`
}

レスポンス用

controllers/responses/chat.go
package responses

type Chat struct {
    Id       int    `json:"id"`
    UserName string `json:"user_name"`
    Message  string `json:"message"`
}

type Chats struct {
    Chats []Chat `json:"chats"`
}

ルーティングの設定

Controllerへのルーティングをrouters/router.goに追加する

routers/router.go
package routers

import (
    "go_friendly_chat/controllers"

    "github.com/astaxie/beego"
)

func init() {
    ns := beego.NewNamespace("/v1",
        beego.NSNamespace("/chat",
            beego.NSInclude(
                &controllers.ChatController{},
            ),
        ),
    )
    beego.AddNamespace(ns)
}

データベースの準備

下記を作成する。
データベース: chat_db
テーブル: chats

COLUMNS
+-----------+--------------+------+-----+---------+----------------+
| Field     | Type         | Null | Key | Default | Extra          |
+-----------+--------------+------+-----+---------+----------------+
| id        | int unsigned | NO   | PRI | NULL    | auto_increment |
| user_name | varchar(255) | YES  |     | NULL    |                |
| message   | varchar(255) | YES  |     | NULL    |                |
+-----------+--------------+------+-----+---------+----------------+

gormのマイグレーションで自動で作成することも可能
先程のmodels/chat.goで定義した、Chatデータを元にテーブルを作成してくれる。

db.AutoMigrate(&models.Chat{})

今回はサーバーを起動する前に、毎回マイグレーションするようにしている。(マイグレーション処理は分離したほうが良い気がしますが…)

main.go
package main

import (
    "fmt"
    "go_friendly_chat/models"
    _ "go_friendly_chat/routers"
    "log"

    "github.com/astaxie/beego"
    "github.com/jinzhu/gorm"
)

func main() {
    db := setupDB()
    db.AutoMigrate(&models.Chat{})

    if beego.BConfig.RunMode == "dev" {
        beego.BConfig.WebConfig.DirectoryIndex = true
        beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
    }
    beego.Run()
}

func setupDB() *gorm.DB {
    dbDriverName := beego.AppConfig.String("db_driver_name")
    dbName := beego.AppConfig.String("db_name")
    dbUserName := beego.AppConfig.String("db_user_name")
    dbUserPassword := beego.AppConfig.String("db_user_password")
    dbHost := beego.AppConfig.String("db_host")

    connectTemplate := "%s:%s@%s/%s"
    connect := fmt.Sprintf(connectTemplate, dbUserName, dbUserPassword, dbHost, dbName)
    db, err := gorm.Open(dbDriverName, connect)

    if err != nil {
        log.Println(err.Error())
    }

    return db

apiの確認

サーバーを起動して

bee run -downdoc=true -gendoc=true

swaggerから確認できる.。
http://127.0.0.1:8080/swagger/

おわりに

bee api {アプリ名}でapiのテンプレートを作成してくれて、起動したらswaggerからすぐに動作確認できるのでサクッとapi開発できました。

今回はDBへの基本的なCRUD操作しかやらなかったですが、より複雑な処理は公式ドキュメントを参照してください。
(https://gorm.io/ja_JP/docs/index.html)

1
1
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
1
1