How to create and publish Hex.pm package (Elixir)

Krzysztof Kempiński
kkempin’s dev blog
6 min readNov 29, 2017
How to build and publish Hex.pm Elixir package

In that article I’m going to show you how to create your own Elixir package from scratch and how to upload it to Hex.pm website which is a package manager for Elixir (and Erlang) ecosystem.

Although you don’t have to upload your package to hex.pm to make it available for others, it’s good to do this as this is the biggest catalog of useful Erlang and Elixir libraries (you can have it on GitHub for example). Hex.pm also gives some useful information like number of downloads.

I have my own Hex.pm package already. You can find it here https://hex.pm/packages/exiban and it’s a library to validate and manipulate on IBAN bank account numbers.

I’m gonna show you in a few steps how I’ve built it and made it available.

Requirements

There is no any bigger requirement before you can start, except of knowledge of Elixir and what mix tool is. Building and publishing Hex.pm package is really easy and it’s nothing more than creating a new Mix application.

I’ll show you how to register your own Hex.pm account, so you don’t need to have it now.

Creating a project

Let’s start with building the project and let’s get our hands dirty with some code!

mix new exiban

👈 Your new project has a structure like this. Let’s see what are these files responsible for.

config/config.exs

This is a configuration file for your project and its dependencies (other packages for example). It’s loaded before any dependency, which means that it will not affect parent project that depends on it.
We can add our own configuration, for example:

config :exiban, iban_length: 28

You can use that setting in your code like this:

Application.get_env(:exiban, :iban_length)

Also any application that will use your package can change that configuration same as you can configure any third party packages that you use in your project.

mix.exs

It’s a file when you add dependencies (any 3-rd party packages) and describe your package. Main function looks like this:

def project do
[app: :exiban,
version: "0.0.1",
elixir: "~> 1.2",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
description: description,
package: package,
deps: deps]
end

description and package can look like below. It’s a good place to describe your package:

defp description do
"""
Library for manipulating and validating IBAN account numbers.
"""
end
defp package do
[
files: ["lib", "mix.exs", "README*", "LICENSE*"],
maintainers: ["Krzysztof Kempiński"],
licenses: ["MIT"],
links: %{"GitHub" => "https://github.com/kkempin/exiban"}
]
end

Important part of mix.exs file is a definition of dependencies. In my case I want to add ex_doc that we will use in a minute to generate the documentation. deps function can be like this:

defp deps do
[
{:ex_doc, "~> 0.18", only: :dev}
]
end

lib/

Your business logic is located in that directory. That means that everything that represents features of your package will be here.

At the beginning we have aexiban.ex file with a module:

defmodule Exiban do
@moduledoc """
Documentation for Exiban.
"""
@doc """
Hello world.
## Examples iex> Exiban.hello
:world
"""
def hello do
:world
end
end

Except of a documentation and examples that we’ll discuss later, that file is where everything starts, so most probably it will expose public API and will import other modules. That is only a hint because that how you wire up your package it’s totally up to you. Next most common step is to create modules in the namespace of Exiban . That means that you need to create new folder lib/exiban and put there file like parser.ex that will define a module Exiban.Parser etc.

Good to have files

README.md
It’s a good practice to describe your project, give some examples of usage and provide some useful information (https://github.com/kkempin/exiban/blob/master/README.md)

Changelog.md
Most probably you will develop further your package. It’s good to document what kind of changes are introduced in each version (https://github.com/kkempin/exiban/blob/master/CHANGELOG.md)

Documentation

As a part of our dependencies we’ve added a package called ex_doc (https://hex.pm/packages/ex_doc) that generates a documentation in static HTML files based on @moduledoc and @doc sections that are a part of a codebase of our package.

@moduledoc should provide useful information about a whole module whereas @doc for a function. Let’s look at the example below:

defmodule ExIban do
@moduledoc """
Validate and manipulate IBAN account number.
"""
@doc """
Returns country code extracted from IBAN.
## Examples iex> ExIban.country_code("GB82 WEST 1234 5698 7654 32")
"GB"
"""
def country_code(iban) do
{country_code, _, _, _, _} = parse(iban)
country_code
end
end

There are two important things to remember:

  1. we document only public functions with @doc — it makes no sense to document private functions
  2. if possible we should provide examples of usage. It should start with ## Examples followed by empty line. After that goes a fragment of code that should start with iex> . Next line shows an output. Keeping that format is important as we can use it for testing as we will see in a minute.

After we have written whole documentation, we can call

mix docs

to generate HTML format of the documentation in doc/ folder in our app. Now you can open index.html in your browser and see how good your documentation looks like :)

Next advantage is visible in an interactive shell for Elixir (iex -S mix) where with a command h you can see a documentation on a module (h Exiban) or a function (h Exiban.country_code).

In a section about publishing we will see how to upload that documentation on hex.pm so it is visible there.

Next cool feature of hex.pm is that it enables to download that documentation.

Testing

I’ll not start with convincing you that it’s worth to write tests :) In my opinion it’s a must!

You can use three strategies to add tests to your package:

  • write unit tests in test/ folder
  • use a doctest feature
  • mix of these

In my package I use a doctest (https://hexdocs.pm/ex_unit/ExUnit.DocTest.html) feature. In a nutshell, it allows you to generate tests that are a part of a documentation of functions. Do you remember ## Examples section of @doc ? ExUnit (testing framework for Elixir) can use this piece of code (ExIban.country_code(“GB82 WEST 1234 5698 7654 32”)) and see if the output is the same as we provided in the documentation (GB). How cool is that?

Sometimes it’s not enough to cover your package with tests but in most cases that’s exactly what we need. So we have a fancy documentation and tests in one shot. And the only think you have to do is to put one additional line to each file with tests. For example test/exiban_test.exs :

defmodule ExibanTest do
use ExUnit.Case
doctest Exiban
end

Hint! Before you want to publish your package, run mix.test

Publishing to Hex.pm

Finally we have our package with a good documentation and tests. Last thing we need to do is to publish it on hex.pm and tweet about it.

First, let’s register on hex.pm:

mix hex.user register

You will be asked for a username, an email and a password.

Once the process is successfully finished you are ready to go.

Let’s build the project:

mix build

… and publish

mix hex.publish

If everything went ok you will be able to see your package on Hex.pm https://hex.pm/packages/NAME_OF_YOUR_PACKAGE and your documentation on https://hexdocs.pm/NAME_OF_YOUR_PACKAGE

Please remember that a versioning is exactly as you specified in mix.exs file in project function, so keep it consistent and change when you want to publish next version.

I hope that wasn’t difficult and I encourage you to publish your own packages as Elixir ecosystem is still young and needs more tooling.

Want to know first about new articles from that blog?

Subscribe to my newsletter now! — http://eepurl.com/cVPm_v

If you like this article and consider it useful for you, please support it with 👏.

--

--

Krzysztof Kempiński
kkempin’s dev blog

IT expert. Ruby on Rails/iOS/Elixir programmer. Blogger. Podcaster.