How do you structure your absinthe projects?

Many of my GraphQL types map kind of well to the database.
Therefore my current structure is:

  1. The schema files define the schemas and nearly every mutation that needs more than one input has its own input object. They call resolver functions in the resolver files, no inline anonymous functions used.
  2. The resolver functions basically pass the full input object to a context function. All they do is unpack the map %{input: args} to args and pass args directly to the context. The resolver functions also modify the returns from the context which look like {:error, :unauthorized} to a human-readable error message.
  3. The context gets the args and often can pass the full map (while only modifying it a bit) to an Ecto changeset.

What I like about this: If I am adding new fields I only need to add them in the database/ecto schema/changeset and the graphql object often, since the full args map gets passed through anyways.

What I dislike about this: The non-existent typing. The resolver and context functions just get these weird maps, I have no auto-completion to help me understand what it is there and always have to switch around files to see. Also sometimes I do not know if I did some small modification to the args map already in the resolver function or if I need to do it in the context function, since I have no clear rule where what should happen.
I am asking myself if I am using Elixir Structs not enough (should I be defining structs for all of this?). I really miss strict typing and the immense amount of help the editor can give you when using it.

How does everyone else structure their absinthe projects?

1 Like