Responses (0)
Hey there! 👋 Want to get 5 free lessons for our Reliable Webservers with Go course?
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
'smiddleware
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:

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):

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:

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:

Then, set the go
executable to Go 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.:

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.


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!" messageGET /posts
- Retrieve a list of posts.POST /posts
- Creates a post.GET /posts/{id}
- Retrieve a single post identified by itsid
.PUT /posts/{id}
- Update a single post identified by itsid
.DELETE /posts/{id}
- Delete a single post identified by itsid
.
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
- Thechi
library itself.
Inside of the main
function...
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 tovar port string = "8080"
.Set
fromEnv
to the value of thePORT
environment variable. If it is defined, then set theport
to this specified port. In other words, if we run the Go program withPORT
provided (PORT=9000 go run .
), thenport
would be set to"9000"
. If not, thenport
would be set to"8080"
by default.Output to the terminal "Starting up on http://localhost:."
Set
r
to a new instance of thechi
router, which will receive every HTTP request to this server.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 aResponseWriter
, which constructs an HTTP response, and aRequest
, 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!"
.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 theposts.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
oralbums.go
for "albums" resources mapped to/albums
).http.ListenAndServe(":" + port, r)
spins up the server to listen for requests on the specified port and send HTTP requests to thechi
router.log.Fatal
logs any errors encountered by the server and exits when it does.
(posts.go
)
This source file contains the sub-router for the /posts
route and its sub-routes (/posts/{id}
).
Here, we import three standard library packages and one third-party package:
context
- A package that provides theContext
type for data sharing and control flow (i.e. cancelling requests/streams) in a concurrent environment.http
- An HTTP networking package.io
- A package that provides primitive-level, I/O functionality (buffers, bytes, etc.).github.com/go-chi/chi
- Thechi
library itself.
An empty struct
postsResources
is defined to namespace all functionality related to "posts" resources. func (rs postsResource) Routes() chi.Router
defines the Routes
method on the struct
postsResource
. This method returns an instance of the chi
router that handles the /posts
route and its sub-routes. Recall previously how we attached this sub-router to the parent router in main.go
:
Note: A struct
is a typed collection of fields.
In this Routes
function, several endpoints for /posts
and its sub-routes are defined on this sub-router. Each method call on the r
sub-router instance corresponds to a single endpoint:
r.Get("/", rs.List)
-GET
requests sent to/posts
are handled by thers.List
request handler.r.Post("/", rs.Create)
-POST
requests sent to/posts
are handled by thers.Create
request handler.r.Route("/{id}", func(r chi.Router) { /* ... */ }
- Mount a sub-router along a pattern string (/{id}
).{id}
is a placeholder that tells the router to match for any sequence of characters up to the next/
. Requests sent to/posts/{id}
(i.e./posts/1
) are handled by routes defined under this sub-router.r.Use(PostCtx)
- Appends middleware that is executed for any type of HTTP request on the/posts/{id}
routes.r.Get("/", rs.Get)
-GET
requests sent to/posts/{id}
are handled by thers.Get
request handler.r.Put("/", rs.Update)
-PUT
requests sent to/posts/{id}
are handled by thers.Update
request handler.r.Delete("/", rs.Delete)
-DELETE
requests sent to/posts/{id}
are handled by thers.Delete
request handler.
Along with the Routes
function, each of these request handler functions is also defined on the struct
postsResource
.
Let's explore the body of a request handler, List
, to understand how request handlers work.
When the user sends a GET
request to /posts
, the request handler for this endpoint sends a GET
request to https://jsonplaceholder.typicode.com/posts
via the http.Get
method to fetch a list of posts. If an error is encountered while fetching this list of posts (err
is not nil
), then the http.Error
method replies to the original request with the err
object's message and a 500 status code (http.StatusInternalServerError
). If the list is fetched successfully, then close the body of this response (resp
) to avoid a resource leak. Set the "Content-Type"
header of the response (w
, the one to send back to the user) to "application/json"
since the posts data is in a JSON format. To extract the list of posts from resp
, copy resp.Body
to the w
buffer with io.Copy
, which uses a fixed 32 kB buffer to copy chunks of up to 32 kB at a time. Then, send this response with the retrieved posts back to the user. Once again, if an error is encountered, then reply to the original request with an error message and a 500 status code.
Note: _
is commonly used to denote an unused variable. It serves as a placeholder.
When sending a POST/PUT request to the RESTful API service, the body of this request can be accessed via r.Body
.
When the user sends a GET
request to /posts/1
, the router must first pass the request through the PostCtx
middleware handler. This middleware handler...
Plucks out the value of the
id
URL parameter from the request viachi.URLParam
. In this case,id
is1
.Creates a new
Context
that associates the keyid
with this parameter value and assigns thisContext
toctx
.Context
allows all goroutines involved in handling an incoming request to share values specific to that request, such as an authorization token.Passes the request-scoped values in
ctx
off to the next request handler in the chain (rs.Get
,rs.Update
orrs.Delete
) vianext.ServeHTTP
.
In a request handler, values from the request's Context
can be accessed by the value's corresponding key. So to get the URL parameter we extracted in the previous step and set as a Context
value with the key id
, call r.Context().Value("id")
. Because both keys and values are of type interface{}
in Context
, cast this URL parameter value as a string
.
The http
package provides shortcut methods for sending GET and POST requests via the Get
and Post
methods respectively. However, for PUT and DELETE requests, use the newRequest
method to create these requests. You must explicitly pass the HTTP method to newRequest
and manually add request headers. client.Do
sends the HTTP request created by newRequest
and returns an HTTP response. Once again, if an error is encountered, then reply to the original request with an error message and a 500 status code.
io.Copy
vs. io.ReadAll
#
We could have used io.ReadAll
to extract the response body, like so...
However, io.ReadAll
is inefficient compared to io.Copy
because it loads the entirety of the response body into memory. Therefore, if the service must handle a lot of users simultaneously, and the size of resp.Body
is large (i.e. contents of a file), then it may run out of available memory and eventually crash.
Running the RESTful API Service#
To run install RESTful API service, first install all required third-party dependencies. In this case, there is only one third-party dependency to install: github.com/go-chi/chi
. Let's add the project's code to its own module using the following command:

If you have previously worked with Node.js, then you can think of go.mod
as Go's package.json
.
By having the project's code in its own module, the project's dependencies can now be tracked and managed.
Run the following command to install the project's dependencies and generate a go.sum
file that contains checksums of the modules that this project depends on:

These checksums are used by Go to verify the integrity of the downloaded modules.
If you have previously worked with Node.js, then you can think of go.mod
as Go's package.json
and go.sum
as Go's package-lock.json
.
Run the following command to run the command:

If you would like to run the service on a different port, then set the environment variable PORT
to a different port number:

Test the service by sending a POST request to /posts
:

It works! Try sending requests to the other endpoints.
Adding chi
Middleware#
chi
offers a lot of useful, pre-defined middleware handlers via its optional middleware
package. Visit this link for a list of these middleware handlers.
One such middleware handler is Logger
, which prints on every incoming request:
The date and time the request was received.
The endpoint the request was sent to (HTTP method and route).
The IP address of the client sending the request.
The response status code.
The response size (in bytes).
The amount of time it took to process the request (in milliseconds).
Let's import the github.com/go-chi/chi/middleware
package and add the Logger
middleware to main.go
:
(main.go
)
In a separate terminal tab/window, send the same POST request to /posts
:
Information about this request is logged to the terminal.

Next Steps#
Click here for a final version of the RESTful API.
Try adding more routes to this RESTful API! JSONPlaceholder provides five other resources (comments, albums, photos, todos and users) that you can create endpoints for.