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に接続(初期化)する。
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データの定義
今回は、ユーザーの名前
とメッセージ
を持つデータを定義する。
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データのスライスを送る。
func GetAllChats() []Chat {
var allChats []Chat
// SELECT * FROM chat
db.Find(&allChats)
return allChats
}
単一データの検索はFirst
にchatデータとidの値を送る。
見つからなかった場合はidが0となる。
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
で保存する
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
で値を更新する。
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
で値を削除する。
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
のメソッドをそれぞれ呼び出す。
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()
}
}
リクエスト/レスポンス用のデータを定義する。
リクエスト用
package requests
type Chat struct {
UserName string `json:"user_name"`
Message string `json:"message"`
}
type UpdateChat struct {
Message string `json:"message"`
}
レスポンス用
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
に追加する
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
+-----------+--------------+------+-----+---------+----------------+
| 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{})
今回はサーバーを起動する前に、毎回マイグレーションするようにしている。(マイグレーション処理は分離したほうが良い気がしますが…)
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)