Build a REST API with Go and Gorilla Mux
Go is very attractive when considering a backend API
. Its multi-threading and concurrent processing make it an excellent choice for web services. Let's play around with it and setup a very basic REST API
base project.
main.go
First we need to kick things off in our our main.go
file. If you haven't already, create a rest-api
folder in your GOPATH/src
directory. Next, create a new main.app
as a starting point to our application.
What is a GOPATH?
If you are unfamiliar with what the GOPATH is you may need to follow our guide on how to Install and Setup Go for Development on Mac or Windows.
package main
func main() {
a := App{}
a.Initialize()
a.Run(":8000")
}
Inside the main.app
we will be using a new App
object to keep our application logic in its own area to make things a bit cleaner. We will define this functionality in app.go
.
The App
Now that main.go
is out of the way, it is time to create the App
logic. This is initially organized within our Intitalize
and Run
methods. Initialize
will setup our routes and Run
will start the actual server responsible for handling requests.
package main
type App struct {
}
func (a *App) Initialize() {
}
func (a *App) Run(addr string) {
}
Routes
With the scaffolding done, we need a way of receiving our GET
, POST
and other REST
requests and mapping them to a function. We could simply write our own library to match requests but rather than re-inventing the wheel we can use gorilla/mux.
Start by adding an importing gorilla/mux
in app.go
just below package main
:
package main
import (
"github.com/gorilla/mux"
)
Add a Router
property to the App
struct definition to hold our router.
type App struct {
Router *mux.Router
}
Our first route
Now that mux
is accessible, it is time to create our router
along with our first route. Below, we initialize a new mux
router and use the Handle method to define a route.
func (a *App) Initialize() {
router := mux.NewRouter()
router.Handle("/hello/", HelloWorldHandler() ).Methods("GET")
a.Router = router
}
HelloWorldHandler()
Before creating our HelloWorldHandler()
method, we are going to use some methods from the net/http
library, so you will need to add it to your import:
With a proper IDE, these imports will get automatically added for you. Check out our article on how to Install and Setup Go for Development on Mac or Windows.
import (
"github.com/gorilla/mux"
"net/http"
)
The HelloWorldHandler
is the logic behind the route. It returns an http.Handler
which mux
will call when the /hello/
is requested. Inside the handler we will return a []byte
array from a string as our output.
func (a *App) Initialize() {
// Initialize method already defined ...
}
func HelloWorldHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World"))
})
}
func (a *App) Run(addr string) {
// Run method already defined ...
}
With our router setup, we need to tell our Run
method to start the http server.
func (a *App) Run(addr string) {
http.ListenAndServe(addr, a.Router)
}
First Contact
At this point we can try our server to verify everything is running. If you haven't already, open a terminal or command prompt and run the following
Grab and install our third party packages:
$ go get .
Run the server
$ go run .
Open your browser and head to http://localhost:8000/hello/ and you should see:
GET http://localhost:8000/hello/
Hello, World
We have contact!
JSON
Output is great, but our API won't be much use if we didn't process input and spit out data structures with something like JSON
.
Enter JSONResponse
. This receives our http.ResponseWriter
from our route handler and allows us set our response code
. The magic here is with our JSON
marshaling of the output which we pass as any object and transform it into JSON
.
func JSONResponse(w http.ResponseWriter, code int, output interface{}) {
// Convert our interface to JSON
response, _ := json.Marshal(output)
// Set the content type to json for browsers
w.Header().Set("Content-Type", "application/json")
// Our response code
w.WriteHeader(code)
w.Write(response)
}
Our new HelloWorldResponse
object will be used to hold our response and show how to convert a Go struct
to JSON
. Can be placed anywhere in app.go.
type HelloWorldResponse struct {
Message string `json:"msg"`
ProvidedName string `json:"name"`
}
Now we can get our HelloWorldHandler
to receive a some user input, manipulate the data and return an object back automatically formatted to JSON
.
func HelloWorldHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := r.FormValue("name")
response := HelloWorldResponse{
ProvidedName: name,
Message: fmt.Sprintf("Hello, %s!", name),
}
JSONResponse(w, 200, response)
})
}
We have added a few new dependencies, so if your IDE has not added the imports, here is the updated list:
import (
"encoding/json"
"fmt"
"net/http"
"github.com/gorilla/mux"
)
Try it out
Install packages and run the server
$ go get .
$ go run .
Open your browser and head to http://localhost:8000/hello/?name=Matt and you should see:
{
"msg": "Hello, Matt!",
"name": "Matt"
}
What about POST?
No REST API
is complete without adding new entries to a database or removing old data. While database interaction is outside this article, let's explore defining routes to receive complex objects and using them in our handlers.
Let us start with a new request struct
defining what our POST
request is going to look like. We are going to accept an object with Age
and Name
as properties.
type HelloWorldRequest struct {
Age int `json:"age"`
Name string `json:"name"`
}
To handle our request, we need a new handler. Below the HelloWorldHandler
add a new HelloWorldPostHandler
.
func HelloWorldPostHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var helloRequest HelloWorldRequest
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&helloRequest); err != nil {
JSONResponse(w, http.StatusBadRequest, map[string]string{"error": "Invalid request"})
return
}
defer r.Body.Close()
response := HelloWorldResponse{
ProvidedName: helloRequest.Name,
Message: fmt.Sprintf("Hello, %s! You are %d years old!", helloRequest.Name, helloRequest.Age),
}
JSONResponse(w, 200, response)
})
}
This handler is very similar to the HelloWorldHandler
with the exception of our request processing with the json.Decoder
.
The decoder will look at the request body (r.Body
) and attempt to decode it into the passed in object helloRequest
which is the struct we defined in the previous step.
If the request can be decoded, our helloRequest
variable holds the user input. If not we spit out a bad request response with our JSONResponse
object.
Lastly, defer r.Body.Close()
is called to free the the body resource for later use.
Add your route
Before we can fire off POST requests, we need to tell mux
about our new route back in our Initialize
method.
func (a *App) Initialize() {
router := mux.NewRouter()
router.Handle("/hello/", HelloWorldHandler()).Methods("GET")
router.Handle("/hello/", HelloWorldPostHandler()).Methods("POST")
a.Router = router
}
Try it out
Re-run the server
$ go run .
Using a command like curl, you are now set to POST
away!
$ curl -d '{"Name":"Jimmy Bytes", "age":0}' -H "Content-Type: application/json" -X POST http://localhost:8000/hello/
{
"msg":"Hello, Jimmy Bytes! You are 0 years old!",
"name":"Jimmy Bytes"
}
If you don't have curl
or are unfortunate enough to be on Windows, stuck with PowerShell's attempt at masking curl, you can use an application like Postman or Insomnia to test POST
requests.
Go REST API
There you have it! Go
has great multi-threading and concurrency making it a great choice for building web based REST API
's. This should give you a great starting point to building out your very first Go
based API
.
Some follow up ideas would be to explore adding application logging to keep track of errors and a database connectivity. If you have any other ideas please share in the comments below!