GraphQL

Benefits of Idiomatic Code-First GraphQL Development

48views

I’m Derek from Apollo, and today I’d like to discuss the benefits of idiomatic code-first GraphQL development. I work on Apollo Federation, which is an open architecture designed for building distributed GraphQL APIs. Essentially, Apollo Federation allows you to compose a single, unified GraphQL API from multiple GraphQL services. This means clients interact with one cohesive endpoint, while behind the scenes the gateway routes requests to the appropriate underlying services. If you’re building or planning to build GraphQL APIs at scale, Apollo is definitely worth a look.

Let’s begin with a quick refresher on GraphQL itself. GraphQL is a query language for APIs, backed by a runtime that executes requests and returns exactly the requested data. At its core is a schema that defines all available data clients can access. Clients use this schema to craft queries, and when the server receives a query, it returns data precisely matching the requested shape. It’s a powerful and flexible approach to building APIs, and there are numerous GraphQL implementations for nearly every major programming language and framework.

When building GraphQL servers, you’ll generally encounter two high-level approaches: schema-first and code-first. The difference lies in how you define your GraphQL schema and implement your business logic. In a traditional schema-first (sometimes called SDL-first) approach, you start by manually writing your schema in GraphQL’s schema definition language (SDL). Then, you implement the resolver logic—essentially the functions that return data for each field—based on that schema.

In contrast, a code-first approach involves defining your GraphQL API using code. Instead of writing the schema first and then attaching logic, you write code that naturally produces the schema as a result. This often means that the schema is generated directly from code structures like classes, functions, and annotations, making the code itself the single source of truth for the shape and behavior of your API.

To illustrate this, let’s consider a simple “Hello World” query. In a schema-first approach, you’d start with a GraphQL SDL file that defines a “helloWorld” query taking a “name” argument and returning a string. Then you’d implement a resolver function in code. However, this approach can sometimes lead to subtle bugs. For example, if you forget to add an argument in the resolver that the SDL expects, the mismatch can lead to confusion or runtime errors.

By contrast, in a code-first approach—especially what I call “idiomatic code-first”—you write a function in your programming language of choice (like Kotlin, Java, or TypeScript), and this function directly maps to a GraphQL field. If you change the function signature, the schema updates automatically. This eliminates the risk of certain kinds of schema drift, since the schema is generated from the code that actually produces the data.

It’s important to clarify terminology here: both schema-first and code-first approaches ultimately result in a GraphQL schema. After all, the schema is what clients see and interact with. What we’re really distinguishing is the manner of authoring the schema. In a schema-first (SDL-first) approach, you handcraft the schema file and then write logic. In a code-first approach, you write logic and let the framework generate the schema. But every GraphQL server, no matter how it’s built, ends up with a schema at runtime.

Initially, building GraphQL servers directly with code (for example, using the raw GraphQL Java libraries) could be verbose and tedious. You’d end up manually constructing type definitions, fields, and arguments. This might work, but it’s not fun or efficient. Over time, libraries have emerged to improve this process. They provide higher-level abstractions so you can focus on the meaningful part—returning the correct data—while the library generates the schema. This is what I mean by “idiomatic code-first”: relying on a GraphQL framework that leverages your language’s features (such as type safety, annotations, or reflection) to generate the schema seamlessly.

Let’s consider the characteristics of a GraphQL schema and how a good idiomatic code-first library might handle them. GraphQL schemas are strongly typed. Types, fields, arguments, interfaces, unions, and enums all appear in the schema. Certain fields may be non-nullable, represented by an exclamation mark in the SDL. GraphQL also supports polymorphism through interfaces and unions, and it allows rich descriptions for self-documentation. Furthermore, directives can be used to modify behavior or provide metadata, such as marking certain fields as deprecated or requiring authentication.

In a code-first world, it’s ideal if these characteristics can be expressed naturally using the language’s idioms. Suppose you’re using Kotlin with a GraphQL library like “GraphQL Kotlin” from Expedia (which I helped create). With this library, you write simple Kotlin classes and functions, and it uses reflection and annotations to produce the schema. You don’t write schema definitions by hand. Instead, your code—your classes, functions, parameters, and return types—directly map to GraphQL types, fields, and arguments.

For example, if you have a function like:

 

 

 

 

 

 

This code snippet defines a query class, implements a query method taking an optional “name” argument, and returns a string. The GraphQL Kotlin library translates this into a GraphQL query field named “helloWorld” that takes an optional “name” argument and returns a non-null string. There’s no risk of schema-resolver mismatch. If you remove the argument in the code, it disappears from the schema. If you change the return type, the schema updates accordingly.

Moving beyond simple examples, consider a more complex schema, such as a Star Wars API where you might query characters and their attributes. Characters can be implemented as interfaces, and subtypes like “Human” or “Droid” can implement that interface. With an idiomatic code-first approach, you simply define an interface in code and let the library map it to a GraphQL interface. To indicate that a certain field is expensive to resolve and should be fetched lazily, you can turn it into a function instead of a simple property. You can also leverage language features like Kotlin coroutines for asynchronous, non-blocking data fetching, making it easy to optimize performance.

The developer experience benefits are clear. You work with code you understand, and the schema emerges from that code. There’s no separate SDL file to maintain, no chance of drift, and fewer runtime errors due to mismatches. Testing is straightforward because you can write unit tests for your resolver functions without needing to spin up a full GraphQL server. For integration tests, you can still verify the entire system, but the everyday developer workflow becomes smoother and more maintainable.

Another big advantage is type safety and compile-time verification. In a code-first approach, especially when using a statically typed language like Kotlin or TypeScript, your GraphQL schema effectively becomes a natural extension of your language’s type system. This catches many errors at compile time. If you change a field’s type, the code that consumes it must adjust accordingly, preventing subtle bugs from sneaking into production.

Additionally, code-first approaches support GraphQL’s self-documentation. You can annotate classes, methods, and arguments with descriptions and directives, and these annotations will appear in the generated schema’s documentation. Instead of maintaining separate documentation or comments in SDL files, you keep everything in one place—your code. This reduces duplication and ensures that the schema documentation remains up-to-date as you evolve your logic.

Finally, code-first development still gives you all the benefits of GraphQL’s flexibility. You can introduce custom directives, integrate with Apollo Federation, and expose a complete schema that clients can introspect. Because the schema is generated at build time, you know clients are seeing exactly what your code defines. You get a single source of truth, tighter feedback loops, and an easier time scaling as your API grows more complex.

In summary, idiomatic code-first GraphQL development offers numerous benefits:

  • Improved Developer Experience: Write natural code and let the library generate the schema.
  • Single Source of Truth: Your code defines your schema, preventing mismatches and drift.
  • Type Safety & Compile-Time Checks: Leverage your language’s type system to catch errors early.
  • Easier Testing: Test resolvers as normal functions without complex setup.
  • Rich Documentation: Maintain descriptions, directives, and other metadata right in your code.
  • Complete, Consistent Schema: The schema generated at build time matches what clients see, ensuring accuracy and consistency.

If you’re building GraphQL APIs, especially at scale, embracing an idiomatic code-first approach can streamline development, reduce errors, and improve the overall stability and maintainability of your API.

Derek Kuc

Derek Kuc

Principal Engineer at Apollo GraphQL

APIdays | Events | News | Intelligence

Attend APIdays conferences

The Worlds leading API Conferences:

Singapore, Zurich, Helsinki, Amsterdam, San Francisco, Sydney, Barcelona, London, Paris.

Get the API Landscape

The essential 1,000+ companies

Get the API Landscape
Industry Reports

Download our free reports

The State Of Api Documentation: 2017 Edition
  • State of API Documentation
  • The State of Banking APIs
  • GraphQL: all your queries answered
  • APIE Serverless Architecture