# Request-edge search with QueryParams and Phoenix
This guide is the canonical v1.21 request-edge story: browser params enter at the web edge, `Scrypath.QueryParams` normalizes them into plain data, optional `Scrypath.Phoenix` helpers round-trip params and attempted values, your context calls `Scrypath.search/3`, and the runtime stops there.
If you want the broader onboarding path first, read [Getting Started](getting-started.md) or the [Golden path](golden-path.md). If you want reusable search defaults, metadata-backed host rendering, or multi-search composition after this shared contract, continue with [Composing real-app search](composing-real-app-search.md).
## The Boundary
Keep the lane narrow and explicit:
1. Browser params arrive in a controller, LiveView, or another app-owned web edge.
2. `Scrypath.QueryParams.normalize/1` turns request-shaped params into one stable plain-data contract.
3. `Scrypath.Phoenix` is optional glue for params, forms, and URL round-tripping.
4. `QueryParams.to_search_args/1` prepares `{query, search_opts}` for your context.
5. Your context calls `Scrypath.search/3`.
`Scrypath.Phoenix` does not execute search, own socket lifecycle, or replace contexts. `%Scrypath.Query{}` is not public API.
## Framework-light core
`Scrypath.QueryParams` is the framework-light public edge seam:
```elixir
case Scrypath.QueryParams.normalize(params) do
{:ok, query_params} ->
{query, search_opts} = Scrypath.QueryParams.to_search_args(query_params)
MyApp.Content.search_posts(query, search_opts)
{:error, error_map} ->
{:error, error_map}
end
```
That shape works outside Phoenix too. The normalized output is plain data that feeds the same context-owned runtime path.
## Optional Phoenix glue
If you are in Phoenix, `Scrypath.Phoenix` removes repeated request-edge glue without becoming a second runtime:
```elixir
alias Scrypath.Phoenix, as: SearchPhoenix
alias Scrypath.QueryParams
case SearchPhoenix.from_params(params) do
{:ok, query_params} ->
form = SearchPhoenix.to_form_data(query_params)
{query, search_opts} = QueryParams.to_search_args(query_params)
{:ok, result} = MyApp.Content.search_posts(query, search_opts)
{:ok, %{form: form, result: result}}
{:error, error_map} ->
{:error, SearchPhoenix.to_form_data(params, error_map)}
end
```
Use that helper layer for:
- browser-shaped param normalization
- renderable attempted values plus field/form errors
- URL param round-tripping
Do not use it for:
- search execution
- repo access
- controller macros or `use Scrypath.Phoenix`
- LiveView socket ownership
## Contexts stay canonical
Contexts remain the application boundary for search orchestration:
```elixir
defmodule MyApp.Content do
alias MyApp.Blog.Post
alias MyApp.Repo
def search_posts(query, opts \\ []) do
Scrypath.search(Post, query,
Keyword.merge([backend: Scrypath.Meilisearch, repo: Repo], opts)
)
end
end
```
That is where repo-backed hydration, backend choice, preload policy, sync mode choice, and feature-level defaults belong.
## Controller and LiveView flow
Controllers and LiveView stay thin:
- controllers normalize params, call the context, and render HTML or JSON
- `handle_params/3` remains the canonical LiveView source of truth for URL-driven search state
- the same `QueryParams` / `SearchPhoenix` contract feeds both
See:
- [Phoenix controllers and JSON](phoenix-controllers-and-json.md)
- [Phoenix LiveView](phoenix-liveview.md)
- [Faceted search with Phoenix LiveView](faceted-search-with-phoenix-liveview.md)
## Example app vs guides
HexDocs is the teaching surface for this public contract. The runnable example app is the proof/runbook surface for the real Postgres + Meilisearch + Oban path, CI parity, and local smoke commands:
- teaching surface: this guide plus the rest of `guides/`
- proof/runbook surface: [`examples/phoenix_meilisearch/README.md`](../examples/phoenix_meilisearch/README.md)
Use the guide to understand the boundary. Use the example when you want to prove the operational path against real services.
## Continue
- [Composing real-app search](composing-real-app-search.md)
- [Phoenix Walkthrough](phoenix-walkthrough.md)
- [Phoenix Contexts](phoenix-contexts.md)
- [Phoenix Controllers and JSON](phoenix-controllers-and-json.md)
- [Phoenix LiveView](phoenix-liveview.md)