Scrypath fits Phoenix best when your context is the application-facing boundary for search orchestration.
What Belongs In The Context
Keep these responsibilities in the context:
- repo persistence
- explicit calls to
Scrypath.sync_record/3,Scrypath.sync_records/3, or delete verbs Scrypath.search/3calls plus repo-backed hydration policy- preload, filter, sort, and paging defaults that belong to the feature
- error handling and operator-visible sync semantics
What Stays Out Of Controllers And LiveView
Do not teach controllers or LiveView modules to compose raw Repo and Scrypath.* calls as the main pattern.
That boundary matters because Phoenix web modules should stay focused on HTTP or UI concerns, while the context owns feature logic and operational decisions.
Example
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
def publish_post(post, attrs) do
with {:ok, post} <- update_post(post, attrs),
{:ok, _sync} <-
Scrypath.sync_record(Post, post,
backend: Scrypath.Meilisearch,
sync_mode: :inline
) do
{:ok, post}
end
end
endMyApp.Content.search_posts/2 is the shared read path for controllers and LiveView. MyApp.Content.publish_post/2 is the write path that owns the sync decision.
Why This Boundary Holds Up
- controllers stay thin
- LiveView state stays UI-focused
- sync failures and rebuild decisions stay close to the feature that owns them
- docs and snippets can derive from one canonical example instead of teaching competing architectures