Build a Go REST API with Fiber and MongoDB
In this tutorial, we will create a Movie Catchphrase API that allows you to Create, Read, Update and Delete Catchphrases, or in short perform CRUD operations.
We are going to use Fiber (a Go web framework) and the go mongo driver in order to interact with the MongoDB instance.
For this project, I assume you have already installed go onto your computer. You can verify the installation by running the following command in the terminal:
go
If you haven’t installed it yet, you can download the necessary installer on the official golang website.
MongoDB Setup
For this project, I assume you already have set up a MongoDB cluster (or a local MongoDB installation) and have the connection URI. If not you can refer to these links for an installation guide: MongoDB cluster or MongoDB local
Project Setup
The first thing we need to do is set up the project by creating a folder for the project. inside the folder, we will initialize the project with go mod and install the packages we are going to use. Run the following commands to set up the project:
go mod init
go get -u github.com/gofiber/fiber/v2
go get go.mongodb.org/mongo-driver/mongo
go get github.com/joho/godotenv
godotenv
will allow us to pull in environment variables from a .env
file. Create a .env
file in the root of the project and add the following:
MONGO_URI=Your_MongoDB_URI_comes_here
DB=Your_DB_name_comes_here
PORT=3000
APP_ENV=development
Next, let’s create a .gitignore
file in the root of the project and add the following:
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
.env
Start Building the API
Let’s create a main.go
file at the root of the project. This will contain a basic server setup with a basic route. Add the following to the file:
package main
import "github.com/gofiber/fiber/v2"
func main() {
app := fiber.New()
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello, World 👋!")
})
app.Listen(":3000")
}
To start the application, run the following command:
go run main.go
Navigate to localhost:3000
in the browser to view the application.
Configuring and Connecting to the database
Always keep all the configurations for the app in a separate folder. Let’s create a new folder config
in the root folder of our application for keeping all the configurations.
Create a new file db.go
inside the config folder with the following contents:
package config
import (
"context"
"fmt"
"log"
"os"
"time"
"github.com/joho/godotenv"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
)
type MongoInstance struct {
Client *mongo.Client
DB *mongo.Database
}
var MI MongoInstance
func ConnectDB() {
if os.Getenv("APP_ENV") != "production" {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
}
client, err := mongo.NewClient(options.Client().ApplyURI(os.Getenv("MONGO_URI")))
if err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err = client.Connect(ctx)
if err != nil {
log.Fatal(err)
}
err = client.Ping(ctx, readpref.Primary())
if err != nil {
log.Fatal(err)
}
fmt.Println("Database connected!")
MI = MongoInstance{
Client: client,
DB: client.Database(os.Getenv("DB")),
}
}
We are going to use the MI object to query the collection.
Creating the Catchphrase Model
Let’s create a new folder models
in the root folder of our application for keeping all the models.
Create a new file Catchphrase.go
inside the models folder with the following contents:
package models
import (
"go.mongodb.org/mongo-driver/bson/primitive"
)
type Catchphrase struct {
ID primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
MovieName string `json:"movieName,omitempty" bson:"movieName,omitempty"`
Catchphrase string `json:"catchphrase,omitempty" bson:"catchphrase,omitempty"`
MovieContext string `json:"movieContext,omitempty" bson:"movieContext,omitempty"`
}
Creating the Catchphrases Controller
Let’s create a new folder controllers
in the root folder of our application for keeping all the controllers.
Create a new file catchphraseController.go
inside the controllers folder with the following contents:
package controllers
import (
"context"
"log"
"math"
"strconv"
"time"
"github.com/gofiber/fiber/v2"
"github.com/mikefmeyer/catchphrase-go-mongodb-rest-api/config"
"github.com/mikefmeyer/catchphrase-go-mongodb-rest-api/models"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
func GetAllCatchphrases(c *fiber.Ctx) error {
catchphraseCollection := config.MI.DB.Collection("catchphrases")
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
var catchphrases []models.Catchphrase
filter := bson.M{}
findOptions := options.Find()
if s := c.Query("s"); s != "" {
filter = bson.M{
"$or": []bson.M{
{
"movieName": bson.M{
"$regex": primitive.Regex{
Pattern: s,
Options: "i",
},
},
},
{
"catchphrase": bson.M{
"$regex": primitive.Regex{
Pattern: s,
Options: "i",
},
},
},
},
}
}
page, _ := strconv.Atoi(c.Query("page", "1"))
limitVal, _ := strconv.Atoi(c.Query("limit", "10"))
var limit int64 = int64(limitVal)
total, _ := catchphraseCollection.CountDocuments(ctx, filter)
findOptions.SetSkip((int64(page) - 1) * limit)
findOptions.SetLimit(limit)
cursor, err := catchphraseCollection.Find(ctx, filter, findOptions)
defer cursor.Close(ctx)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"success": false,
"message": "Catchphrases Not found",
"error": err,
})
}
for cursor.Next(ctx) {
var catchphrase models.Catchphrase
cursor.Decode(&catchphrase)
catchphrases = append(catchphrases, catchphrase)
}
last := math.Ceil(float64(total / limit))
if last < 1 && total > 0 {
last = 1
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"data": catchphrases,
"total": total,
"page": page,
"last_page": last,
"limit": limit,
})
}
func GetCatchphrase(c *fiber.Ctx) error {
catchphraseCollection := config.MI.DB.Collection("catchphrases")
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
var catchphrase models.Catchphrase
objId, err := primitive.ObjectIDFromHex(c.Params("id"))
findResult := catchphraseCollection.FindOne(ctx, bson.M{"_id": objId})
if err := findResult.Err(); err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"success": false,
"message": "Catchphrase Not found",
"error": err,
})
}
err = findResult.Decode(&catchphrase)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"success": false,
"message": "Catchphrase Not found",
"error": err,
})
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"data": catchphrase,
"success": true,
})
}
func AddCatchphrase(c *fiber.Ctx) error {
catchphraseCollection := config.MI.DB.Collection("catchphrases")
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
catchphrase := new(models.Catchphrase)
if err := c.BodyParser(catchphrase); err != nil {
log.Println(err)
return c.Status(400).JSON(fiber.Map{
"success": false,
"message": "Failed to parse body",
"error": err,
})
}
result, err := catchphraseCollection.InsertOne(ctx, catchphrase)
if err != nil {
return c.Status(500).JSON(fiber.Map{
"success": false,
"message": "Catchphrase failed to insert",
"error": err,
})
}
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"data": result,
"success": true,
"message": "Catchphrase inserted successfully",
})
}
func UpdateCatchphrase(c *fiber.Ctx) error {
catchphraseCollection := config.MI.DB.Collection("catchphrases")
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
catchphrase := new(models.Catchphrase)
if err := c.BodyParser(catchphrase); err != nil {
log.Println(err)
return c.Status(400).JSON(fiber.Map{
"success": false,
"message": "Failed to parse body",
"error": err,
})
}
objId, err := primitive.ObjectIDFromHex(c.Params("id"))
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"success": false,
"message": "Catchphrase not found",
"error": err,
})
}
update := bson.M{
"$set": catchphrase,
}
_, err = catchphraseCollection.UpdateOne(ctx, bson.M{"_id": objId}, update)
if err != nil {
return c.Status(500).JSON(fiber.Map{
"success": false,
"message": "Catchphrase failed to update",
"error": err.Error(),
})
}
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"success": true,
"message": "Catchphrase updated successfully",
})
}
func DeleteCatchphrase(c *fiber.Ctx) error {
catchphraseCollection := config.MI.DB.Collection("catchphrases")
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
objId, err := primitive.ObjectIDFromHex(c.Params("id"))
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"success": false,
"message": "Catchphrase not found",
"error": err,
})
}
_, err = catchphraseCollection.DeleteOne(ctx, bson.M{"_id": objId})
if err != nil {
return c.Status(500).JSON(fiber.Map{
"success": false,
"message": "Catchphrase failed to delete",
"error": err,
})
}
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"success": true,
"message": "Catchphrase deleted successfully",
})
}
The controller file will contain the logic used to query our database.
Creating the Catchphrases Route
Let’s create a new folder routes
in the root folder of our application for keeping all the routes.
Create a new file catchphrases.go
inside the routes folder with the following contents:
package routes
import (
"github.com/gofiber/fiber/v2"
"github.com/mikefmeyer/catchphrase-go-mongodb-rest-api/controllers"
)
func CatchphrasesRoute(route fiber.Router) {
route.Get("/", controllers.GetAllCatchphrases)
route.Get("/:id", controllers.GetCatchphrase)
route.Post("/", controllers.AddCatchphrase)
route.Put("/:id", controllers.UpdateCatchphrase)
route.Delete("/:id", controllers.DeleteCatchphrase)
}
Put it all together
Modify the main.go
file as follows:
package main
import (
"log"
"os"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/joho/godotenv"
"github.com/mikefmeyer/catchphrase-go-mongodb-rest-api/config"
"github.com/mikefmeyer/catchphrase-go-mongodb-rest-api/routes"
)
func setupRoutes(app *fiber.App) {
app.Get("/", func(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"success": true,
"message": "You are at the root endpoint 😉",
"github_repo": "<https://github.com/MikeFMeyer/catchphrase-go-mongodb-rest-api>",
})
})
api := app.Group("/api")
routes.CatchphrasesRoute(api.Group("/catchphrases"))
}
func main() {
if os.Getenv("APP_ENV") != "production" {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
}
app := fiber.New()
app.Use(cors.New())
app.Use(logger.New())
config.ConnectDB()
setupRoutes(app)
port := os.Getenv("PORT")
err := app.Listen(":" + port)
if err != nil {
log.Fatal("Error app failed to start")
panic(err)
}
}
After running the application you should be able to navigate to the following route localhost:3000/api/catchphrases
to see the catchphrases in your database.
Hosting on Heroku
Heroku allows you to host your application free of charge but with limited resources. To set up the project use the following webpage from the official Heroku documentation.
Note: You might need to add the following config vars to run the application:
MONGO_URI = <Your mongo uri>
DB = <Your database name>
PORT = 8080
APP_ENV = production
Extra
This is the dataset that I used for this API.
Thanks for reading
This is a very basic example of a REST API built with Fiber (Go) and MongoDB. The code can be downloaded from GitHub.