Golang: How To Guide

Streamline Your Productivity in Go Using “Air”

A Simple Walkthrough Using Go Live Reload Development With Docker

Marvin
The GoDev Corner

--

Picture of Air code with a Formula One car in the background, symbolizing agileness, speed, and portability
Air is fast and reliable for Go applications [Background picture by Spencer Davis on Unsplash]

Build and Run Burnout

As a software engineer, I can tell you that the efficiency of your software development is dependent not only on your tools, knowledge, and skills but also on proper setup and development environment configuration.

If you started out as a Go developer, you most likely had to repeatedly re-run or re-build a codebase after every change. Alternatively, if you’re coming from another language, such as PHP or NodeJS, you’re probably sick of the build and run cycle in Go development.

Meme saying “My message is this — don’t repeat; do it once”

Live reloading can be done in a variety of ways, but there is an optimal method when using Go, in my opinion. And that’s to use Air.

☝️ Live reloading is the process of keeping track of file changes based on the file types designated to be monitored inside of a directory (often an application directory), building the application, and relaunching it after a file save (or a delay duration). It differs slightly from hot reloading:

Live reloading: Re-compiles and reloads the entire application on file(s) changed and saved.

Hot reloading: Only refreshes the files that were changed and saved, not the entire application without losing the application state.

As a quick aside, I’ve tested the majority of live reloading methods on real projects, and have subsequently found Air to be:

  • Easily configurable (primary benefit)
  • Portable and used with Docker (primary benefit)
  • Chock-full of good documentation (honestly, no one wants to figure things out from code, do they?)
  • Averagely fast
  • Great at tracking errors in a log file
  • Clear in the building process

That said, there are numerous approaches to live reloading Go applications that may suit you, depending on your preferences or use case: Gin, Fresh, Reflex, Nodemon (especially if you’re coming from NodeJS), etc.

Some are more flexible than others. Some are more performance-based. But, in my opinion, Air offers the most streamlined workflow for Go applications, especially as your projects grow in size and dependencies.

So let’s explore Air a bit, and if you have any questions or comments, feel free to leave them at the end of this article. I’ll be happy to respond.

Short Preface

By the end of this article, you should be able to:
[Feel free to skip ahead to a specific section]

  1. Install Air (with live reload example)
  2. Configure Air
  3. Use Air with environment variables
  4. Distinguish air arguments from passed arguments
  5. Use Air with Docker and Docker-compose
  6. Gather 3 main takeaways

So, without further ado, let’s get started.

The GoDev Corner unique separator

Live reloading with Air

To gain a better understanding of Air. Let’s start with a simple application.

👉 Note: Feel free to follow along with the project with your personal project or clone the article project from this repository.

Air installation

To install Air, follow the instructions below in the terminal, but you should look at the other installation instructions and the updated script here.

curl -sSfL https://raw.githubusercontent.com/cosmtrek/air/master/install.sh | sh -s

And to verify that Air is installed, run the following command

air -v

and you should get an output similar to this:

Code verifying that Air is installed
Output verifying Air is installed

Live reload example

Now that we have Air installed on our machine, we’ll use the article sample project that you cloned a few steps ago, or you can use your own project. After every 10 minutes, the article application will display a random single quote from the Zenquotes API ‘https://zenquotes.io/api'.

Now, before we run the application with Air, open the article project and replace the code on line 23 of the main.go file with the code below.

baseUrl, ok := "https://zenquotes.io/api/", true //os.LookupEnv("BASE_URL")

Now run the application with Airin the project root directory as shown below:

air

and your output should be similar to this:

Output of running the application with Air (after replacing the code on line 23 of the main.go file)
Output of running the application with Air (after replacing the code on line 23 of the main.go file)

Now that Air is watching for our changes, return to the main.goinside the main function and replace the code in line 42 with the code below and watch the magic on the terminal:

time.Sleep(time.Second * 10)

Terminal output:

main.go has changed 
building...
running...

Air will automatically recompile, build, and run our application.

👉 Note: Air does run the application binary in the temp directory.

The GoDev Corner unique separator

Air configuration

Air will use the default configuration, but wouldn't it be better to create our own? Let's see how we might be able to do that.

In our project root directory, run the following command:

air init

This is the output of the command above:

Output of running air init
Output of running air init

And if you open the project in your favorite IDE, you’ll notice that a new file named .air.toml with default settings have been added.

I won’t go through every configuration in the .air.toml file, but I will mention a few that I believe are important. You can tinker with the configuration, but the default settings should be sufficient for any basic Go project.

👉 Note: Sample .air.toml file can be found here https://github.com/cosmtrek/air/blob/master/air_example.toml

[build]
cmd = "go build -o ./tmp/main ."

The command configuration will compile our application; you can change it depending on your application’s entry point:

[build]
bin = "./tmp/main"

The bin configuration specifies the location of the compiled binary.

👉 Note: I don’t recommend changing this configuration.

[build]
full_bin = ""

The full_bin specifies how an application will be launched after compilation; the way you run your application after building should be reflected in this configuration. And you can pass environment variables here; we’ll take a look at passing variables later:

[build]
delay=1500

The delay configuration simply delays the build trigger after a file change and save:

[build]
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
...
[build]
exclude_file = []

👉 Note: This configuration prevents Air from monitoring the directories and files listed respectively.

[build]
include_ext = ["go", "tpl", "tmpl", "html", "yaml", "yml"]

This is the gist of it; all files that you want to be monitored by Air should have their extensions added here, for example, YAML or HTML files in my case.

[build]
log = "build-errors.log"

Air logs output errors in a file, in this case, build-errors.log, which can help you debug your application.

💡 Remember to add tmp in your .gitignore file unless your team is using the same configuration file

The GoDev Corner unique separator

Passing arguments to Air

Air can also be used by command-line applications created with tools such as go cobra. As demonstrated below, Air can accept and work with arguments passed to it.

air serve

Using environment configuration with Air

Air supports passing environment variables such as database configuration, ports, and so on. Although I prefer using .env to manage environment variables, let’s look at how to use Air with environment variables.

Updating the configuration on .air.toml as shown below

[build]
full_bin = "BASE_URL=https://zenquotes.io/api/ ./tmp/main"

Now go ahead and revert the first change we made on main.go file on line 21 to this.

baseUrl, ok := os.LookupEnv("BASE_URL")

Now hit it, stop Air and run it one more time as shown

air
Meme that says “It’s so simple”

Basically, we are adding BASE_URL as an environment variable when running the application with Air.

However, adding and updating environment variables in the .air.toml file is not appealing. Rather, it’s best to include the environment variables in an .env file.

Examine the project sample. Undo the .air.toml file and use the terminal to run the following commands to add the environment variables:

export $(egrep -v ‘^#’ .env | xargs) && air

👉 Note: There are several methods for adding environment variables, one of which is to use this package. github.com/joho/godotenv.

Focus on one configuration file

For the sake of productivity, running Air init and configuring .air.toml in every project is counterproductive.

Instead, let’s just use the latter. Copy your default .air.toml to your user directory:

cp .air.toml ~/.air.toml

👉 Note: When developing Go projects, you can use the default .air.toml file:

air -c ~/.air.toml

Better yet, we can add the above command as an alias by including the following content in the bashrc or zshrc file. Open the bashrc or zshrc file and add the alias below:

alias air="air -c ~/.air.toml -- "

💡 If you’ve noticed, I’ve added characters (i.e., the dashes) to the bashrc or zshrc files to distinguish between Air arguments and passed arguments.

As a result, we can still run the instruction below.

air serve
The GoDev Corner unique separator

Using Air with Docker and Docker-compose

Let’s look at how to use Air with Docker first.

You can start our sample application Docker container by following the steps below. Let’s take a closer look:

docker run -it --rm --env-file <ENV_FILE> -w <WORKING_DIR> -v <PROJECT_FOLDER>:<MOUNT_POINT> <IMAGE_NAME>

which equates to this in our case:

docker run -it --rm --env-file .env -w "/app" -v $(pwd):/app cosmtrek/air

👉 Note: Essentially, the Docker instruction mounts the project directory to the Docker work directory using the -v flag and the -w flag to specify the working directory inside the container. Air will monitor changes to the container's mounted directory.

Here’s how the—env-file flag is read in a file of environment variables:

Output of how the .env file flag is read with environmental variables
Output of how the .env file flag is read

Docker-compose

We can also use Docker-compose with Air.

Create a Docker-compose file called docker-compose-dev.yml in our project’s root directory and fill it with the content below.

Content for the docker-compose-dev.yml file

💡 Air is a development tool that should not be used for production or deployment. That is why our Docker-compose file is called docker-compose-dev.yml

Now, if we run Docker-compose up in our project directory in the terminal as shown below…

docker compose -f docker-compose-dev.yml up

We get the following output:

Output running docker-compose
Output running Docker-compose

Both Docker and Docker-compose are outside the scope of this article, but all I wanted to do was demonstrate how to use Air with Docker and Docker-compose. Please update your Docker and Docker-compose files to meet your development requirements.

With that, you can take control of the development process and increase your productivity by using Air, Air configuration, and Docker.

The GoDev Corner unique separator

3 Key Takeaways

Over time, you’ll realize that being an experienced developer revolves around being efficient and saving time, whether in application code or during development. Software development is similar to logistics delivery in that everyone benefits from faster delivery. That’s what Air provides you during Go development.

If you find yourself constantly restarting & recompiling a Go app in development, do yourself a favor and try Air:

  • It’s easy to install, highly configurable, portable, and works well with Docker-compose
  • It will save you time and energy, and allow you to get out of the “Build and Run” burnout by speeding up your development process
  • Don’t runAir init and configure .air.toml simultaneously — it’s counterproductive

Thanks for reading. If you liked this article, please clap and share this with others so we can build on each others’ experiences.

The GoDev Corner unique separator

Sample project code

The project’s source code can be found here.

Related Articles (if you liked this one)

Disclosure: In accordance with Medium.com’s rules and guidelines, I publicly acknowledge financial compensation from UniDoc for this article. All thoughts, opinions, code, pictures, writing, etc. are those of my own.

The GoDev Corner unique separator

--

--

Marvin
The GoDev Corner