SuperAPI AI Directory

phoenix

Coding & development

View repository

Updated 10-15 20:31

About this skill

Phoenix 1.8

Treat Phoenix as the HTTP/realtime edge over OTP. Language, processes, and Mix: skill elixir. New code: Phoenix 1.8 — scopes for data access, LiveView when the UI is server-rendered, JSON controllers when the client is separate.

Stack and scope

Ask Role
Phoenix 1.8 Endpoint, router, controllers, LiveView
Ecto Schemas, changesets, migrations, Repo
Postgrex Postgres (preferred)
PubSub Fan-out / LiveView updates
Swoosh E-mail (auth magic link included)
Bandit HTTP server (recent default)

Preserve what mix phx.new / the repo already generated (Tailwind, esbuild, core_components).

Organization

lib/
  my_app/                 # contexts (domínio)
    accounts/
    blog/
  my_app_web/
    router.ex
    endpoint.ex
    controllers/
    live/
    components/
    plugs/
priv/repo/migrations/
test/
  • Contexts (MyApp.Blog) are the domain API: list_posts(scope), get_post!(scope, id).
  • MyAppWeb only HTTP/LiveView: plug, params, render. No SQL in LiveView.
  • router.ex declares pipelines and live_session; do not accumulate logic.

Non-negotiable rules

  • Authn ≠ Authz. Scope (user, org, tenant) goes into every context function that reads/writes user data.
  • Generators (phx.gen.live, html, json) already thread scope through — do not remove the parameter "to simplify".
  • Changesets validate at the edge; never Repo.insert with a raw params map.
  • Migrations: never edit a migration already applied in production — create another.
  • Do not expose Ecto schema with internal fields (password hash, tokens) in JSON/LiveView — use structs/JSON views.
  • Secrets via runtime config / env (config/runtime.exs), not hardcoded.
  • force_ssl and secure cookies in prod.

Scopes (1.8)

The scope centralizes the request context (current_user, org, …) and filters queries/PubSub.

def list_posts(%Scope{} = scope) do
  Post
  |> where(user_id: ^scope.user.id)
  |> Repo.all()
end

def get_post!(%Scope{} = scope, id) do
  get_post_query(scope)
  |> Repo.get!(id)
end
  • LiveView: authenticated routes inside live_session with on_mount that defines current_scope.
  • PubSub: already scoped topics (org:#{id}) to avoid leaking cross-tenant events.
  • mix phx.gen.auth defines the default scope and magic link — do not switch to password just "because it was always like that" without a request.

Router and pipelines

pipeline :browser do
  plug :accepts, ["html"]
  plug :fetch_session
  plug :fetch_live_flash
  plug :put_root_layout, html: {MyAppWeb.Layouts, :root}
  plug :protect_from_forgery
  plug :put_secure_browser_headers
end

pipeline :api do
  plug :accepts, ["json"]
end

scope "/", MyAppWeb do
  pipe_through [:browser, :require_authenticated_user]

  live_session :authenticated,
    on_mount: [{MyAppWeb.UserAuth, :ensure_authenticated}] do
    live "/posts", PostLive.Index, :index
  end
end
  • Browser vs API in separate pipelines.
  • Auth plugs in the pipeline; fine-grained authorization in the context (scope).

LiveView

  • mount / handle_params to load data via context + scope.
  • Events: handle_event lean → context → assign / stream.
  • Streams for lists; avoid sending giant collections over the socket.
  • live_redirect / patches for navigation; CSRF is already in the Phoenix form.
  • JS hooks only for browser APIs (clipboard, map); not for business rules.

Ecto

  • Schema ≠ public API. Changeset per action (create_changeset, update_changeset).
  • Ecto.Multi / Repo.transaction for compound writes.
  • Avoid N+1: preload conscious or join.
  • Database constraints (unique, FK) + changeset (unique_constraint).
  • Soft delete only with consistent filtering in all queries of the context.

JSON / controllers

  • action_fallback and stable views/JSON {data | errors}.
  • Params: params["id"] + changeset; strong-ish via embedded schema or changeset.
  • Correct HTTP statuses (404, 401, 403, 422). Contracts: skill api-design.

Tests

  • ConnCase / LiveViewTest / DataCase.
  • Cover: happy path, 401/403, invalid changeset, scope (user A does not read user B's resource).
  • Ecto sandbox; do not share a real DB between async tests without setup.

Anti-patterns

  • Repo.get without scope on a user resource
  • SQL/Ecto in the handle_event
  • Migration rewritten after deploy
  • LiveView for a mobile API (prefer JSON)
  • Subscribe to global PubSub and filter "on the client"
  • String.to_atom in params

Integration with other skills

  • Language/OTP: elixir
  • HTTP contracts: api-design
  • Deep authz: authentication-authorization
  • Container: docker
  • Go-live: production-readiness

Completion criteria

  • Contexts receive scope; queries filtered
  • LiveView or JSON aligned with the real client
  • Changesets + new migration (not edited) if the schema changed
  • Authn in the pipeline; authz in the domain
  • Ownership/403 tests passing
  • Config via env; SSL/cookies ok for the target environment