Welcome to the second lesson in our journey of interacting with APIs using Go. In the previous lesson, we established a strong understanding of RESTful APIs and how HTTP requests facilitate interactions with them. We used curl
to interact directly with an API endpoint. Now, we will automate this process using Go's net/http
package. This lesson will guide you through making HTTP requests in Go, equipping you with essential skills for web development and API integration.
To make HTTP requests in Go, we'll use the standard library's net/http
package, which is powerful and does not require any third-party installation. If you're developing locally, make sure you have Go installed on your system. You can download and install it from the official website. Once installed, verify your installation by running:
Bash1go version
This command should show the currently installed version of Go. With Go set up, you're ready to write and run scripts that automate HTTP requests, saving time and boosting efficiency.
In Go, we'll define a base URL for the API service we are using. This approach keeps our code modular and maintainable.
Go1package main 2 3import ( 4 "fmt" 5) 6 7const baseURL = "http://localhost:8000" 8 9func main() { 10 fmt.Println("Base URL is set to:", baseURL) 11}
By defining the base URL this way, we can easily append endpoints for different API services, keeping our code clean and adaptable.
Let's move on to fetching data from an API using Go's http.Get()
function. Our goal is to retrieve a list of to-do items from the /todos
endpoint.
Go1package main 2 3import ( 4 "fmt" 5 "io" 6 "log" 7 "net/http" 8) 9 10func main() { 11 resp, err := http.Get(baseURL + "/todos") 12 if err != nil { 13 log.Fatalf("Failed to get todos: %v", err) 14 } 15 16 body, err := io.ReadAll(resp.Body) 17 if err != nil { 18 log.Fatalf("Failed to read response body: %v", err) 19 } 20 21 fmt.Println("Raw Response:") 22 fmt.Println(string(body)) 23}
Here, http.Get()
sends the GET request, and io.ReadAll()
reads the response body. We print the raw response as a string, giving us the immediate result returned by the server.
When the server returns a 200 status code, it indicates a successful request. Below is an example of retrieving and displaying the response body.
Go1package main 2 3import ( 4 "fmt" 5 "io" 6 "log" 7 "net/http" 8) 9 10func main() { 11 resp, err := http.Get(baseURL + "/todos") 12 if err != nil { 13 log.Fatalf("Failed to get todos: %v", err) 14 } 15 16 // http.StatusOK == 200 17 if resp.StatusCode == http.StatusOK { 18 body, err := io.ReadAll(resp.Body) 19 if err != nil { 20 log.Fatalf("Failed to read response body: %v", err) 21 } 22 23 fmt.Println("Response received successfully:") 24 fmt.Println(string(body)) 25 } 26}
In Go, http.StatusOK
is interchangeable with its integer representation (200
). Using the named constant improves readability and helps avoid magic numbers in your code, making it more maintainable and self-explanatory.
This ensures the response is successfully retrieved and displayed as a string. Here’s an example of what the raw response might look like:
Plain text1Raw Response: 2[ 3 { 4 "description": "Milk, eggs, bread, and coffee", 5 "done": false, 6 "id": 1, 7 "title": "Buy groceries" 8 }, 9 { 10 "description": "Check in and catch up", 11 "done": true, 12 "id": 2, 13 "title": "Call mom" 14 }, 15 { 16 "description": "Summarize Q4 performance metrics", 17 "done": false, 18 "id": 3, 19 "title": "Finish project report" 20 }, 21 { 22 "description": "30 minutes of cardio", 23 "done": true, 24 "id": 4, 25 "title": "Workout" 26 } 27]
By reading the response body and printing it, we can inspect the returned data before processing it further.
If the request is malformed, a 400 status code is returned to signify this. Let's handle it gracefully in Go.
Go1// http.StatusBadRequest == 400 2else if resp.StatusCode == http.StatusBadRequest { 3 fmt.Println("\nBad Request. The server could not understand the request due to invalid syntax.") 4 body, err := io.ReadAll(resp.Body) 5 if err != nil { 6 log.Fatalf("Failed to read error response: %v", err) 7 } 8 fmt.Printf("Error Details: %s\n", body) 9}
For cases involving a 401 Unauthorized status code, which typically means authorization issues, we'll handle them as follows.
Go1// http.StatusUnauthorized == 401 2else if resp.StatusCode == http.StatusUnauthorized { 3 fmt.Println("\nUnauthorized. Access is denied due to invalid credentials.") 4 body, err := io.ReadAll(resp.Body) 5 if err != nil { 6 log.Fatalf("Failed to read error response: %v", err) 7 } 8 fmt.Printf("Error Details: %s\n", body) 9}
A 404 status code indicates that the requested resource was not found.
Go1// http.StatusNotFound == 404 2else if resp.StatusCode == http.StatusNotFound { 3 fmt.Println("\nNot Found. The requested resource could not be found on the server.") 4 body, err := io.ReadAll(resp.Body) 5 if err != nil { 6 log.Fatalf("Failed to read error response: %v", err) 7 } 8 fmt.Printf("Error Details: %s\n", body) 9}
The 500 status code denotes an internal server error, often requiring server-side fixes.
Go1// http.StatusInternalServerError == 500 2else if resp.StatusCode == http.StatusInternalServerError { 3 fmt.Println("\nInternal Server Error. The server has encountered a situation it doesn't know how to handle.") 4 body, err := io.ReadAll(resp.Body) 5 if err != nil { 6 log.Fatalf("Failed to read error response: %v", err) 7 } 8 fmt.Printf("Error Details: %s\n", body) 9}
Finally, for any unexpected status codes, we generalize error handling.
Go1else { 2 fmt.Printf("\nUnexpected Status Code: %d\n", resp.StatusCode) 3 body, err := io.ReadAll(resp.Body) 4 if err != nil { 5 log.Fatalf("Failed to read error response: %v", err) 6 } 7 fmt.Printf("Error Details: %s\n", body) 8}
In this lesson, we've explored how to use Go's net/http
package to make GET requests, handle responses, and interpret HTTP status codes. This knowledge is crucial for building reliable API interactions. As you proceed to practice exercises, focus on implementing these code snippets and handling different status codes to deepen your understanding. In future lessons, we will build on this foundation to perform more complex tasks such as updating and manipulating API data. Keep progressing as you advance through the course!