Since we’ve integrated MySQL into our API, we can implement our endpoints properly.
We will implement the following handlers:
- Index
- Show
- Store
- Update
- Destroy
The Index function will list all expenses available.
// Index to list all expenses
func (h ExpenseHandler) Index(ctx *fiber.Ctx) error {
var expenses []entity.Expense
h.DB.Find(&expenses)
return ctx.JSON(fiber.Map{"data": expenses})
}
With the function Find provided by GORM we can retrieve all expenses and if needed we can add some conditions as well for filtering. Since we are passing as a reference, our variable now has all records stored in the database table to be returned.
I’m going to implement the Store function next. I’m doing this because the others will be very similar and you can check all the code down below.
// Store a new expense
func (h ExpenseHandler) Store(ctx *fiber.Ctx) error {
expense := new(entity.Expense)
if err := ctx.BodyParser(expense); err != nil {
return ctx.Status(422).JSON(fiber.Map{"errors": [1]string{"We were not able to process your request"}})
}
if validate := validation.CreateOrUpdate(*expense); len(validate) > 0 {
return ctx.Status(422).JSON(fiber.Map{"errors": validate})
}
h.DB.Create(&expense)
return ctx.JSON(fiber.Map{"message": "Expense successfully registered"})
}
The important pieces to be aware of are, the Fiber’s Context BodyParser, who will get the request’s content, I’m initializing the expense entity on memory, using our validation package that will be shown next, and afterward, we use the Create function from GORM to store a new expense in our database.
Before running the application you need to create a folder called “validation” and inside of it two files: pkg/validation/common.go and pkg/validation /expense.go.
pkg/validation/common.go
package validation
import "reflect"
// isValid do a simple check for strings and integers
func isValid(field interface{}) bool {
value := reflect.ValueOf(field)
valid := false
switch value.Kind() {
case reflect.String:
if value.String() != "" {
valid = true
}
case reflect.Float64:
if value.Float() >= 1 {
valid = true
}
}
return valid
}
To our isValid function, we are using Go’s reflect package, to do a generic validation, for now just string and integers, but you can add more functionality when needed.
The pkg/validation/expense.go will check the required fields for actions such as create and update.
package validation
import (
"github.com/bootmind/figo/pkg/entity"
)
// CreateOrUpdate to verify the required fields
func CreateOrUpdate(expense entity.Expense) []string {
var messages []string
if !isValid(expense.Title) {
messages = append(messages, "Title is required")
}
if !isValid(expense.Total) {
messages = append(messages, "Total should be greater than zero")
}
return messages
}
The main functions to keep in mind when using GORM for our application are:
| First | To get the first expense |
| Find | To list all expenses |
| Create | To insert a new expense |
| Updates | To update the specified expense’s fields |
| Delete | To remove an expense |
| Model | To specify the model to run the operations |
For functions like Show, Update, or Destroy, we need to get the parameters from the request.
id, err := strconv.ParseInt(ctx.Params("id"), 10, 64)
We are using the strconv package to do the type conversion for the ID.
You can see the full code for our expenses handler.
package handler
import (
"strconv"
"github.com/bootmind/figo/pkg/entity"
"github.com/bootmind/figo/pkg/validation"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
)
// ExpenseHandler type
type ExpenseHandler struct {
DB *gorm.DB
}
// Index to list all expenses
func (h ExpenseHandler) Index(ctx *fiber.Ctx) error {
var expenses []entity.Expense
h.DB.Find(&expenses)
return ctx.JSON(fiber.Map{"data": expenses})
}
// Show an expense
func (h ExpenseHandler) Show(ctx *fiber.Ctx) error {
id, err := strconv.ParseInt(ctx.Params("id"), 10, 64)
if err != nil {
return ctx.Status(422).JSON(fiber.Map{"errors": [1]string{"We were not able to process your expense"}})
}
var expenseDB entity.Expense
expenseDB.ID = uint(id)
h.DB.First(&expenseDB)
return ctx.JSON(fiber.Map{"data": expenseDB})
}
// Store a new expense
func (h ExpenseHandler) Store(ctx *fiber.Ctx) error {
expense := new(entity.Expense)
if err := ctx.BodyParser(expense); err != nil {
return ctx.Status(422).JSON(fiber.Map{"errors": [1]string{"We were not able to process your request"}})
}
if validate := validation.CreateOrUpdate(*expense); len(validate) > 0 {
return ctx.Status(422).JSON(fiber.Map{"errors": validate})
}
h.DB.Create(&expense)
return ctx.JSON(fiber.Map{"message": "Expense successfully registered"})
}
// Update an expense
func (h ExpenseHandler) Update(ctx *fiber.Ctx) error {
expense := new(entity.Expense)
if err := ctx.BodyParser(expense); err != nil {
return ctx.Status(422).JSON(fiber.Map{"errors": [1]string{"We were not able to process your request"}})
}
id, err := strconv.ParseInt(ctx.Params("id"), 10, 64)
if err != nil {
return ctx.Status(422).JSON(fiber.Map{"errors": [1]string{"We were not able to process your expense"}})
}
if validate := validation.CreateOrUpdate(*expense); len(validate) > 0 {
return ctx.Status(422).JSON(fiber.Map{"errors": validate})
}
var expenseDB entity.Expense
h.DB.First(&expenseDB, id)
h.DB.Model(&expenseDB).Updates(map[string]interface{}{
"title": expense.Title,
"total": expense.Total,
})
return ctx.JSON(fiber.Map{"message": "Expense successfully updated"})
}
// Destroy an expense
func (h ExpenseHandler) Destroy(ctx *fiber.Ctx) error {
id, err := strconv.ParseInt(ctx.Params("id"), 10, 64)
if err != nil {
return ctx.Status(422).JSON(fiber.Map{"errors": [1]string{"We were not able to process your expense"}})
}
var expenseDB entity.Expense
expenseDB.ID = uint(id)
h.DB.Delete(&expenseDB)
return ctx.JSON(fiber.Map{"message": "Expense successfully removed"})
}
We then we need to update our expense route (pkg/route/expense.go).
// Expenses route
func Expenses(app *fiber.App, db *gorm.DB) {
h := &handler.ExpenseHandler{
DB: db,
}
r := app.Group("/expenses")
r.Get("/", h.Index)
r.Get("/:id", h.Show)
r.Post("/", h.Store)
r.Put("/:id", h.Update)
r.Delete("/:id", h.Destroy)
}
Using :id we are indicating to Fiber that this should be added to our context and it will be a route parameter.
Now we can run our application again, on your terminal, but we won’t use the migration flag.
DB_NAME=figo DB_USER=root DB_PASS=4321 DB_HOST=172.17.0.2 go run .
Github repository: https://github.com/bootmind/figo-api
Part 01 https://blog.bootmind.com/golang/how-to-create-an-api-with-golang-and-fiber-part-01/
Part 02 https://blog.bootmind.com/golang/how-to-create-an-api-with-golang-and-fiber-part-02/
Part 03 https://blog.bootmind.com/golang/how-to-create-an-api-with-golang-and-fiber-part-03/
Part 05 https://blog.bootmind.com/golang/how-to-create-an-api-with-golang-and-fiber-part-05/

Leave a Reply