PROJECTMarch 2, 2022

Creating a QR Code CLI with Rust

Join me on my journey of learning to make a CLI and make API requests in Rust

Rust is difficult. And like any difficult thing, I realized that the best way to learn is by getting my hands dirty and doing some project so that I can start learning and creating useful things that people can use in their everyday lives

Since Saumya, a good friend of mine was working on an API wrapper for, I thought that making a CLI with similar functionality would be a nice idea.

I decided to go only with QR code for now, because I’m just a beginner at rust, and let’s be honest, no one is actually going to use the cli, it’s more of a learning experience.

By the end of this project, I learnt:

  • How to make a CLI in Rust (using clap)
  • How to make API request in rust (using reqwest )
  • How to deal with Bytes and save to file (using std::io::Write)

This is the end product:

Starting out

Since I had no idea how to make a cli using Rust, I searched it up and ended up at, a really good tutorial.

Start the project with

cargo new qrcode-cli

Added the following dependencies to Cargo.toml

clap = { version = "3.0", features = ["derive"] }
reqwest = { version = "^0.10.0", features=["blocking"] }

Yeah, I used blocking reqwest because I have no idea how the async stuff works in rust yet, so not to get ahead of myself, I just used the blocking version

For cli, I’m using this really useful package called clap - It does all the CLI part for me, all I had to do is define a structure

Here’s the code for the structure:

#[derive(Parser, Debug)]
struct Cli {
    #[clap(short = 'd', long = "data")]
    // The information contained by the QR code
    data: String,

    #[clap(short = 'o', long = "output", default_value = ".")]
    output: std::path::PathBuf,

std::path::PathBuf is just a way to store a String-like value but for file paths

I learnt that the #[] is kinda just like a decorator in python

#[clap(parse(from_os_str))] is to tell clap to support file autocomplete in the -o output flag

Finally, I defined the main function, and got the args

fn main() {
    let args = Cli::parse();

This provides a really good interface so I can get and args.output easily

So, the CLI part was done, that was easy, huh. The tutorial made it simple for me. But I had no idea what came next.

First, I used Hyper to deal with all the request part, however, I soon realised that all the async stuff will make me crazy if i go without any knowledge, so I switched to reqwest - An easier way of doing the same thing

Even in reqwest i used the Blocking feature and not the async part, I’ll switch when I learn how to

Now, I made a simple API GET request and handled any errors that may come

// Using blocking request
    let res = reqwest::blocking::get(format!("{}",;

    // Check if the request was successful
    if res.is_err() {
        println!("Error: {}", res.err().unwrap());

Errors are inevitable here, it could be because of my server not responding, or internet issue, or a 500 error, I handled all of them using the is_err() function

So now I had to do the following:

  • Make a file in the user-specified output directory
  • Get the body from the res response (as bytes)
  • Write the bytes to the .png file

I looked into how to make a new file, and came to know that I have to use the standard library std::fs::File to create a qrcode.png

// Create a file in the output directory
    let mut file_ = std::fs::File::create(args.output.join("qrcode.png")).unwrap();

I added the underscore because i thought it could mess with the standard library. Fortunately, I don’t need that because Rust handles it really well

Now I simply got the response body in bytes,

I first tries to_bytes() but that didn’t seem to work, then I saw the docs and I had to get the Body struct from response, but turns out I didn’t need that either.

// Get the response body in bytes
    let body = res.unwrap().bytes().unwrap();

This seemed to work pretty well it basically just checks if res is valid, converts to bytes and checks again. The unwrap might be unnecessary but meh

and finally, I tried to use file.write but again, that didn’t work 💀 fortunately the VS code autocomplete suggested write_all so I just used that and it worked perfectly!

// Save the body to file

Finally I just printed a line telling them that the qr code has been saved successfully

println!("QR code saved to {}", args.output.join("qrcode.png").display());

More stuff I want to do with this project

There are some more things I want to do in this project, I’ll list them out here for future me to handle

  • Add help for everything

    Currently it just uses the help stuff that clap provides out of the box i would like to customise that

  • Add more arguments to the CLI

    my API provides so many customizations for the QR code (check it out at That could be utilised in this CLI as more args, and taking the input in such a way that it’s easily understandable for the user

  • Async I would like this program to be async (probably using tokio and hyper or reqwest). I tried to make it async this time but I didn’t know stuff about future in rust (and the syntax) so I avoided going that route

  • Publish on maybe? I could make this into a full fledged QR wrapper for my API and publish it on for others to use

This project is open source on github.

Leave a ⭐ if you liked the project, and try out the CLI here

Thanks for reading. You made it!

Thanks for reading so far! Not everyone comes here.

If you liked the blog and would like to know whenever I post one, make sure to subscribe to the newsletter at

And follow me on Twitter, where I try to post interesting stuff every day.

Comment your thoughts and ideas down below! Let’s start a conversation.

Anyways, that’s it. Bye!