Hamburger Icon
  • Labs icon Lab
  • Core Tech
Labs

Guided: Build a Simple REST API in Go

This Code Lab will guide you through implementing key features of REST APIs in Go. You'll create an HTTP server, implement request handlers, and make your application production ready. By the end of this lab, you'll be equipped to build your own REST APIs from scratch.

Labs

Path Info

Level
Clock icon Intermediate
Duration
Clock icon 58m
Published
Clock icon Dec 12, 2023

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Table of Contents

  1. Challenge

    Introduction

    In this Guided Code Lab, you are responsible for developing the API server for an application called Globotickets. This is a platform used to buy and sell tickets for local events. You will have to create REST API endpoints to purchase tickets and record transactions.

    Each task can be validated individually by clicking on the Validate button next to it.

    If you want to test the application code at any time, you can follow these steps:

    1. Click on the Run button at the bottom right of the Terminal.
    2. To test a particular REST API route, you can use the second Terminal tab after completing the previous step and use the curl command, along with the route you want to test. You will see examples of how to use this command the the next steps.
  2. Challenge

    Step 1: Creating a New HTTP Server

    Currently, you have an empty application, with the package name and main function defined within main.go.

    You will need to implement an empty HTTP server that listens on port 8000 using the native "net/http" package. Once you complete the above task, you'll have a server running on port 8000.

    The ListenAndServer method actually blocks while the server is running so that the program doesn't halt. This means that when you run the application the program will not exit until you interrupt it.

    To try this yourself, run the application in the Terminal. It will run indefinitely, until you quit the application using Ctrl+C.

    info> Note: Make sure to quit the application when validating your tasks in each step.

    Remember to quit the terminal application when you want to test any new code.

    You can use the other Terminal to test the server, as you'll see in the next steps.

  3. Challenge

    Step 2: Creating a POST Route to Add Ticket Purchases

    Now that you've created a server, let's add a handler to add ticket purchases for various events.

    First, you'll need a place to store this information. To do this, you will need to create a variable that can store event names and the number of tickets purchased for each event name,

    The easiest way to do this is to use a map type. Maps are declared using the syntax:

    var mapVariableName = make(map[KeyType]ValueType)
    

    Maps are useful because they provide a convenient and efficient way to store and retrieve data associated with unique keys. They are similar to dictionaries in Python or objects in JavaScript. They allow for fast lookups, additions, and deletions, making them ideal for tasks that involve a mutable, unordered collection of unique elements, such as our ticket purchases. Next, you need to create a handler for the POST /ticket HTTP route.

    You can create an empty handler for now. The only important thing to check here is that only incoming requests for the POST method and /ticket route are handled.

    For an unsupported method, you should return a 405 HTTP error code.

    info> Note: In Go, the standard HTTP status codes are available as named constants within the net/http package (e.g. http.StatusOK for 200). Finally, let's tie everything together - when you receive a POST request, you need to read the JSON data from the request body.

    The request body will look something like this:

    {
    	"eventName": "some_event",
    	"tickets":   5,
    }
    

    This data will be available in the request body. You'll then need to find the event entry in the ticketPurchases map and increment its value by the number specified in "tickets", which in this case would be 5. Nice! You now have a functional route handler. Any POST requests going to localhost:8000/ticket will be handled by your code.

    If you want to test this out yourself, try running the application, and on the other Terminal, you can execute:

    curl -v -X POST -d '{"eventName":"my_event", "tickets":2}' http://localhost:8000/ticket 
    

    You should see a HTTP/1.1 200 OK message which indicates that it ran successfully.

  4. Challenge

    Step 3: Creating a GET Route to View Ticket Purchases

    The POST /ticket route that you created earlier allows you to add ticket purchase details for any number of events.

    Now, you need to create another route that allows you to see the number of tickets purchased for a given event.

    The best practice in this case is to use a GET route. The event name can be read from the query parameter. For example, if you want to view the ticket purchases for an event named "my_event", you can make the following request:

    GET http://localhost:8000/tickets?eventName=my_event
    

    And this should give you the response in JSON:

    {
    	"eventName": "my_event",
    	"tickets": 3
    }
    

    There are different ways to create a JSON string in Go. You could, of course, construct the string yourself. For example:

    myJsonString := "{\"eventName\":\"my_event\"}"
    

    However, this is very brittle, since you'll need to take care of special characters and end quotes.

    A better way would be to create a struct that represents the JSON structure of the object and use the json.NewEncoder function to convert an object of that type into a JSON string.

    For example:

    type MyStruct struct {
    	MyField int `json:"my_field"` // remember to add these `json` tags - this will be the actual name of the field in the JSON string
    }
    
    s := MyStruct{}
    json.NewEncoder(w).Encode(s)
    ``` Awesome! You can now read event ticket purchase details.
    
    Try using the curl command like before to view the number of tickets purchased for different event names:
    
    

    curl http://localhost:8000/tickets?eventName=my_event

    Does this number change after calling the `POST /ticket` route repeatedly?
  5. Challenge

    Step 4: Error Handling and Production Readiness

    If you've completed the previous steps, you should end up with a working application.

    However, there are still some issues that you may face in the real world, where users don't call the API in exactly the way you intended.

    For example, when calling the POST /ticket API, the user may not provide valid JSON data. Or, when calling the GET /tickets API, the event name might be missing.

    To build a production-ready application, you'll need to handle these cases and send a useful error message, so that the end user knows why they're getting an error,

    Almost all error-prone functions in Go return an error type. In Go, all errors are values and they need to be check explicitly. This means that you should ideally have error checks for every error-returning method in your code.

    The most common way to handle errors is using a nil check:

    err := someFunction()
    if err != nil {
    	// handle errors here
    }
    

    If you observe carefully, some of the responses are in a different format than others.

    1. For the POST /ticket endpoint, an empty response is sent in case it's successful.
    2. For the GET /tickets endpoint, JSON data is sent as the response.
    3. For errors, a plain text message is sent along with a bad-request status code.

    By default, the response is expected to be in plain text, which is okay for points 1 and 3.

    However, for JSON data the best practice is to set the Content-Type response header to application/json, so that any user calling our API knows which format to expect.

    The http.ResponseWriter type has a Header() method which returns the header object. You can use the Add method of this object to add new headers. For example:

    w.Header().Add("Header Name", "Header Value")
    

    Now that you've handled some edge cases, your handlers are more stable and user-friendly.

    Remember, this is an ongoing process and you should always look for edge cases and unexpected behavior when adding new functionality.

    There are some more things you could do to make this application better as next steps:

    1. Adding logs to your code helps you debug your application if things don't work as they should. Try using the fmt or log package to add informational logs. For example, every time a ticket is purchased, you can add a log: fmt.Printf("Purchased %d tickets for event %s", tickets, eventName)
    2. Adding unit tests helps with testing your code in a reproducible way. You can use Go's testing package to do this.
    3. Moving the code for each handler to its own file helps in organizing the overall code base. Try and see if you can do this with the two handler you defined so far.

Soham is a full stack developer with experience in building large scale web applications and services for clients across the globe.

What's a lab?

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Provided environment for hands-on practice

We will provide the credentials and environment necessary for you to practice right within your browser.

Guided walkthrough

Follow along with the author’s guided walkthrough and build something new in your provided environment!

Did you know?

On average, you retain 75% more of your learning if you get time for practice.