Craft

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

Golang is one of the top-paying, in-demand programming languages with many applications. When paired with frameworks like Gin, Revel, and gorilla/mux, you can easily create an API with Go.

Learn how to create 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 done so.

Once installed, the next step is to create a project root folder on your machine 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’ll see your module name (e.g. CRUD_API) and its version when you open the go.mod file. All custom packages will come from this parent module. So any imported custom package takes the form:

import(package CRUD_API/package-directory-name)

Next, install the packages necessary for creating the CRUD API. In this case, use Gin Gonic to route the API endpoints:

go get github.com/gin-gonic/gin

Now install the MongoDB Driver to store data:

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

How to Connect Go to MongoDB

All you need is your MongoDB URI to connect Golang with the database. It typically looks like this if you’re connecting to MongoDB Atlas locally:

Mongo_URL = "mongodb://127.0.0.1:27017"

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

This is your database package, and it starts by 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 best practice to hide environment variables like the database connection string in a .env file using the dotenv package. This makes your code more portable and comes in handy when using a MongoDB cloud cluster instance, for example.

The ConnectDB function establishes a connection and returns a new MongoDB Client object.

Create Database Collection

MongoDB stores data in Collections, which provide an interface to the underlying database data.

To handle the collection-fetching functionality, start by creating a new folder, Collection, in your project root. Now create a new Go file, getCollection.go, that gets the collection 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 gets the Collection from the MongoDB database. The database name, in this case, is myGoappDB, with Posts as its collection.

Create the Database Model

Make a new folder inside your root directory and call it model. This folder handles your database model.

Create a new Go file inside that folder and call 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
}

Creating a CRUD API With Go

Next up is the CRUD API creation. To start with this section, make a new folder within your project root directory to handle your endpoints. Call it routes.

Create a separate Go file in this folder for each action. For example, you can name them create.go, read.go, update.go, and delete.go. You’ll export these handlers as the routes package.


How to Create the POST Endpoint in Go

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

Inside routes/create.go, add 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 by importing the project’s custom modules. It then imports third-party packages including Gin and MongoDB Driver.

Further, postCollection holds the database collection. Notably, c.BindJSON(“post”) is a JSONified model instance that calls each model field as postPayload; this goes into the database.

How to Create the GET Endpoint

The GET endpoint, in routes/read.go, reads a single document from the database via its unique ID. It also starts by 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})
}

The postId variable is a parameter declaration. It gets a document’s object ID as objId.

However, result is an instance of the database model, which later holds 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 by 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.

The result.MatchedCount condition prevents the code from running if there’s no record in the database or the passed ID is invalid.

Creating a DELETE Endpoint

The DELETE endpoint, in delete.go, removes 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 using the DeleteOne function. It also uses the result.DeletedCount property to prevent the code from running if the database is empty or the object ID is invalid.

Create the API Runner File

Finally, create a main.go inside 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 router variable, a gin instance that evokes the HTTP actions and calls each endpoint by its function name from the routes package.

Your CRUD project runs on localhost:3000. To run the server and test the CRUD API, run the following command in your base directory:

go run main.go

Turn Your Golang CRUD Project Into a Usable Product

You’ve successfully created a CRUD API with Go; congratulations! While this is a minor project, you’ve seen what it takes to execute regular HTTP requests in Go.

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

[quads id=2]
Read the full article here

Related Articles

Leave a Reply

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

Back to top button