andreashasse

andreashasse

PhoenixSpectral - FastAPI-style type-driven APIs for Phoenix

Hi all,

I’ve been working on PhoenixSpectral, a library that makes Phoenix controller @spec annotations drive both OpenAPI generation and runtime request/response validation — no separate schema definitions.

The main design departure from standard Phoenix is the action signature. Instead of action(conn, params), controllers receive five explicit arguments — (conn, path_args, query_params, headers, body):

@spec update(Plug.Conn.t(), %{id: integer()}, %{notify: boolean()}, %{"x-api-key": String.t()}, User.t()) ::
        {200, %{}, User.t()} | {404, %{}, Error.t()}
def update(_conn, %{id: id}, %{notify: notify}, %{"x-api-key": _key}, body) do
  case MyApp.Users.update(id, body, notify: notify) do
    {:ok, user} -> {200, %{}, user}
    :not_found  -> {404, %{}, %Error{message: "User not found"}}
  end
end

By the time update/5 is called, the incoming HTTP request has already been validated and decoded against the typespec: id is an integer() (not a string), body is a fully populated %User{} struct, etc. Invalid requests are rejected with a 400 before reaching the function. Each source is kept separate because the body is a typed struct (which can’t be merged into a flat map), and the OpenAPI generator needs to know whether a field comes from path, query, header, or body to emit a correct spec. Actions return a {status, headers, body} 3-tuple (union return types produce multiple OpenAPI response entries automatically) or a Plug.Conn directly for streaming and file responses. The repo also contains an example app.

Before stabilising the API I’d love to hear your feedback. Eg, Does five separate arguments feel ergonomic? Does {status, headers, body} as a return type feel right?

Thanks for any thoughts!

Where Next?

Popular in RFCs Top

pknoth
Built on top of boruta | Hex, I am on the way to creating a standalone OAuth 2.0/OpenID Connect server thinking of a lightweight Keycloak...
New
weakwire
Hello people, Big fan of elixir & phoenix LiveView. While designing LiveView applications I use LiveComponent extensively. Issue-I...
New
manuel-rubio
There was some time when I started thinking about giving a boost to Lambdapad, the initiative from @garretsmith in Erlang that I loved wa...
New
BartOtten
This thread once discussed Routex in it’s early form. It has been repurposed to gather feedback and discusses pre-releases. Currently: p...
New
Billzabob
Hello! I have an idea for an Elixir library I’d like to work on, but wanted to get some thoughts from the community first. It would allo...
New
zachallaun
Note: There are a few folks I’d really love to hear from, time permitting. Pinging in case the title isn’t catchy enough :slight_smile: @...
New
adamwight
I’ve written a crude module for retrieving files from a remote server using rsync, since I didn’t see any existing tools already. This c...
New

Other popular topics Top

joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 31586 112
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 49266 226
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New

We're in Beta

About us Mission Statement