How to create a rest API client Golang vs Rust?

How to create a rest API client Golang vs Rust?

Programming Languages war

Carlos Rivas's photo
Carlos Rivas
·Jan 24, 2022·

10 min read

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.

rest.png

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.

CebC4f1WIAAiJW0.jpg

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.

spoileralert.jpg

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.

dejavu.jpg

#[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!

 
Share this