paradox-phoenix

Generate Phoenix applications with controller stubs and PostgreSQL backends from Paradox specifications — the Phoenix equivalent of paradox-express.

Define your types and URI endpoints in .dox, and paradox-phoenix generates a Phoenix project with stub controllers, Ecto schemas, database migrations, and optionally LiveView pages. You fill in the handler logic.

Usage

paradox-phoenix --title "My App" --uris createUser --uris listUsers --uris getUser

All flags:

Flag Default Description

--title

(required)

The name of your app (used as the Phoenix project name)

--uris

(repeatable, required)

URI constant name from the .dox specification (at least one required)

--port

4000

Port to run the Phoenix server on

--live

false

Also generate LiveView stub pages

--database-url

postgres://localhost:5432/paradox

PostgreSQL connection URL

Example

Given this .dox specification:

import std

type Pet
  name: Text
  species: Text

createPet: URI
  http://localhost/pets POST (Pet. Pet)

getPet: URI
  http://localhost/pets/${Integer} GET (Unit. Pet)

listPets: URI
  http://localhost/pets GET (Unit. [Pet])

Run:

paradox-phoenix --title "PetShop" --uris createPet --uris getPet --uris listPets

This generates a Phoenix project at .dox/pet_shop/ with stub controllers you fill in.

How It Works

paradox-phoenix operates in six stages:

  1. Generate specification — Runs paradox generate --spec to produce a JSON representation of the full .dox AST

  2. Generate Elixir modules — Runs paradox generate --elixir to produce Elixir modules with types and validation functions

  3. Generate PostgreSQL DDL — Runs paradox generate --postgresql to produce CREATE TABLE statements, concatenated into schema.sql

  4. Scan Dox modules — Scans generated .ex files for valid_<type>/1 functions that can be bridged into Ecto changesets

  5. Resolve URIs — Looks up each --uris name in the specification, extracts path, HTTP method, and input/output types

  6. Generate Phoenix project — Produces stub controllers, Ecto schemas (for URI-referenced types), router, migrations, and optionally LiveView pages

Generated Output

The generated project lives at .dox/<app_name>/:

.dox/<app_name>/
  mix.exs
  config/
    config.exs
    dev.exs
    runtime.exs
  lib/
    <app_name>/
      application.ex
      repo.ex
      schemas/
        <type>.ex              # Ecto schema for types in URI io fields
    <app_name>_web/
      router.ex                # Routes from URI definitions
      endpoint.ex
      controllers/
        <prefix>_controller.ex # Stub handlers grouped by path prefix
      live/                    # Only if --live
        home_live.ex
        <prefix>_live/
          index.ex
      components/              # Only if --live
        core_components.ex
        layouts.ex
    dox/
      *.ex                     # Copied Dox validation modules
  priv/
    repo/
      migrations/
        *_create_tables.exs

Stub Controllers

Each URI becomes a controller action with a 501 Not Implemented stub. Controllers are grouped by the first path segment:

defmodule PetShopWeb.PetsController do
  use PetShopWeb, :controller

  @doc "POST /pets\nInput: Pet, Output: Pet"
  def create_pet(conn, _params) do
    # TODO: Implement create_pet
    conn
    |> put_status(:not_implemented)
    |> json(%{error: "create_pet not implemented"})
  end

  @doc "GET /pets/:integer\nOutput: Pet"
  def get_pet(conn, %{"integer" => _integer}) do
    # TODO: Implement get_pet
    conn
    |> put_status(:not_implemented)
    |> json(%{error: "get_pet not implemented"})
  end
end

Dox Validation Bridging

When a .dox specification defines valid rules for a type, paradox generate --elixir emits a valid_<type>/1 function in the corresponding Dox module. paradox-phoenix detects these functions and bridges them into Ecto changesets via a validate_with_dox/1 step:

  1. After standard Ecto validations pass, the changeset fields are extracted into a Dox struct

  2. The Dox valid_<type>/1 function is called

  3. If validation fails, error messages are split on "; " and added to the changeset

API Routes

Each --uris flag generates a route under /api/:

Method Path Description

Derived from URI meta

Derived from URI path

Stub handler for the URI endpoint

LiveView Routes (--live)

When --live is passed, a browser scope is added with stub LiveView pages grouped by path prefix. These are minimal stubs for you to fill in.

See Also