iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Input Types

Input types are the noun shape for mutations. They keep arguments composable, type-safe, and easy to evolve.

GraphQL — input types

EXAMPLE
# ===== The problem =====
# Mutation arguments can be N positional fields. That gets ugly fast:
type Mutation {
  createOrder(
    customerId: ID!
    line1Sku: String!
    line1Qty: Int!
    line2Sku: String
    line2Qty: Int
    note: String
  ): Order!
}

# Hard to evolve, hard to validate, no reuse.

# ===== The shape: input type =====
input OrderLineInput {
  sku: String!
  qty: Int!
}

input CreateOrderInput {
  customerId: ID!
  lines: [OrderLineInput!]!
  note: String
}

type Mutation {
  createOrder(input: CreateOrderInput!): Order!
}

# Clients send:
# mutation {
#   createOrder(input: {
#     customerId: 'c-1',
#     lines: [{ sku: 'A-1', qty: 2 }, { sku: 'B-2', qty: 1 }],
#     note: 'gift'
#   }) { id total }
# }

# ===== Rules =====
# 1. Input types CANNOT have fields of object/interface/union types.
# 2. Input types CAN nest other input types and lists of them.
# 3. Input fields can be required (!) or optional.
# 4. Input types are independent of output types — design them for the mutation.

# ===== Resolver (apollo / yoga / mercurius) =====
const resolvers = {
  Mutation: {
    createOrder: async (_parent, { input }, ctx) => {
      const total = input.lines.reduce((s, l) => s + l.qty * await ctx.price(l.sku), 0);
      const order = await ctx.db.orders.create({
        data: {
          customer_id: input.customerId,
          note: input.note ?? null,
          total_cents: total,
          lines: { createMany: { data: input.lines } },
        },
      });
      return order;
    },
  },
};

# ===== Evolution: never break clients =====
# Add optional fields with sensible defaults; never reuse or remove required ones.
input CreateOrderInput {
  customerId: ID!
  lines: [OrderLineInput!]!
  note: String
  giftWrap: Boolean = false       # added in v2; old clients unaffected
}

# ===== Patterns to internalise =====
# - One input type per mutation, named <Verb><Subject>Input
# - Input types model intent, NOT storage shape
# - Required vs optional should mirror the business rule, not the DB schema
# - Lists of inputs let you batch (create many in one round trip)
# - Validate in the resolver AND with custom scalars/directives where possible

# ===== Pitfalls =====
# - Reusing the output Order type as input -> not allowed; input is a distinct kind
# - Promoting every existing field of a type into the input -> leaks storage shape
# - Optional everywhere -> resolver has to defensively handle every combination
# - Versioning by suffix (CreateOrderInputV2) -> avoid; evolve the existing input with optionals
# - Returning bools from mutations -> return the affected entity so clients update caches

Why it matters

Input types are how you keep the mutation API tidy as the system grows. One input per mutation, model the intent (not the storage), evolve by adding optionals. The clients never break, the resolvers stay readable, and the schema doc tells the story of what the system can do.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
input UserInput { name: String! email: String! }
type Mutation { createUser(input: UserInput!): User! }
Try it Yourself »

Discussion

Loading…