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.
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
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
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(¬e)
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(), ¬es)
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(¬e)
if err != nil{
panic(err)
}
json.NewEncoder(response).Encode(note)
case "PUT":
err := json.NewDecoder(request.Body).Decode(¬e)
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))
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(¬e)
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(), ¬es)
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(¬e)
if err != nil{
panic(err)
}
json.NewEncoder(response).Encode(note)
case "PUT":
err := json.NewDecoder(request.Body).Decode(¬e)
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()
}