Build an API endpoint with GOlang , Gin & mongoDB
Tags: go, mongodb, api, restful
Published: 2021-01-21 06:26:34

### Note worthy📝
* This article assumes that you already have golang installed on your computer. If you haven't, kindly do so [here](https://golang.org/dl/)
* This article assumes you have an idea of http request methods.


#Lets get started!!
* Since we will be using [MongoDB](https://www.mongodb.com/cloud/atlas/lp/try2?utm_source=google&utm_campaign=gs_apac_malaysia_search_brand_atlas_desktop&utm_term=mongodb&utm_medium=cpc_paid_search&utm_ad=e&utm_ad_campaign_id=9510384846&gclid=CjwKCAiAxp-ABhALEiwAXm6IyZ3NqnclVSs8BnCh5FdMwqP1opex4kT8OgnT-YFydqvGXyZbKknEyRoCWVwQAvD_BwE), we will first start by setting that up. Follow the steps below to setup your MongoDB free cluster. 

1. Head over to MongoDB and [register](https://www.mongodb.com/cloud/atlas/signup)

2. Set your account up by filling in the details and select GO as your prefered language. (https://www.mongodb.com/cloud/atlas/register)  
![register](https://dev-to-uploads.s3.amazonaws.com/i/2rqqqgm1iqge29ede6ha.png)

3. Since this is not a production application, we'll be using the free cluster. ![select free cluster](https://dev-to-uploads.s3.amazonaws.com/i/rmv9znren0lejbgzfp5a.png)

4. Now select the M0 Sandbox cluster tier and leave the rest of the items as default
![select cluster tier](https://dev-to-uploads.s3.amazonaws.com/i/mn0j7gf8tll9oyu9nmb1.png)

5. 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.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/7cx33v2t0gk4xrahwd0p.png)

6. 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:
1. Name ✅ , 
2. price 💰 (because food is not free), 
3. An image 🖼 , 
4. An ID number to uniquely identify it, and 
5. Time stamps to indicated creation and update time ⏲ .  

Now that we know our attributes, we are going to create a [struct](https://gobyexample.com/structs) 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](https://github.com/go-playground/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](https://pkg.go.dev/github.com/gin-gonic/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 type `Food`(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](https://www.postman.com/product/api-client/) like so: 
![Postman](https://dev-to-uploads.s3.amazonaws.com/i/famw6fd7067qrad0eiov.png) 

###Check the [repository] (https://github.com/Joojo7/DEVTO-GO-api-tutorial) for this tutorial out!