Launch GraphOS Studio

Designing response types

Best practices for query and mutation response types


Response types for mutations


See also Structuring mutation responses.

This response type pattern is useful for because they can result in many valid failure states (such as attempting to delete an object that doesn't exist).

For example, let's say we have an e-commerce . When executing a checkout , it's valid for that mutation to fail if a purchased item is out of stock or the buyer has insufficient funds.

If we define a set of ErrorResponse types in our schema, frontend developers can provide customized experiences based on the type of failure that occurred:

interface MutationResponse {
status: ResponseStatus!
type CheckoutSuccess implements MutationResponse {
status: ResponseStatus!
cart: [Product!]!
invoice: Invoice!
interface ErrorResponse {
status: ResponseStatus!
message: String!
type CheckoutError implements ErrorResponse {
status: ResponseStatus!
message: String!
union CheckoutResponse = CheckoutSuccess | CheckoutError
type Mutation {
checkout(cart: ID!): CheckoutResponse!

Response types for queries

Let's say we have a basic API that defines the following Query type:

type Query {
users: [User!]!

This Query.users makes intuitive sense: if you it, you get back a list of User objects. However, this return type doesn't provide any insight into the result:

  • If the list is empty, is that because there are zero users, or did an error occur?
  • Even if the list is populated, did the API return all users or just a subset?
  • Are there multiple pages of results?

To answer questions like these, it may be helpful for top-level of Query to return "wrapper" objects that can include both the result and metadata about the operation's execution. For example, in cases where you need to paginate your results or implement Relay-style connections this pattern can be helpful.

Example: UsersResponse

The following example defines a UsersResponse type for our Query.users field:

type User {
id: ID!
firstName: String!
lastName: String!
type UsersResponse {
offset: Int!
limit: Int!
totalResults: Int!
data: [User!]!
type Query {
users(limit: Int = 10, offset: Int = 0): UsersResponse!

With this change, a client's query can specify how many results per page, what page to start on, and understand the total number of results that can be paginated:

query FetchUsers {
users(limit: 20, offset: 0) {
data {
Edit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc.

Privacy Policy