Object Types
View SourceObject types are the fundamental building blocks of a GraphQL schema. They represent entities in your domain with fields that can be queried.
Basic Usage
defmodule MyApp.GraphQL.Types.User do
use GreenFairy.Type
type "User", struct: MyApp.User do
@desc "A user in the system"
field :id, non_null(:id)
field :email, non_null(:string)
field :name, :string
field :bio, :string
end
endThis generates:
"""
A user in the system
"""
type User {
id: ID!
email: String!
name: String
bio: String
}Options
The type macro accepts these options:
| Option | Description |
|---|---|
:struct | Backing Elixir struct (enables CQL and auto resolve_type) |
:description | Type description (can also use @desc) |
:on_unauthorized | Default behavior for unauthorized fields (:error or :return_nil) |
type "User", struct: MyApp.User, on_unauthorized: :return_nil do
# fields...
endFields
Simple Fields
Fields map directly to struct/map keys. Use atoms for built-in scalars:
type "User", struct: MyApp.User do
field :id, non_null(:id)
field :email, non_null(:string)
field :name, :string
field :age, :integer
field :is_active, :boolean
field :joined_at, :datetime
endField Descriptions
type "User", struct: MyApp.User do
@desc "Unique identifier"
field :id, non_null(:id)
@desc "Primary email address"
field :email, non_null(:string)
field :name, :string, description: "Display name"
endComputed Fields
Use resolve for fields not directly on the struct:
type "User", struct: MyApp.User do
field :id, non_null(:id)
field :first_name, :string
field :last_name, :string
field :full_name, :string do
resolve fn user, _, _ ->
{:ok, "#{user.first_name} #{user.last_name}"}
end
end
field :initials, :string do
resolve fn user, _, _ ->
initials = "#{String.first(user.first_name)}#{String.first(user.last_name)}"
{:ok, String.upcase(initials)}
end
end
endFields with Arguments
For fields returning other types, use module references:
defmodule MyApp.GraphQL.Types.User do
use GreenFairy.Type
alias MyApp.GraphQL.Types
alias MyApp.GraphQL.Enums
type "User", struct: MyApp.User do
field :avatar_url, :string do
arg :size, :integer, default_value: 100
resolve fn user, %{size: size}, _ ->
{:ok, "#{user.avatar_base_url}?s=#{size}"}
end
end
field :posts, list_of(Types.Post) do
arg :limit, :integer, default_value: 10
arg :status, Enums.PostStatus
resolve fn user, args, _ ->
{:ok, MyApp.Posts.list_for_user(user.id, args)}
end
end
end
endAssociations
Associations are automatically batch-loaded using DataLoader:
defmodule MyApp.GraphQL.Types.User do
use GreenFairy.Type
alias MyApp.GraphQL.Types
type "User", struct: MyApp.User do
field :id, non_null(:id)
# Automatically batch-loaded
field :organization, Types.Organization
field :posts, list_of(Types.Post)
end
endNo custom loaders needed - GreenFairy detects associations from your Ecto schema and uses DataLoader automatically.
See the Relationships Guide for advanced patterns.
Connections (Pagination)
Use connection for Relay-style pagination:
defmodule MyApp.GraphQL.Types.User do
use GreenFairy.Type
alias MyApp.GraphQL.Types
alias MyApp.GraphQL.Enums
type "User", struct: MyApp.User do
field :id, non_null(:id)
connection :posts, Types.Post do
arg :status, Enums.PostStatus
resolve fn user, args, _ ->
MyApp.Posts.paginate_for_user(user.id, args)
end
end
end
endSee the Connections Guide for details.
Implementing Interfaces
defmodule MyApp.GraphQL.Types.User do
use GreenFairy.Type
alias MyApp.GraphQL.Interfaces
type "User", struct: MyApp.User do
implements Interfaces.Node
implements Interfaces.Timestamped
field :id, non_null(:id)
field :inserted_at, non_null(:datetime)
field :updated_at, non_null(:datetime)
# ... other fields
end
endAuthorization
Control field visibility with the authorize callback:
type "User", struct: MyApp.User do
authorize fn user, ctx ->
if ctx[:current_user]?.admin, do: :all, else: [:id, :name]
end
field :id, non_null(:id)
field :name, :string
field :email, :string # Hidden from non-admins
endSee the Authorization Guide for details.
CQL Filtering
Types with a :struct option automatically get CQL filter and order inputs:
type "User", struct: MyApp.User do
field :id, non_null(:id)
field :name, :string
field :age, :integer
end
# Generates: CqlFilterUserInput, CqlOrderUserInputSee the CQL Guide for details.
Complete Example
defmodule MyApp.GraphQL.Types.User do
use GreenFairy.Type
alias MyApp.GraphQL.Interfaces
alias MyApp.GraphQL.Types
alias MyApp.GraphQL.Enums
type "User", struct: MyApp.User do
@desc "A user account in the system"
implements Interfaces.Node
# Basic fields - auto-resolved from struct
field :id, non_null(:id)
field :name, :string
field :email, non_null(:string)
field :status, Enums.UserStatus
field :inserted_at, non_null(:datetime)
# Association - auto batch-loaded
field :organization, Types.Organization
field :posts, list_of(Types.Post)
# Computed field (only when you need custom logic)
field :display_name, non_null(:string) do
resolve fn user, _, _ ->
{:ok, user.name || user.email}
end
end
# Connection with pagination
connection :friends, Types.User
end
endModule Functions
Every type module exports:
| Function | Description |
|---|---|
__green_fairy_kind__/0 | Returns :object |
__green_fairy_identifier__/0 | Returns the type identifier (e.g., :user) |
__green_fairy_struct__/0 | Returns the backing struct module |
__green_fairy_definition__/0 | Returns the full definition map |
__authorize__/3 | Authorization callback |
__cql_filter_input_identifier__/0 | CQL filter input type |
__cql_order_input_identifier__/0 | CQL order input type |
Naming Conventions
| GraphQL Name | Elixir Identifier | Module Suggestion |
|---|---|---|
User | :user | MyApp.GraphQL.Types.User |
BlogPost | :blog_post | MyApp.GraphQL.Types.BlogPost |
APIKey | :api_key | MyApp.GraphQL.Types.APIKey |
Next Steps
- Relationships - Associations and DataLoader
- Connections - Relay-style pagination
- Authorization - Field-level access control
- CQL - Automatic filtering and sorting