Using mix aliases to improve your workflow

When developers onboard new projects, we are uniquely positioned to help improve the onboarding experience for future developers. So, when I join a new project, I relish the opportunity to clarify and simplify setup instructions.

Recently, I joined a new project and took a look at the setup instructions:

## Dev setup

To set up the project, run:

mix deps.get
mix event_store.create
mix event_store.init
mix ecto.create -r R1
mix ecto.create -r R2
mix ecto.migrate -r R1

So, after getting the project’s dependencies, we create an event store and initialize it. Then, we create two ecto repos (R1 and R2) and migrate R1. R2 is a read-only repo, so it doesn’t need to be migrated.

That’s six steps. And they’re all things we could script. So, I asked myself, could we consolidate those steps into a single mix setup step?

I frequently use mix aliases to simplify my workflow, so I checked the project’s mix.exs file to see what was there. To my surprise, the project didn’t have any aliases defined. And that was an opportunity for improvement.

Improving setup

I added the aliases entry in project and defined a setup task:

def project do
  [
    ...
    aliases: aliases()
  ]
end

defp aliases do
  [
    setup: ["deps.get", "event_store.setup", "ecto.setup"],
    "event_store.setup": ["event_store.create", "event_store.init"],
    "ecto.setup": ["ecto.create -r R1", "ecto.create -r R2", "ecto.migrate -r R1"]
  ]
end

Taking inspiration from other Phoenix apps, I defined three aliases: setup, event_store.setup, and ecto.setup. We can run mix event_store.setup and mix ecto.setup separately if we ever need to, but running mix setup performs all the desired actions.

Having done that, I could then change the setup to be a single command:

  ## Dev setup

  To set up the project, run:
-
- mix deps.get
- mix event_store.create
- mix event_store.init
- mix ecto.create -r R1
- mix ecto.migrate -r R1
- mix ecto.create -r R2
+ mix setup

Beautiful! 🤩

Improving tests

As I read further down the README, I noticed we had a similar setup with tests:

## Test setup

MIX_ENV=test mix ecto.create -r R1
MIX_ENV=test mix ecto.create -r R2
MIX_ENV=test mix ecto.migrate -r R1

I could have created a test.setup alias, but instead, I took inspiration from Phoenix apps to create an improved mix test alias.

The alias quietly creates and migrates test databases without having a separate test-setup step:

def aliases do
  [
    ...
    test: [
      "ecto.create -r R1 --quiet",
      "ecto.create -r R2 --quiet",
      "ecto.migrate -r R1 --quiet",
      "test"
    ]
  ]
end

So, when we run mix test, we do the following:

  • create the databases if they don’t exist,
  • migrate R1’s data, and
  • run the tests

Perfect! We no longer need a separate test-setup step. We can just run our tests with mix test, and the alias will take care of creating databases and migrating data.

Even more aliases

There’s a lot more you can do with mix aliases. For example, in other projects, I’ve defined an alias to watch assets and prepare them for deployment:

defp aliases do
  [
    ...
    "assets.deploy": [
      "tailwind default --minify",
      "esbuild default --minify",
      "phx.digest"
    ],
    "assets.watch": ["esbuild default --sourcemap=inline --watch"]
  ]
end

The more I can consolidate within mix, the better I like it.

So give mix aliases a try. And let me know what useful aliases you create. I’d love to incorporate those into my own projects! 😁

Want my latest thoughts, posts, and projects in your inbox?

    I will never send you spam. Unsubscribe any time.