REST-friendly query parameters for Flop.
Flop is excellent for filtering, sorting, and paginating Ecto queries. But its query parameter format isn't ideal for API consumers:
GET /events
?filters[0][field]=status
&filters[0][op]==
&filters[0][value]=published
&filters[1][field]=starts_at
&filters[1][op]=>=
&filters[1][value]=2024-01-01
&order_by[0]=starts_at
&order_directions[0]=desc
&first=20
This is verbose, error-prone, and unfamiliar to developers used to modern REST APIs.
FlopRest transforms intuitive, Stripe-style query parameters into Flop format:
GET /events?status=published&starts_at[gte]=2024-01-01&sort=-starts_at&limit=20
Same query. Same Flop power underneath. Better developer experience on top.
Building a Phoenix HTML/LiveView app? Use Flop Phoenix — it provides UI components for pagination, tables, and filters.
Building a JSON API? Use FlopRest — it transforms REST-style query parameters that frontend developers expect.
Standard JavaScript works out of the box:
// No special serialization needed
const params = new URLSearchParams({
status: 'published',
'created_at[gte]': '2024-01-01',
sort: '-created_at',
limit: '20'
});
fetch(`/api/events?${params}`);Works naturally with TanStack Query, SWR, RTK Query, axios, or any HTTP client.
Add flop_rest to your list of dependencies in mix.exs:
def deps do
[
{:flop_rest, "~> 0.1"},
{:flop, "~> 0.26"}
]
enddef index(conn, params) do
flop_params = FlopRest.normalize(params)
with {:ok, {events, meta}} <- Flop.validate_and_run(Event, flop_params) do
render(conn, :index, events: events, meta: meta)
end
endBare values become equality filters:
status=published → %{field: "status", op: "==", value: "published"}
Operators are specified as nested keys:
amount[gte]=100 → %{field: "amount", op: ">=", value: "100"}
amount[lt]=500 → %{field: "amount", op: "<", value: "500"}
Multiple operators on the same field create multiple filters:
amount[gte]=100&amount[lt]=500 → two separate filters
List values for in and not_in:
status[in][]=draft&status[in][]=review → %{field: "status", op: "in", value: ["draft", "review"]}
| REST Operator | Flop Operator | Description |
|---|---|---|
eq |
== |
Equal (also bare value) |
ne |
!= |
Not equal |
lt |
< |
Less than |
lte |
<= |
Less than or equal |
gt |
> |
Greater than |
gte |
>= |
Greater than or equal |
in |
in |
In list |
not_in |
not_in |
Not in list |
contains |
contains |
Array contains |
not_contains |
not_contains |
Array does not contain |
like |
like |
SQL LIKE |
not_like |
not_like |
SQL NOT LIKE |
like_and |
like_and |
LIKE with AND |
like_or |
like_or |
LIKE with OR |
ilike |
ilike |
Case-insensitive LIKE |
not_ilike |
not_ilike |
Case-insensitive NOT LIKE |
ilike_and |
ilike_and |
Case-insensitive LIKE with AND |
ilike_or |
ilike_or |
Case-insensitive LIKE with OR |
empty |
empty |
Is NULL |
not_empty |
not_empty |
Is NOT NULL |
search |
=~ |
Search (configurable in Flop) |
Unknown operators are passed through for Flop to validate.
Use - prefix for descending, + or no prefix for ascending:
sort=name → order_by: ["name"], order_directions: ["asc"]
sort=-created_at → order_by: ["created_at"], order_directions: ["desc"]
sort=-created_at,name → order_by: ["created_at", "name"], order_directions: ["desc", "asc"]
FlopRest supports all three Flop pagination types.
limit=20 → first: 20
limit=20&starting_after=abc123 → first: 20, after: "abc123"
limit=20&ending_before=xyz789 → last: 20, before: "xyz789"
page=2&page_size=25 → page: 2, page_size: 25
offset=50&limit=25 → offset: 50, limit: 25
FlopRest is a pure transformation layer. It does not validate parameters - that's Flop's job. Invalid operators or conflicting pagination params are passed through, and Flop will return appropriate errors.
This keeps FlopRest simple and ensures Flop remains the single source of truth for validation rules.
MIT License. See LICENSE for details.