How to create an API with Golang and Fiber – Part 04

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:

FirstTo get the first expense
FindTo list all expenses
CreateTo insert a new expense
UpdatesTo update the specified expense’s fields
DeleteTo remove an expense
ModelTo 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/


Posted

in

,

by

Tags:

Comments

2 responses to “How to create an API with Golang and Fiber – Part 04”

  1. Osh Pilaf Avatar
    Osh Pilaf

    If I have another pkg i.e. user, how would I add that for validation? I can’t use the same func CreateOrUpdate as I’ll get redeclared error. I want to keep the validations in the same folder:
    pkg/validation /expense.go
    pkg/validation /user.go

    1. Andrew Esteves Avatar

      Hey Osh! If you want to reuse the CreateOrUpdate func you could use Go embedding types within a more specific for your use case. i.e Having another func CreateOrUpdateUser with the CreateOrUpdate as an embedded struct. The error you’ve mentioned is because, within the same package, the function name must be unique. If you still have questions, let me know.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.