API implementation to toggle values in two ways (go)

things to do

The purpose is to practice to become Go's web framework Echo. Even if you toggle the value, I think there are cases where the bool value of the database is set to true <=> false, and there are cases where records in the database are inserted or deleted (such as creating a favorite function using an intermediate table). .. This time, after implementing the former as a BoolToggler model, we will implement the latter by adding a favorite function for BoolToggler to the User model. (It's a surreal and impractical application, but it's a practice so I don't care ...) I put the whole code on GitHub.

API to toggle DB bool value

models

This is the main subject 1 of this article. Toggle True <=> False for a column value of type bool in the model. I defined such a model and migrated.

models/bool_toggler.go

package models

import (
    "github.com/jinzhu/gorm"
)

type BoolToggler struct {
	// type gorm.Model struct {
			// ID        uint           `gorm:"primaryKey"`
			// CreatedAt time.Time
			// UpdatedAt time.Time
			// DeletedAt gorm.DeletedAt `gorm:"index"`
	// }
  gorm.Model
  Toggler bool `json:"toggler"`
}

It is a structure with a simple bool value called Toggler. Implement the api part.

Implementation of API

web/api/toggle_bool_toggler.go

package api

import (
	"github.com/labstack/echo"
	"github.com/valyala/fasthttp"
	"strconv"
	"hello/models"
	"hello/middlewares"
)

func ToggleBoolToggler() echo.HandlerFunc {
	return func(c echo.Context) error {
        //DB connection middleware
		dbs := c.Get("dbs").(*middlewares.DatabaseClient)
		//Since the path parameter is string, convert it to uint
		intId, _ := strconv.Atoi(c.Param("id"))
		uintId := uint(intId)

		boolToggler := models.BoolToggler{}
		if dbs.DB.First(&boolToggler, uintId).RecordNotFound() {
			//If the id is specified incorrectly, status code 404 is returned.
			return c.JSON(fasthttp.StatusNotFound, "The boolToggler with the specified id was not found.")
		} else {
			//Invert bool value and save
			boolToggler.Toggler = !boolToggler.Toggler
			dbs.DB.Save(&boolToggler)
			return c.JSON(fasthttp.StatusOK, boolToggler)
		}
	}
}

Endpoint that returns the currently configured Toggler

web/api/get_bool_toggler.go

package api

import (
	"github.com/labstack/echo"
	"github.com/valyala/fasthttp"
	"hello/models"
	"hello/middlewares"
	"strconv"
)

func GetBoolToggler() echo.HandlerFunc {
	return func(c echo.Context) error {
		dbs := c.Get("dbs").(*middlewares.DatabaseClient)
		intId, _ := strconv.Atoi(c.Param("id"))
		uintId := uint(intId)

		boolToggler := models.BoolToggler{}
		if dbs.DB.First(&boolToggler, uintId).RecordNotFound() {
			return c.JSON(fasthttp.StatusNotFound, "The boolToggler with the specified id was not found.")
		} else {
			return c.JSON(fasthttp.StatusOK, boolToggler.Toggler)
		}
	}
}

It's basically the same as before. Instead of updating the data, I just take the Boolean value and put it in the response.

routes/api.go

func Init(e *echo.Echo) {
	g := e.Group("/api")
    {
        g.PUT("/bool_toggler/:id/toggle", api.ToggleBoolToggler())
        g.GET("/bool_toggler/:id", api.GetBoolToggler()) 
    }
}

Let's test the operation so far.

curl http://localhost:8080/api/bool_toggler/1
>> false

curl -XPUT http://localhost:8080/api/bool_toggler/1/toggle
>> {"ID":1,"CreatedAt":"2020-10-05T14:54:27Z","UpdatedAt":"2020-10-07T10:49:12.1435735Z","DeletedAt":null,"toggler":true}

curl http://localhost:8080/api/bool_toggler/1
>> true

//Unregistered data
curl http://localhost:8080/api/bool_toggler/3
>> "The boolToggler with the specified id was not found.

I was able to confirm that it was working well.

API to toggle favorite state

models

We will implement an API that deletes and inserts values in the data that expresses the relationship in the intermediate table.

We'll start by creating a simple User model. Since the purpose is to implement relational many-to-many data as a simple application with an intermediate table, we do not create an authentication function. Anyway, it is a model with only a time stamp and a name. Migrate in the same way.

To create a many-to-many database with GORM, https://gorm.io/ja_JP/docs/many_to_many.html

models/user.go

package models

import (
  "github.com/jinzhu/gorm"
)

type User struct {
	gorm.Model
	name string `json:"name"`
	//Bool with the name Favorites_Store toggler
	Favorites []*BoolToggler `gorm:"many2many:user_favorite_togglers;"`
}

API implementation

Although it is a URI design first, there is no authentication function, so

/api/favorite/users/:user_id/bool_togglers/:toggler_id

You can see that it is necessary to refer to each entity from multiple path parameters with the URI. The following is the API part, but since it has become quite complicated, I left a lot of comments

web/api/toggle_favorite_bool_toggler.go

package api

import (
	"github.com/labstack/echo"
	"github.com/valyala/fasthttp"
	"strconv"
	"hello/models"
	"hello/middlewares"
)

func ToggleFavoriteToggler() echo.HandlerFunc {
	return func(c echo.Context) error {

		//Structure that creates JSON for response
		type Response struct {
			UserId         uint
			BoolTogglerId  uint
			Favorite       bool
		}

		//DB connection
		dbs := c.Get("dbs").(*middlewares.DatabaseClient)
		
		//Convert path parameters to uint
		intUserId, _ := strconv.Atoi(c.Param("user_id"))
		uintUserId := uint(intUserId)
		intTogglerId, _ := strconv.Atoi(c.Param("toggler_id"))
		uintTogglerId := uint(intTogglerId)

		//Instantiate a Response structure
		var resJSON Response
		resJSON.UserId = uintUserId
		resJSON.BoolTogglerId = uintTogglerId

		//Pass boolToggler with ID when Appending
		var boolToggler models.BoolToggler
		boolToggler.ID = uintTogglerId
		
		//Enable user relations in Preload and select
		user := &models.User{}
		dbs.DB.Preload("Favorites", "bool_toggler_id = ?", uintTogglerId).
		Find(&user, uintUserId)

		//Add a new record if it hasn't been favorited yet
		if len(user.Favorites) < 1 {
			dbs.DB.Model(&user).Association("Favorites").Append(&boolToggler)
			resJSON.Favorite = true

		//Delete existing record if it was favorited
		} else {
			dbs.DB.Model(&user).Association("Favorites").Delete(&boolToggler)
			resJSON.Favorite = false
		}
		return c.JSON(fasthttp.StatusOK, resJSON)
	}
}

routes/api.go

g.POST("/favorite/users/:user_id/bool_togglers/:toggler_id", api.ToggleFavoriteToggler()) //Postscript

When I try curl, the value is toggled properly.

curl -XPOST http://localhost:8080/api/favorite/users/1/bool_togglers/1
{"UserId":1,"BoolTogglerId":1,"Favorite":false}
/go/src/app # curl -XPOST http://localhost:8080/api/favorite/users/1/bool_togglers/1
{"UserId":1,"BoolTogglerId":1,"Favorite":true}

Furthermore, when I checked the mysql table, I was able to confirm the intended behavior.

mysql> select * from user_favorite_togglers;
+---------+-----------------+
| user_id | bool_toggler_id |
+---------+-----------------+
|       1 |               1 |
+---------+-----------------+
1 row in set (0.00 sec)

//After access
mysql> select * from user_favorite_togglers;
Empty set (0.00 sec)

Endpoint that returns user information

web/api/show_user.go

package api

import (
	"github.com/labstack/echo"
	"github.com/valyala/fasthttp"
	"hello/models"
	"hello/middlewares"
	"strconv"
)

func ShowUserInfo() echo.HandlerFunc {
	return func(c echo.Context) error {

		type ResToggler struct {
			BoolTogglerId uint
			Toggler       bool
		}

		type Response struct {
			UserId uint
			Name   string
			Favorites []ResToggler
		}

		dbs := c.Get("dbs").(*middlewares.DatabaseClient)
		//Convert path parameters to uint
		intUserId, _ := strconv.Atoi(c.Param("id"))
		uintUserId := uint(intUserId)
		
		//Enable user relations in Preload and select
		user := models.User{}
		dbs.DB.Preload("Favorites").Find(&user, uintUserId)

		var resJSON Response
		resJSON.UserId = user.ID
		resJSON.Name   = user.Name
		for _, v := range user.Favorites {
			toggler := ResToggler{
				BoolTogglerId: v.ID,
				Toggler: v.Toggler,
			}
			resJSON.Favorites = append(resJSON.Favorites, toggler)
		}
		return c.JSON(fasthttp.StatusOK, resJSON)
	}
}

The nice thing about the Echo framework is that if you define a structure for Response, you can pass it as JSON as it is. The result returns a structure like this:

curl http://localhost:8080/api/user/1
{"UserId":1,"Name":"test_user01","Favorites":[{"BoolTogglerId":1,"Toggler":false}]}

Recommended Posts

API implementation to toggle values in two ways (go)
I tried two ways to combine multiple commits in Git
Post to slack in Go language
Two ways to display multiple graphs in one image with matplotlib
How to make a request to bitFlyer Lightning's Private API in Go language
Function to extract the maximum and minimum values ​​in a slice with Go
A confusing story with two ways to implement XGBoost in Python + overall notes
3 ways to parse time strings in python [Note]
Minimal implementation to do Union Find in Python
Two Ways to Decode Binary Vector Tiles to GeoJSON
Sample API server to receive JSON in Golang
How to create a Rest Api in Django