Austin Cunningham
Posted on June 28, 2020
I wanted to learn how to create a rest Api with Golang and MongoDb, found a lot of tutorials using the mgo library but as this was unmaintained. I used the Mongo driver instead with gorilla/mux. First set up package and imports.
package main
import (
"fmt"
"log"
"net/http"
"context"
"encoding/json"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"github.com/gorilla/mux"
"github.com/austincunningham/GolangUserRestApi/models"
)
Setup the routes and Mongo db connection
// setting a global DB struct to be accessible to route functions
type DB struct {
collection *mongo.Collection
}
// Define the routes
func main() {
fmt.Printf("REST API User from golang\n")
// Set client options
clientOptions := options.Client().ApplyURI("mongodb://localhost:27017")
// Connect to MongoDB
client, err := mongo.Connect(context.TODO(), clientOptions)
if err != nil {
log.Fatal(err)
}
// Check the connection
err = client.Ping(context.TODO(), nil)
if err != nil {
log.Fatal(err)
}
defer client.Disconnect(context.TODO())
collection := client.Database("golang-user").Collection("users")
db := &DB{collection: collection }
fmt.Println("Connected to MongoDB!")
//outputs
fmt.Printf("Server listing on http://localhost:8080/users")
fmt.Printf("\nCTRL C to exit\n")
// Controller for endpoints
r := mux.NewRouter()
r.HandleFunc("/users", db.AllUsers).Methods("GET")
r.HandleFunc("/users/{id}", db.FindUser).Methods("GET")
r.HandleFunc("/users", db.CreateUser).Methods("POST")
r.HandleFunc("/users/{id}", db.UpdateUser).Methods("PUT")
r.HandleFunc("/users/{id}", db.DeleteUser).Methods("DELETE")
if err := http.ListenAndServe(":8080", r); err != nil {
log.Fatal(err)
}
}
Setup the model package
package models
import (
"go.mongodb.org/mongo-driver/bson/primitive"
)
// the properties in Mongodb document
type User struct {
Id primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
Name string `bson:"name" json:"name"`
Email string `bson:"email" json:"email"`
Password string `bson:"password" json:"password"`
}
Back in the main package define the
GET all users function
// find all users
func (db *DB)AllUsers(res http.ResponseWriter, req *http.Request){
fmt.Println("AllUsers GET")
// create an array of users
var results []models.User
var user models.User
// set the api header
res.Header().Set("content-type", "application/json")
// set the find options, not sure I need this
findOptions := options.Find()
// use the find command to get all
result , err := db.collection.Find(context.TODO(), bson.D{{}}, findOptions)
if err != nil {
fmt.Println("AllUsers GET failed to query DB", err)
}
//go through the result and decode each element at a time
for result.Next(context.TODO()){
err := result.Decode(&user)
if err != nil {
log.Fatal(err)
}
// add to the array
results = append(results, user)
}
//return the array as json
json.NewEncoder(res).Encode(results)
}
GET find user by id function
// find a single user
func (db *DB)FindUser(res http.ResponseWriter, req *http.Request){
fmt.Println("FindUser GET")
var user models.User
params := mux.Vars(req)
objectId, _ := primitive.ObjectIDFromHex(params["id"])
res.Header().Set("content-type", "application/json")
filter := bson.M{"_id": objectId}
err := db.collection.FindOne(context.TODO(), filter).Decode(&user)
if err != nil{
fmt.Println("error",err)
}
json.NewEncoder(res).Encode(user)
}
DELETE user by id is pretty similar
func (db *DB)DeleteUser(res http.ResponseWriter, req *http.Request){
fmt.Println("DeleteUser DELETE")
params := mux.Vars(req)
objectId, _ := primitive.ObjectIDFromHex(params["id"])
res.Header().Set("content-type", "application/json")
filter := bson.M{"_id": objectId}
result, err := db.collection.DeleteOne(context.TODO(), filter)
if err != nil{
fmt.Println("DeleteUser DELETE failed to query DB",err)
}
json.NewEncoder(res).Encode(result)
}
PUT update user by id
func (db *DB)UpdateUser(res http.ResponseWriter, req *http.Request){
fmt.Println("UpdateUser PUT")
var user models.User
// get the id from the url
params := mux.Vars(req)
objectId, _ := primitive.ObjectIDFromHex(params["id"])
// set the header info
res.Header().Set("content-type", "application/json")
//set the filter on the id
filter := bson.M{"_id": objectId}
// decode the request body
_ = json.NewDecoder(req.Body).Decode(&user)
update := bson.M{"$set": &user}
result,err := db.collection.UpdateOne(context.TODO(), filter, update)
if err != nil{
fmt.Println("UpdateOne PUT failed to query DB ",err)
}
json.NewEncoder(res).Encode(result)
}
POST create user
func (db *DB)CreateUser(res http.ResponseWriter, req *http.Request){
fmt.Println("CreateUser POST")
var user models.User
res.Header().Set("content-type", "application/json")
_ = json.NewDecoder(req.Body).Decode(&user)
result, err := db.collection.InsertOne(context.TODO(), user)
if err != nil {
fmt.Println("CreateUser Error inserting record ", err)
}
json.NewEncoder(res).Encode(result)
}
So the rest api is all setup at this point to connect to my local MongoDb.
Setup the container and deploy to Openshift
First I set up a project in Openshift 3.11, on Minishift
oc new-project golanguser
Login to the Openshift console and deploy Mongodb from the Openshift catalog. Open the project in the console and click on Browse Catalog
Click on Mongodb
Follow the creation flow until you get to Configuration
set the following as some will be auto generated if you don't
- Database Service Name
- MongoDB Connection Username
- MongoDB Connection Password
- MongoDB Database Name
- MongoDB Admin Password
Once set click the create button
The Results screen will have the connection url. I will be using part of the connection url in the code.
Change the connection url in the main package here
// Set client options
clientOptions := options.Client().ApplyURI("mongodb://myadmin:password@mongodb:27017")
And make sure that your collection is pointing at the correct db
collection := client.Database("golanguser").Collection("users")
Once these are set we are ready to create the golang container with the following dockerfile
# Start from the latest golang base image
FROM golang:latest
# Set the Current Working Directory inside the container
WORKDIR /app
# Copy go mod and sum files
COPY go.mod go.sum ./
# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed
RUN go mod download
# Copy the source from the current directory to the Working Directory inside the container
COPY . .
# Build the Go app
RUN go build -o main .
# Expose port 8080 to the outside world
EXPOSE 8080
# Command to run the executable
CMD ["./main"]
Build the image and push it to a remote registry
docker build -t austincunningham/golanguserrestapi:latest .
docker push austincunningham/golanguserrestapi:latest
You can then add the Image to project in the Openshift console Add to Project/Deploy Image
Enter the Image Name click the search icon and deploy when the image is found
Once the container is deployed you can test the connection between MongoDb and the rest api as follows
oc project golanguser
oc get pods
NAME READY STATUS RESTARTS AGE
golanguserrestapi-1-7px9p 1/1 Running 1 14h
mongodb-1-s9fkd 1/1 Running 6 8d
# log into the pod
oc exec -it golanguserrestapi-1-7px9p bash
# curl and POST some data
curl --header "Content-Type: application/json" --request POST --data '{"name":"austin","email":"austin@austin.com","password":"password"}' http://localhost:8080/users
# successful response
{"InsertedID":"5ef7a5dba99a0cb3de9302dc"}
# curl the GET all endpoint
curl http://localhost:8080/users
[{"_id":"5ef7a5dba99a0cb3de9302dc","name":"austin","email":"austin@austin.com","password":"password"}]
Final step is to add a route click on the rest container and create route
Except the defaults your route will appear in the console it can be accessed from any where with the users route
Code lives on github and the container image on dockerhub
Posted on June 28, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.