DEV Community

Cover image for Schema-first or Code-first in GraphQL
Francisco Mendes
Francisco Mendes

Posted on

Schema-first or Code-first in GraphQL

With either one, we will have a fully functional GraphQL Api, now depending on the choice, the development of the Api and the addition of new features can become easier or more difficult.

What is Schema-first?

The GraphQL schema is a set of rules that describe the functionality available to the client.

With the Schema-first approach, we have to define all the data structures of our Api. That is, from input data to output data. But this entire process is done manually. And later we will still have to map these same data structures with their appropriate resolvers, whether they are queries or mutations.

Thus, it is evident that the implementation of GraphQL in the backend is more verbose, with chances of code duplication, incorrect definition of data types, among others.

However, one of the strengths is the easy acquisition of knowledge, because most articles, courses and tutorials available on the internet teach how to create an Api with a schema-first approach.

Another positive point is the documentation, because as this is the solution that has been on the market for the longest time, it is easier to create a better documented Api.

What is Code-first?

In the Code-first approach, there are several ways to use it. Many libraries use models (the representation of a table in the database) as a schema and so we can only focus on implementing the resolvers.

And others define the schema through the programming language that you are using instead of keeping everything in a string (as in the case of Schema-first).

After that, the schema and its documentation are automatically generated using the code of our favorite programming language.

One of the positive aspects of the Code-first approach is that many of the problems are solved only with the library you use and so you don't need to install external tools to solve specific problems.

Another positive point is that it makes the code easier to maintain in the long term, as well as the intelissence of the language helps to make fewer mistakes during its implementation in the backend.

What are the diferences?

The Schema-first is easier to read and is more intuitive, since its definition is simpler.

Definition of data types in Schema-first:

const typeDefs = `
  type Post {
    id: String!
    title: String!
    content: String!
    user_id: String!
  }
  type Query {
    findPosts: [Post]
    findPost(id: String!): Post
  }
`;
Enter fullscreen mode Exit fullscreen mode

On the other hand, although Code-first offers extra help like autocomplete and provides specific errors during the build process. It is more difficult to read and the larger its structure, the longer it takes to make a simple change. However, its easy modularization helps in the composition of more complex structures.

Definition of data types in Code-first:

const Post = objectType({
    name: 'Post',
    definition(t) {
        t.string('id', { description: 'ID of the post.'})
        t.string('title', { description: 'Title of the post.'})
        t.string('content', { description: 'Content of the post.'})
        t.string('user_id', { description: 'Author ID.'})
    }
})
Enter fullscreen mode Exit fullscreen mode

But if we take into account the creation of our resolvers, we notice more differences between the two.

This is because in Schema-first, the data definition and the resolvers are separated. While in Code-first, at some point, we will always have to import our data types, which leads to more verbose code.

Example of a Schema-first resolver:

const resolvers = {
  Query: {
    async findPosts(root, args, ctx) {
      return await Post.find()
    },
    async findPost(root, { id }, ctx) {
      return await Post.findOne(id)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

As we can see, although not ideal (in a matter of modularization). It's easy to understand the code. Now if we go to see the Code-first resolvers:

const findPosts = queryField('findPosts', {
  type: list(Post),
  resolve: async (root, args, ctx) => {
    return await Post.find()
  }
})

const findPost = queryField('findPost', {
  type: Post,
  args: {
    id: nonNull(stringArg()),
  },
  resolve: async (root, { id }, ctx) => {
    return await Post.findOne(id)
  }
})
Enter fullscreen mode Exit fullscreen mode

As we can see, the resolvers have much more code, however, as mentioned several times, it is possible to notice their modularization since each one is independent.

Which one to use?

I think you should always start by learning Schema-first, to understand if it is the ideal way for you to develop Apis and is the most popular way on the market. However, if you find that over time, it becomes difficult to keep up with the complexity of the project, you can learn Code-first.

But I think one is not better than the other. Nowadays there are several libraries that help you in the development of GraphQL Apis to give you a better experience. If you are working with TypeScript, I would recommend TypeGraphQL, it is a complete library that can help you with some difficulties you may have.

If use JavaScript (or intend to use TypeScript as well) I recommend using Nexus(was used in the examples of this article), it's a simple and lightweight library that helps you define your data types and create your resolvers.

What about you?

Do you prefer Schema-first or Code-first?

Top comments (2)

Collapse
 
gers2017 profile image
Gers2017

Hey Francisco great article, easy to read and concise.
I'd add to the cons of the schema first approach the schema/data duplication while using an orm.
For example with prisma you'd have your .schema file from prisma and your graphql schema in other

Collapse
 
franciscomendes10866 profile image
Francisco Mendes

Thanks! Yes you are absolutely right! ✌️

But I think Prisma and TypeORM would be an exception if you used TypeGraphql, in Prisma's case the graphql schema would be generated according to Prisma's schema. Whereas with TypeORM you would use entities as a base without needing to define your schema.