paint-brush
Building a REST API in Go with MongoDB Integration: A Step-by-Step Guideby@seyiadel
809 reads
809 reads

Building a REST API in Go with MongoDB Integration: A Step-by-Step Guide

by Seyi AdeleyeMarch 2nd, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

In this conversation, we would be writing a REST API in Go and integrating MongoDB as our database using MongoDB Driver to create a Note API. This is an introduction to Go and as you know being a server side language, API writing is one thing you must know. First, we create our simple web server and router using the net/http library. We then connect to our MongoDB Compass database through its driver.

People Mentioned

Mention Thumbnail
featured image - Building a REST API in Go with MongoDB Integration: A Step-by-Step Guide
Seyi Adeleye HackerNoon profile picture

In this article, I’ll be showing you how to write a REST API in Go and integrate MongoDB as your database. You’ll be using MongoDB Driver to create a Note API that performs the four fundamental operations, Create - Retrieve - Update -Delete with Go internal libraries.


This is an introduction to Go, and as you know being a server-side language, API writing is one thing you must know as a backend developer.


Note: I’m using a Windows 10 Operating System.

Setting up the Development Environment

Let’s create our development environment by opening up the Command Prompt to create a folder. I am naming the project tautpad as taut means concise and a pad, something you can write on.


>> mkdir tautpad
>> cd tautpad


~Initialize our go module (You can do this with the go mod init command and suffixing it with the project name or if you own a GitHub account and repository for the project, you suffix the URL to the go mod init command.


go mod init tautpad

Alternatively


go mod init github.com/seyiadel/taut-pad


We go on to start coding by opening our code editor(VScode) and to do that, we use “code .” command to open our current directory.


C:\Users\Seyi Adeleye\tautpad>> code .


Create a main.go file in the current “tautpad” directory. Open the main.go file and write;

package main


Why package main as our first line of code? This is to tell the Go Complier that the package is an executable program and not a shared library.


Now we create our simple web server and router using the net/http library:

import "net/http"

func main(){
  router := http.NewServeMux()

  server := &http.Server(
   Addr: 0.0.0.0:8000
   Handler: router
)
  server.ListenAndServe()
}


func main() - This is a special type of function that can be defined in a package and it acts as an entry point for the executable program.

http.NewServeMux() - This matches a handler to a URL. Read More.


At this point, We need to connect to our MongoDB Compass database through its driver. Make sure you have MongoDB Community Server installed and running.

If you don’t, visit https://www.mongodb.com/try/download/community to download. Read the documentation on installation and get the connection string (database URI). The setup comes with MongoDB Shell and MongoDB Compass, a Graphical User Interface that enhances ease of use.


Once done, It is time to connect our application with the installed database. First, we install the drivers using go get command


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


Now in our main.go, we connect to the database with the installed driver and database connection string(database URI).


Our main.go file looks like this:

package main
import (
	"context"
    "fmt"
    "net/http"
    
    "go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"go.mongodb.org/mongo-driver/mongo/readpref"
)

func main(){
    //Connects to the database with uri generated from the database.
    const uri = "mongodb://localhost:27017"
	client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri))
	if err != nil{
		panic(err)
	}
    
// Makes sure the database disconnects after function/program runs
	defer func(){
		if err = client.Disconnect(context.TODO()); err != nil{
			panic(err)
		}
	}()
//Check the database is connected and active
	if err := client.Ping(context.TODO(), readpref.Primary()); err !=nil {
		panic(err)
	}
//Database name and the collection to store our data
 	collection = client.Database("tautpad").Collection("notes")

	fmt.Println("Database connected and pinged")

	router := http.NewServeMux()

	server := &http.Server{
		Addr : "0.0.0.0:8080",
		Handler: router,
	}

	server.ListenAndServe()
}


context.TODO()

readpref.Primary


Run go mod tidy to include the used driver as a dependency.

>> go mod tidy

About time to CRUD!🎈

A simple note has a title and body. Let's create the note model with these requirements using the struct type. Here is where we start to import the libraries from MongoDB. The first external library is primitive, this is to generate ID per note created.


import (
    //internal libraries
	"context"
	"fmt"
	"net/http"

    //external libraries
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"go.mongodb.org/mongo-driver/mongo/readpref"
)
type Note struct{
  ID primitive.ObjectID `json:"_id" bson:"_id,omitempty"`
  Title string `json:"title bson:"title,omitempty"`
  Body string  `json:"body" bson:"body,omitempty"
}


We’ve made progress, Let’s top it off by creating functions or handlers for performing CRUD. We would be writing the create and retrieve handler which means a POST and GET request. I would be using the switch-case type to make the handlers concise and clear.


func createAndGetNoteHandler(response http.ResponseWriter, request http.Request){
    response.Header().Set("content-type", "application/json")
	switch request.Method {
	case "POST":
		var note Note

		_ = json.NewDecoder(request.Body).Decode(&note)
		
		output, err := collection.InsertOne(context.Background(), note)
		if err != nil{
			log.Fatal(err)
		}
		json.NewEncoder(response).Encode(output)
		
	
	case "GET":
		var notes []Note
		
		output, err := collection.Find(context.TODO(), bson.D{})
		if err != nil{
			panic(err)
		}
		err = output.All(context.TODO(), &notes)
		if err != nil{
			panic(err)
		}
		json.NewEncoder(response).Encode(notes)
		
	}
}


Lastly, We write the update and delete functions to complete it as CRUD (Create-Retrieve-Update-Delete). An Update takes PUT request and Delete takes the DELETE request.


func getUpdateDeleteTautPadHandler(response http.ResponseWriter, request *http.Request){
	response.Header().Set("content-type", "application/json")
	var note Note
	ctx := context.TODO()
	switch request.Method{
	case "GET":
		getId := strings.TrimPrefix(request.URL.Path, "/notes/") 
		id, err := primitive.ObjectIDFromHex(getId)
		if err != nil{
			panic(err)
		}
		err = collection.FindOne(ctx, bson.M{"_id": id} ).Decode(&note)
		if err != nil{
				panic(err)
		}
		json.NewEncoder(response).Encode(note)

	case "PUT":
		err := json.NewDecoder(request.Body).Decode(&note)
		if err != nil{
			panic(err)
		}
		getId := strings.TrimPrefix(request.URL.Path, "/notes/")
		id, err := primitive.ObjectIDFromHex(getId)
		if err != nil{
			panic(err)
		}
		result, err := collection.UpdateOne(ctx, bson.M{"_id": id,}, bson.M{"$set": note,})
		if err != nil{
			panic(err)
		}
		json.NewEncoder(response).Encode(result)

	case "DELETE":
		getID := strings.TrimPrefix(request.URL.Path, "/notes/")
		id, err := primitive.ObjectIDFromHex(getID)
		if err != nil{
			panic(err)
		}
	
		output, err := collection.DeleteOne(ctx, bson.M{"_id": id,})
		if err != nil{
			panic(err)
		}
		json.NewEncoder(response).Encode(output)
	}
}


We add the handlers we’ve created to the routers so as to match a URL to perform its operations.


This is the main function in the main.go file

    router := http.NewServeMux()
	router.Handle("/notes", http.HandlerFunc(tautPadHandler))
	router.Handle("/notes/", http.HandlerFunc(getUpdateDeleteTautPadHandler))

Done!🎉

We have successfully written a Note REST API to perform CRUD operations with MongoDB as persistence.


Your main.go file should be like this:

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"go.mongodb.org/mongo-driver/mongo/readpref"
)

type Note struct {
	/*no space between _id and omitempty else _id generated would be zeros 
	and won't allow another insertion, so as to prompt go drivers for 
	random _id to be generated*/
	Id 			primitive.ObjectID `json:"_id" bson:"_id,omitempty"` 
	Title 		string `json:"title" bson:"title,omitempty"`
	Description string `json:"description" bson:"description,omiempty"`
}


var collection *mongo.Collection 

const uri = "mongodb://localhost:27017"

func tautPadHandler(response http.ResponseWriter, request *http.Request){
	response.Header().Set("content-type", "application/json")
	switch request.Method {
	case "POST":
		var note Note

		_ =json.NewDecoder(request.Body).Decode(&note)
		
		output, err := collection.InsertOne(context.Background(), note)
		if err != nil{
			log.Fatal(err)
		}
		json.NewEncoder(response).Encode(output)
		
	
	case "GET":
		var notes []Note
		
		output, err := collection.Find(context.TODO(), bson.D{})
		if err != nil{
			panic(err)
		}
		err = output.All(context.TODO(), &notes)
		if err != nil{
			panic(err)
		}
		json.NewEncoder(response).Encode(notes)
		
	}
}

func getUpdateDeleteTautPadHandler(response http.ResponseWriter, request *http.Request){
	response.Header().Set("content-type", "application/json")
	var note Note
	ctx := context.TODO()
	switch request.Method{
	case "GET":
		getId := strings.TrimPrefix(request.URL.Path, "/notes/") 
		id, err := primitive.ObjectIDFromHex(getId)
		if err != nil{
			panic(err)
		}
		err = collection.FindOne(ctx, bson.M{"_id": id} ).Decode(&note)
		if err != nil{
				panic(err)
		}
		json.NewEncoder(response).Encode(note)

	case "PUT":
		err := json.NewDecoder(request.Body).Decode(&note)
		if err != nil{
			panic(err)
		}
		getId := strings.TrimPrefix(request.URL.Path, "/notes/")
		id, err := primitive.ObjectIDFromHex(getId)
		if err != nil{
			panic(err)
		}
		result, err := collection.UpdateOne(ctx, bson.M{"_id": id,}, bson.M{"$set": note,})
		if err != nil{
			panic(err)
		}
		json.NewEncoder(response).Encode(result)

	case "DELETE":
		getID := strings.TrimPrefix(request.URL.Path, "/notes/")
		id, err := primitive.ObjectIDFromHex(getID)
		if err != nil{
			panic(err)
		}
	
		output, err := collection.DeleteOne(ctx, bson.M{"_id": id,})
		if err != nil{
			panic(err)
		}
		json.NewEncoder(response).Encode(output)
	}
}

func main(){

	client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri))
	if err != nil{
		panic(err)
	}

	defer func(){
		if err = client.Disconnect(context.TODO()); err != nil{
			panic(err)
		}
	}()

	if err := client.Ping(context.TODO(), readpref.Primary()); err !=nil {
		panic(err)
	}

 	collection = client.Database("tautpad").Collection("notes")

	fmt.Println("Database connected and pinged")


	router := http.NewServeMux()
	router.Handle("/notes", http.HandlerFunc(tautPadHandler))
	router.Handle("/notes/", http.HandlerFunc(getUpdateDeleteTautPadHandler))

	server := &http.Server{
		Addr : "0.0.0.0:8080",
		Handler: router,
	}

	server.ListenAndServe()
}