Native Windows is not supported. On Windows, develop inside WSL2 with Ubuntu, then follow Prerequisites (Ubuntu) and Setup from a WSL shell.
- Install WSL2 and Ubuntu.
- Clone the repo on the Linux filesystem (for example
~/code/map-backend), not under/mnt/c/.... The Windows mount is much slower and makes Elixir file watching unreliable. - Install Elixir, PostgreSQL, Node.js, and
inotify-toolsinside WSL using the Ubuntu steps below. - Run
./scripts/install-git-hooksfrom WSL (not Git for Windows / PowerShell). - Start the server with
mix phx.serverin WSL, then openhttp://localhost:4000in your Windows browser (WSL forwards localhost).
-
Install Xcode Command Line Tools (needed for native Elixir deps such as
bcrypt_elixir):xcode-select --install
-
Install Elixir 1.20+: Follow the official installation guide (Homebrew, asdf, or mise).
-
Install PostgreSQL (Homebrew):
brew install postgresql@16 brew services start postgresql@16
Use another supported major version if you prefer; keep the service running for local development.
-
Set up PostgreSQL user to match
config/dev.exs(postgres/postgres):psql postgres
Then in the PostgreSQL prompt (create the role if it does not exist, or alter it if it does):
CREATE USER postgres WITH PASSWORD 'postgres' SUPERUSER; -- or, if the role already exists: -- ALTER USER postgres WITH PASSWORD 'postgres' SUPERUSER; \q
-
Install Node.js 22 (same major version as CI), then install asset dependencies:
cd assets npm install cd ..
-
File watching: do not install
inotify-tools. On macOS, Phoenix LiveReload uses FSEvents automatically. -
MapTiler API key: same steps as Prerequisites (Ubuntu) (set
MAPTILER_API_KEYin.env).
-
Install Elixir: Follow the official installation guide
-
Install PostgreSQL:
sudo apt install postgresql sudo systemctl start postgresql
-
Set up PostgreSQL user:
sudo -u postgres psql
Then in the PostgreSQL prompt:
ALTER USER postgres WITH PASSWORD 'postgres'; \q
-
Install inotify-tools (for file watching during development):
sudo apt-get install inotify-tools
-
Install Node.js dependencies:
cd assets npm install cd ..
-
MapTiler API key (for map tiles)
The map uses MapTiler for tiles. To get a free key for local development:- Sign up at cloud.maptiler.com.
- Open Account → API keys.
- Use the default key shown there, or click New key and create one (no restrictions needed for dev).
- In your project root, copy
.env.exampleto.envand setexport MAPTILER_API_KEY=your_key_here(replace with your key).
For production, create a separate key and restrict it (e.g. allowed origins) in the MapTiler dashboard.
Once prerequisites are installed (macOS, Linux/Ubuntu, or WSL2), start your Phoenix server:
- Run
mix setupto install and setup dependencies - Run
mix tz_world.install --include-oceansonce to install timezone boundary data (required for pin schedule timezone lookup). WithoutGITHUB_TOKEN, this falls back to the unauthenticated upstream task. - Run
./scripts/install-git-hooksonce somix precommitruns automatically before each commit - Copy
.env.exampleto.envand add your MapTiler API key (see Prerequisites). The app loads.envautomatically in dev; the file is gitignored. - Start Phoenix endpoint with
mix phx.serveror inside IEx withiex -S mix phx.server
build.sh runs mix tz_world.install --include-oceans during deploy. Set a GITHUB_TOKEN environment variable (read access to public repos is enough) so the build can query GitHub’s releases API without hitting unauthenticated rate limits on shared build hosts.
Now you can visit localhost:4000 from your browser.
- LiveDashboard (
/dev/dashboard, dev only) — request and database telemetry while you exercise the app. - See
docs/PERFORMANCE.mdfor performance tests, hot-path notes, and profiling tips.
Dialyzer checks for type inconsistencies and impossible calls. It is not run by mix precommit; use it locally before larger refactors (especially enum or API boundary changes).
One-time setup (after mix setup, or whenever Elixir deps change):
mix dialyzer.setupThis builds a Persistent Lookup Table (PLT) under priv/plts/ (gitignored). The first run can take several minutes; later runs reuse the PLT and are much faster.
Run analysis:
mix dialyzerKnown false positives are listed in .dialyzer_ignore.exs. Fix real warnings when you can; add narrowly scoped ignores only when Dialyzer cannot understand framework patterns (Ecto, LiveView, etc.).
When to run: before merging enum/policy changes, when adding @spec to context modules, or when debugging suspected string-vs-atom mismatches at API boundaries.
When you touch policy or context modules, add or update types alongside the change:
- Shared result types live in
lib/storymap/types.ex(Types.ecto_result/1,Types.authorize_result/0, etc.). - Schemas exposed in public APIs should define
@type t(seelib/storymap/admin_activity/event.ex); reuse existing enum@typealiases on the same module. - Policy modules —
@specevery public function; booleans forcan_*?,Types.authorize_result()forauthorize_*. - Context modules —
@specpublic functions; useScope.t()for scoped calls andStorymap.Typesfor auth/Ecto tuples. - JSONB / form boundary maps — Elixir typespecs do not support string-literal map keys; use
map()orString.t()and keep runtime validation (e.g.@field_typesinlib/storymap/pin_types/schema.ex).
Run mix dialyzer after adding specs; fix real mismatches in the same change rather than broad ignores.
Ready to run in production? Please check our deployment guides.
- Official website: https://www.phoenixframework.org/
- Guides: https://hexdocs.pm/phoenix/overview.html
- Docs: https://hexdocs.pm/phoenix
- Forum: https://elixirforum.com/c/phoenix-forum
- Source: https://github.com/phoenixframework/phoenix