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
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)
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)
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)
...
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)
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)
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)
....
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
- A Microsoft Azure account - go ahead and sign up for a free one!
-
Azure CLI
orAzure Cloud Shell
- you can either choose to install the Azure CLI if you don't have it already (should be quick!) or just use the Azure Cloud Shell from your browser.
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:
- Create an Azure Cosmos DB account
- Add a database and collection and get the connection string
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]'
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
Create an account (notice --kind MongoDB
)
az cosmosdb create --resource-group $RESOURCE_GROUP --name abhishgu-mongodb --kind MongoDB
az cosmosdb mongodb database create --account-name $COSMOS_DB_ACCOUNT --name $COSMOS_DB_NAME --resource-group $RESOURCE_GROUP
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
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
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]
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
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]
please ensure that you use double-quotes (
""
) around the connection string value
az webapp create --resource-group $RESOURCE_GROUP --plan $APP_SERVICE_PLAN_NAME --name $APP_NAME --deployment-container-image-name $DOCKER_IMAGE
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
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')
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
Find a single developer profile using GitHub ID
curl $APP_URL/developers/abhirockzz
If you search for a profile which does not exist
curl $APP_URL/developers/foo
//developer profile with GitHub ID foo does not exist
Fetch all dev profiles
curl $APP_URL/developers
Delete a developer profile
curl -X DELETE $APP_URL/developers/abhishek
Confirm again
curl $APP_URL/developers
Once you're done, you can delete the resource group to delete all the services
az group delete --name $RESOURCE_GROUP --no-wait
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)