In this article, you’ll learn how to build a CRUD API in Golang using the Fiber web framework and GORM. The REST API will run on a Fiber HTTP server and use GORM to persist data in a PostgreSQL database.
What is Fiber? Fiber is a fast and lightweight web framework for building blazingly-fast web applications and APIs in Golang. Fiber’s architecture and syntax are heavily influenced by the Express.js web framework in Node.js, making it familiar and easy to use for those with prior experience with Express.
What is GORM? Golang Object-Relational Mapping, also commonly referred to as GORM, is an open-source ORM library that allows developers to interact with SQL databases using a clean and simple API, rather than writing raw SQL queries.
An ORM like GORM acts as a bridge between your application’s code and the database, allowing you to interact with data using Go structs and data types, instead of having to write raw SQL queries. At the time of writing this article, GORM supports various database servers such as MySQL, PostgreSQL, SQLite, and SQL server, and can be easily configured to work with other databases as well.
More practice:
- Golang CRUD RESTful API with SQLC and PostgreSQL
- Build a Simple API in Rust
- 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
Prerequisites
- Prior knowledge of any Golang web framework like Gin Gonic or Express.js will be beneficial.
- You should have Docker installed. This is needed to run the Postgres and pgAdmin servers in the Docker containers.
- You should have a basic understanding of Golang and its ecosystem.
Run the Golang Fiber Project Locally
- Download or clone the Golang Fiber CRUD project from https://github.com/wpcodevo/golang-fiber and open the source code in a code editor.
- Open the integrated terminal in your IDE or text editor and run the command
docker-compose up -d
to start the Postgres and pgAdmin servers in the containers. - Run the command
go run main.go
to install the necessary packages, migrate the GORM schema to the Postgres database, and start the Fiber HTTP server. - To test the CRUD endpoints, import the
Note App.postman_collection.json
file into Postman and make HTTP requests to the Fiber server. Alternatively, you can set up the frontend application to interact with the API.
Run the Fiber API with React.js App
For a detailed walkthrough on how to build the CRUD app in React.js, see the post Build a React.js CRUD App using a RESTful API. Nonetheless, use the following steps to spin up the React application without writing a single line of code.
- If you haven’t already, please ensure that you have Node.js and Yarn installed on your machine.
- Obtain 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 preferred IDE or text editor.
- Navigate to the root directory in the console and run the command
yarn
oryarn install
to install the project’s 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/
. It is important to note that visiting the React app at http://127.0.0.1:3000 may result in site can’t be reached or CORS errors, therefore it is best to usehttp://localhost:3000/
.
Setup the Golang Project
Upon completing this guide, your folder structure will resemble the screenshot below, excluding the Makefile and Note App.postman_collection.json
files.
Start by going to the go/src folder or any preferred location on your machine and create a folder named golang-fiber
.
Then, navigate into the newly-created folder and run go mod init
to initialize the Golang project. Don’t forget to replace the wpcodevo
with your GitHub username.
mkdir golang-fiber
cd golang-fiber
go mod init github.com/wpcodevo/golang-fiber
After that, open the folder in an IDE or text editor and execute these commands to install the project dependencies.
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/postgres
go get github.com/spf13/viper
go install github.com/cosmtrek/air@latest
fiber
– A web framework for Golang.uuid
– A library to generate and parse UUIDs in Golang.validator
– A library that provides an interface for validating Go structs, values, and variables.gorm
– A powerful and convenient library for database interactions in Golang.postgres
– A Postgres driver for GORMviper
– A library for managing configuration files in Golang.air
– This library allows you to hot-reload the server when required files change.
Let’s get our hands dirty by creating a basic Fiber HTTP server that returns a simple JSON object when a GET request is made to the /api/healthchecker
endpoint.
To do that, create a main.go
file in the root directory and add the following code.
main.go
package main
import (
"log"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
)
func main() {
app := fiber.New()
app.Use(logger.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"))
}
In the above code, we imported the necessary packages at the top of the file and created a new instance of Fiber by calling fiber.New()
. Then, we registered the HTTP middleware logger and defined a health checker route with a GET method that listens to the /api/healthchecker
endpoint.
When the route receives the request, it will call the Fiber context handler to return the JSON object. Finally, we started the server on port 8000 by calling app.Listen(":8000")
.
Since you have installed the https://github.com/cosmtrek/air
binary, you can run air
to start the Fiber HTTP server. If you do not want to hot-reload the Go application, you can run the command go run main.go
to start the server.
Make a GET request to the http://localhost:8000/api/healthchecker URL to see the JSON object.
Setup Postgres and pgAdmin with Docker
Now let’s set up the PostgreSQL and pgAdmin servers with Docker. In the root directory, create a docker-compose.yml
file and add the following Docker compose configurations.
docker-compose.yml
version: '3'
services:
postgres:
image: postgres:latest
container_name: postgres
ports:
- '6500:5432'
volumes:
- progresDB:/data/postgres
env_file:
- ./app.env
pgAdmin:
image: dpage/pgadmin4
container_name: pgAdmin
env_file:
- ./app.env
ports:
- "5050:80"
volumes:
progresDB:
The postgres
service will retrieve the most recent Postgres image from DockerHub, start the Postgres server within the container, and make the container accessible through port 6500.
In a similar manner, the pgAdmin
service will pull the dpage/pgadmin4
image from DockerHub, launch pgAdmin inside the container, and make the container accessible via port 5050.
Since we are sourcing the Postgres and pgAdmin credentials from an app.env
file via the env_file
property, it is necessary to create the file to make the environment variables available to Docker Compose.
To do this, create an app.env
file in the root directory and add the following environment variables.
app.env
POSTGRES_HOST=127.0.0.1
POSTGRES_PORT=6500
POSTGRES_USER=admin
POSTGRES_PASSWORD=password123
POSTGRES_DB=golang_fiber
DATABASE_URL=postgresql://admin:password123@localhost:6500/golang_fiber?schema=public
CLIENT_ORIGIN=http://localhost:3000
PGADMIN_DEFAULT_EMAIL=admin@admin.com
PGADMIN_DEFAULT_PASSWORD=password123
With that out of the way, run the command below to start the Postgres and pgAdmin containers.
docker-compose up -d
You can open the Docker desktop application to see the running Postgres and pgAdmin containers.
Design the Database Model with GORM
Now that the Postgres server is running inside the Docker container, let’s design the database model with GORM. The model represents a SQL table, while the fields represent the columns.
To prevent the user from sending junk values in the request body, we’ll use struct tags along with the https://github.com/go-playground/validator package to define validation rules on the struct fields.
Create a models folder in the root directory and create a note.model.go
within it. Open the models/note.model.go
file and add the following model definitions.
models/note.model.go
package models
import (
"time"
"github.com/go-playground/validator/v10"
"github.com/google/uuid"
)
type Note struct {
ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primary_key" json:"id,omitempty"`
Title string `gorm:"varchar(255);uniqueIndex;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" json:"createdAt,omitempty"`
UpdatedAt time.Time `gorm:"not null" json:"updatedAt,omitempty"`
}
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 code above, we created a ValidateStruct
function that will take a reference to a validation schema, validate the request body against the rules defined in the schema, and return relevant validation errors to the client.
Load Environment Variables and Connect to Postgres
In this section, you will create two functions – LoadConfig and ConnectDB. The LoadConfig function will load the environment variables from the app.env
file and make them available throughout the application.
The ConnectDB function on the other hand will connect the application to the running Postgres server and migrate the GORM model to the Postgres database.
Load the Environment Variables
Here, we’ll use the https://github.com/spf13/viper package to read the environment variables from the app.env
file and make them available in the Golang runtime.
To do this, create an initializers folder in the root directory. Then, within the initializers folder, create a loadEnv.go
file and add the following code.
initializers/loadEnv.go
package initializers
import (
"github.com/spf13/viper"
)
type Config struct {
DBHost string `mapstructure:"POSTGRES_HOST"`
DBUserName string `mapstructure:"POSTGRES_USER"`
DBUserPassword string `mapstructure:"POSTGRES_PASSWORD"`
DBName string `mapstructure:"POSTGRES_DB"`
DBPort string `mapstructure:"POSTGRES_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
}
The LoadConfig
function will read the environment variables from the app.env
file and unmarshal the content into the Config
struct.
Connect to the Postgres Server
Now let’s create a ConnectDB
function to create connection pools between the application and the running PostgreSQL server. Since we are using the uuid_generate_v4()
function as a default value for the id
column, we need to install the PostgreSQL uuid-ossp
module for this to work.
To do this, we’ll use the DB.Exec()
method provided by GORM to execute raw SQL to create the extension if it doesn’t exist. After that, we’ll use the DB.AutoMigrate()
method to migrate the GORM model to the Postgres database.
initializers/db.go
package initializers
import (
"fmt"
"log"
"os"
"github.com/wpcodevo/golang-fiber/models"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
var DB *gorm.DB
func ConnectDB(config *Config) {
var err error
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Shanghai", config.DBHost, config.DBUserName, config.DBUserPassword, config.DBName, config.DBPort)
DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to the Database! \n", err.Error())
os.Exit(1)
}
DB.Exec("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"")
DB.Logger = logger.Default.LogMode(logger.Info)
log.Println("Running Migrations")
DB.AutoMigrate(&models.Note{})
log.Println("🚀 Connected Successfully to the Database")
}
Database Migration with GORM
By incorporating the above configurations, let’s create the init()
function in the main.go
file to call the LoadConfig and ConnectDB functions. Evoking the ConnectDB function will update the schema of the database to match the structure of the GORM model.
main.go
package main
import (
"log"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/wpcodevo/golang-fiber/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.Use(logger.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"))
}
Ensure that the Postgres server is running and then start the Fiber HTTP server by running either air
or go run main.go
.
Next, let’s establish a connection to the Postgres server using a Postgres client to see the SQL table generated by GORM. First, we need to obtain the IP address of the Postgres container.
To get the IP address, run the command docker inspect postgres
in the terminal and scroll down to the “NetworkSettings” section. In the “Networks” JSON object, copy the value of the “IPAddress” property.
Next, access the pgAdmin login page at http://localhost:5050/
and sign in using the credentials specified in the environment variables file. After signing into the pgAdmin dashboard, register the Postgres server by providing the following credentials:
Host name/address
– Enter the IP address you copied.Port
– Enter 5143Maintenance database
– Enterpostgres
Password
– Enter the Postgres password specified in theapp.env
file.
Next, inspect the golang_fiber
database to see the SQL table created by GORM.
Implement the CRUD Functionalities
At this point, we’re ready to create higher-level CRUD functions to perform the CRUD operations against the database by utilizing the lower-level CRUD functions provided by GORM.
Add New Record
First, we’ll create a Fiber context handler to perform a CREATE operation on the database. When a POST request is made to the /api/notes
endpoint, Fiber will call this route function to insert the new data into the database.
when the route handler receives the request, the models.ValidateStruct()
function will be evoked to validate the incoming data against the rules defined in the models.CreateNoteSchema
struct.
Upon the absence of validation errors, GORM’s DB.Create()
method will be executed to insert the new data into the database. If the operation is successful, the newly-inserted record will be returned in the JSON response.
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 key value violates unique") {
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}})
}
If a record with that title already exists in the database, a 409 Conflict error response will be sent to the client.
Fetch All Records
Let’s create a Fiber context handler to return a paginated list of records to the client. When Fiber calls this route function, it will extract the page and limit parameters from the query string. If either of them is missing, default values of 1 and 10 will be assigned to them respectively.
Next, the strconv.Atoi()
function will be called to convert the page and limit parameters to integers. Then, the offset value will be called and assigned to an offset variable.
After that, GORM’s DB.Limit().Offset()
methods will be called to paginate the results and the .Find()
method will be evoked to retrieve all the records that match the query.
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})
}
If the operation fails, a 500 Bad Gateway response will be sent to the client. Otherwise, the selected records will be returned in the JSON response.
Edit a Record
Here, you’ll perform the UPDATE operation to edit the fields of a record in the database. This route function will retrieve the record’s ID from the URL parameter and call the DB.First()
method to check if a record with that ID exists in the database.
If no record was found, a 404 Not Found error will be sent to the client. Otherwise, the DB.Model().Updates()
method will be called to update the fields of the record based on the request payload.
controllers/note.controller.go
unc 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 operation is successful, the newly-updated record will be sent to the client in the JSON response.
Retrieve a Single Record
Now let’s perform the second READ operation to retrieve a single record from the database. When this Fiber context handler is called, it will extract the record’s ID from the URL parameter, and evoke the DB.First()
method to retrieve the record that matches the ID.
If no record with that ID exists, a 404 Not Found response will be sent. Otherwise, the found record will be returned in the JSON response.
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}})
}
Delete a Record
This Fiber context handler will be called to delete a record from the database when a DELETE request is made to the /api/notes/:noteId
endpoint. When Fiber calls this route function, it will extract the record’s ID from the request parameter and evoke the DB.Delete()
method to remove the record that matches the ID.
If the number of affected rows is equal to zero, a 404 Not Found error response will be sent to the client. Otherwise, a 204 No Content status code will be returned.
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)
}
Complete Fiber Route Functions
controllers/note.controller.go
package controllers
import (
"strconv"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/wpcodevo/golang-fiber/initializers"
"github.com/wpcodevo/golang-fiber/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 key value violates unique") {
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 Add CORS
Now that we’ve defined all the Fiber route handlers, let’s create routes for them and configure the server with CORS. By adding CORS, we can specify which origins are authorized to access the server’s resources and the HTTP methods that should be included in the requests.
To accomplish this, replace the content of the main.go
file with the code below.
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/controllers"
"github.com/wpcodevo/golang-fiber/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, and GORM",
})
})
log.Fatal(app.Listen(":8000"))
}
Finally, start the Fiber HTTP server by running air
or go rum main.go
.
Conclusion
If you made it this far, am proud of you. You can find the complete source code of the Golang Fiber CRUD API project on GitHub.
In this article, we built a CRUD API in Golang using GORM, PostgreSQL, and the Fiber web framework. The application provides complete functionality such as adding a new record, editing an existing record, retrieving a single record, fetching all records, and deleting a record.
I hope you found this article informative. Don’t hesitate to leave a comment if you have any questions.