Thinking

GraphQL APIs – Code First or Schema First?

After you’ve decided to build a GraphQL API instead of a REST API, you will be faced with another choice; Schema First or Code First. Ultimately this is a preference choice so I would recommend you try both. The outcome of both approaches is (hopefully) a well designed, efficient API but the journey you take to reach this end goal is very different depending on the approach you choose.

Two people working together on a whiteboard

Photo by Kaleidico

Schema First

In a schema first approach you begin by manually writing out your GraphQL schema using the Schema Definition Language (SDL). This means that the schema is the single source of truth for your API. Here is an example of a Basic GraphQL schema:

type Query {
  getSelf: User!
}
type User {
  email: String!
  facebookId: String
  fullName: String!
  googleId: String
  id: String!
  name: String!
  verified: Boolean
}

Benefits of Schema First

  • Allows frontend development to begin before the backend is complete. As long as the schema definition is complete with types, it is possible for frontend developers to use the API without any actual backend implementation. This can be achieved using mocking tools to mock API responses as the schema defines response structure.
  • Single Source of truth. With schema first you know that whatever is in the schema is what is available to the client. Both the resolvers that implement the schema and the client that interacts with it to retrieve data, have the same central source of truth, arguably making it easier to understand.

Issues of Schema First

  • Resolver/Schema Inconsistencies. With schema first it is possible that there are resolvers in the code base that are no longer used in the schema as the schema and resolvers are completely detached.

Code First

In a code first approach, the schema is programatically generated based on the types used in your code, rather than using the SDL. Code first approaches work particularly well when using a typed language like TypeScript as certain field types can be inferred. Here’s an example of a basic type definition using code first:

@ObjectType()
export default class User {
  @Field()
  id: string;
  @Field()
  email: string;
  @Field()
  name: string;
  @Field({ nullable: true })
  facebookId: string;
  @Field({ nullable: true })
  googleId: string;
  @Field({ defaultValue: false })
  verified: boolean;
  @Field()
  fullName: string;
}

And here’s a basic resolver:

@Resolver(User)
export default class UserResolver {
  @Query(returns => User)
  async getSelf(@Ctx() { db }: Context) {
    // Demo purposes - would normally return the authenticated user
    return db.read.user.findFirst();
  }
  @FieldResolver()
  fullName(@Root() user: User) {
    return user.name + user.email;
  }
}

Benefits of Code First

  • Helps to think in types/resolvers rather than endpoints. When writing a schema using SDL I often fall into the trap of thinking in endpoints, like a REST API. With a code first approach  I find it easier to think about how objects will be resolved, rather than the ‘endpoints’ that are required for the frontend. This helps to keep the flexibility of queries on the frontend.
  • Better developer experience. With code first there is better IDE support which means the speed and efficiency of developing a GraphQL API is often greater than with a schema first approach.

Issues of Code First

  • Potentially harder to read. With a code first approach you must define the field types as well as resolvers, just to generate a schema file that you could arguably do without writing definitions.

There is no right or wrong way of approaching building a GraphQL API. However, we have been using the Schema First approach for the last few years and have seen a general shift away from it, especially in the JavaScript/TypeScript ecosystem. For a larger scale or constantly growing project, a code first approach would be more maintainable and a better approach overall.

This website uses cookies

This website uses cookies to improve your experience. By using PRISM⁵⁵, you accept our use of cookies.