In this tutorial, you’ll learn how to build a Golang CRUD API example using the Fiber framework and GORM to interact with a MySQL database.
Typically, I tend to use Gin Gonic when building APIs in Golang, but after exploring Fiber, I must say that it is much better. It offers straightforward APIs and an architecture that is reminiscent of the Express framework in Node.js. So, if you’re already familiar with working with Express, then you’ll feel right at home when working with Fiber as the transfer of knowledge is seamless.
GORM provides a clean and elegant way to perform CRUD operations on a SQL database, making it a great choice for our project. However, it’s important to note that it may not be the most performant option for handling high traffic volumes. In those cases, SQLC may be a better choice. Nonetheless, for this tutorial, we’ll be using GORM.
Without further ado, let’s dive right into the tutorial to construct the Golang CRUD API using Fiber, GORM, and MySQL.
More practice:
- Golang CRUD RESTful API with SQLC and PostgreSQL
- Create CRUD API in Golang using Fiber and GORM
- Build a Simple API with Rust and Rocket
- Build a CRUD API with Rust and MongoDB
- Build a Simple API with Rust and Actix Web
- Build a CRUD API with Node.js and Sequelize
- Build a CRUD App with FastAPI and SQLAlchemy
- Build CRUD API with Django REST framework
Run the CRUD API Project on Your Machine
If you want to run the Golang CRUD API project on your computer, you can follow these steps:
- Firstly, download or clone the Golang Fiber CRUD project from https://github.com/wpcodevo/golang-fiber-mysql and open the source code in a code editor of your choice.
- Next, open the integrated terminal in your IDE or text editor and start the MySQL server in Docker by running the command
docker-compose up -d
. - After that, run the command
go run main.go
in the terminal to install the necessary packages, migrate the GORM schema to the MySQL database, and start the Fiber HTTP server. - To test the CRUD endpoints, you can import the
Note App.postman_collection.json
file into Postman and make HTTP requests to the Fiber server. Alternatively, if you want to set up a frontend application to interact with the API, you can follow the instructions below.
Run the CRUD API with a Frontend App
If you want a more detailed walkthrough on building a React.js CRUD app, you can check out the post titled “Build a React.js CRUD App using a RESTful API“. However, the steps below should be enough to get you started without having to write any code.
- Before you start, make sure you have Node.js and Yarn installed on your machine. If not, you can download and install them from the official websites.
- Next, get the source code for the React CRUD project by either cloning the repository or downloading it from https://github.com/wpcodevo/reactjs-crud-note-app, and open it in your favorite IDE or text editor.
- In the console, navigate to the root directory of the project and run the command
yarn
oryarn install
to install all of the necessary dependencies. - Once the dependencies have been installed, run the command
yarn dev
to start the Vite development server. - To test the CRUD functionalities, open the app in your browser at
http://localhost:3000/
. Keep in mind that accessing the React app at http://127.0.0.1:3000 may result in a site can’t be reached or CORS errors. Therefore, it’s best to usehttp://localhost:3000/
.
Bootstrap the Golang Project
Upon completing this tutorial, you’ll have a file and folder organization that mirrors the screenshot displayed below, except for the Makefile and Note App.postman_collection.json
.
To start building the project, navigate to a preferred directory on your computer and open a terminal there. Once in the directory, run the following commands to create a new project folder, switch to the new folder, and initialize a new Golang project. Be sure to replace wpcodevo
with your GitHub username.
mkdir golang-fiber-mysql
cd golang-fiber-mysql
go mod init github.com/wpcodevo/golang-fiber-mysql
Now that you have initialized the Golang project, it’s time to open it in your preferred code editor. Once you have done that, you can launch the integrated terminal of your IDE and install the necessary packages for the project by running the following commands.
go get github.com/gofiber/fiber/v2
go get github.com/google/uuid
go get github.com/go-playground/validator/v10
go get -u gorm.io/gorm
go get gorm.io/driver/mysql
go get github.com/spf13/viper
go install github.com/cosmtrek/air@latest
fiber
– A high-performance web framework for Golang that supports middleware, routing, and error handling, as well as many other features commonly found in web frameworks.uuid
– A library to generate and parse UUIDs in Golang.validator
– A library for validating Go structs, values, and variables.gorm
– Provides a rich set of features such as automatic database schema generation, association handling, and query building, making it a popular choice for building web applications and services in Go.mysql
– A MySQL driver for GORMviper
– A library for managing configuration files in Golang.air
– This library automatically rebuilds and restarts your application.
Before we start implementing the CRUD functionality, let’s first create a simple Fiber server that responds with a JSON object when a GET request is made to the health checker route. To do this, create a file named main.go
in the root directory and add the following code to it.
main.go
package main
import (
"log"
"github.com/gofiber/fiber/v2"
)
func main() {
app := fiber.New()
app.Get("/api/healthchecker", func(c *fiber.Ctx) error {
return c.Status(200).JSON(fiber.Map{
"status": "success",
"message": "Welcome to Golang, Fiber, and GORM",
})
})
log.Fatal(app.Listen(":8000"))
}
You now have the choice to either build and run the project using the standard Go command-line tool or to use the air
tool. If you choose to use air
, it will automatically monitor your source code for any changes and reload the server accordingly. This means that you won’t have to manually stop and restart the server each time you make changes to the code.
After starting the server, you can check that it’s running correctly by visiting http://localhost:8000/api/healthchecker
in your web browser. This should display a JSON object.
Setup MySQL Server with Docker
To set up the MySQL database for this project, we’ll launch a MySQL server inside a Docker container. This approach offers greater flexibility and control than downloading the MySQL binary from the official website.
To get started, create a new file called docker-compose.yml
in the root directory and add the following Docker Compose configurations.
docker-compose.yml
version: '3'
services:
mysql:
image: mysql:latest
container_name: mysql
env_file:
- ./app.env
ports:
- '6500:3306'
volumes:
- mysqlDB:/var/lib/mysql
volumes:
mysqlDB:
If you take a closer look at the Docker Compose configuration, you will notice that the MySQL credentials are being read from an app.env
file, which has not been created yet. To create this file, navigate to the root directory of the project and add the following environment variables to the app.env
file.
app.env
MYSQL_HOST=127.0.0.1
MYSQL_PORT=6500
MYSQL_DATABASE=golang_gorm
MYSQL_USER=admin
MYSQL_PASSWORD=password123
MYSQL_ROOT_PASSWORD=password123
DATABASE_URL=mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@localhost:${MYSQL_PORT}/${MYSQL_DATABASE}
CLIENT_ORIGIN=http://localhost:3000
Create the Database Model with GORM
Now that the MySQL server is up and running, it’s time to define the database model using GORM. Start by creating a folder named ‘models‘ in the root directory of your project. Inside the ‘models‘ folder, create a file called note.model.go
and add the following code. For the sake of simplicity, we’ll only create one model for this tutorial.
models/note.model.go
package models
import (
"time"
"github.com/go-playground/validator/v10"
"github.com/google/uuid"
"gorm.io/gorm"
)
type Note struct {
ID string `gorm:"type:char(36);primary_key" json:"id,omitempty"`
Title string `gorm:"type:varchar(255);uniqueIndex:idx_notes_title,LENGTH(255);not null" json:"title,omitempty"`
Content string `gorm:"not null" json:"content,omitempty"`
Category string `gorm:"varchar(100)" json:"category,omitempty"`
Published bool `gorm:"default:false;not null" json:"published"`
CreatedAt time.Time `gorm:"not null;default:'1970-01-01 00:00:01'" json:"createdAt,omitempty"`
UpdatedAt time.Time `gorm:"not null;default:'1970-01-01 00:00:01';ON UPDATE CURRENT_TIMESTAMP" json:"updatedAt,omitempty"`
}
func (note *Note) BeforeCreate(tx *gorm.DB) (err error) {
note.ID = uuid.New().String()
return nil
}
var validate = validator.New()
type ErrorResponse struct {
Field string `json:"field"`
Tag string `json:"tag"`
Value string `json:"value,omitempty"`
}
func ValidateStruct[T any](payload T) []*ErrorResponse {
var errors []*ErrorResponse
err := validate.Struct(payload)
if err != nil {
for _, err := range err.(validator.ValidationErrors) {
var element ErrorResponse
element.Field = err.StructNamespace()
element.Tag = err.Tag()
element.Value = err.Param()
errors = append(errors, &element)
}
}
return errors
}
type CreateNoteSchema struct {
Title string `json:"title" validate:"required"`
Content string `json:"content" validate:"required"`
Category string `json:"category,omitempty"`
Published bool `json:"published,omitempty"`
}
type UpdateNoteSchema struct {
Title string `json:"title,omitempty"`
Content string `json:"content,omitempty"`
Category string `json:"category,omitempty"`
Published *bool `json:"published,omitempty"`
}
In the note.model.go
file, you can see the CreateNoteSchema and UpdateNoteSchema structs, which we’ll use to validate the incoming data when creating or updating records. Normally, these validation schemas would be in a separate file called note.schema.go
, but for simplicity, we’ve included them in the model file.
Additionally, we have a ValidateStruct
function that allows us to validate the request bodies using the validation schemas and return any relevant validation errors.
Load the Environment Variables with Viper
To make the environment variables in the app.env
file accessible to our application, we can use the Viper package. Let’s create a utility function that will handle this task. Start by creating an ‘initializers‘ folder in the project’s root directory. Within this folder, create a file called loadEnv.go
and add the following code to it.
initializers/loadEnv.go
package initializers
import (
"github.com/spf13/viper"
)
type Config struct {
DBHost string `mapstructure:"MYSQL_HOST"`
DBUserName string `mapstructure:"MYSQL_USER"`
DBUserPassword string `mapstructure:"MYSQL_PASSWORD"`
DBName string `mapstructure:"MYSQL_DATABASE"`
DBPort string `mapstructure:"MYSQL_PORT"`
ClientOrigin string `mapstructure:"CLIENT_ORIGIN"`
}
func LoadConfig(path string) (config Config, err error) {
viper.AddConfigPath(path)
viper.SetConfigType("env")
viper.SetConfigName("app")
viper.AutomaticEnv()
err = viper.ReadInConfig()
if err != nil {
return
}
err = viper.Unmarshal(&config)
return
}
Create a Function to Connect to the MySQL Server
Now that we have successfully read the environment variables from the app.env
file, we can proceed to establish a connection between our application and the MySQL server using GORM. Once connected, we can migrate the database model to the MySQL database.
To achieve this, create a new file named db.go
inside the initializers
folder and add the following code to it. This will set up the database connection and migrate the Note
model to the MySQL database.
initializers/db.go
package initializers
import (
"fmt"
"log"
"os"
"github.com/wpcodevo/golang-fiber-mysql/models"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
var DB *gorm.DB
func ConnectDB(config *Config) {
var err error
// dsn := fmt.Sprintf("user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local")
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", config.DBUserName, config.DBUserPassword, config.DBHost, config.DBPort, config.DBName)
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to the Database! \n", err.Error())
os.Exit(1)
}
DB.Logger = logger.Default.LogMode(logger.Info)
log.Println("Running Migrations")
DB.AutoMigrate(&models.Note{})
log.Println("🚀 Connected Successfully to the Database")
}
Let’s put everything together and make sure that our application is set up correctly by testing if we can connect to the MySQL server and sync our GORM model with the MySQL database schema.
To achieve this, we will make some changes to the main.go file. Firstly, let’s create an init
function and call the initializers.LoadConfig()
function to load the environment variables. This will be followed by calling the initializers.ConnectDB()
function to establish a connection pool between the MySQL server and the Fiber server.
Here is the updated code that you can replace the main.go
file with:
main.go
package main
import (
"log"
"github.com/gofiber/fiber/v2"
"github.com/wpcodevo/golang-fiber-mysql/initializers"
)
func init() {
config, err := initializers.LoadConfig(".")
if err != nil {
log.Fatalln("Failed to load environment variables! \n", err.Error())
}
initializers.ConnectDB(&config)
}
func main() {
app := fiber.New()
app.Get("/api/healthchecker", func(c *fiber.Ctx) error {
return c.Status(200).JSON(fiber.Map{
"status": "success",
"message": "Welcome to Golang, Fiber, MySQL, and GORM",
})
})
log.Fatal(app.Listen(":8000"))
}
Once you’ve updated the main.go
file as described, ensure that the MySQL server is still running, and then start the Fiber server once again. This time, you should see GORM executing a series of queries in the terminal.
If there are no errors, it means you’ve successfully connected to the MySQL server and synchronized the GORM model with the database schema.
Implement the CRUD Functionality
Now that we have successfully connected the Fiber server to the MySQL database, it’s time to create route controllers that will allow us to perform CRUD (Create, Read, Update, Delete) operations on the database using GORM. These route handlers will act as higher-level functions, building on the lower-level CRUD functionality provided by GORM.
Fiber Route Handler to Create a New Record
This route handler is designed to handle incoming POST requests to the /api/notes
endpoint and add new records to the database.
When called, the function first parses the request body and validates the incoming data. If there are any parsing or validation errors, the function will return a JSON response with the corresponding errors.
Assuming the incoming data passes validation, the function creates a new instance of the Note
struct, populates it with data from the parsed CreateNoteSchema struct, and timestamps it with the current time.
Next, the function uses the Create
method of the DB
instance provided by GORM to insert the new data into the MySQL database. If there is an error during the insertion, such as a duplicate entry error, the function will return a JSON response with the appropriate error.
If the insertion is successful, the function will return a JSON response with a 201 Created status code and the newly created note data in the response body.
controllers/note.controller.go
func CreateNoteHandler(c *fiber.Ctx) error {
var payload *models.CreateNoteSchema
if err := c.BodyParser(&payload); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"status": "fail", "message": err.Error()})
}
errors := models.ValidateStruct(payload)
if errors != nil {
return c.Status(fiber.StatusBadRequest).JSON(errors)
}
now := time.Now()
newNote := models.Note{
Title: payload.Title,
Content: payload.Content,
Category: payload.Category,
Published: payload.Published,
CreatedAt: now,
UpdatedAt: now,
}
result := initializers.DB.Create(&newNote)
if result.Error != nil && strings.Contains(result.Error.Error(), "Duplicate entry") {
return c.Status(fiber.StatusConflict).JSON(fiber.Map{"status": "fail", "message": "Title already exist, please use another title"})
} else if result.Error != nil {
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"status": "error", "message": result.Error.Error()})
}
return c.Status(fiber.StatusCreated).JSON(fiber.Map{"status": "success", "data": fiber.Map{"note": newNote}})
}
Fiber Route Handler to Retrieve All Records
In order to efficiently handle large amounts of data in our application, we’ll create a route handler that retrieves a paginated list of records from the MySQL database. By implementing pagination, we can ensure that we’re not sending excessive amounts of data to the user.
This route handler is triggered when a GET request is made to the /api/notes
endpoint. The function first reads the page
and limit
query parameters from the request. If these parameters are not provided, they will default to page 1 and limit 10.
controllers/note.controller.go
func FindNotes(c *fiber.Ctx) error {
var page = c.Query("page", "1")
var limit = c.Query("limit", "10")
intPage, _ := strconv.Atoi(page)
intLimit, _ := strconv.Atoi(limit)
offset := (intPage - 1) * intLimit
var notes []models.Note
results := initializers.DB.Limit(intLimit).Offset(offset).Find(¬es)
if results.Error != nil {
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"status": "error", "message": results.Error})
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "success", "results": len(notes), "notes": notes})
}
Next, the function converts these parameters to integers and calculates the offset
for the query. It then uses the Find
method of the DB
instance provided by GORM to retrieve the relevant notes, limiting the results to the specified page and offset.
Finally, the handler sends a JSON response with a 200 OK status code and the paginated list of notes, along with the total number of notes returned in the current page. If there is an error during the query, the function returns a JSON response with the appropriate error.
Fiber Route Handler to Edit a Record
This route handler is responsible for editing records in the database when a PATCH request is made to the /api/notes/:noteId
endpoint.
Upon receiving a request, the function first extracts the noteId
parameter from the request. It then attempts to parse the request body, returning a JSON response with a status code of 400 (Bad Request) and an error message if parsing fails.
Next, the function queries the database to check if a record with the given ID exists. If the query fails or no record is found, the function returns a JSON response with an error message and a status code of either 404 (Not Found) or 502 (Bad Gateway).
controllers/note.controller.go
func UpdateNote(c *fiber.Ctx) error {
noteId := c.Params("noteId")
var payload *models.UpdateNoteSchema
if err := c.BodyParser(&payload); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"status": "fail", "message": err.Error()})
}
var note models.Note
result := initializers.DB.First(¬e, "id = ?", noteId)
if err := result.Error; err != nil {
if err == gorm.ErrRecordNotFound {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"status": "fail", "message": "No note with that Id exists"})
}
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"status": "fail", "message": err.Error()})
}
updates := make(map[string]interface{})
if payload.Title != "" {
updates["title"] = payload.Title
}
if payload.Category != "" {
updates["category"] = payload.Category
}
if payload.Content != "" {
updates["content"] = payload.Content
}
if payload.Published != nil {
updates["published"] = payload.Published
}
updates["updated_at"] = time.Now()
initializers.DB.Model(¬e).Updates(updates)
return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "success", "data": fiber.Map{"note": note}})
}
If the query is successful, the function creates a updates
map to hold the updated fields of the note record. This is because the handler is for a PATCH request, and the user may only want to update certain fields.
The function then uses the Updates
method of the gorm.DB
object to update the note in the database with the fields in the updates
map.
Finally, the function returns a JSON response with a status code of 200 (OK) and the updated note in the response body.
Fiber Route Handler to Fetch a Single Record
This route handler retrieves a single note from the database, identified by the noteId
parameter in the request. The function first extracts the noteId
from the request parameters and uses it to query the database for the corresponding note. If the note is found, the function returns a JSON response with the note data and a status code of 200 (OK).
However, if the requested note is not found, the function returns a JSON response with a status code of 404 (Not Found) and an error message. Similarly, if there is an unexpected error while querying the database, the function returns a JSON response with a status code of 502 (Bad Gateway) and an error message.
controllers/note.controller.go
func FindNoteById(c *fiber.Ctx) error {
noteId := c.Params("noteId")
var note models.Note
result := initializers.DB.First(¬e, "id = ?", noteId)
if err := result.Error; err != nil {
if err == gorm.ErrRecordNotFound {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"status": "fail", "message": "No note with that Id exists"})
}
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"status": "fail", "message": err.Error()})
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "success", "data": fiber.Map{"note": note}})
}
Fiber Route Handler to Delete a Record
The final route function in our API is responsible for permanently deleting a note from the database. This function is triggered by a DELETE request to the /api/notes/:noteId
endpoint.
When called, the function extracts the noteId
parameter from the request and queries the database to find the corresponding record. If the record exists, the function deletes it from the database. If no record is deleted, it means that there was no note in the database with the specified ID, so the function returns a 404 error.
If the deletion is successful, the function returns a 204 (No Content) response.
controllers/note.controller.go
func DeleteNote(c *fiber.Ctx) error {
noteId := c.Params("noteId")
result := initializers.DB.Delete(&models.Note{}, "id = ?", noteId)
if result.RowsAffected == 0 {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"status": "fail", "message": "No note with that Id exists"})
} else if result.Error != nil {
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"status": "error", "message": result.Error})
}
return c.SendStatus(fiber.StatusNoContent)
}
The Complete Code of the Fiber Route Handlers
controllers/note.controller.go
package controllers
import (
"strconv"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/wpcodevo/golang-fiber-mysql/initializers"
"github.com/wpcodevo/golang-fiber-mysql/models"
"gorm.io/gorm"
)
func CreateNoteHandler(c *fiber.Ctx) error {
var payload *models.CreateNoteSchema
if err := c.BodyParser(&payload); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"status": "fail", "message": err.Error()})
}
errors := models.ValidateStruct(payload)
if errors != nil {
return c.Status(fiber.StatusBadRequest).JSON(errors)
}
now := time.Now()
newNote := models.Note{
Title: payload.Title,
Content: payload.Content,
Category: payload.Category,
Published: payload.Published,
CreatedAt: now,
UpdatedAt: now,
}
result := initializers.DB.Create(&newNote)
if result.Error != nil && strings.Contains(result.Error.Error(), "Duplicate entry") {
return c.Status(fiber.StatusConflict).JSON(fiber.Map{"status": "fail", "message": "Title already exist, please use another title"})
} else if result.Error != nil {
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"status": "error", "message": result.Error.Error()})
}
return c.Status(fiber.StatusCreated).JSON(fiber.Map{"status": "success", "data": fiber.Map{"note": newNote}})
}
func FindNotes(c *fiber.Ctx) error {
var page = c.Query("page", "1")
var limit = c.Query("limit", "10")
intPage, _ := strconv.Atoi(page)
intLimit, _ := strconv.Atoi(limit)
offset := (intPage - 1) * intLimit
var notes []models.Note
results := initializers.DB.Limit(intLimit).Offset(offset).Find(¬es)
if results.Error != nil {
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"status": "error", "message": results.Error})
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "success", "results": len(notes), "notes": notes})
}
func UpdateNote(c *fiber.Ctx) error {
noteId := c.Params("noteId")
var payload *models.UpdateNoteSchema
if err := c.BodyParser(&payload); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"status": "fail", "message": err.Error()})
}
var note models.Note
result := initializers.DB.First(¬e, "id = ?", noteId)
if err := result.Error; err != nil {
if err == gorm.ErrRecordNotFound {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"status": "fail", "message": "No note with that Id exists"})
}
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"status": "fail", "message": err.Error()})
}
updates := make(map[string]interface{})
if payload.Title != "" {
updates["title"] = payload.Title
}
if payload.Category != "" {
updates["category"] = payload.Category
}
if payload.Content != "" {
updates["content"] = payload.Content
}
if payload.Published != nil {
updates["published"] = payload.Published
}
updates["updated_at"] = time.Now()
initializers.DB.Model(¬e).Updates(updates)
return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "success", "data": fiber.Map{"note": note}})
}
func FindNoteById(c *fiber.Ctx) error {
noteId := c.Params("noteId")
var note models.Note
result := initializers.DB.First(¬e, "id = ?", noteId)
if err := result.Error; err != nil {
if err == gorm.ErrRecordNotFound {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"status": "fail", "message": "No note with that Id exists"})
}
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"status": "fail", "message": err.Error()})
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "success", "data": fiber.Map{"note": note}})
}
func DeleteNote(c *fiber.Ctx) error {
noteId := c.Params("noteId")
result := initializers.DB.Delete(&models.Note{}, "id = ?", noteId)
if result.RowsAffected == 0 {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"status": "fail", "message": "No note with that Id exists"})
} else if result.Error != nil {
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"status": "error", "message": result.Error})
}
return c.SendStatus(fiber.StatusNoContent)
}
Create the API Routes and Setup CORS
Now that we have implemented all the API route handlers, it’s time to bring them together and define routes for our API. Additionally, we’ll configure CORS middleware to allow cross-origin resource sharing for specific origins, headers, methods, and credentials.
To achieve this, let’s open the main.go
file and replace its current content with the following code snippets.
main.go
package main
import (
"log"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/wpcodevo/golang-fiber-mysql/controllers"
"github.com/wpcodevo/golang-fiber-mysql/initializers"
)
func init() {
config, err := initializers.LoadConfig(".")
if err != nil {
log.Fatalln("Failed to load environment variables! \n", err.Error())
}
initializers.ConnectDB(&config)
}
func main() {
app := fiber.New()
micro := fiber.New()
app.Mount("/api", micro)
app.Use(logger.New())
app.Use(cors.New(cors.Config{
AllowOrigins: "http://localhost:3000",
AllowHeaders: "Origin, Content-Type, Accept",
AllowMethods: "GET, POST, PATCH, DELETE",
AllowCredentials: true,
}))
micro.Route("/notes", func(router fiber.Router) {
router.Post("/", controllers.CreateNoteHandler)
router.Get("", controllers.FindNotes)
})
micro.Route("/notes/:noteId", func(router fiber.Router) {
router.Delete("", controllers.DeleteNote)
router.Get("", controllers.FindNoteById)
router.Patch("", controllers.UpdateNote)
})
micro.Get("/healthchecker", func(c *fiber.Ctx) error {
return c.Status(200).JSON(fiber.Map{
"status": "success",
"message": "Welcome to Golang, Fiber, MySQL, and GORM",
})
})
log.Fatal(app.Listen(":8000"))
}
To start the application again with the changes made to the main.go file, you have two options: run the command go run main.go
or use air
. If you encounter any errors in the terminal, it’s advisable to review the previous steps to locate the source of the errors.
Test the CRUD API with a Frontend
Now that we have completed the implementation of the CRUD API, it’s time to test the endpoints to ensure that they function correctly. You can perform this test using any API testing software such as Postman or the Thunder Client VS Code extension.
To access the collection used in testing the API, download the project from https://github.com/wpcodevo/golang-fiber-mysql and import the Note App.postman_collection.json
file into either Thunder Client or Postman.
Once you’ve imported the collection, you can make requests to the Fiber server to test the CRUD functionality of the Golang API.
Alternatively, you can use a frontend application built with React to test the API. To spin up the React CRUD app in seconds, follow the instructions outlined in the “Run the CRUD API with a Frontend App” section.
Perform the CREATE Operation of CRUD
To create a new note, just click on the plus icon or the “Add new note” button, and a popup modal will appear, allowing you to enter the note’s title and content. Once you’ve filled out the form, click on the “Create Note” button to submit the data to the Golang API.
If the request is successful, the newly created note will be displayed. However, if an error occurs, an alert notification will be shown to indicate the issue.
Perform the UPDATE Operation of CRUD
To edit a note, you can access the note’s settings menu by clicking on the three dots located on the note item. From there, choose the Edit option and a popup modal will appear where you can make the desired changes.
Once you have made the necessary modifications, click on the ‘Update Note‘ button to submit the updated data to the Golang API. If the request is successful, the changes will be reflected in the note.
Perform the READ Operation of CRUD
Now, let’s move on to testing the READ operation, which occurs automatically when you land on the root route of the application. A GET request will be sent to retrieve a paginated list of notes from the Golang API. If the request is successful, the list of note items will be displayed.
Perform the DELETE Operation of CRUD
Let’s wrap up by testing the DELETE operation. To delete a note, simply click on the three dots on the note item and select the “Delete” option from the settings menu that appears. A confirmation prompt will appear to ensure that you want to proceed since the delete operation is irreversible.
After confirming the deletion, a DELETE request will be made to the Golang API to remove the note from the database. If the request is successful, the note item will be immediately removed from the page.
Conclusion
We’ve reached the end of this tutorial! You can access the source code for the project on the Golang CRUD API with GORM, Fiber, and MySQL repository on GitHub.
Throughout this tutorial, we explored how to create a Golang CRUD API using GORM, MySQL, and the Fiber framework. I hope that you found this tutorial helpful and engaging. If you have any questions or feedback, please feel free to leave a comment below.
I tried to test your example “Golang CRUD API Example with GORM and MySQL” in windows 10, it requires to install “docker” and this requires to install “WSL 2 backend” and who knows how long the installation chain is, is there an easier way to do this test? at least previously mention what is the software chain that is required.
I just want to create an API in golang and test it on my pc
I understand that you would like to run the project without installing Docker. In that case, an alternative approach is to utilize Supabase, a hosted database service, to run the project.
Here’s what you can do:
1. Sign up for an account on Supabase (https://supabase.com/) and create a new project.
2. Once your project is set up, you’ll be provided with a PostgreSQL connection URL. This URL allows your Golang project to connect to the Supabase database.
3. In your Golang project, locate the
app.env
file and add the PostgreSQL connection URL you obtained from Supabase. This configuration enables your project to establish a connection with the Supabase database.