- Lab
-
Libraries: If you want this lab, consider one of these libraries.
- Core Tech
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.
Lab Info
Table of Contents
-
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:
- Click on the Run button at the bottom right of the Terminal.
- To test a particular REST API route, you can use the second Terminal tab after completing the previous step and use the
curlcommand, along with the route you want to test. You will see examples of how to use this command the the next steps.
-
Challenge
Step 1: Creating a New HTTP Server
Currently, you have an empty application, with the package name and
mainfunction defined withinmain.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 port8000.The
ListenAndServermethod 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.
-
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
maptype. 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 /ticketHTTP route.You can create an empty handler for now. The only important thing to check here is that only incoming requests for the
POSTmethod and/ticketroute are handled.For an unsupported method, you should return a
405HTTP error code.info> Note: In Go, the standard HTTP status codes are available as named constants within the
net/httppackage (e.g.http.StatusOKfor200). 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
ticketPurchasesmap 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 tolocalhost:8000/ticketwill 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/ticketYou should see a
HTTP/1.1 200 OKmessage which indicates that it ran successfully. -
Challenge
Step 3: Creating a GET Route to View Ticket Purchases
The
POST /ticketroute 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
GETroute. 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_eventAnd 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
structthat represents the JSON structure of the object and use thejson.NewEncoderfunction 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? -
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 /ticketAPI, the user may not provide valid JSON data. Or, when calling theGET /ticketsAPI, 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
errortype. 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
nilcheck:err := someFunction() if err != nil { // handle errors here }If you observe carefully, some of the responses are in a different format than others.
- For the
POST /ticketendpoint, an empty response is sent in case it's successful. - For the
GET /ticketsendpoint, JSON data is sent as the response. - 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-Typeresponse header toapplication/json, so that any user calling our API knows which format to expect.The
http.ResponseWritertype has aHeader()method which returns the header object. You can use theAddmethod 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:
- Adding logs to your code helps you debug your application if things don't work as they should. Try using the
fmtorlogpackage 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) - Adding unit tests helps with testing your code in a reproducible way. You can use Go's
testingpackage to do this. - 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.
- For the
About the author
Real skill practice before real-world application
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.
Learn by doing
Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.
Follow your guide
All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.
Turn time into mastery
On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.