HTTP requests are a very fundamental part of the web as a whole. They are used to access resources hosted on a server (which could be remote).
HTTP is an acronym for hypertext transfer protocol, a communication protocol that ensures the transfer of data between a client and a server. A perfect instance of an HTTP client-server interaction is when you open your browser and type in a URL. Your browser acts as a client and fetches resources from a server which it then displays.
In web development, cases where we need to fetch resources, are very common. You might be making a weather application and need to fetch the weather data from an API. In such a case, using your browser as a client would no longer be possible from within your application. So you have to set up an HTTP client within your application to handle the making of these requests.
Most programming languages have various structures in place for setting up HTTP clients for making requests. In the coming sections, we will take a hands-on approach in exploring how you can make HTTP requests in Golang or Go, as I will refer to the language for the rest of the article.
Prerequisites
To follow this article you will need:
- Go (version 1.14 or higher)
- A text editor of your choice
- Basic knowledge of Go
Making HTTP requests in Go
GET request
The first request we will be making is a GET request. The HTTP GET method is used for requesting data from a specified source or server. The GET method is mostly used when data needs to be fetched.
For the sake of clarity, it is important to note that the HTTP methods, as seen in this article, are always capitalized.
For our example, we will be fetching some example JSON data from https://jsonplaceholder.typicode.com/posts using the GET method.
The first step in making an HTTP request with Go is to import the net/http
package from the standard library. This package provides us with all the utilities we need to make HTTP requests with ease. We can import the net/http
package and other packages we will need by adding the following lines of code to a main.go
file that we create:
import ( "io/ioutil" "log" "net/http" )
The net/http
package we imported has a Get function used for making GET requests. The Get function takes in a URL and returns a response of type pointer to a struct and an error. When the error is nil
, the response returned will contain a response body and vice versa:
resp, err := http.Get("https://jsonplaceholder.typicode.com/posts/1") if err != nil { log.Fatalln(err) }
To make the request, we invoke the Get function, passing in a URL string (https://jsonplaceholder.typicode.com/posts) as seen above. The values returned from the invocation of this function are stored in two variables typically called resp and err. Although the variable resp contains our response, if we print it out we would get a load of incoherent data which includes the header and properties of the request made. To get the response we are interested in, we have to access the Body
property on the response struct and read it before finally printing it out to the terminal. We can read the response body using the ioutil.ReadMe
function.
Similar to the Get
function, the ioutil.ReadMe
function returns a body and an error. It is important to note that the response Body
should be closed after we are done reading from it to prevent memory leaks.
The defer keyword which executes resp.Body.Close()
at the end of the function is used to close the response body. We can then go ahead and print out the value of the response to the terminal. As good programmers it is important to handle possible errors, so we use an if statement to check for any errors and log the error if it exists:
package main import ( "io/ioutil" "log" "net/http" ) func main() { resp, err := http.Get("https://jsonplaceholder.typicode.com/posts") if err != nil { log.Fatalln(err) } //We Read the response body on the line below. body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatalln(err) } //Convert the body to type string sb := string(body) log.Printf(sb) }
At this point, we are all set and can execute the file containing our code. If everything went well you will notice some JSON data similar to the image below gets printed to the terminal:
Congratulations, you have just made your first HTTP request with Go. Now that we have seen how we can fetch resources from a server using the HTTP GET method, we will look at how to post resources to a server next.
POST request
The HTTP POST method is used to make requests that usually contain a body. It Is used to send data to a server, the data sent is usually used for creating or updating resources.
A clear instance where a POST request is used is when a user tries to create a social media account, the user is required to provide their data (name, email, and password). This data is then parsed and sent as a POST request to a server which then creates and saves the user. Just like for the GET method seen above, Go’s net/http
package also provides functionality for making POST requests through the Post function. The Post function takes three parameters.
- The URL address of the server
- The content type of the body as a string
- The request body that is to be sent using the POST method of type
io.Reader
The Post function returns a response and an error. For us to invoke the Post function we have to convert our request body to the accepted type. For this example, we will make a post request to https://postman-echo.com/post and pass in JSON data containing a name and an email. To get started we convert our JSON data to a type that implements the Io.Reader interface the Post function expects, this is a two-way step:
- The first step is to encode our JSON data so it can return data in byte format, to do this we use the Marshall function Go’s Json package provides
- Next, we convert the encoded JSON data to a type implemented by the
io.Reader
interface, we simply use theNewBuffer
function for this, passing in the encoded JSON data as an argument. TheNewBuffer
function returns a value of type buffer which we can then pass unto the Post function
postBody, _ := json.Marshal(map[string]string{ "name": "Toby", "email": "[email protected]", }) responseBody := bytes.NewBuffer(postBody)
Now that we have all the arguments the Post function requires, we can go ahead and invoke it, passing in https://postman-echo.com/post as the URL string, application/JSON as the content type, and the request body returned by the NewBuffer
function as the body. The values returned by the Post
function is then assigned to resp and err representing the response and error, respectively. After handling the error, we read and print in the response body as we did for the Get function in the previous section. At this point, your file should look like this:
import ( "bytes" "encoding/json" "io/ioutil" "log" "net/http" ) func main() { //Encode the data postBody, _ := json.Marshal(map[string]string{ "name": "Toby", "email": "[email protected]", }) responseBody := bytes.NewBuffer(postBody) //Leverage Go's HTTP Post function to make request resp, err := http.Post("https://postman-echo.com/post", "application/json", responseBody) //Handle Error if err != nil { log.Fatalf("An Error Occured %v", err) } defer resp.Body.Close() //Read the response body body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatalln(err) } sb := string(body) log.Printf(sb) }
When the file is executed, if everything works well we should have the response printed out. Amazing, right? We just made a post request with Go using the net/http
package which provides functionality that makes HTTP requests easier. In the next section, we will work on a project, to help us see HTTP requests being used in a real-life scenario.
HTTP requests in action
In this section, we will be building a cryptocurrency price checker CLI tool! This exercise aims to enable you to see a real-life use case of HTTP requests. The tool we are building will check the price of whatever cryptocurrency as specified by the user in the specified fiat currency. We will use the crypto market cap and pricing data provided by Nomics to get the price of the cryptocurrencies in real-time! To get started, create the needed files and folders to match the tree structure below:
├── model/ │ ├── crypto-model.go ├── client/ │ ├── crypto-client.go └── main.go
- The crypto-client file will house the code that fetches the cryptocurrency data from the API
- The crypto-model file houses a couple of utility functions necessary for our application
- The main file is the central engine of the application, it will merge all the parts of the application to make it functional
In the crypto-model file, we create a struct that models the data received from the API, this struct includes only the specific data we need/intend to work with. Next, we create a function called TextOutput
which is a receiver that belongs to the Cryptoresponse
struct we created up above. The purpose of the TextOutput
function is to format the data gotten from the API to plain text which is easier to read than JSON(which we receive from the server). We use the fmt.Sprintf
function to format the data:
package model import ( "fmt" ) // Cryptoresponse is exported, it models the data we receive. type Cryptoresponse []struct { Name string `json:"name"` Price string `json:"price"` Rank string `json:"rank"` High string `json:"high"` CirculatingSupply string `json:"circulating_supply"` } //TextOutput is exported,it formats the data to plain text. func (c Cryptoresponse) TextOutput() string { p := fmt.Sprintf( "Name: %s\nPrice : %s\nRank: %s\nHigh: %s\nCirculatingSupply: %s\n", c[0].Name, c[0].Price, c[0].Rank, c[0].High, c[0].CirculatingSupply) return p }
Now that the crypto-model
file is ready, we can move on to the crypto-client
file, which is the most relevant to us. In the crypto-client
file, we create a FetchCrypto
function that takes in the name of the cryptocurrency and fiat currency as parameters.
Note that we capitalize the first letter of the function name, this is to ensure it is exported.
In the FetchCrypto
function, we create a variable called URL, the variable is a concatenation of the URL string provided by the Nomics API and the various variables that will be passed into our application. Remember our application takes in the name of the desired cryptocurrency and the preferred fiat currency? These are the variables that are then used to build our URL string. Our URL string would look like this.
URL := "...currencies/ticker?key=3990ec554a414b59dd85d29b2286dd85&interval=1d&ids="+crypto+"&convert="+fiat
After setting up the URL, we can go ahead and use the Get function we saw up above to make a request. The Get function returns the response and we handle the error elegantly. To get the data we want, in the format we want, we have to decode it! To do so, we use the Json.NewDecoder
function that takes in the response body and a decode function which takes in a variable of type cryptoresponse which we created in the crypto-model
file. Lastly, we invoke the TextOutput
function, on the decoded data to enable us to get our result in plain text:
package client import ( "encoding/json" "fmt" "log" "net/http" "github.com/Path/to/model" ) //Fetch is exported ... func FetchCrypto(fiat string , crypto string) (string, error) { //Build The URL string URL := "https://api.nomics.com/v1/currencies/ticker?key=3990ec554a414b59dd85d29b2286dd85&interval=1d&ids="+crypto+"&convert="+fiat //We make HTTP request using the Get function resp, err := http.Get(URL) if err != nil { log.Fatal("ooopsss an error occurred, please try again") } defer resp.Body.Close() //Create a variable of the same type as our model var cResp model.Cryptoresponse //Decode the data if err := json.NewDecoder(resp.Body).Decode(&cResp); err != nil { log.Fatal("ooopsss! an error occurred, please try again") } //Invoke the text output function & return it with nil as the error value return cResp.TextOutput(), nil }
From what we have above, the application is coming together nicely. However, if you try to run the file above, you will encounter a couple of errors, this is because we are not invoking the FetchCrypto
function and so the value of the fiat and crypto parameters are not provided. We will put all the various parts of our application together in the main.go
file we created. Since our application is a command-line tool, users will have to pass in data through the terminal, We will handle that using Go’s flag package.
In the main function, we create two variables fiatcurrency
and nameofcrypto
. These variables both invoke the flag.string
function, passing in:
- The name of the commands as the first argument
- The fallback values as the second
- The information on how to use the command as the third argument
Next, we invoke the FetchCrypto
function we defined in the crypto-client
file and pass in the fiatcurrency
and nameofcrypto
variables. We can then go ahead and print the result of the call to FetchCrypto
:
package main import ( "flag" "fmt" "log" "github.com/path/to/client" ) func main() { fiatCurrency := flag.String( "fiat", "USD", "The name of the fiat currency you would like to know the price of your crypto in", ) nameOfCrypto := flag.String( "crypto", "BTC", "Input the name of the CryptoCurrency you would like to know the price of", ) flag.Parse() crypto, err := client.FetchCrypto(*fiatCurrency, *nameOfCrypto) if err != nil { log.Println(err) } fmt.Println(crypto) }
At this point, we are good to go, if we run the command go run main.go -fiat=EUR -crypto=ETH
we would get an output similar to the image below:
This shows our application is working fine which is pretty awesome. We have an application that fetches data from a remote server using the HTTP protocol.
Conclusion
In this article, we discussed how to make HTTP requests in Go, and we built a CLI tool for checking the prices of cryptocurrencies. I highly recommend checking out the source code and documentation of the net/http
package to explore the other amazing functionalities it provides.
Get setup with LogRocket's modern error tracking in minutes:
- Visit https://logrocket.com/signup/ to get an app ID.
- Install LogRocket via NPM or script tag.
LogRocket.init()
must be called client-side, not server-side. - (Optional) Install plugins for deeper integrations with your stack:
- Redux middleware
- ngrx middleware
- Vuex plugin
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>