mirror of
https://github.com/ash-project/ash_graphql.git
synced 2024-09-21 13:53:20 +12:00
306 lines
7 KiB
Markdown
306 lines
7 KiB
Markdown
# GraphQL Query Generation
|
|
|
|
Following where we left off from [Getting Started with GraphQL](/documentation/tutorials/getting-started-with-graphql.md), this guide explores what the GraphQL requests and responses look like for different queries defined with the AshGraphql DSL.
|
|
|
|
All of the following examples apply to queries & mutations places on the domain as well.
|
|
|
|
## Fetch Data by ID
|
|
|
|
```elixir
|
|
defmodule Helpdesk.Support.Ticket do
|
|
use Ash.Resource,
|
|
...,
|
|
extensions: [
|
|
AshGraphql.Resource
|
|
]
|
|
|
|
attributes do
|
|
# Add an autogenerated UUID primary key called `:id`.
|
|
uuid_primary_key :id
|
|
|
|
# Add a string type attribute called `:subject`
|
|
attribute :subject, :string
|
|
end
|
|
|
|
actions do
|
|
# Add a set of simple actions. You'll customize these later.
|
|
defaults [:read, :update, :destroy]
|
|
end
|
|
|
|
graphql do
|
|
type :ticket
|
|
|
|
queries do
|
|
# create a field called `get_ticket` that uses the `read` read action to fetch a single ticket
|
|
get :get_ticket, :read
|
|
end
|
|
end
|
|
end
|
|
```
|
|
|
|
For the `get_ticket` query defined above, the corresponding GraphQL would look like this:
|
|
|
|
```graphql
|
|
query ($id: ID!) {
|
|
getTicket(id: $id) {
|
|
id
|
|
subject
|
|
}
|
|
}
|
|
```
|
|
|
|
And the response would look similar to this:
|
|
|
|
```json
|
|
{
|
|
"data": {
|
|
"getTicket": {
|
|
"id": "",
|
|
"subject": ""
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Let's look at an example of querying a list of things.
|
|
|
|
```elixir
|
|
graphql do
|
|
type :ticket
|
|
|
|
queries do
|
|
# create a field called `get_ticket` that uses the `read` read action to fetch a single ticket
|
|
get :get_ticket, :read
|
|
|
|
# create a field called `list_tickets` that uses the `read` read action to fetch a list of tickets
|
|
list :list_tickets, :read
|
|
end
|
|
end
|
|
```
|
|
|
|
This time, we've added `list :list_tickets, :read`, to generate a GraphQL query for listing tickets.
|
|
The request would look something like this:
|
|
|
|
```graphql
|
|
query {
|
|
listTickets {
|
|
id
|
|
subject
|
|
}
|
|
}
|
|
```
|
|
|
|
And the response would look similar to this:
|
|
|
|
```json
|
|
{
|
|
"data": {
|
|
"listTickets": [
|
|
{
|
|
"id": "",
|
|
"subject": ""
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
## Filter Data With Arguments
|
|
|
|
Now, let's say we want to add query parameters to `listTickets`. How do we do that?
|
|
Consider `list :list_tickets, :read` and the `actions` section:
|
|
|
|
```elixir
|
|
actions do
|
|
# Add a set of simple actions. You'll customize these later.
|
|
defaults [:read, :update, :destroy]
|
|
end
|
|
|
|
graphql do
|
|
type :ticket
|
|
|
|
queries do
|
|
# create a field called `list_tickets` that uses the `read` read action to fetch a list of tickets
|
|
list :list_tickets, :read
|
|
end
|
|
end
|
|
```
|
|
|
|
The second argument to `list :list_tickets, :read` is the action that will be called when the query is run.
|
|
In the current example, the action is `:read`, which is the generic Read action.
|
|
Let's create a custom action in order to define query parameters for the `listTickets` query.
|
|
|
|
We'll call this action `:query_tickets`:
|
|
|
|
```elixir
|
|
actions do
|
|
defaults [:read, :update, :destroy]
|
|
|
|
read :query_tickets do
|
|
argument :representative_id, :uuid
|
|
|
|
filter(
|
|
expr do
|
|
is_nil(^arg(:representative_id)) or representative_id == ^arg(:representative_id)
|
|
end
|
|
)
|
|
end
|
|
end
|
|
|
|
graphql do
|
|
type :ticket
|
|
|
|
queries do
|
|
# create a field called `list_tickets` that uses the `:query_tickets` read action to fetch a list of tickets
|
|
list :list_tickets, :query_tickets
|
|
end
|
|
end
|
|
```
|
|
|
|
In the `graphql` section, the `list/2` call has been changed, replacing the `:read` action with `:query_tickets`.
|
|
|
|
The GraphQL request would look something like this:
|
|
|
|
```graphql
|
|
query ($representative_id: ID) {
|
|
list_tickets(representative_id: $representative_id) {
|
|
id
|
|
representative_id
|
|
subject
|
|
}
|
|
}
|
|
```
|
|
|
|
## Mutations and Enums
|
|
|
|
Now, let's look at how to create a ticket by using a GraphQL mutation.
|
|
|
|
Let's say you have a Resource that defines an enum-like attribute:
|
|
|
|
```elixir
|
|
defmodule Helpdesk.Support.Ticket do
|
|
use Ash.Resource,
|
|
...,
|
|
extensions: [
|
|
AshGraphql.Resource
|
|
]
|
|
|
|
|
|
attributes do
|
|
uuid_primary_key :id
|
|
attribute :subject, :string
|
|
attribute :status, :atom, constraints: [one_of: [:open, :closed]]
|
|
end
|
|
|
|
actions do
|
|
defaults [:create, :read, :update, :destroy]
|
|
end
|
|
|
|
graphql do
|
|
type :ticket
|
|
|
|
queries do
|
|
get :get_ticket, :read
|
|
end
|
|
|
|
mutations do
|
|
create :create_ticket, :create
|
|
end
|
|
end
|
|
end
|
|
```
|
|
|
|
Above, the following changes have been added:
|
|
|
|
1. In the `attributes` section, the `:status` attribute has been added.
|
|
2. In the `actions` section, the `:create` action has been added.
|
|
3. The `:create_ticket` mutation has been defined in the new `graphql.mutations` section.
|
|
|
|
The `:status` attribute is an enum that is constrained to the values `[:open, :closed]`.
|
|
When used in conjunction with AshGraphql, a GraphQL enum type called `TicketStatus` will be generated for this attribute.
|
|
The possible GraphQL values for `TicketStatus` are `OPEN` and `CLOSED`.
|
|
See [Use Enums with GraphQL](/documentation/guides/use-enums-with-graphql.md) for more information.
|
|
|
|
We can now create a ticket with the `createTicket` mutation:
|
|
|
|
```graphql
|
|
mutation ($input: CreateTicketInput!) {
|
|
createTicket(input: $input) {
|
|
result {
|
|
id
|
|
subject
|
|
status
|
|
}
|
|
errors {
|
|
code
|
|
fields
|
|
message
|
|
shortMessage
|
|
vars
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Note**
|
|
|
|
- The resulting ticket data is wrapped in AshGraphql's `result` object.
|
|
- Validation errors are wrapped in a list of error objects under `errors`, also specified in the query.
|
|
AshGraphql does this by default instead of exposing errors in GraphQL's standard `errors` array.
|
|
This behavior can be changed by setting `root_level_errors? true` in the `graphql` section
|
|
of your Ash domain module:
|
|
|
|
```elixir
|
|
defmodule Helpdesk.Support do
|
|
use Ash.Domain, extensions: [AshGraphql.Domain]
|
|
|
|
graphql do
|
|
root_level_errors? true
|
|
end
|
|
end
|
|
```
|
|
|
|
If we were to run this mutation in a test, it would look something like this:
|
|
|
|
```elixir
|
|
input = %{
|
|
subject: "My Ticket",
|
|
status: "OPEN"
|
|
}
|
|
|
|
resp_body =
|
|
post(conn, "/api/graphql", %{
|
|
query: query,
|
|
variables: %{input: input}
|
|
})
|
|
|> json_response(200)
|
|
```
|
|
|
|
Notice that the `status` attribute is set to `"OPEN"` and not `"open"`. It is important that the value of the `status` be uppercase.
|
|
This is required by GraphQL enums. AshGraphql will automatically convert the value to the correct case.
|
|
|
|
The response will look something like this:
|
|
|
|
```json
|
|
{
|
|
"data": {
|
|
"createTicket": {
|
|
"result": {
|
|
"id": "b771e433-0979-4d07-a280-4d12373849aa",
|
|
"subject": "My Ticket",
|
|
"status": "OPEN"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Again, AshGraphql will automatically convert the `status` value from `:open` to `"OPEN"`.
|
|
|
|
## More GraphQL Docs
|
|
|
|
If you haven't already, please turn on the documentation tag for AshGraphql. Tags can be controlled
|
|
at the top of the left navigation menu, under "Including Libraries:".
|
|
|
|
- [Getting Started With GraphQL](/documentation/tutorials/getting-started-with-graphql.md)
|
|
- `AshGraphql.Domain`
|