Phoenix webapp starter template
Template to create a starter phoenix web app
Build Steps
used to create this template
Install Elixir
https://elixir-lang.org/install.html
Install phoenix
mix archive.install hex phx_new
Create a new project
mix phx.new starter
cd starter
DB setup
- Install postgres
- Create
starter_devdb - Edit
config/dev.exsif running postgres on a different port
# Configure your database
config :starter, Starter.Repo,
username: "postgres",
password: "postgres",
hostname: "localhost",
database: "starter_dev",
stacktrace: true,
show_sensitive_data_on_connection_error: true,
pool_size: 10,
port: 6432
- run
mix ecto.create
Start server
run phoenix app and verify that everything is working mix ecto.create
[Step 1] Add auth
Generate Account context and users table along with auth code.
mix phx.gen.auth Accounts User users
mix deps.get
mix ecto.migrate
auth generation adds two tables users and user_tokens. The user table only contains three columns
- hashed_password
- confirmed_at but can be extended to add more columns or etended to support multitenancy model
This includes routes, pages and logic to
- register user
- login user
- changing email and password
[Step 2] UUID as id
change the migration file priv/repo/migrations/<>_name.exs to remove default integer id primary key and add id column that is of type uuid
create table(:users, primary_key: false) do
add(:id, :uuid, primary_key: true)
update all references and add type option
create table(:users_tokens, primary_key: false) do
add(:id, :uuid, primary_key: true)
add(:user_id, references(:users, type: :uuid, on_delete: :delete_all), null: false)
in model schema files add following line to indicate how to autogenerate uuid
lib/starter/accounts/user.ex
lib/starter/accounts/user_token.ex
import Ecto.Changeset
@primary_key {:id, :binary_id, autogenerate: true}
schema ".....
add type info in any references in schema
lib/starter/accounts/user_token.ex
schema "users_tokens" do
field(:token, :binary)
field(:context, :string)
field(:sent_to, :string)
belongs_to(:user, Starter.Accounts.User, type: :binary_id)
reset DB and run migration again
mix ecto.migrate
[Step 3] Add seed data
priv/repo/seeds.exs
# Cleanup tables
Starter.Repo.delete_all(Starter.Accounts.User)
Starter.Repo.delete_all(Starter.Accounts.UserToken)
Starter.Accounts.register_user(%{
email: "email@example.com",
password: "paswordpaswordpasword"
})
execute seed file
mix run priv/repo/seeds.exs
Verify data by running the service in repl
iex -S mix phx.server
iex(1)> Starter.Repo.all(Starter.Accounts.User)
[debug] QUERY OK source="users" db=7.3ms decode=1.2ms queue=1.2ms idle=1816.0ms
SELECT u0."id", u0."email", u0."hashed_password", u0."confirmed_at", u0."inserted_at", u0."updated_at" FROM "users" AS u0 []
↳ :elixir.eval_external_handler/3, at: src/elixir.erl:396
[
#Starter.Accounts.User<
__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
id: "1e1cf2d7-912b-48c8-8723-a15123eb7797",
email: "email@example.com",
confirmed_at: nil,
inserted_at: ~U[2024-01-18 11:24:35Z],
updated_at: ~U[2024-01-18 11:24:35Z],
...
>
]
[Step 4] UI changes
Install daisyUI
cd assets
npm init # accept all default options
npm i -D daisyui@latest
add daisyui as plugin in tailwind config assets/tailwind.config.js
plugins: [
...,
require("daisyui")
]
Add app components
create a new component file lib/starter_web/components/library_components.ex
defmodule StarterWeb.LibraryComponents do
use Phoenix.Component
slot(:left_actions, default: nil)
slot(:middle_actions, default: nil)
slot(:right_actions, default: nil)
def navbar(assigns) do
~H"""
<div class="navbar bg-primary min-h-[48px] max-h-[48px]">
<div class="navbar-start">
<img alt="starter logo" src="/images/logo.svg" class="w-[48px] h-[48px]" />
<%= render_slot(@right_actions) %>
</div>
<div class="navbar-center">
<%= render_slot(@middle_actions) %>
</div>
<div class="navbar-end">
<%= render_slot(@left_actions) %>
</div>
</div>
"""
end
slot(:inner_block, required: true)
def content_placeholder(assigns) do
~H"""
<div class="hero">
<div class="hero-content text-center">
<%= render_slot(@inner_block) %>
</div>
</div>
"""
end
end
register the components
lib/starter_web.ex
defp html_helpers do
quote do
# HTML escaping functionality
import Phoenix.HTML
# Core UI components and translation
import StarterWeb.CoreComponents
import StarterWeb.LibraryComponents
import StarterWeb.Gettext
Update existing templates
lib/starter_web/components/layouts/app.html.heex
<main class="px-4 py-20 sm:px-6 lg:px-8">
<div class="mx-auto"><%= @inner_content %></div>
</main>
lib/starter_web/controllers/page_html/home.html.heex
<.flash_group flash={@flash} />
<.content_placeholder>
<div>Home Page</div>
</.content_placeholder>
Add navbar to root template
lib/starter_web/components/layouts/root.html.heex
Replace the ul with the navbar component
<.navbar>
<:right_actions>
</:right_actions>
<:middle_actions>
</:middle_actions>
<:left_actions>
<%= if @current_user do %>
<li class="text-[0.8125rem] leading-6 text-zinc-900">
<%= @current_user.email %>
</li>
<li>
<.link
href={~p"/users/settings"}
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
>
Settings
</.link>
</li>
<li>
<.link
href={~p"/users/log_out"}
method="delete"
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
>
Log out
</.link>
</li>
<% else %>
<li>
<.link
href={~p"/users/register"}
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
>
Register
</.link>
</li>
<li>
<.link
href={~p"/users/log_in"}
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
>
Log in
</.link>
</li>
<% end %>
</:left_actions>
</.navbar>
The app's home page should look like this:
use daisyUI theme/component and tailwind to improve further
[Step 5] Deploy using fly.io
fly.io supports phoenix application out-of-the-box so just follow the lates documentation
The docker image build might fail. This happens if you have node_modules installed in assets, like daisyUI in this template.
To fix add following lines in your fly generated Dockerfile and run fly deploy
after # install build dependencies step
# node and npm for assets
RUN apt-get update && apt-get install -y nodejs
RUN apt-get update && apt-get install npm -y
afetr COPY assets assets step
# compile assets npm packages
RUN npm --prefix ./assets ci --progress=false --no-audit --loglevel=error
The sample is not included in this repo.