GoでCRUD処理をする簡単なAPIを作った話

※注意

Ginというフレームワークを使います。

環境

WSL2 Ubuntu20.04LTS

mongoDB

go version go1.14.7 linux/amd64

 

ディレクトリ構成

recipes/

 controllers/controller.go

 models/models.go

 router/router.go

 main.go

 

コード

 main.go

package main

import (
	"recipes/router"
)

func main() {
	router.Router()
}

Router()という関数を呼び出すだけ。

 

router.go

package router

import(
	"time"

	"recipes/controllers"

	"github.com/gin-gonic/gin"
	"gopkg.in/mgo.v2"
)

func Router() {
	mongoInfo := &mgo.DialInfo{
		Addrs:    []string{"localhost:27017"},
		Timeout:  20 * time.Second,
		Database: "cook-suggestioner",
		Username: "",
		Password: "",
		Source: "cook-suggestioner",
	}
	
	session, err := mgo.DialWithInfo(mongoInfo)
	if err != nil {
		panic(err)
	}
	defer session.Close()
	db := session.DB("cook-suggestioner").C("recipes")

	r := gin.Default()
	recipesGroup := r.Group("/recipes")
	recipesGroup.Use(Middleware(db))
	{
		recipesGroup.GET("", controllers.GetAll)
		recipesGroup.GET("/:id", controllers.Get)
		recipesGroup.POST("", controllers.Create)
		recipesGroup.PUT("/:id", controllers.Update)
		recipesGroup.DELETE("/:id", controllers.Delete)
	}
	
	r.Run() // listen and serve on 0.0.0.0:8080
}

func Middleware(db *mgo.Collection) gin.HandlerFunc {
    return func(c *gin.Context) {
	c.Set("db", db)
        c.Next()
    }
}

DBのセッションを取得して各エンドポイントとハンドラを記述。

 

controllers.go

package controllers

import(
	"errors"
	"net/http"
	"strings"

	"recipes/models"

	"github.com/google/uuid"
	"github.com/gin-gonic/gin"
	"gopkg.in/mgo.v2"
)

func GetAll(c *gin.Context) {
	db := models.DbCollection{}
	db.DB = c.MustGet("db").(*mgo.Collection)
	var results []models.Recipe
	results, err := db.GetAll()
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, err)
		return
	}
	c.JSON(http.StatusOK, results)
	return
}

func Create(c *gin.Context){
	db := models.DbCollection{}
	db.DB = c.MustGet("db").(*mgo.Collection)

	var request models.PostForm
	err := c.ShouldBindJSON(&request)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, err)
		return
	}

	err = db.Create(request)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, err)
		return
	}
	c.JSON(http.StatusOK,"")
	return
}

func Get(c *gin.Context) {
	db := models.DbCollection{}
	db.DB = c.MustGet("db").(*mgo.Collection)
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, err)
		return
	}
	result, err := db.Get(id)
	if err != nil {
		if strings.Contains(err.Error(), "not found"){
			c.AbortWithStatusJSON(http.StatusNotFound, errors.New("Not found"))
			return
		}
		c.AbortWithStatusJSON(http.StatusInternalServerError, err)
		return
	}
	c.JSON(http.StatusOK, *result)
	return
}

func Update(c *gin.Context) {
	db := models.DbCollection{}
	db.DB = c.MustGet("db").(*mgo.Collection)

	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, err)
		return
	}

	_, err = db.Get(id)
	if err != nil {
		if strings.Contains(err.Error(), "not found"){
			c.AbortWithStatusJSON(http.StatusNotFound, errors.New("Not found"))
			return
		}
		c.AbortWithStatusJSON(http.StatusInternalServerError, err)
		return
	}

	var request models.PostForm
	err = c.ShouldBindJSON(&request)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, err)
		return
	}
	err = db.Update(id, request)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, err)
	}
	c.JSON(http.StatusOK, "") 
}

func Delete(c *gin.Context) {
	db := models.DbCollection{}
	db.DB = c.MustGet("db").(*mgo.Collection)

	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, err)
		return
	}

	_, err = db.Get(id)
	if err != nil {
		if strings.Contains(err.Error(), "not found"){
			c.AbortWithStatusJSON(http.StatusNotFound, errors.New("Not found"))
			return
		}
		c.AbortWithStatusJSON(http.StatusInternalServerError, err)
		return
	}

	err = db.Delete(id)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, err)
	}
	c.JSON(http.StatusOK, "") 
}

エンドポイントにリクエストが来たら呼び出されるハンドラ達

 

model.go

package models

import(
	"github.com/google/uuid"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

type PostForm struct {
	Name string `json:"name" binding:"required"`
	Foods []string `json:"foods" binding:"required"`
}

type Recipe struct {
	Id	uuid.UUID	`json:"id" bson:"id"`
	Name  string `json:"name" bson:"name"`
	Foods []string `json:"foods" bson: "foods"`
}

type DbCollection struct{
	DB *mgo.Collection
}

func (db DbCollection) GetAll() ([]Recipe, error) {
	var results []Recipe
	err := db.DB.Find(bson.M{}).All(&results)
	if err != nil {
		return nil, err
	}
	return results, nil
}

func (db DbCollection) Create(request PostForm) error {
	var recipe Recipe
	recipe.Id = uuid.New()
	recipe.Name = request.Name
	recipe.Foods = request.Foods

	err := db.DB.Insert(&recipe)
	if err != nil {
		return err
	}
	return nil
}

func (db DbCollection) Get(id uuid.UUID) (*Recipe, error) {
	var result Recipe
	err := db.DB.Find(bson.M{"id": id}).One(&result)
	if err != nil {
		return nil, err
	}
	return &result, nil
}

func (db DbCollection) Update(id uuid.UUID, request PostForm) error {
	update := bson.M{"$set": bson.M{"name": request.Name, "foods": request.Foods}}
	err := db.DB.Update(bson.M{"id": id}, update)
	if err != nil {
		return err
	}
	return nil
}

func (db DbCollection) Delete(id uuid.UUID) error {
	err := db.DB.Remove(bson.M{"id": id})
	if err != nil {
		return err
	}
	return nil
}

ハンドラ内でDBとやり取りをする処理をここに記述

色々足りなさ過ぎて丸一日かかった...