Note worthy📝
- This article assumes that you already have golang installed on your computer. If you haven't, kindly do so here
- This article assumes you have an idea of http request methods.
Lets get started!!
- Since we will be using MongoDB, we will first start by setting that up. Follow the steps below to setup your MongoDB free cluster.
Head over to MongoDB and register
Set your account up by filling in the details and select GO as your prefered language. (https://www.mongodb.com/cloud/atlas/register)
Since this is not a production application, we'll be using the free cluster.
Now select the M0 Sandbox cluster tier and leave the rest of the items as default
Once everything is done loading click the connect to set up your connection. Select "Allow access from Anywhere" (in a production application you may want to specify the IP address) and fill in the description. Also create a database user by entering your prefered details.
Now choose "Connect your application", select the driver and version, and then copy the string (store it in a safe place). Remember to substitute the details in the connection string with the database user you created earlier.
Creating the API
Running the application
$ go build
$ ./new
go build
compiles the application. ./new
is used to run the compiled file. This file will be located in your root folder and may have another name. I simply named mine new.
Create a folder and name it whatever you please. I'm gonna name mine DEVTO. This is going to be your root folder.
My prefered IDE is VS code. open the terminal in VScode and run
go mod init
. This will automatically create your go.mod file (a file that stores all the required packages for your app) and save it in your root folder.Create a .env file (a file for storing your environmental variables) and fill it with the following.
PORT=5000
MONGODB_URL={{insert the db link here}}
- Create a folder (package) named "database" this folder will contain a file which manages your database connection and opens your collections. create the file and name "databaseConnection.go" and fill it with the following code.
package database
import (
"context"
"fmt"
"log"
"os"
"time"
"github.com/joho/godotenv"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
//DBinstance func
func DBinstance() *mongo.Client {
err := godotenv.Load(".env")
if err != nil {
log.Fatal("Error loading .env file")
}
MongoDb := os.Getenv("MONGODB_URL")
client, err := mongo.NewClient(options.Client().ApplyURI(MongoDb))
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)
}
fmt.Println("Connected to MongoDB!")
return client
}
//Client Database instance
var Client *mongo.Client = DBinstance()
//OpenCollection is a function makes a connection with a collection in the database
func OpenCollection(client *mongo.Client, collectionName string) *mongo.Collection {
var collection *mongo.Collection = client.Database("cluster0").Collection(collectionName)
return collection
}
- Create a Main.go file in your root folder and add the package name and needed packages for this application
package main
import (
"context"
"fmt"
"math"
"net/http"
"os"
"time"
"dev.to/new/database"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
_ "github.com/heroku/x/hmetrics/onload"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
)
- Let's assume we need an endpoint that can be used to create food items for a restaurant. A food item should have the following attributes:
- Name ✅ ,
- price 💰 (because food is not free),
- An image 🖼 ,
- An ID number to uniquely identify it, and
- Time stamps to indicated creation and update time ⏲ .
Now that we know our attributes, we are going to create a struct to contain all the attributes of our food object. A struct contains the name and type of the attributes. It may also contain the json names and validator instructions for each attribute.
//this object/struct determines what a food object should look like.
type Food struct {
ID primitive.ObjectID `bson:"_id"`
Name *string `json:"name" validate:"required,min=2,max=100"`
Price *float64 `json:"price" validate:"required"`
Food_image *string `json:"food_image" validate:"required"`
Created_at time.Time `json:"created_at"`
Updated_at time.Time `json:"updated_at"`
Food_id string `json:"food_id"`
}
- Now we need to create a new validator object which will be used to corroborate request payloads
// create a validator object
var validate = validator.New()
- We will also need to add two functions that come together to round the price of the food to 2 decimal places. This is to keep a consistent record of pricing in the DB.
//this function rounds the price value down to 2 decimal places
func ToFixed(num float64, precision int) float64 {
output := math.Pow(10, float64(precision))
return float64(Round(num*output)) / output
}
func Round(num float64) int {
return int(num + math.Copysign(0.5, num))
}
- Since we already have our mongoDB connection string and our database connection package, we can now both connect to the DB and open the food collection.
//connect to to the database and open a food collection
var foodCollection *mongo.Collection = database.OpenCollection(database.Client, "food")
- Now we are at the main function which is the entry point to your application. In this function we are going to first retrieve the PORT number from the .env file we created earlier, while error-checking it for empty values so that our app doesn't crash if in any case it's not found.
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8000"
}
- We are now about to have our first encounter with the gin framework. we will create a new router object from gin and use their logger function. This function prints API requests to the console when they are made. All this is happening in the main function.
router := gin.New()
router.Use(gin.Logger())
- Everything seems ready to create our endpoint in our main fucntion so that's exactly what we're gonna do. SInce this is a create endpoint, we'll be using the
POST
method in the router object we created earlier. Our endpoint is going to be named/foods-create
.
// this is the create food endpoint
router.POST("/foods-create", func(c *gin.Context) {
- Now declare the context for the context for API calls to your application.
//this is used to determine how long the API call should last
var ctx, cancel = context.WithTimeout(context.Background(), 100*time.Second)
- Also Declare a
food
variable of typeFood
(the struct we wrote earlier)
//declare a variable of type food
var food Food
- We will use the
food
variable we just declared to contain the food object from any request payload by binding the JSON object in the request payload.
//bind the object that comes in with the declared varaible. thrrow an error if one occurs
if err := c.BindJSON(&food); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
- We will now use the validation object we created earlier to validate the payload based on the
Food
struct we created.
// use the validation packge to verify that all items coming in meet the requirements of the struct
validationErr := validate.Struct(food)
if validationErr != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": validationErr.Error()})
return
}
- Now we need to set the timestamp for creation.
// assign the time stamps upon creation
food.Created_at, _ = time.Parse(time.RFC3339, time.Now().Format(time.RFC3339))
food.Updated_at, _ = time.Parse(time.RFC3339, time.Now().Format(time.RFC3339))
- A new ID must be generated to uniquely identify the food item while rounding the value of the price attribute.
//generate new ID for the object to be created
food.ID = primitive.NewObjectID()
// assign the the auto generated ID to the primary key attribute
food.Food_id = food.ID.Hex()
var num = ToFixed(*food.Price, 2)
food.Price = &num
- Our endpoint has now reached the end of its process for creating the food item. It's time for us to insert it into the DB and return the created ID number to the frontend.
//insert the newly created object into mongodb
result, insertErr := foodCollection.InsertOne(ctx, food)
if insertErr != nil {
msg := fmt.Sprintf("Food item was not created")
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
return
}
defer cancel()
//return the id of the created object to the frontend
c.JSON(http.StatusOK, result)
})
- Configure your application to listen for API requests and close your main function. Our endpoint is good to GO!
//this runs the server and allows it to listen to requests.
router.Run(":" + port)
}
- Run you application as stated earlier and test it in postman like so:
Top comments (5)
First I saw basic tutorial at youtube.com/playlist?list=PLs1BaES... and then i implemented code using this blog it makes life easy.. thank you So much @joojodontoh
I have a question here.
Looking at the DBInstance and OpenCollection functions, it seems the app is dialing up connection to the database for every api call, or at every instance a collection is being used. So isn't there a roundabout way to have the database connection on as long as the server is running so that you don't have to dial up and end connections to the db for every call.
Hi Ayush,
Thank you for you question. As far as I know, you can use the "gopkg.in/mgo.v2" package to create connection sessions which can be sent in the context of your router/http handler when API calls are made. However the common occurrence is that most people close (db.cose/ defer cancel) the db session after it has been used to free up resources that may be put back in the pool or collected, depending on the case. This also prevents memory leaks. You are absolutely right about dialing up a connection to the database for every api call. I can change this by creating the database client once the server starts running in the main.go file, and then send the client within context to various routes that need it. This should keep the connection open during server life for I/O actions on collections
Alright, it's a lot clear now.
Thank you for clearing my doubt!
A connection to the database has to be closed after finished using to avoid leaks. And so, the connection would only open when you need to access that resources again and closes after which you are done accessing that resource.