Tutorials on Golang

Learn about Golang from fellow newline community members!

  • React
  • Angular
  • Vue
  • Svelte
  • NextJS
  • Redux
  • Apollo
  • Storybook
  • D3
  • Testing Library
  • JavaScript
  • TypeScript
  • Node.js
  • Deno
  • Rust
  • Python
  • GraphQL
  • React
  • Angular
  • Vue
  • Svelte
  • NextJS
  • Redux
  • Apollo
  • Storybook
  • D3
  • Testing Library
  • JavaScript
  • TypeScript
  • Node.js
  • Deno
  • Rust
  • Python
  • GraphQL

RESTful API Documentation with Go and chi docgen Package

An important chore that gets neglected by developers is writing documentation for their RESTful APIs. Often, this task ends up being assigned lower priority than other tasks, such as building a new feature or modifying an existing feature. Although it delivers no immediate tangible value to end users like features, documentation produces intangible value for developers and their companies. By having detailed information about a RESTful API's endpoints, developers can quickly know how to obtain authorization for protected endpoints, access and interact with certain resources, format the data that's required in a request's body, etc. Ultimately, the value in documentation comes from increased developer productivity and saved development time. The more developers the documentation serves, the more value the documentation produces. Fortunately, you don't have to spend any time or effort to manually build and maintain documentation from scratch. There are open source tools like Swagger that automate the process of designing and generating RESTful API documentation for developers. These tools: Best of all, most of these tools only need, as input, the source code of the RESTful API or a JSON representation of the RESTful API that's compliant with the OpenAPI Specification . Or, these tools can get fed other representations of the RESTful API that are based on alternative API specifications like RAML ( R ESTful A PI M odeling L anguage). After generating the documentation, all that's left is to host and share the documentation. If you built your RESTful API with the Go chi library, then you can automatically generate routing documentation with one of its optional subpackages: docgen . Below, I'm going to show you how to leverage docgen to: To get started, clone the following repository to your local machine: This repository contains source code for a simple RESTful API built with Go and chi . The router uses a middleware stack that consists of the following middlewares: Additionally, render.SetContentType(render.ContentTypeJSON) forces the Content-Type of responses to be  application/json . Most of these middlewares are ported from Goji , a minimalistic web framework for Go, and they are built with native Go packages. The router defines several endpoints for a resource that represents posts: To start up the RESTful API, first install the project's dependencies. Then, compile and execute the code. docgen lets you print a list of available routes to STDOUT at runtime, like so: This gives an overview of the resources that can be accessed from this RESTful API. To log routing documentation to STDOUT, first add the github.com/go-chi/docgen dependency to the list of imported packages and dependencies in main.go , like so: Note #1 : If you encounter the error message cannot use r (variable of type *"github.com/go-chi/chi".Mux) as type "github.com/go-chi/chi/v5".Routes in argument to docgen.JSONRoutesDoc: , then you should change the imported dependency from github.com/go-chi/chi to "github.com/go-chi/chi/v5 . Note #2 : If you encounter the error message http: panic serving 127.0.0.1:50114: runtime error: slice bounds out of range [-1:] , then you should change the imported dependency from github.com/go-chi/chi/middleware to github.com/go-chi/chi/v5/middleware . Then, inside of the main() function, call the method docgen.PrintRoutes() after mounting the sub-router defined on the postsResource struct to the main router, like so: Stop the RESTful API service and re-run the following commands to install the newly added dependency ( github.com/go-chi/docgen ) and restart the RESTful API service: Now, the RESTful API's routes get logged to STDOUT. Since documentation should be shared with other developers, let's output the routing information to a pre-formatted Markdown document. Using the docgen.MarkdownRoutesDoc() method, docgen crawls the router to build a route tree. For each route and subroute, docgen collects: Then, using this tree, docgen structures and outputs the Markdown as a string. docgen will link each middleware and handler function to its implementation in the source code (the file and line where the implementation can be located). The docgen.MarkdownRoutesDoc() method accepts two arguments: Note : Any fields omitted from the struct are zero-valued. Unlike logging the route documentation to STDOUT, the Markdown should not be generated every time the RESTful API service starts up, especially if you want to write the stringified Markdown to a separate file. Instead, let's generate the Markdown only when the user passes a specific flag to the go run command. First, let's add the flag and errors packages to the list of imported packages and dependencies in main.go , like so: Just before the main() function, declare a string flag docs with a default value of an empty string (second argument) and a short description (third argument). Note : This flag will support three values: markdown , json and raml . -docs=markdown will output the routing information to a pre-formatted Markdown document, while -docs=json and -docs=raml will output JSON and RAML representations of the RESTful API respectively. We will implement the json and raml flag values in the next section of this tutorial. At the start of the main() function body, call the flag.Parse() method to parse the command-line flags into any flags defined before the main() function. At the end of the main() function body, after calling the docgen.PrintRoutes() method, check if the docs flag has been set to markdown . If so, then call the docgen.MarkdownRoutesDoc() method to generate the Markdown and write it to a routes.md file, like so: Note #1 : f, err := os.Create("routes.md") does not follow the same pattern as the other conditional statements (of having a declaration precede conditionals). Otherwise, f.Close() would cause the error undefined: f since f would only be scoped to its corresponding conditional statement's branches. For the statement to follow the same pattern as the other conditional statements, you would need to declare the variables f and err outside and replace the short declaration with a standard assignment. Note #2 : Even if the routes.md file does not exist, os.Remove will return an error remove routes.md: no such file or directory . Therefore, to prevent this error from causing the program to exit, check that the error is not an os.ErrNotExist error via the errors.Is() method. Now, add a new rule to the Makefile that compiles and executes the code with the -docs=markdown flag. ( Makefile ) Also, add the following code to the top of the Makefile so that docgen can detect the GOPATH environment variable when the go run command is executed via a make command. ( Makefile ) Note : Without this, you will encounter the error message ERROR: docgen: unable to determine your $GOPATH . When you run the make command with the target gen_docs_md , Go generates the routing documentation in Markdown and outputs it to a routes.md file: Below is what the Markdown file should look like. The bulleted middleware and handler functions link directly to their implementation in the source code (file and line). ( routes.md ) Having JSON and RAML representations of a RESTful API gives other software and tools the ability to understand the RESTful API's endpoints, middleware stack, etc. These representations can be consumed to create beautiful, custom UIs for displaying the documentation, build testing and scaffolding tools, etc. The APIs provided by docgen for generating JSON and RAML representations of the RESTful API are quite different. Therefore, let's start with the much simpler task of generating the JSON representation of the RESTful API. In the main() function, add another conditional branch ( else if statement) to the conditional statement *docs == "markdown" that checks if the docs flag is set to json . If so, then call the docgen.JSONRoutesDoc() method to generate the JSON representation and write it to a routes.json file, like so: Now, add a new rule to the Makefile that compiles and executes the code with the -docs=json flag. ( Makefile ) When you run the make command with the target gen_docs_json , Go generates the JSON representation and outputs it to a routes.json file: Below is what the JSON file should look like. The JSON data organizes the routes in a hierarchical order and contains information like the different HTTP methods allowed for a given route, the handler function and middleware stack that's called for a given endpoint, the location of the handler function in the source code, any comments written above the handler function's definition, etc. ( routes.json ) Note : Although the JSON representation of the RESTful API that's generated by docgen does not fully adhere to the OpenAPI specification , you can still apply a few transformations to make it compliant and work with tools like Swagger UI. For the RAML representation, we need to add the strings package and two more dependencies to the list of imported packages and dependencies in main.go : Then, in the main() function, add another conditional branch ( else if statement) to the conditional statement *docs == "markdown" that checks if the docs flag is set to raml . If so, then perform the following steps to generate the RAML representation of the RESTful API: Note : The fields in the raml.RAML struct come directly from the RAML specification, and they describe some of the basic aspects of the RESTful API like its title and version. This information ends up in the root section of the outputted RAML document. If you are not familiar with RAML, then please visit the RAML documentation . Now, add a new rule to the Makefile that compiles and executes the code with the -docs=raml flag. ( Makefile ) Stop the RESTful API service and install the newly added dependencies. When you run the make command with the target gen_docs_raml , Go generates the RAML representation and outputs it to a routes.raml file: Below is what the RAML file should look like. Notice how docgen organizes the routes in a similar hierarchical order like what's seen in the JSON representation of the RESTful API, but in a YAML-based format. To turn this RAML file into an interactive, documentation UI that can be shared with others, let's use the raml2html HTML documentation generator. You can install raml2html globally via npm and run its CLI tool to generate the documentation UI from RAML. However, if you do not want to install raml2html globally on your local machine, then you can also run a Dockerfile that containerizes raml2html . Run the following docker command to run the raml2html CLI tool in a new container, giving it the routes.raml file as an input and having it output a routes.raml.html file. Note : If you encounter the error message docker: Error response from daemon: Mounts denied: The path <full_project_directory_path> is not shared from the host and is not known to Docker. You can configure shared paths from Docker -> Preferences... -> Resources -> File Sharing. , then you should add the full path of the project directory (whatever gets printed by the pwd ) to the list of directories that can be bind mounted into Docker containers. After that's completed, open a new terminal session and re-run the docker command. Once the routes.raml.html file is generated (located in the root of the project directory), open this file in a browser to see the documentation UI. If you find yourself stuck at any point while working through this tutorial, then feel free to visit the main branch of this GitHub repository here for the code. Explore and try out other automated documentation solutions. If you want to learn more advanced back-end web development techniques with Go, then check out the Reliable Webservers with Go course by Nat Welch, a site reliability engineer at Time by Ping (and formerly a site reliability engineer at Google), and Steve McCarthy, a senior software engineer at Etsy.

Thumbnail Image of Tutorial RESTful API Documentation with Go and chi docgen Package

Integrating JWT Authentication with Go and chi jwtauth Middleware

Accessing an e-mail account anywhere in the world on any device requires authenticating yourself to prove the data associated with the account (e.g., e-mail address and inbox messages) actually belongs to you. Often, you must fill out a login form with credentials, such as an e-mail address and password, that uniquely identify your account. When you first create an account, you provide this information in a sign-up form. In some cases, the service sends either a confirmation e-mail or an SMS text message to ensure that you own the supplied e-mail address or phone number. Because it is highly likely that only you know the credentials to your account, authentication prevents unwanted actors from accessing your account and its data. Each time you log into your e-mail account and read your most recent unread messages, you, and like many other end users, don't think about how the service implements authentication to protect/secure your data and hide your activity history. You're busy, and you only want to spend a few minutes in your e-mail inbox before closing it out and resuming your day. For developers, the difficulty in implementing authentication comes from striking a balance between the user experience and the strength of the authentication. For example, a sign up form may prompt the user to enter a password that contains not only alphanumeric characters, but also must meet other requirements such as a minimum password length and containing punctuation marks. Asking for a stronger password decreases the likelihood of a malicious user correctly guessing it, but simultaneously, this password is increasingly more difficult for the user to remember. Keep in mind that poorly designed authentication can easily be bypassed and introduce more vulnerabilities into your application. In most cases, applications implement either session-based or token-based authentication to reliably verify a user's identity and persist authentication for subsequent page visits. Since Go is a popular choice for building server-side applications, Go's ecosystem offers many third-party packages for implementing these solutions into your applications. Below, I'm going to show you how to integrate JWT authentication within a Go and chi application with the chi jwtauth middleware. Let's imagine the following scenario. Within your e-mail inbox, you are asked to re-enter your e-mail address and password on every single action you take (e.g., opening an unread e-mail or switching to a different inbox tab) to continuously verify your identity. This implementation could be useful in the context of accidentally leaving your e-mail inbox open on a publicly-shared library computer when you have to step out to take a phone call. However, if the login credentials are sent over a non-HTTPS connection, then the login credentials are susceptible to a MITM (man-in-the-middle) attack and can be hijacked. Plus, it would result in a frustrating user experience and immediately drive users away to a different service. Traditionally, to persist authentication, an application establishes a session and saves an http-only cookie with this session's ID inside the user's browser. Usually, this session ID maps to the user's ID, which can then be used to fetch the user's information. If you have ever built an Express.js application with the authentication middleware library Passport and session middleware library express-session , then you are probably familiar with the connect.sid http-only cookie, which is a session ID cookie, and managing sessions with Redis . In Redis, the connect.sid cookie's corresponding key is the session ID (the substring proceeding s%3A and preceding the first dot of this cookie's value) prefixed with sess: , and its value contains information about the cookie and user authenticated by Passport. When a user sends an authentication request (via the standard username/password combination or an OAuth 2.0 provider such as Google / Facebook / Twitter ), Passport determines which of these authentication mechanisms ("strategies") to use to process the request. For example, if the user chooses to authenticate via Google, then Passport uses GoogleStrategy , like so: The done function supplies Passport with the authenticated user. To avoid exposing credentials in subsequent requests, the browser uses a unique cookie that identifies the user's session. Passport serializes the least amount of information that's required to map the user to the session. Often, the user's ID gets serialized. By serializing as little information as needed, this means there is less data stored in the user's session. Upon receiving a subsequent requests, Passport deserializes the user's ID (serialized via serializeUser ) into an object with the user's information, which allows it to be up to date with any recent changes. Whenever an Express.js route needs to access this information, it can via the req.user object. With session-based authentication, authentication is stateful because the server persists/tracks the session (either within the server's internal memory or an in-memory data store like Redis or Memcached). With token-based authentication, authentication is stateless . With tokens, nothing needs to be persisted on the server-side, and the server doesn't need to fetch the user's information on every subsequent request. One of the most popular token standards is JSON Web Token (JWT). JWTs are used for authorization, information exchange and verifying the user's authentication. Instead of creating a session, the server creates a cryptographically-signed JWT and saves an http-only cookie with this token inside of the user's browser, which allows the JWT to automatically be sent on every subsequent request. If the JWT is saved in plain memory, then it should be sent in the Authorization header using the Bearer authentication scheme ( Bearer <token> ). A JWT consists of three strings encoded in Base64URL : These strings are concatenated together (separated by dots) to form a token. Example : The following is a simple JWT, which follows the format <BASE64_URL_HEADER>.<BASE64_URL_PAYLOAD>.<BASE64_URL_SIGNATURE> : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c Constructing JWTs is relatively straight-forward. Decoding a JWT is also relatively straight-forward. Try out different signing algorithms, adding scope: [ "admin", "user" ] to the payload or modifying the secret in the JWT debugger . Note : Since a JWT is digitally signed, its content is protected from tampering. Tampering invalidates the token. Having sensitive data in the payload or header requires the JWT to be encrypted. It is recommended to first sign the JWT and then encrypt it . Referring back to the previous Express.js and Passport example, we can remove both Redis, the session middleware and the serialization/deserialization logic (relies on sessions), and then add the Passport JWT strategy passport-jwt for authenticating with a JWT. We no longer have to devote any backend infrastructure/resources to managing sessions with the introduction of token-based authentication via JWT. This will significantly reduce the number of times we need to query the database for the user's information. Like any other authentication method, token-based authentication comes with its own set of unique problems. For example, when we store the token in a cookie, this cookie is sent on every request (bound to a single domain), even those that don't require the user to be authenticated. Although this cookie is stored with the HttpOnly attribute (inaccessible to JavaScript), it is still susceptible to a Cross-Site Request Forgery attack, which happens when a third-party website successfully sends a request to a service without the user's explicit consent due to cookies (those set by the service's server) being sent on all requests to that service's server. If you're running an online banking service, and one of your users is authenticated and visits a malicious website that sends the request POST https://examplebankingservice.com/transfer when they click on a harmless-looking button, then money will be transferred from the user's bank account since their valid token is sent with the request. To mitigate this vulnerability, set the token's cookie SameSite attribute ( sameSite: "lax" or sameSite: "strict" depending on your needs) and include a CSRF token specific to each user of your service in case of malicious subdomains. It should be set as a hidden form field in forms that send requests to protected endpoints upon being submitted, and your service should regenerate a new CSRF token for the user upon them logging in. This way, malicious websites cannot send requests to protected endpoints unless they also know that specific user's CSRF token. Note : By default, the latest versions of some modern browsers already treat cookies without the SameSite attribute as if this attribute was set to Lax . Setting the SameSite attribute of a cookie to Strict restricts a cookie to its originating website only and prevents cookies from being sent on any cross-site request or iframe. Setting the SameSite attribute of a cookie to Lax causes the same behavior as Strict , but relaxes the cross-site request restriction to target only POST requests. The alternative is to store the token in localStorage , but this is not recommended because localStorage is accessible by any JavaScript code running on your website. Therefore, it is susceptible to a Cross-Site Scripting attack, which allows unwanted JavaScript code to be injected into and executed within your website. Common attack vectors for XSS are passing unsanitized user input directly to eval and appending unsanitized HTML (contains a <script /> tag with malicious code). Unlike sessions, an individual JWT cannot be forcefully invalidated when security concerns arise. Rather, there are approaches that can be taken to invalidate a JWT, such as... Fortunately, supporting JWT authentication in a Go and chi application is made easy with the third-party jwtauth library. Similar to the Express.js and Passport example, jwtauth validates and extracts payload information from a JWT for route handlers via several pre-defined middleware handlers ( jwtauth.Verifier and jwtauth.Authenticator ) and context respectively. To demonstrate this, let's walkthrough a simple login flow: Inside of a Go file, scaffold out the routes using the chi router library. This application involves only four routes: ( main.go ) Let's think about these routes in-depth. When the user logs in, the navigation bar should no longer display a "Log In" link. Instead, the navigation bar should display the user's username as a link, which opens the user's "Profile" page when clicked, and a "Log Out" link. This means that all pages that display the navigation bar should be aware of whether or not the user is logged in, as well as the identity of the user. Let's group the GET / , GET /login and GET /profile endpoints together via the r.Group method, and then execute the middleware handler jwtauth.Verifier to seek, verify and validate the user's JWT token. This handler accepts a pointer to a JWTAuth struct, which is returned by the jwtauth.New method. Essentially, this method creates a factory for generating JWT tokens using a specified algorithm and secret (an additional key must be provided for RSA and ECDSA algorithms). The POST /login and POST /logout endpoints can be grouped together to establish them as routes that don't require a JWT token. Behind-the-scenes, jwtauth.Verifier automatically searches for a JWT token in an incoming request in the following order: Once the JWT token is verified, it is decoded and then set on the request context. This allows subsequent handlers to have direct access to the payload claims and the token itself. When the user submits a login form, their credentials are sent to the endpoint POST /login . It's corresponding route handler checks if the credentials are valid, and when they are, the handler generates a token that encodes parts of the user's information (i.e., their username) as payload claims via a MakeToken function and stores the token cookie within the user's browser, all before redirecting the user to their "Profile" page. Note : Underscores indicate placeholders for unused variables. For this simplicity's sake, we're going to accept any username and password combination as long as each is at least one character long. When the user logs out, this token cookie needs to be deleted. To delete this cookie, set its MaxAge to a value less than zero. After this cookie is deleted, redirect the user back to the homepage. Although the GET / , GET /login and GET /profile endpoints rely on the jwtauth.Verifier middleware, they each need to be grouped individually (not together) to add custom middleware to account for these scenarios: When rendering the webpages via data-driven templates , we need to extract the user's username from the JWT token's payload, which we encoded via the MakeToken function, to display it within the navigation bar. The payload's claims can be accessed from the request's context. Once the templates are parsed and prepared via the template.ParseFiles and template.Must methods respectively, apply these templates ( tmpl ) to the page data data via the ExecuteTemplate method. The second argument of ExecuteTemplate method names the root template that contains the other parsed templates (partials). The output is written to the ResponseWriter , which will render the result as a webpage. Note : If building a service, such as a RESTful API, that requires a 401 response to be returned on protected routes that can only be accessed by an authenticated user, use the jwtauth.Authenticator middleware. Finally, spin up the server by running the go run . command. Within a browser, visit the application at http://localhost:8080 and open the browser's developer console. Observe how the browser sets and unsets the cookie when you log in and out of the application, and watch as the user's username gets extracted from the JWT and displayed in the navigation bar. If you find yourself stuck at any point while working through this tutorial, then feel free to visit the main branch of this GitHub repository here for the code. Explore and try out other token standards for authentication. If you want to learn more advanced back-end web development techniques with Go, then check out the Reliable Webservers with Go course by Nat Welch, a site reliability engineer at Time by Ping (and formerly a site reliability engineer at Google), and Steve McCarthy, a senior software engineer at Etsy.

Thumbnail Image of Tutorial Integrating JWT Authentication with Go and chi jwtauth Middleware

I got a job offer, thanks in a big part to your teaching. They sent a test as part of the interview process, and this was a huge help to implement my own Node server.

This has been a really good investment!

Advance your career with newline Pro.

Only $30 per month for unlimited access to over 60+ books, guides and courses!

Learn More

Benchmarking a Go and chi RESTful API

The amount of time and effort a developer dedicates towards writing a function depends on the details they choose to focus on: coding conventions, structure, programming style, etc. Suppose a group of developers is presented a high-level prompt to write the same function: given some input, return some output. For example, given a list of numbers, return a sorted list of numbers. The actual implementation of the function is left entirely to the discretion of the developer. A quick, mathematical way to evaluate each developer's implementation of this function, without any additional code, is by its time complexity . Particularly, knowing each implementation's Big-O complexity tells us how it might perform in the worst case scenario, commonly when the size of the input is very large. However, time complexity fails to account for the hardware the function is executed upon, and it does not provide any tangible, quantifiable metrics to base decisions on. Metrics such as operation speed and total execution time assign real numerical values to the performance of a function. By adding benchmarks , developers can leverage these metrics to better inform them on how to improve their code. The Go programming language has a benchmarking utility in its built-in, standard library package testing . To benchmark code in Go, define a function with a name prefixed with Benchmark (followed by a capitalized segment of text) and accepts an argument of struct type B , which contains methods and values for determining the number of iterations to run, running multiple benchmarks in parallel, timing execution times, etc. Example : Note : The structure of a benchmark is similar to the structure of a test. Replace Test with Benchmark and t *testing.T with b *testing.B . This benchmark runs the Sum function for b.N iterations. Here, the benchmark runs for one billion iterations, which allows the benchmark function to reliably time and record each execution. Once the benchmark is completed, the results of this benchmark and the CPU of the machine running this benchmark are outputted to the terminal. On average, each iteration ran 0.6199 ns. Below, I'm going to show you... Clone a copy of the Go and chi RESTful API from GitHub to you machine: This RESTful API specifies five endpoints for performing operations on posts: If you would like to learn how to build this RESTful API, then please visit this blog post . In the basic-tests branch, a simple unit test is already provided in the routes/posts_test.go file. Because benchmarks must be placed in _tests.go files, let's place the benchmarks for the posts sub-router in the routes/posts_test.go file. Run the following command to install the project's dependencies: Note : If you run into installation issues, then verify that the version of Go running on your machine is v1.16 . Run the following command to execute the unit tests within the routes/posts_test.go file: To get started, open the routes/posts_test.go file. Let's name the benchmark function BenchmarkGetPostsHandler : Combine the code snippets together: ( routes/posts_test.go ) Run the benchmark: Here, benchmarks run sequentially. The benchmark ran a total of 97220 iterations with each iteration running, on average, 11439 ns. This represents the average time it took for each handler.ServeHTTP function (and by extension, PostsResource{}.List ) call to complete. Each iteration involved the allocation of, on average, 33299 bytes of memory. Memory was allocated, on average, 8 times per iteration. The for loop forces the benchmark to execute the function handler.ServeHTTP sequentially, one after the other. By running the benchmark with b.RunParallel , the total iterations b.N is divided amongst the machine's available threads (distributed amongst multiple goroutines). Having these iterations run concurrently helps to benchmark code that's inherently concurrent, such as sending requests and receiving responses, and deals with mutexes and/or shared resources. To parallelize benchmarks, wrap the benchmark code in b.RunParallel and replace the for loop with for pb.Next() : ( routes/posts_test.go ) Run the benchmark again. Notice that the benchmark values are similar to when we ran the benchmark sequentially with a for loop. To increase the number of cores (and goroutines) to run this benchmark against, add the cpu flag: Increasing the number of cores increases the number of goroutines running the benchmark iterations, which results in better performance. Click here for a final version of the route handler unit test. Try writing benchmarks for the other route handlers.

Thumbnail Image of Tutorial Benchmarking a Go and chi RESTful API

Testing a Go and chi RESTful API - Route Handlers and Middleware (Part 2)

Disclaimer - If you are unfamiliar with writing a simple unit test for a route handler of a Go and chi RESTful API, then please read this blog post before proceeding on. Go's testing library package provides many utilities for automating the testing of Go code. To write robust tests for Go code, you must already understand how to write a basic Go test suite that contains several TestXxx functions. Writing tests for code, especially within the context of test-driven development (TDD), prioritizes the code's correctness over the code's flexibility to adapt to new/updated requirements. The guarantee of less unexpected regressions offsets the upfront cost of spending more time to write tests alongside application code. In a fast-paced, high-pressure environment, it can be difficult to convince other team members and stakeholders of the value in testing code when time is an extremely limited resource. Another factor that must be considered is the amount of code covered by the tests. If the tests cover only a small percentage of the application code (or a small subset of use cases), then the benefits of having these tests probably won't outweigh the benefits of adding new features or improving existing features. Plus, anytime you decide to refactor the application code, you will also have to update the corresponding tests to reflect these changes. When time is so valuable, the time spent on writing tests could have been spent elsewhere. Therefore, to fully benefit from tests, you must write enough tests such that they cover a large percentage of the application code. If a RESTful API exposes multiple endpoints, then testing a single route handler won't bring much value for the time spent writing it. Testing RESTful APIs built with Go and chi requires testing not only all of the route handlers, but also, the chi /custom middleware handlers. Let's write tests for... Clone a copy of the Go and chi RESTful API (with a basic test for a single endpoint) from GitHub to you machine: This RESTful API specifies five endpoints for performing operations on posts: If you would like to learn how to build this RESTful API, then please visit this blog post . Run the following command to install the project's dependencies: Note : If you run into installation issues, then verify that the version of Go running on your machine is v1.16 . There is a single test within the routes/posts_test.go file: TestGetPostsHandler . It tests the route handler for the GET /posts endpoint, and it mocks out the GetPosts function called by the route handler to avoid sending a network request to the JSONPlaceholder API when executing tests. Compared to testing route handlers for GET requests, testing route handlers for other HTTP methods (e.g., POST and PUT ) involves slightly more code to test requests that carry data in the form of an optional request body: Let's write a test for the POST /posts endpoint. Before starting, we must first review the route handler for this endpoint ( Create ) in the routes/posts.go file. ( routes/posts.go ) To test the route handler Create , we must first mock out the CreatePost function to avoid sending a network request to the JSONPlaceholder API when executing tests. CreatePost accepts a request body and returns an HTTP response and error (if encountered). Therefore, the mock function must follow the same function signature as CreatePost , like so: CreatePost is defined on type JsonPlaceholderMock . The request body must contain the following information: Since this request body (JSON data) must be passed as type io.ReadCloser to CreatePost , it must be read into a buffer, converted into a byte slice and then decoded into a Go struct so the data can be accessed normally. When a POST /posts request is sent to the JSONPlaceholder API, it returns a response that contains the newly created post. This post contains the exact same information as the request body with an additional Id field. This Id field is set to 101 to imply that a new post was added since there is a total of 100 posts, and each post has an Id field set to 1 , 2 , 3 , etc., up to 100 . The JSONPlaceholder API doesn't actually create this new resource, but rather, fakes it. So anytime you send a request to POST /posts , the response returned will have a newly created post with an Id field set to 101 . In our mock function, let's create this dummy data to send back in the response. Encode this struct to JSON via the json.Marshal method and return it within the body of a HTTP response. This HTTP response should return with a 200 status code to indicate a successful request. Note : This HTTP response returned must adhere to the http.Response struct , which accepts an integer value for its StatusCode field and a value of type io.ReadCloser for its Body field. The ioutil.NopCloser method returns a ReadCloser that wraps the Reader (in this case, bytes.NewBuffer(respBody) , which prepares a buffer to read respBody ) with a no-op Close method, which allows the Reader to adhere to the ReadCloser interface. Putting it altogether... With the mock function now implemented, let's write the test for the route handler Create for POST /posts . Start by naming the unit test TestCreatePostHandler , like so... Then, write this test similar to the TestGetPostsHandler test, but with several adjustments to account for it testing a POST route handler: Putting it altogether... To test the chi logger middleware handler ( middleware.Logger ), we must first understand the implementation details of this handler function: ( go-chi/chi/middleware/logger.go ) The middleware.Logger function only calls a package-scoped function, DefaultLogger , which logs information for each incoming request. ( go-chi/chi/middleware/logger.go ) This package-scoped function accepts a handler function and returns a handler function. Therefore, we can write a test that involves passing a simple route handler to middleware.DefaultLogger and calling middleware.DefaultLogger 's ServeHTTP method with a response recorder and created request: Try adding more endpoints and writing tests for those endpoints' route handlers.

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: 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: 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. Create a new project directory named "go-chi-restful-api": This RESTful API service will offer the following endpoints for accessing/manipulating "posts" resources: 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: 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: Inside of the main function... ( 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: 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: 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... 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. 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. 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. 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: 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. 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.

Thumbnail Image of Tutorial Building a Simple RESTful API with Go and chi

Testing a Go and chi RESTful API - Route Handlers (Part 1)

Testing plays a fundamental role in the development of quality software. Shipping and deploying software with undetected bugs and regressions opens up a can of terrible consequences such as losing the trust of end users or costing the business time and resources. In a large collaborative setting, having developers manually test each and every feature and user flow for bugs and regressions wastes valuable time that can be put towards improving other aspects of the software. As the codebase and team grows, this approach will not scale. By writing unit/integration/end-to-end tests, identifying and catching bugs and regressions throughout an entire codebase becomes a painless, automatable task that can easily be integrated into any continuous integration pipeline. Unlike most other languages, the Go programming language provides a built-in, standard library package for testing: testing . The testing package offers many utilities for automating the testing of Go source files. To write a test in Go, define a function with a name prefixed with Test (followed by a capitalized segment of text) and accepts an argument of struct type T , which contains methods for failing and skipping tests, running multiple tests in parallel, formatting test logs, etc. Example : This test checks whether the Sum function correctly calculates the sum of two integer numbers. If the sum does not match the expected value, then the test logs an error message and marks itself as having failed. Try it out in the Go Playground here . Below, I'm going to show you: Clone a copy of the Go and chi RESTful API from GitHub to your machine: This RESTful API specifies five endpoints for performing operations on posts: If you would like to learn how to build this RESTful API, then please visit this blog post . In the testless branch's version of the RESTful API, the posts.go file has been moved to a routes subdirectory, and the route handlers within this file have been refactored into a routes package to allow the testing of packages independent of the main package. Run the following command to install the project's dependencies: Note : If you run into installation issues, then verify that the version of Go running on your machine is v1.16 . To get started, let's create a posts_test.go file within the routes subdirectory. This file will contain tests for each of the route handlers within the posts.go file. Inside of posts.go , each endpoint is defined on the chi router via a corresponding routing method named as an HTTP method. To register the GET /posts endpoint on this router, call the .Get method with the route / (in main.go , this posts sub-router is attached to the main router along the /posts route). ( routes/posts.go ) rs.List ( rs refers to the PostsResource{} struct) is a route handler method defined on the PostsResource{} struct. It retrieves a list of posts from the JSONPlaceholder API: ( routes/posts.go ) Let's write a unit test , which tests a single unit of code (commonly a function), for the PostsResource{}.List route handler function. Start off with a simple failing test that will immediately fail and print the message "Not yet implemented." to the terminal: ( routes/posts_test.go ) To test all packages within the current working directory recursively... To test the route handler for GET /posts : Let's combine these code snippets together: ( routes/posts_test.go ) Run the test: Congrats! The route handler test passes! Notice how the TestGetPostsHandler takes approximately half a second to run due to the network request the PostsResource{}.List route handler sent to the JSONPlaceholder API. If the JSONPlaceholder API experiences heavy traffic or any server outages/downtime, then the network request may either take too long to send back a response or completely time out. Because our tests rely on the status of a third-party service, which we have no control over, our tests take much longer to finish running. Let's work on eliminating this unreliability factor from TestGetPostsHandler . PostsResource{}.List calls the GetPosts function, which sends this network request and pipes the response back into PostsResource{}.List if there was no network request error encountered. ( routes/posts.go ) Since the function responsible for sending the network request ( GetPosts ) is a package-scoped variable within the routes package, this function can be replaced with a mock function , which replaces the actual implementation of a function with one that simulates its behavior. Particularly, the network request will be simulated. As long as the mock function has the same function signature as the original function, calling the route handler in a test will remain the same. Inside of posts_test.go , add a mock function for GetPosts : ( posts_test.go ) This mock function creates some dummy data ( mockedPosts , which is a list containing one post), encodes this data as JSON via json.Marshal and returns a minimal HTTP response with a status code and body. These fields adhere to the http package's Response struct. At the top of the TestGetPostsHandler test, set the GetPosts package-scoped variable to this mock function and change the expectedTotal to 1 : Run the test: Wow! The mock allows our test to run much faster. Click here for a final version of the route handler unit test. Try writing tests for the other route handlers.

Thumbnail Image of Tutorial Testing a Go and chi RESTful API - Route Handlers (Part 1)