How to create a rest API client Golang vs Rust?
Programming Languages war
Hey, my friends, a new post about this war.
I don't know which is better, but I'll try to figure it out. I hope y'all enjoy this series. Here we go!
A RESTful API is an architectural style for an application program interface (API) that uses HTTP requests to access and use data.
Why the people use that? It's because every time we have to communicate between microservices o services, obviously exist other ways to communicate that, but it's the most popular.
In this case, I'm going to use an API about Chuck Norris Jokes, and how I can get information to manipulate between endpoints.
The endpoint is the URL where we can get information.
Golang/Go
The first part is to import all packages necessary to execute this code.
package main
import (
"encoding/json" // package json implements encoding and decoding of JSON
"fmt" // implements formatted I/O
"io/ioutil" // implements some I/O utility functions
"log" // implements a simple logging package
"math/rand" // implements pseudo-random number generators unsuitable for security-sensitive work
"net/http" // provides HTTP client and server implementations
"time" // provides functionality for measuring and displaying time
"github.com/davecgh/go-spew/spew" // implements a deep pretty printer for Go data structures to aid in debugging.
)
The next code define the structure of how to manipulate the response from API
type Joke struct {
ID string `json:"id"`
Value string `json:"value"`
Categories []string `json:"categories"`
URL string `json:"url"`
CreatedAt string `json:"created_at"`
IconURL string `json:"icon_url"`
UpdatedAt string `json:"updated_at"`
}
The next code is the main function, remember it's the main function because is the entry point to run.
What does the main function do?
I initialize the time, why I do that, it's to know how long the execution lasts, next, I call the function getCategory (I'm going to explain this function after spoiler alert, get a category), after that with this information I call other function call getJoke( You know what happen here), in the end, I calculate the time and print the execution lasts.
func main() {
start := time.Now()
var category = getCategory()
getJoke(category)
elapsed := time.Since(start)
fmt.Printf("Execution lasts: %s\n", elapsed)
}
I hope you're enjoying this post, I enjoy doing that. OK, I'm going to continue with this:
getCategory function:
I start my HTTP client, this client is who can get information from endpoint or services, so, It's who will tell us the joke, but first the category, after that, I use another method from HTTP, NewRequest, this method makes the request, what do I need for a request?
Verb + Endpoint or URL + data, in this case, we don't need data but what happens here, in Golang we have the use the keyword nil, to say I don't have data to send you.
this method returns two values, request or error.
I validate if I don't have errors and if I don't have anything to cry I can continue.
After, I use our client to call the method Do with the value request and to receive two values, response or error.
Again I cross my fingers and pray for good news.
I read the body responds with the method ReadAll from the package ioutil, and again I can receive two values (I know, I know), body or error.
It's important to know-how is the response, you can use the client to that (Hoppscotch or Postman) or print your body (fmt.Printf("%s", body)), Exist different ways to show you the response, but the most popular is JSON.
What means []string, this is a type of data, it says an array of strings, after that I use the method Unmarshal from JSON to say this information that is a JSON and I know is an array of string put in a variable that I defined as an array of strings, but, this method only returns one value, if it's an error, what happens is not an error? the variable bodyArray gets the data in this format.
You can ask why I have to convert my data in an array of strings, this is because is the ways easiest to use this information.
I receive an array with 16 positions, which means I have 16 strings which mean different categories, but I need one.
My option is to get this information randomly, for that, I use the method Seed from rand package to say that for each execution I want other numbers.
In the end, I have to return a value from my array, with this method I get an integer value between 0 and the length of array rand.Intn(len(bodyArray).
This return something like that for instance "bodyArray[10]"
func getCategory() string {
client := http.Client{}
request, err := http.NewRequest("GET", "https://api.chucknorris.io/jokes/categories", nil)
if err != nil {
log.Fatal(err)
}
response, err := client.Do(request)
if err != nil {
log.Fatal(err)
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatal(err)
}
var bodyArray []string
newErr := json.Unmarshal(body, &bodyArray)
if newErr != nil {
log.Fatal(newErr)
}
rand.Seed(time.Now().UnixNano())
return bodyArray[rand.Intn(len(bodyArray))]
}
getJoke function:
The first steps are the same as the before function, the unique difference is the endpoint and response format.
The Unmarshal method is from the JSON package, translating the body response to joke structure.
Now it's time to use the Dump and the structure. Well, I'm going to start at the beginning.
the Dump is only to show the pretty way a structure something like that
{
ID: (string) (len=22) "0wdewlp2tz-mt_upesvrjw",
Value: (string) (len=136) "Chuck Norris does not follow fashion trends, they follow him. But then he turns around and kicks their ass. Nobody follows Chuck Norris.",
Categories: ([]string) (len=1 cap=4) {
(string) (len=7) "fashion"
},
URL: (string) (len=55) "https://api.chucknorris.io/jokes/0wdewlp2tz-mt_upesvrjw",
CreatedAt: (string) (len=26) "2020-01-05 13:42:18.823766",
IconURL: (string) (len=59) "https://assets.chucknorris.host/img/avatar/chuck-norris.png",
UpdatedAt: (string) (len=26) "2020-01-05 13:42:18.823766"
}
But if you want to use any value from this structure, you have to use variable name + . + structure value name.
Ex: joke.Value
func getJoke(category string) {
client := http.Client{}
request, err := http.NewRequest("GET", "https://api.chucknorris.io/jokes/random?category="+category, nil)
if err != nil {
log.Fatal(err)
}
response, err := client.Do(request)
if err != nil {
log.Fatal(err)
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatal(err)
}
var joke Joke
newErr := json.Unmarshal(body, &joke)
if newErr != nil {
log.Fatal(newErr)
}
spew.Dump(joke)
fmt.Println(joke.Value)
}
Here is all my code in Golang.
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"math/rand"
"net/http"
"time"
"github.com/davecgh/go-spew/spew"
)
type Joke struct {
ID string `json:"id"`
Value string `json:"value"`
Categories []string `json:"categories"`
URL string `json:"url"`
CreatedAt string `json:"created_at"`
IconURL string `json:"icon_url"`
UpdatedAt string `json:"updated_at"`
}
func main() {
start := time.Now()
var category = getCategory()
getJoke(category)
elapsed := time.Since(start)
fmt.Printf("Execution lasts: %s\n", elapsed)
}
func getCategory() string {
client := http.Client{}
request, err := http.NewRequest("GET", "https://api.chucknorris.io/jokes/categories", nil)
if err != nil {
log.Fatal(err)
}
response, err := client.Do(request)
if err != nil {
log.Fatal(err)
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatal(err)
}
var bodyArray []string
newErr := json.Unmarshal(body, &bodyArray)
if newErr != nil {
log.Fatal(newErr)
}
rand.Seed(time.Now().UnixNano())
return bodyArray[rand.Intn(len(bodyArray))]
}
func getJoke(category string) {
client := http.Client{}
request, err := http.NewRequest("GET", "https://api.chucknorris.io/jokes/random?category="+category, nil)
if err != nil {
log.Fatal(err)
}
response, err := client.Do(request)
if err != nil {
log.Fatal(err)
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatal(err)
}
var joke Joke
newErr := json.Unmarshal(body, &joke)
if newErr != nil {
log.Fatal(newErr)
}
spew.Dump(joke)
fmt.Println(joke.Value)
}
Rust
The first part is to add all dependencies necessary to execute this code. Remember these dependencies should add in the file Cargo.toml
[dependencies]
reqwest = { version = "0.11", features = ["json"] } # provides a convenient, higher-level HTTP Client.
tokio = { version = "1", features = ["full"] } # an event-driven, non-blocking I/O platform for writing asynchronous applications
serde = { version = "1", features = ["derive"] } # framework for serializing and deserializing Rust data structures efficiently and generically.
serde_json = "1.0" # A JSON serialization file format
futures = "0.3" # provides a number of core abstractions for writing asynchronous code
rand = "0.8.4" # provides utilities to generate random numbers
Now I'm going to the file main.rs
First, call the modules necessary to run this code.
use rand::Rng;
use serde::Deserialize;
use std::time::Instant;
use reqwest::Client;
#[derive] This is able to implement a trait, which means that, how you can see, after this line, define a structure, I know that I need to get this information from a JSON, so, to achieve to deserializing this information, I have to say how to structure with deserializing or debug.
So, #[derive(Deserialize, Debug)], Deserialize can translate a JSON in a struct and Debug allows me to show variable with this operator {:?}
#[derive(Deserialize, Debug)]
struct Joke {
id: String,
value: String,
categories: Vec<String>,
created_at: String,
icon_url: String,
updated_at: String,
url: String,
}
The #[tokio::main] function is a macro. It transforms the async fn main() into a synchronous fn main() that initializes a runtime instance and executes the async main function. Here can you read a little more information about tokio
The next code is the main function, remember it's the main function because is the entry point to run.
What does the main function do?
I initialize the time, why do I do that? it's to know how long the execution lasts, next, I call the function get_category (I'm going to explain this function after, spoiler alert, get a category, looks like a Deja bu), after that with this information I call another function call get_joke( You know what happen here), in the end, I calculate the time and print the execution lasts.
#[tokio::main]
async fn main() {
let start = Instant::now();
let category = get_category().await;
get_joke(category).await;
let end = start.elapsed();
println!("Execution lasts: {:?}", end);
}
get_category function:
First, I define the function and say what type of data I have to return.
After I instantiate and I use the method get who needs the endpoint or URL how parameter, next, the method send() and await (Can you guess what does it do?) and unwrap.
To “unwrap” something in Rust is to say, “Give me the result of the computation, and if there was an error, panic and stop the program.” If you want to know a little more about Error Handling
Then, I translate the JSON response to Vector of String.
Count the length to the categories response.
Retrieve the lazily-initialized thread-local random number generator, seeded by the system. More info
return a string randomly from 0 and the length in categories.
async fn get_category() -> String {
let res1 = Client::new()
.get("https://api.chucknorris.io/jokes/categories")
.send()
.await
.unwrap();
let categories = res1.json::<Vec<String>>().await.unwrap();
let count = categories.len();
let mut rng = rand::thread_rng();
categories[rng.gen_range(0..count)].to_string()
}
get_joke function:
The first steps are the same as the before function, the unique difference is the endpoint and response format.
format! is a macro that allows uniting two or more strings.
Then, I translate the JSON response to the Joke struct.
If you ask the difference between {#?} and {?} It's only to show pretty the structure and looks like that:
Joke {
id: "6sdvoj-msgi6miv07ekctq",
value: "Chuck Norris is currently suing myspace for taking the name of what he calls everything around you.",
categories: [
"dev",
],
created_at: "2020-01-05 13:42:19.104863",
icon_url: "https://assets.chucknorris.host/img/avatar/chuck-norris.png",
updated_at: "2020-01-05 13:42:19.104863",
url: "https://api.chucknorris.io/jokes/6sdvoj-msgi6miv07ekctq",
}
But if you want to use any value from this structure, you have to use variable name + . + structure value name.
Ex: joke.value
async fn get_joke(category: String) {
let res1 = Client::new()
.get(format!(
"https://api.chucknorris.io/jokes/random?category={}",
category
))
.send()
.await
.unwrap();
let joke = res1.json::<Joke>().await.unwrap();
println!("{:#?}", joke);
println!("{:?}", joke.value);
}
Here is all my code in Rust.
use rand::Rng;
use serde::Deserialize;
use std::time::Instant;
use reqwest::Client;
#[derive(Deserialize, Debug)]
struct Joke {
id: String,
value: String,
categories: Vec<String>,
created_at: String,
icon_url: String,
updated_at: String,
url: String,
}
#[tokio::main]
async fn main() {
let start = Instant::now();
let category = get_categories().await;
get_joke(category).await;
let end = start.elapsed();
println!("Execution lasts: {:?}", end);
}
async fn get_category() -> String {
let res1 = Client::new()
.get("https://api.chucknorris.io/jokes/categories")
.send()
.await
.unwrap();
let categories = res1.json::<Vec<String>>().await.unwrap();
let count = categories.len();
let mut rng = rand::thread_rng();
categories[rng.gen_range(0..count)].to_string()
}
async fn get_joke(category: String) {
let res1 = Client::new()
.get(format!(
"https://api.chucknorris.io/jokes/random?category={}",
category
))
.send()
.await
.unwrap();
let joke = res1.json::<Joke>().await.unwrap();
println!("{:#?}", joke);
println!("{:?}", joke.value);
}
Conclusion
Structure:
Golang needs more steps to get and to read the information from the endpoint.
Golang uses camelCase structure to define variables and functions, but, Rust uses snake_case.
Golang used 88 lines of code and Rust used 54 plus 16 lines from Cargo.toml files
Lasts
I run 5 times each code and get this average:
Golang: 3.33s
Rust: 4.44s
But I think it is not enough to know who is better.
What do you think? Do you want to know a little more about API Client and other methods?
If you want to know a little more about Golang or Rust
I hope you enjoy my post and remember that I am just a Dev like you!
Top comments (0)