elli_openapi
Library for building type-safe HTTP APIs with automatic OpenAPI documentation generation using Elli and Spectra. This library is not ready for production use, but it wont take long to finish it.
Usage
- Add to your rebar.config dependencies:
{deps, [
{elli_openapi, "~> 0.1.1"}
]}.- Start Elli with elli_openapi_handler as the callback and your routes as arguments to elli_openapi_handler:
%% Define your routes
Routes = [
{<<"POST">>, <<"/api/users">>, fun user_handler:create_user/4},
{<<"GET">>, <<"/api/users/{userId}">>, fun user_handler:get_user/4}
],
%% Configure and start Elli, preferably in your supervisor spec.
ElliOpts = [
{callback, elli_openapi_handler},
{callback_args, Routes},
{port, 3000}
],
{ok, Pid} = elli:start_link(ElliOpts).
You can optionally pass custom OpenAPI metadata by wrapping callback_args in a {MetaData, Routes} tuple:
MetaData = #{title => <<"My API">>, version => <<"1.0.0">>},
ElliOpts = [
{callback, elli_openapi_handler},
{callback_args, {MetaData, Routes}},
{port, 3000}
].
See the example/ directory for a runnable example application with handler implementations.
Handler Functions
All handler functions must follow this signature:
handler_name(PathArgs, QueryArgs, Headers, Body) -> {StatusCode, ResponseHeaders, ResponseBody}Arguments
PathArgs (
map()): URL path parameters extracted from the route-
Example: For route
<<"/api/users/{userId}">>, PathArgs would be#{userId => <<"42">>} -
Empty map
#{}if no path parameters
-
Example: For route
QueryArgs (
map()): URL query parameters-
Example:
#{page => 1, per_page => 20} - Declare expected query params in the function spec; undeclared params are ignored
-
Example:
Headers (
map()): HTTP request headers with atom keys-
Example:
#{'Authorization' => <<"Bearer ...">>, 'Content-Type' => <<"application/json">>} - Required headers must be declared in the function spec
-
Example:
Body (
any()): Request body, automatically decoded based on the type in your function spec-
JSON requests:
map()or record type -
Plain text requests:
binary() -
Bodyless methods (GET, HEAD, etc.): declare as
binary()— an empty body decodes cleanly to<<"">> - The library validates and decodes the body according to your spec
-
JSON requests:
Return Value
Must be a 3-tuple: {StatusCode, ResponseHeaders, ResponseBody}
- StatusCode: HTTP status code integer (200, 201, 400, etc.)
- ResponseHeaders: Map with atom keys (e.g.,
#{'Location' => <<"...">>, 'ETag' => <<"...">>}) - ResponseBody: Response body (record, map, or binary) - will be encoded based on content type
To return different status codes from the same handler, use union types in your function spec where each branch represents a possible response:
-spec my_handler(PathArgs, QueryArgs, Headers, Body) ->
{200, Headers1, SuccessBody}
| {400, Headers2, ErrorBody}
| {404, Headers3, NotFoundBody}.Spec placement
-spectra() metadata attributes and -spec declarations must appear before any function clause in the file. The Erlang compiler processes attributes in declaration order — placing them after a function clause will cause them to be ignored or crash at startup.
%% Correct order
-spectra(#{summary => <<"Create user">>}).
-spec create_user(#{}, #{}, #{}, #user{}) -> {201, #{}, #user{}}.
create_user(#{}, #{}, #{}, User) -> ...
%% Wrong — attributes after a function clause are not processed
some_other_function() -> ...
-spectra(#{summary => <<"Create user">>}). %% too late
-spec create_user(...) -> ...
create_user(...) -> ...Handler specs use Spectra's type system. See the Spectra documentation for supported types and serialization rules.
For complete handler examples, see example/src/elli_openapi_demo.erl.
Example Application
The example/ directory contains a runnable demo application showcasing multiple handler implementations including user management, echo, status updates, and item updates with conflict detection.
To run the example:
make demoThe demo starts on port 3000. Access the API documentation at:
- Swagger UI: http://localhost:3000/swagger
- ReDoc: http://localhost:3000/redoc
- OpenAPI JSON: http://localhost:3000/api-docs