Building a Simple RESTful API with Go and chi

The Go programming language is designed to address the types of software infrastructure issues Google faced back in late 2007. At this time, Google predominantly built their infrastructure using the programming languages C++, Java and Python. With a large codebase and having to adapt to rapid technological changes in both computing and key infrastructure components (e.g., multi-core processors, computer networks and clusters), Google found it difficult to remain productive within this environment using these languages. Not only does Go's design focus on improving the productivity of developers/engineers, but Go's design incorporates the best features of C++, Java and Python into a single statically-typed, compiled, high-performance language:

  • Concise Syntax (Readability and Usability)

  • Comprehensive Standard Library

  • First-Class Concurrency Support - Goroutines and Channels

  • Full-Featured Runtime - Garbage Collection, Memory Allocation, Scheduler, Goroutine/Channel Management, etc.

Using Go and a lightweight router library, such as chi, writing and launching a simple RESTful API service requires little time and effort, even if you have no experience with Go. chi's compatibility with the Go standard library package net/http, optional subpackages (middleware, render and docgen) and consistent API allows you to compose mantainable, modular services that run fast. Plus, these services can be deployed to and thrive in a production environment. If you have previously built RESTful APIs using Node.js and a minimal web application framework, such as Express.js, then you will find this to be quite similar!

Below, I'm going to show you:

  • How to download and install Go on your machine (if you already have Go installed, then you may skip this step).

  • How to create a RESTful API service with Go and chi.

  • How to integrate layers of middleware with chi's middleware subpackage.

  • How to capture the URL parameters of an incoming request.

Installing Go#

To install Go, visit the official Go downloads page and follow the instructions. If you want your machine to support multiple versions of Go, then I recommend installing GVM (Go Version Manager).

Note: This installation walkthrough will be for Mac OS X systems.

Run the following command to install GVM to your machine:

Installing GVM

Either...

A.) Close the current terminal window and open a new terminal window.

Or...

B.) Run the source command mentioned in the output to make the gvm executable available to the current terminal window.

Run the following command to install the latest stable version of Go (as of 02/21, v1.16):

Installing Go v1.16 - Failed

Notice how the installation fails. The following error message is printed within the output:

If we check the contents of the log file /Users/kenchan/.gvm/logs/go-go1.16-compile.log, then the actual cause of the failure will be shown:

Because the go executable could not be found, and GVM assumes that this go executable should be Go v1.4, let's install Go v1.4 from binary:

Installing Go v1.4 - Failed

Apparently, there is an known issue with installing Go v1.4 on certain versions of Mac OS X. Instead, let's install Go v1.7.1 from binary:

Installing Go v1.7.1

Then, set the go executable to Go v1.7.1:

Set GVM Go Version to v1.7.1

Set the environment variable $GOROOT_BOOTSTRAP to the root of this Go installation to build the new Go toolchain, which will provide the necessary tools to compile (build), format (fmt), run (run) source code, download and install packages/dependencies (get), etc.:

Set GOROOT_BOOTSTRAP Environment Variable

Finally, let's install the latest stable version of Go and set the go executable to it:

Note: The --default option sets this version of Go as the default version. Whenever you open a new terminal window and execute go version, this default version will be printed.

Installing Go v1.16
Set GVM Default Go Version to v1.16

Creating a RESTful API Service with Go and chi#

Create a new project directory named "go-chi-restful-api":

This RESTful API service will offer the following endpoints for accessing/manipulating "posts" resources:

  • GET / - Verify whether or not the service is up and running ("health check"). Returns the "Hello World!" message

  • GET /posts - Retrieve a list of posts.

  • POST /posts - Creates a post.

  • GET /posts/{id} - Retrieve a single post identified by its id.

  • PUT /posts/{id} - Update a single post identified by its id.

  • DELETE /posts/{id} - Delete a single post identified by its id.

A post represents an online published piece of writing. It consists of a title, content, an ID and an ID of the user who authored the post:

These endpoints will interface directly with JSONPlaceholder, a fake API for testing with dummy data. For example, when it receives a GET /posts request from a user, our Go RESTful API will check for this route and execute the route handler mapped to it. This route handler will send a request to https://jsonplaceholder.typicode.com/posts and pipe the response of that request back to the user.

This project will be comprised of two Go files:

  • main.go - Contains a parent router, which sub-routers mount to.

  • posts.go - Contains a sub-router and its handlers for the /posts route and its sub-routes (i.e. /posts/{id}).

Create these files within the root of the project directory:

(main.go)

A Go source file begins with a package clause. All source files within the same folder should have the same package name. main happens to be a special package name that declares the package outputs an executable rather than a library and has a main function defined within it. This main function is executed when we run the Go program.

This program imports three standard library packages and one third-party package:

  • log - A logging package.

  • http - An HTTP networking package.

  • os - A package that provides OS-level functionality.

  • github.com/go-chi/chi - The chi library itself.

Inside of the main function...

  1. Assign the variable port to "8080". The := short assignment statement is the same as a variable declaration, but it uses an implicit type based on the value being assigned. In other words, port := "8080" is equivalent to var port string = "8080".

  2. Set fromEnv to the value of the PORT environment variable. If it is defined, then set the port to this specified port. In other words, if we run the Go program with PORT provided (PORT=9000 go run .), then port would be set to "9000". If not, then port would be set to "8080" by default.

  3. Output to the terminal "Starting up on http://localhost:."

  4. Set r to a new instance of the chi router, which will receive every HTTP request to this server.

  5. The r.Get method routes HTTP GET requests to a specified route (first argument) and executes a route handler (second argument) when a request is sent to this route. The route handler provides a ResponseWriter, which constructs an HTTP response, and a Request, which represents an incoming HTTP request. Here, we set the response header "Content-Type" to "text/plain", and then, we set the body of the response to the message "Hello World!".

  6. The r.Mount method attaches a route handler, postsResource{}.Routes(), along a specified route pattern (matches all sub-routes prefixed with /posts). This handler function is defined within the posts.go source file, and it returns a sub-router that handles all requests sent to /posts/*. If our API supported more endpoints of different routes (and by extension, sub-routes), then this approach would lead to greater modularity in the codebase, especially if we devote each non-main.go source file to a different API resource (e.g., users.go for "users" resources mapped to /users or albums.go for "albums" resources mapped to /albums).

  7. http.ListenAndServe(":" + port, r) spins up the server to listen for requests on the specified port and send HTTP requests to the chi router. log.Fatal logs any errors encountered by the server and exits when it does.

(posts.go)