DEV Community

Abhishek Gupta for Microsoft Azure

Posted on

Tutorial: Getting started with MongoDB on Azure using Go

As a Go enthusiast, it's great to see first class support for for MongoDB in the form of an official MongoDB Go driver. We will learn how to use it by building a simple REST API for a good old CRUD-style app!

In this blog, will cover the following topics:

  • Walkthrough the application: CRUD operations
  • Setup Azure Cosmos DB for MongoDB API
  • Setup Azure App service and deploy the application to the cloud
  • Take the REST API for a spin

To follow along, you're free to use a MongoDB cluster of your choice (Docker is probably the fastest/easiest option). I will be using Azure Cosmos DB, which is Microsoft's globally distributed, multi-model database service that supports document, key-value, graph, and columnar data models. It implements the wire protocol for MongoDB making if possible for any MongoDB client driver (including the Go driver) that understands this protocol version to natively connect to Cosmos DB.

You can Try Azure Cosmos DB for Free (for a limited time) without an Azure subscription, or use the Azure Cosmos DB free tier to get an account with the first 400 RU/s and 5 GB of storage free.

The application (API) will be deployed to Azure App Service. It enables you to build and host web apps, mobile back ends, and RESTful APIs in the programming language of your choice without managing infrastructure. You can use it on Linux to host web apps natively on Linux for supported application stacks with support for built-in Docker images for multiple languages such as Node.js, Java, Python etc.

Although we have a Go app in this example, we can still host it on App Service since it supports custom Docker images as well!

Overview

The application is a simple one that exposes a REST API to create, read, update and delete developer profiles with a GitHub ID, blog and skills.

As usual, the code is available on GitHub, but let's walk through it real quick!

Here is the code layout:

.
├── Dockerfile
├── api
│   ├── crud.go
│   └── crud_test.go
├── go.mod
├── go.sum
├── main.go
├── model
│   └── developer.go
└── test.sh
Enter fullscreen mode Exit fullscreen mode

The main CRUD operations are in crud.go file within the api package. It contains implementation for create, read, update and delete operations.

The Create function gets a handle to the MongoDB collection and marshals the request body (JSON payload) into a struct (model.Developer). The struct is then inserted using the InsertOne function, the error is handled (not shown above) and an HTTP 201 is sent back in case of success.

coll := a.Connection.Database(a.DBName).Collection(a.CollectionName)
...
var dev model.Developer
json.NewDecoder(req.Body).Decode(&dev)
...
res, err := coll.InsertOne(ctx, dev)
...
rw.WriteHeader(http.StatusCreated)
Enter fullscreen mode Exit fullscreen mode

Here is how the read operation works:

vars := mux.Vars(req)
githubID := vars["github"]
...
coll := a.Connection.Database(a.DBName).Collection(a.CollectionName)
r := coll.FindOne(context.Background(), bson.M{githubIDAttribute: githubID})
...
var p model.Developer
r.Decode(&p)
json.NewEncoder(rw).Encode(&p)
Enter fullscreen mode Exit fullscreen mode

FindOne function is used with a filter criteria of github_id which is the partition key for the collection bson.M{githubIDAttribute: githubID}. If found, the result is converted to the struct and returned to the caller.

Fetching all developer profiles is similar

r, err := coll.Find(context.Background(), bson.D{})
...
devs := []model.Developer{}
err = r.All(context.Background(), &devs)
...
Enter fullscreen mode Exit fullscreen mode

Find uses an empty bson document as a filter in order to list all the documents in the collection and the result is sent back to the caller in the form of a JSON array

FindOneAndReplace is used to update the a specific record. github_id is used as the filter criteria and the passed in JSON payload is the updated record.

var dev model.Developer
json.NewDecoder(req.Body).Decode(&dev)
...
r := coll.FindOneAndReplace(context.Background(), bson.M{githubIDAttribute: githubID}, &dev)
...
rw.WriteHeader(http.StatusNoContent)
Enter fullscreen mode Exit fullscreen mode

Delete is accomplished using FindOneAndDelete which accepts the github_id as the filter criteria for the record to be deleted

vars := mux.Vars(req)
githubID := vars["github"]
...
r := coll.FindOneAndDelete(context.Background(), bson.M{githubIDAttribute: githubID})
...
rw.WriteHeader(http.StatusNoContent)
Enter fullscreen mode Exit fullscreen mode

Everything is tied together in main.go. It associates the CRUD operation handlers to HTTP routes, starts the server and also sets a graceful exit mechanism

.....
func main() {
    r := mux.NewRouter() 
        r.Methods(http.MethodPost).Path("/developers")
                                  .HandlerFunc(crudAPI.Create)
    r.Methods(http.MethodGet).Path("/developers/{github}")
                                 .HandlerFunc(crudAPI.Read)
        r.Methods(http.MethodGet).Path("/developers")
                                 .HandlerFunc(crudAPI.ReadAll)
        r.Methods(http.MethodPut).Path("/developers")
                                 .HandlerFunc(crudAPI.Update)
        r.Methods(http.MethodDelete).Path("/developers/{github}")
                                    .HandlerFunc(crudAPI.Delete)

    server := http.Server{Addr: ":" + port, Handler: r}

    go func() {
        err := server.ListenAndServe()
        if err != nil && err != http.ErrServerClosed {
            log.Fatalf("could not start server %v", err)
        }
    }()
....
    exit := make(chan os.Signal)
    signal.Notify(exit, syscall.SIGTERM, syscall.SIGINT)
    <-exit
....
    c, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer func() {
        crudAPI.Close()
        cancel()
    }()
    err := server.Shutdown(c)
....
Enter fullscreen mode Exit fullscreen mode

Alright, with that aside, it's time to provision the required services and deploy the application. Before that you let's go over some of the pre-requisites

Pre-requisites

Setup Azure Cosmos DB

You need to create an Azure Cosmos DB account with the MongoDB API support enabled along with a Database and Collection. You can use the Azure Portal or the Azure CLI

Learn more about how to Work with databases, containers, and items in Azure Cosmos DB

Use the Azure portal

Follow these steps:

Use Azure CLI

(same commands can be used for Azure CLI or Azure Cloud Shell)

Export the following environment variables:

export RESOURCE_GROUP=[to be filled]
export LOCATION=[to be filled]
export COSMOS_DB_ACCOUNT=[to be filled]
export COSMOS_DB_NAME=[to be filled]
export COSMOS_COLLECTION_NAME=[to be filled]
export SHARDING_KEY_PATH='[to be filled]'
Enter fullscreen mode Exit fullscreen mode

Start by creating a new resource group under which you can place all your resources. Once you're done, you can delete the resource group which in turn will delete all the services

az group create --name $RESOURCE_GROUP --location $LOCATION
Enter fullscreen mode Exit fullscreen mode

Create an account (notice --kind MongoDB)

az cosmosdb create --resource-group $RESOURCE_GROUP --name abhishgu-mongodb --kind MongoDB
Enter fullscreen mode Exit fullscreen mode

Create the database

az cosmosdb mongodb database create --account-name $COSMOS_DB_ACCOUNT --name $COSMOS_DB_NAME --resource-group $RESOURCE_GROUP
Enter fullscreen mode Exit fullscreen mode

Finally, create a collection within the database

az cosmosdb mongo collection create --account-name $COSMOS_DB_ACCOUNT --database-name $COSMOS_DB_NAME --name $COSMOS_COLLECTION_NAME --resource-group-name $RESOURCE_GROUP --shard $SHARDING_KEY_PATH
Enter fullscreen mode Exit fullscreen mode

Get the connection string and save it. You will be using it later

az cosmosdb list-connection-strings --name $COSMOS_DB_ACCOUNT --resource-group $RESOURCE_GROUP -o tsv --query connectionStrings[0].connectionString
Enter fullscreen mode Exit fullscreen mode

Deploy to Azure App service

Build a Docker image for the app

This step is optional. You can use the pre-built image abhirockzz/mongodb-go-app, which I have made available on DockerHub

You can use the Dockerfile to build your own image and push it to a Docker registry (public/private) of your choice

Here is a tutorial which provides an example of how to use Azure Container Registry with Azure Web App Service

docker build -t [REPOSITORY_NAME/YOUR_DOCKER_IMAGE:TAG] .
//e.g.
docker build -t [abhirockzz/mongodb-go-app] .

//login to your registry
docker login

//push image to registry
docker push [REPOSITORY_NAME/YOUR_DOCKER_IMAGE:TAG]
Enter fullscreen mode Exit fullscreen mode

Setup Azure App Service

It's time to deploy our app to the cloud - let's quickly do that using Azure App service. Start by creating an App Service Plan which defines a set of compute resources for our web app to run.

Refer to the documentation for details on App Service plans

The plan is associated with an SKU - just like Cognitive Services, you need to choose an SKU (pricing tier) for App Service as well. We will use a small tier (B1) for this example.

Accepted values are B1, B2, B3, D1, F1, FREE, P1V2, P2V2, P3V2, PC2, PC3, PC4, S1, S2, S3, SHARED

export APP_SERVICE_PLAN_NAME=[to be filled]
export APP_SERVICE_SKU=B1

az appservice plan create --name $APP_SERVICE_PLAN_NAME --resource-group $RESOURCE_GROUP --sku $APP_SERVICE_SKU --is-linux
Enter fullscreen mode Exit fullscreen mode

documentation for az appservice plan create

Setup environment variables

export APP_NAME=[to be filled]
export DOCKER_IMAGE=[to be filled]
export MONGODB_CONNECTION_STRING="[to be filled]"
export DATABASE_NAME=[to be filled]
export COLLECTION_NAME=[to be filled]
Enter fullscreen mode Exit fullscreen mode

please ensure that you use double-quotes ("") around the connection string value

Deploy the application

az webapp create --resource-group $RESOURCE_GROUP --plan $APP_SERVICE_PLAN_NAME --name $APP_NAME --deployment-container-image-name $DOCKER_IMAGE
Enter fullscreen mode Exit fullscreen mode

Add the environment variables as configuration settings required by the application

az webapp config appsettings set --resource-group $RESOURCE_GROUP --name $APP_NAME --settings MONGODB_CONNECTION_STRING=$MONGODB_CONNECTION_STRING DATABASE_NAME=$DATABASE_NAME COLLECTION_NAME=$COLLECTION_NAME
Enter fullscreen mode Exit fullscreen mode

The API should be deployed in some time. When its complete, go ahead and give it a try!

Test the API

I have used curl for demonstration, but you can use any other tool of your choice

Get the endpoint/host name for the deployed app

APP_URL=$(az webapp show --name $APP_NAME --resource-group $RESOURCE_GROUP -o tsv --query 'defaultHostName')
Enter fullscreen mode Exit fullscreen mode

Create a few developer profiles

curl -X POST -d '{"github_id":"abhirockzz", "blog":"dev.to/abhirockzz", "skills":["java","azure"]}' $APP_URL/developers

curl -X POST -d '{"github_id":"abhishek", "blog":"medium.com/@abhishek1987/", "skills":["go","mongodb"]}' $APP_URL/developers
Enter fullscreen mode Exit fullscreen mode

Find a single developer profile using GitHub ID

curl $APP_URL/developers/abhirockzz
Enter fullscreen mode Exit fullscreen mode

If you search for a profile which does not exist

curl $APP_URL/developers/foo

//developer profile with GitHub ID foo does not exist
Enter fullscreen mode Exit fullscreen mode

Fetch all dev profiles

curl $APP_URL/developers
Enter fullscreen mode Exit fullscreen mode

Delete a developer profile

curl -X DELETE $APP_URL/developers/abhishek
Enter fullscreen mode Exit fullscreen mode

Confirm again

curl $APP_URL/developers
Enter fullscreen mode Exit fullscreen mode

Once you're done, you can delete the resource group to delete all the services

az group delete --name $RESOURCE_GROUP --no-wait
Enter fullscreen mode Exit fullscreen mode

That's all for this blog. I would love to have your feedback and suggestions! Simply tweet or drop a comment. And, if you found this article useful, please like and follow 😃😃

Top comments (0)