Claude-skill-registry absinthe-resolvers
Use when implementing GraphQL resolvers with Absinthe. Covers resolver patterns, dataloader integration, batching, and error handling.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/absinthe-resolvers" ~/.claude/skills/majiayu000-claude-skill-registry-absinthe-resolvers && rm -rf "$T"
manifest:
skills/data/absinthe-resolvers/SKILL.mdsource content
Absinthe - Resolvers
Guide to implementing efficient and maintainable resolvers in Absinthe.
Key Concepts
Basic Resolvers
defmodule MyApp.Resolvers.User do alias MyApp.Accounts def list_users(_parent, args, _resolution) do {:ok, Accounts.list_users(args)} end def get_user(_parent, %{id: id}, _resolution) do case Accounts.get_user(id) do nil -> {:error, "User not found"} user -> {:ok, user} end end def create_user(_parent, %{input: input}, _resolution) do case Accounts.create_user(input) do {:ok, user} -> {:ok, user} {:error, changeset} -> {:error, changeset} end end end
Resolution Context
def current_user(_parent, _args, %{context: %{current_user: user}}) do {:ok, user} end def current_user(_parent, _args, _resolution) do {:error, "Not authenticated"} end
Dataloader Integration
defmodule MyApp.Schema do use Absinthe.Schema def context(ctx) do loader = Dataloader.new() |> Dataloader.add_source(MyApp.Repo, Dataloader.Ecto.new(MyApp.Repo)) Map.put(ctx, :loader, loader) end def plugins do [Absinthe.Middleware.Dataloader] ++ Absinthe.Plugin.defaults() end end # In type definitions object :user do field :posts, list_of(:post) do resolve dataloader(MyApp.Repo) end end
Custom Dataloader Source
defmodule MyApp.Loaders.User do def data() do Dataloader.KV.new(&fetch/2) end defp fetch(:posts_count, user_ids) do counts = MyApp.Posts.count_by_user_ids(user_ids) Map.new(user_ids, fn id -> {id, Map.get(counts, id, 0)} end) end end
Best Practices
- Use Dataloader - Prevents N+1 queries
- Keep resolvers thin - Delegate to context modules
- Handle errors gracefully - Return meaningful error messages
- Use middleware - For cross-cutting concerns like auth
- Batch related queries - Use dataloader batching
Middleware
defmodule MyApp.Middleware.Auth do @behaviour Absinthe.Middleware def call(resolution, _config) do case resolution.context do %{current_user: %{}} -> resolution _ -> resolution |> Absinthe.Resolution.put_result({:error, "Unauthorized"}) end end end # Apply to fields field :admin_data, :string do middleware MyApp.Middleware.Auth resolve &MyApp.Resolvers.Admin.get_data/3 end
Error Handling
defmodule MyApp.Resolvers.Helpers do def handle_changeset_errors(%Ecto.Changeset{} = changeset) do errors = Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} -> Enum.reduce(opts, msg, fn {key, value}, acc -> String.replace(acc, "%{#{key}}", to_string(value)) end) end) {:error, errors} end end
Anti-Patterns
- Avoid business logic in resolvers
- Don't query database directly in resolvers
- Avoid returning raw Ecto changesets
- Don't skip error handling