Back to Blog
Lesson 30 of the GraphQL: Your First GraphQL Schema & Server course
API ArchitectureAugust 17, 20263 min read

Input Types: Structuring GraphQL Mutations for Clean Code

Learn how to define Input types in your GraphQL SDL to group mutation arguments. Stop passing long lists of parameters and start writing cleaner, maintainable APIs.

GraphQLSDLAPI ArchitectureClean CodeMutations
Close-up of hands coding on a laptop, showcasing software development in action.

Previously in this course, we explored implementing a simple mutation to modify our server's data. As your applications grow, you’ll find that creating entities—like a new user or a product—requires passing many individual arguments, which quickly leads to "argument soup." This lesson adds Input types to your toolkit, allowing you to group these arguments into single, reusable objects.

The Problem with Scalar Arguments

When you start a project, a mutation might look like this:

GraphQL
type Mutation {
  addUser(name: String, email: String, age: Int, bio: String): User
}

This is manageable with four arguments. But what happens when you add address, phoneNumber, preferences, and newsletterOptIn? Your resolver function becomes difficult to read, and the schema becomes cluttered. This violates the principles of implementing minimal code, as you end up repeating the same argument structure across different mutations.

Defining Input Types in SDL

In GraphQL, an input type is a special kind of object type used exclusively as an argument. You define it using the input keyword in your SDL. Unlike standard object types, Input types cannot contain fields that are other object types; they can only contain scalars, enums, or other Input types.

Here is how we refactor our addUser mutation to use a structured input:

GraphQL
input UserInput {
  name: String!
  email: String!
  age: Int
  bio: String
}

type Mutation {
  addUser(input: UserInput!): User
}

By grouping these fields into UserInput, we achieve three goals:

  1. Cleaner Schema: The mutation signature is now concise.
  2. Reusability: You can reuse UserInput for an updateUser mutation later.
  3. Better Documentation: Clients can clearly see the structure of the data required to create an object.

Worked Example: Implementing Input Objects

Let's update our resolver to handle this new structure. When you use an input type, the arguments passed to your resolver will be wrapped in an input object.

1. The Schema (typeDefs):

GraphQL
input CreateBookInput {
  title: String!
  author: String!
  publishedYear: Int
}

type Mutation {
  addBook(input: CreateBookInput!): Book
}

2. The Resolver:

JAVASCRIPT
const resolvers = {
  Mutation: {
    addBook: (_, { input }) => {
      // input contains: { title: "...", author: "...", publishedYear: ... }
      const newBook = {
        id: String(books.length + 1),
        ...input,
      };
      books.push(newBook);
      return newBook;
    },
  },
};

This approach mirrors refactoring for modularity by ensuring that the data shape is defined once and handled consistently throughout your application.

Hands-on Exercise

In your current project, identify a mutation that takes more than two arguments.

  1. Create an input type in your typeDefs that encapsulates those arguments.
  2. Update your Mutation type to use this new input.
  3. Modify your resolver to destructure the input object from the args parameter.

Common Pitfalls

  • Mixing Object Types and Input Types: You cannot use a regular type (like User) as an argument for a mutation. You must define a specific input type.
  • Over-nesting: While you can nest input types, avoid deep nesting. If your input object is too complex, it might be a sign that you are trying to perform too many operations in one mutation.
  • Naming Conventions: Use clear, descriptive names for your inputs, such as CreateUserInput or UpdateProductInput, rather than generic names like DataInput.

FAQ

Q: Can I use the same Input type for both create and update mutations? A: You can, but it's often better to create separate inputs. A Create input often requires fields to be non-nullable (!), while an Update input should usually have all fields as optional since the client might only want to update one specific field.

Q: Are Input types cached by GraphQL? A: GraphQL cache behavior is primarily driven by the object types returned, not the input types used to send data.

Recap

Input types allow you to group mutation arguments into a single, structured object. This leads to cleaner, more maintainable APIs and follows the DRY (Don't Repeat Yourself) principle. By adopting this pattern early, you ensure your schema remains scalable as your application complexity grows.

Up next: We will explore how to manage mutation state within our server's memory to keep our lists updated after additions.

Similar Posts