Technical Thoughts musings on technology


Back

Creating Restful Services with Go and Couchbase

Written by Ryan Moore on 22 May 2014

In this post we will talk about using Couchbase with Go, but this time by writing a Restful interface. This is a continuation of Couchbase with Golang.

We'll be using the great Gorilla Mux library for handling the HTTP requests. While you don't have to use the Gorilla Mux library, it provides a nice way of handling urls that contain parameters which the default Golang HTTP Handlers don't do out of the box. Using Gorilla Mux is really easy. Create the router, add your handlers and you are off and running.

r := mux.NewRouter()

r.HandleFunc("/", HomeHandler)
r.HandleFunc("/users", UsersHandler)
r.HandleFunc("/users/{id}", UserHandler)

http.Handle("/", r)

http.ListenAndServe(":8080", nil)

Each handler is written using Go's default net/http handler functions. To get the id parameter from the "/users/{id}" url Gorilla Mux provides a mux.Vars method which makes it easy to retrieve.

func UserHandler(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    id := vars["id"]

    fmt.Fprintf(w, "Page for user (%s)", id)
}

Now that the basics of using Gorilla Mux are out of the way, let's throw in Couchbase and actually create a data driven Restful service. We'll first start by retrieving data from Couchbase. Then we'll write new data.

As was shown in Couchbase with Golang, getting data from Couchbase is pretty straight forward. So lets start by getting back data on a specific user. We'll update the example from above to return back a user struct from couchbase.

type User struct {
    Id string `json:"id"`
    Name string `json:"name"`
}

func UserHandler(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    id := vars["id"]

    user := User{}
    
    //userBucket is created in the global scope for the package
    err := userBucket.Get(id, &user)
    if err != nil {
        if strings.Contains(err.Error(), "KEY_ENOENT") {
            fmt.Printf("The entry %s does not exist\n", id)
            http.NotFound(w, r)
            return
        }
        fmt.Println(err)
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    encoder := json.NewEncoder(w)
    encoder.Encode(user)
}

For the most part the code above is pretty straight forward. Get the id parameter. Get the user. Write it out using the built in Go json encoder. The one thing that is kind of weird is the error handling. Currently the Couchbase API does not provide a way to inspect the result from the Get method call to see if the id we passed in exists. That's what the strings.Contains(err.Error(), "KEY_ENOENT") is all about. It's inspecting the error message that Couchbase returns to see if the reason for the error is KEY_ENOENT which means the id does not exist and we should return a HTTP 404 instead of a HTTP 500 error message.

Next, we'll add the ability to POST a new user to the UsersHandler. Then we'll modify the UsersHandler into separate handlers so that GET and POST are processed in different handlers. To avoid confusion I want to make it explicit which Handler we are modifying. We are not modifying the UserHandler from above but the UsersHandler. This is important because when we do the POST we do not want to post to /users/{identifier}. We want to POST to /users which is the UsersHandler.

// Using the same User struct from earlier
func UsersHandler(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case "POST":
        user := User{}
        decoder := json.NewDecoder(r.Body)
        err := decoder.Decode(&user)
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }

        // Pass in 0 here to tell Couchbase to not expire the object
        added, err := userBucket.Add(user.Id, 0, user)
        if !added {
            http.Error(w, "User with that id already exists", http.StatusConflict)
            return
        }
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        // We need to be sure that we send a response to the client telling them
        // that we successfully created the new entry
        w.WriteHeader(http.StatusCreated)
        break
    default:
        fmt.Fprint(w, "List All Users here")
    }
}

No real gotchyas here. Again due to all the nice json handling code built into Go we were able to Decode the json payload {"id": "2", "name": "UserName"} into a User struct and then Add it to Couchbase. We added some extra code to inform a user with a HTTP 409 Conflict if they are attempting to create a user with a duplicate id.

Finally, lets clean up the UsersHandler to remove the switch statement so that the POST is in it's own handler. To do this we'll use one more feature from Gorilla Mux which makes removing the switch statement easy. First we'll modify the code that registers the handlers to the following.

r.HandleFunc("/users", UsersHandler).Methods("GET")
r.HandleFunc("/users", NewUserHandler).Methods("POST")
r.HandleFunc("/users/{id}", UserHandler)

We add a new handler that will only handle POST and modify the origial to only handle GET. Then we split the code in the UsersHandler into two handlers.

func NewUserHandler(w http.ResponseWriter, r *http.Request) {
    user := User{}
    decoder := json.NewDecoder(r.Body)
    err := decoder.Decode(&user)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    // Pass in 0 here to tell Couchbase to not expire the object
    added, err := userBucket.Add(user.Id, 0, user)
    if !added {
        http.Error(w, "User with that id already exists", http.StatusConflict)
        return
    }
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // We need to be sure that we send a response to the client telling them
    // that we successfully created the new entry
    w.WriteHeader(http.StatusCreated)
}

func UsersHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "List All Users here")
}

And with that we are able to create new users, display them, all with different handlers. I hope this has been helpful for those who are starting out writing Rest interfaces with Couchbase and Go. The final code for this post is hosted on Github here. In the next post we'll look at making our code testable.

comments powered by Disqus