Dependencies
Need to manage external libraries? Hex is Elixir’s package manager for dependency management and publishing.
Prerequisites
- Elixir and Mix installed
- Basic understanding of Mix projects
- Completed Quick Start Tutorial
Problem
Modern applications rely on external libraries for functionality like HTTP clients, JSON parsing, database access, and more. You need a reliable way to declare, fetch, compile, and update these dependencies while managing version compatibility.
Challenges:
- Declaring dependencies with appropriate version constraints
- Resolving version conflicts between transitive dependencies
- Keeping dependencies updated without breaking changes
- Managing development vs production dependencies
- Understanding dependency trees and compilation order
Solution
Use Hex.pm package manager integrated with Mix for comprehensive dependency management.
How It Works
1. Adding Dependencies
Edit your mix.exs file to declare dependencies:
defmodule MyApp.MixProject do
use Mix.Project
def project do
[
app: :my_app,
version: "0.1.0",
elixir: "~> 1.14",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
defp deps do
[
# Phoenix framework
{:phoenix, "~> 1.7.0"},
# Database
{:ecto_sql, "~> 3.10"},
{:postgrex, ">= 0.0.0"},
# JSON
{:jason, "~> 1.4"},
# HTTP client
{:httpoison, "~> 2.0"},
# Testing
{:ex_machina, "~> 2.7", only: :test},
# Development
{:credo, "~> 1.7", only: [:dev, :test], runtime: false}
]
end
end2. Fetching Dependencies
mix deps.get
mix deps.compile
mix do deps.get, deps.compile3. Version Constraints
Hex uses semantic versioning (SemVer):
{:package, "~> 1.2"} # >= 1.2.0 and < 2.0.0
{:package, "~> 1.2.3"} # >= 1.2.3 and < 1.3.0
{:package, "~> 1.2.3-rc"} # >= 1.2.3-rc and < 1.3.0
{:package, ">= 1.0.0"} # Any version >= 1.0.0
{:package, ">= 1.2.0 and < 2.0.0"}
{:package, "== 1.2.3"} # Exactly 1.2.3
{:package, ">= 0.0.0"}Best practice: Use ~> for flexibility with safety:
{:phoenix, "~> 1.7.0"} # Gets patches and minor updates, not major4. Dependency Sources
From Hex.pm (Default)
{:phoenix, "~> 1.7.0"}From Git Repository
{:my_lib, git: "https://github.com/user/my_lib.git", tag: "v1.0.0"}
{:my_lib, git: "https://github.com/user/my_lib.git", branch: "main"}
{:my_lib, git: "https://github.com/user/my_lib.git",
ref: "abc123def456"}
{:my_lib, github: "user/my_lib", tag: "v1.0.0"}From Local Path
{:my_lib, path: "../my_lib"}Useful for:
- Umbrella applications
- Local development
- Testing changes before publishing
5. Environment-Specific Dependencies
defp deps do
[
# All environments
{:phoenix, "~> 1.7.0"},
# Test only
{:ex_machina, "~> 2.7", only: :test},
# Development only
{:phoenix_live_reload, "~> 1.4", only: :dev},
# Dev and test, not production
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
# Runtime false = compile-time only
{:dialyxir, "~> 1.3", only: :dev, runtime: false}
]
end6. Optional Dependencies
defp deps do
[
{:poison, "~> 5.0", optional: true},
{:jason, "~> 1.4", optional: true}
]
endThe application can use either JSON library without requiring both.
7. Dependency Management Commands
mix deps.tree
mix hex.outdated
mix deps.update phoenix
mix deps.update --all
mix deps.clean --all
mix deps.get
mix deps.compile
mix hex.info phoenix
mix deps.unlock --unused
mix deps.clean --unused8. Lock File
mix.lock ensures reproducible builds:
%{
"phoenix": {:hex, :phoenix, "1.7.10", "..."},
"plug": {:hex, :plug, "1.15.2", "..."},
# ... all transitive dependencies
}Best practices:
- Commit
mix.lockto version control - Update lock file:
mix deps.update package_name - Verify integrity:
mix deps.get --check-locked
9. Private Hex Repositories
For company-internal packages:
config :hex,
api_url: "https://hex.mycompany.com/api",
api_key: System.get_env("HEX_API_KEY")
defp deps do
[
{:internal_lib, "~> 1.0", organization: "mycompany"}
]
end10. Publishing Your Package
mix hex.user registerdef project do
[
app: :my_library,
version: "1.0.0",
description: "A helpful library",
package: package(),
docs: [main: "readme", extras: ["README.md"]]
]
end
defp package do
[
name: "my_library",
files: ~w(lib mix.exs README.md LICENSE),
licenses: ["MIT"],
links: %{"GitHub" => "https://github.com/user/my_library"},
maintainers: ["Your Name"]
]
endmix hex.build
mix hex.publishVariations
Umbrella Application Dependencies
defp deps do
[
{:app_b, in_umbrella: true},
{:phoenix, "~> 1.7.0"}
]
endOverride Transitive Dependencies
defp deps do
[
{:parent_lib, "~> 1.0"},
# Override parent_lib's dependency on old_lib
{:old_lib, "~> 2.0", override: true}
]
endSparse Checkout for Git Dependencies
{:my_lib,
git: "https://github.com/user/monorepo.git",
sparse: "packages/my_lib",
tag: "v1.0.0"
}Advanced Patterns
1. Conditional Dependencies
defp deps do
base_deps() ++ env_deps(Mix.env())
end
defp base_deps do
[{:phoenix, "~> 1.7.0"}]
end
defp env_deps(:test), do: [{:ex_machina, "~> 2.7"}]
defp env_deps(:dev), do: [{:credo, "~> 1.7"}]
defp env_deps(_), do: []2. Dependency Aliases
def project do
[
aliases: aliases()
]
end
defp aliases do
[
setup: ["deps.get", "ecto.setup"],
"ecto.setup": ["ecto.create", "ecto.migrate"],
"deps.refresh": ["deps.clean --all", "deps.get"]
]
end3. Hex Organization Management
mix hex.organization create mycompany
mix hex.organization member add mycompany user@example.com
mix hex.publish --organization=mycompanyUse Cases
Public Packages:
- Web frameworks (Phoenix, Plug)
- Database clients (Ecto, Postgrex)
- HTTP clients (HTTPoison, Finch)
- JSON libraries (Jason, Poison)
Private Packages:
- Shared business logic across services
- Internal authentication libraries
- Company-specific utilities
- Proprietary algorithms
Development Tools:
- Code analysis (Credo, Dialyxir)
- Testing (ExUnit, ExMachina)
- Documentation (ExDoc)
- Live reload (Phoenix.LiveReload)
Troubleshooting
Dependency Conflicts
1. Check dependency tree
mix deps.tree
2. Try updating
mix deps.update --all
3. Use override if necessary
{:package, "~> 1.0", override: true}Compilation Errors
mix deps.clean --all
mix deps.get
mix compile --forceLock File Issues
rm mix.lock
mix deps.getBest Practices
Pin versions in production:
{:phoenix, "~> 1.7.10"} # Not "~> 1.7"Commit mix.lock: Ensures team uses same versions
Review updates:
mix hex.outdated # Review changelog before updating mix deps.update package_nameMinimize dependencies: Each dependency adds compilation time and potential vulnerabilities
Use runtime: false for compile-time tools:
{:credo, "~> 1.7", only: :dev, runtime: false}Document unusual constraints:
# Pinned due to bug in 2.x - see issue #123 {:library, "~> 1.9.0"}
Common Pitfalls
- Using exact versions: Prevents patch updates
- Not committing mix.lock: Team gets different versions
- Ignoring security updates: Run
mix hex.auditregularly - Circular dependencies: Design issue, refactor into shared lib
- Too many dependencies: Each adds complexity and attack surface