How to Create a CRUD API With Golang’s Gin and MongoDB

Golang is one of the highest paid and most demanded programming languages ​​with many applications. Combined with frameworks like Gin, Revel and Gorilla/Mux, you can easily create an API with Go.

Learn how to build a CRUD API in Golang using the Gin HTTP framework.

Initial setup and installation

Get started with Golang by installing it on your computer if you haven’t already.

After installation, the next step is to create a project root folder on your computer and initialize a Go module in that root directory.

To do this, open a CLI, navigate to your project root folder and run:

go mod init module_name

You will see your module name (e.g CRUD_API) and its version when opening the go.mod File. All custom packages come from this parent module. So each imported custom package has the form:

import(package CRUD_API/package-directory-name)

Next, install the packages required to build the CRUD API. In this case, use Gin Gonic to forward the API endpoints:

go get github.com/gin-gonic/gin

Now install the MongoDB driver for storing data:

go get go.mongodb.org/mongo-driver/mongo

How to connect to MongoDB

All you need is your MongoDB URI to connect Golang to the database. It usually looks like this when you connect to MongoDB Atlas locally:

Mongo_URL = "mongodb://127.0.0.1:27017"

Now create a new folder in your project root directory and go to it databases. Create a Go file in this folder and name it database.go.

This is your database package and it starts importing the required libraries:

package database

import (
"context"
"fmt"
"log"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)

func ConnectDB() *mongo.Client {
Mongo_URL := "mongodb://127.0.0.1:27017"
client, err := mongo.NewClient(options.Client().ApplyURI(Mongo_URL))

if err != nil {
log.Fatal(err)
}

ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Second)
err = client.Connect(ctx)
defer cancel()

if err != nil {
log.Fatal(err)
}

fmt.Println("Connected to mongoDB")
return client
}

It’s good practice to hide environment variables like the database connection string in a .env File with the package dotenv. This makes your code more portable and is useful, for example, when using a MongoDB cloud cluster instance.

That ConnectDB function connects and returns a new MongoDB client object.

Create database collection

MongoDB stores data in collections that provide an interface to the underlying database data.

To handle the collection retrieval functionality, first create a new folder, collection, in your project root. Now create a new Go file, getCollection.gowhich the collection gets from the database:

package getcollection

import (
"go.mongodb.org/mongo-driver/mongo"
)

func GetCollection(client *mongo.Client, collectionName string) *mongo.Collection {
collection := client.Database("myGoappDB").Collection("Posts")
return collection
}

This function retrieves the collection from the MongoDB database. The database name is in this case myGoappDBWith posts as his collection.

Create the database model

Create a new folder in your root directory and go to it model. This folder manages your database model.

Create a new Go file in this folder and go to it model.go. Your model in this case is a blog post with its title:

package model

import (
"go.mongodb.org/mongo-driver/bson/primitive"
)

type Post struct {
ID primitive.ObjectID
Title string
Article string
}

Building a CRUD API with Go

Next comes the CRUD API creation. To start this section, create a new folder in your project root to manage your endpoints. To name stretch.

In this folder, create a separate Go file for each action. For example, you can name them create.go, read.go, update.goand delete.go. You export these handlers as stretch Package.


How to create the POST endpoint in Go

Start by defining the POST endpoint to write data to the database.

Inside routes/create.goadd the following:

package routes

import (
getcollection "CRUD_API/Collection"
database "CRUD_API/databases"
model "CRUD_API/model"
"context"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/bson/primitive"
)

func CreatePost(c *gin.Context) {
var DB = database.ConnectDB()
var postCollection = getcollection.GetCollection(DB, "Posts")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
post := new(model.Posts)
defer cancel()

if err := c.BindJSON(&post); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": err})
log.Fatal(err)
return
}

postPayload := model.Posts{
Id: primitive.NewObjectID(),
Title: post.Title,
Article: post.Article,
}

result, err := postCollection.InsertOne(ctx, postPayload)

if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": err})
return
}

c.JSON(http.StatusCreated, gin.H{"message": "Posted successfully", "Data": map[string]interface{}{"data": result}})
}

This code starts importing the project’s custom modules. It then imports third party packages including gin and MongoDB driver.

Further, post collection contains the database collection. Above all, c.BindJSON(“post”) is a JSONified model instance that calls each model field as postpayload; that goes in the database.

How to create the GET endpoint

The GET endpoint, in routes/read.go, reads a single document from the database using its unique ID. It also starts importing custom and third-party packages:

package routes

import (
getcollection "CRUD_API/Collection"
database "CRUD_API/databases"
model "CRUD_API/model"
"context"
"net/http"
"time"
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)

func ReadOnePost(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
var DB = database.ConnectDB()
var postCollection = getcollection.GetCollection(DB, "Posts")

postId := c.Param("postId")
var result model.Posts

defer cancel()

objId, _ := primitive.ObjectIDFromHex(postId)

err := postCollection.FindOne(ctx, bson.M{"id": objId}).Decode(&result)

res := map[string]interface{}{"data": result}

if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": err})
return
}

c.JSON(http.StatusCreated, gin.H{"message": "success!", "Data": res})
}

That postId variable is a parameter declaration. It gets the object ID of a document as objId.

However, result is an instance of the database model that later contains the returned document as res.

How to create the PUT endpoint

The PUT handler, in routes/update.go, is similar to the POST handler. This time it updates an existing post using its unique object ID:

package routes

import (
getcollection "CRUD_API/Collection"
database "CRUD_API/databases"
model "CRUD_API/model"
"context"
"net/http"
"time"
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)

func UpdatePost(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Second)
var DB = database.ConnectDB()
var postCollection = getcollection.GetCollection(DB, "Posts")

postId := c.Param("postId")
var post model.Posts

defer cancel()

objId, _ := primitive.ObjectIDFromHex(postId)

if err := c.BindJSON(&post); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": err})
return
}

edited := bson.M{"title": post.Title, "article": post.Article}

result, err := postCollection.UpdateOne(ctx, bson.M{"id": objId}, bson.M{"$set": edited})

res := map[string]interface{}{"data": result}

if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": err})
return
}

if result.MatchedCount < 1 {
c.JSON(http.StatusInternalServerError, gin.H{"message": "Data doesn't exist"})
return
}

c.JSON(http.StatusCreated, gin.H{"message": "data updated successfully!", "Data": res})
}

A JSON format of the model instance (post) calls each model field from the database. The result variable uses the MongoDB $set Operator to update a required document called by its object id.

That result.MatchedCount Condition prevents the code from running if there is no record in the database or the passed ID is invalid.

Creating a DELETE endpoint

The DELETE endpoint, in delete.goremoves a document based on the object id passed as a url parameter:

package routes

import (
getcollection "CRUD_API/Collection"
database "CRUD_API/databases"
"context"
"net/http"
"time"
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)

func DeletePost(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
var DB = database.ConnectDB()
postId := c.Param("postId")

var postCollection = getcollection.GetCollection(DB, "Posts")
defer cancel()
objId, _ := primitive.ObjectIDFromHex(postId)
result, err := postCollection.DeleteOne(ctx, bson.M{"id": objId})
res := map[string]interface{}{"data": result}

if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": err})
return
}

if result.DeletedCount < 1 {
c.JSON(http.StatusInternalServerError, gin.H{"message": "No data to delete"})
return
}

c.JSON(http.StatusCreated, gin.H{"message": "Article deleted successfully", "Data": res})
}

This code deletes a record with the DeleteOne Function. It also uses the result.DeletedCount property to prevent the code from running when the database is empty or the object ID is invalid.

Create the API runner file

Finally, create one main.go in your project root directory. Your final project structure should look like this:

This file handles router execution for each endpoint:

package main

import (
routes "CRUD_API/routes"
"github.com/gin-gonic/gin"
)

func main() {
router := gin.Default()

router.POST("/", routes.CreatePost)


router.GET("getOne/:postId", routes.ReadOnePost)

// called as localhost:3000/update/{id}
router.PUT("/update/:postId", routes.UpdatePost)

// called as localhost:3000/delete/{id}
router.DELETE("/delete/:postId", routes.DeletePost)

router.Run("localhost: 3000")
}

This file is the main package that runs other files. It starts by importing the route handlers. Next is the routers variable, a gin Instance that invokes the HTTP actions and calls each endpoint with its function name from the stretch Package.

Your CRUD project continues local host: 3000. To run the server and test the CRUD API, run the following command from your home directory:

go run main.go

Turn your Golang CRUD project into a usable product

You have successfully created a CRUD API using Go; Congratulations! Although this is a small project, you’ve seen what it takes to run regular HTTP requests in Go.

You can get more creative by expanding this into a more practical application that adds value to users. Go is a suitable programming language for a number of use cases.

Leave a Reply

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