Seamless Digital Experience.
Happy Customers.

Digital Experience and Error Monitoring Platform - Zipy

Learn how to use Modularization with GraphQL Modules

Aryan Raj
~ 6 min read | Published on Jan 24, 2024





TABLE OF CONTENT

Fix bugs faster with Zipy!

  • Session replay
  • Network calls
  • Console Logs
  • Stack traces
  • User identification
Get Started for Free

Finding strategies to control the expanding complexity of your resolvers and schema as your GraphQL application grows is crucial. Creating distinct files for your associated resolvers and schema types is one of the most efficient methods to organise your code.

The GraphQL schema code can be modularized using a variety of techniques. Additionally, a simple setup will suffice to produce good results. Your schema and resolver code can be organised in different files using a few basic JavaScript ideas.

The ‘graphql-tools’ schemas will be modularized in this post using a simple technique that you can adapt to your taste and codebase.

What is GraphQL?

In 2012, Facebook created GraphQL, a query language and API runtime that was made available as an open-source project in 2015. Instead of needing to retrieve huge volumes of unnecessary data, as is sometimes the case with conventional REST APIs, it enables clients to specify precisely what data they need from an API and receive only that data.

Query languages GraphQL, gRPC, and REST - all approach client-server communication and data transfer differently, with varying levels of flexibility, performance, and complexity. GraphQL can handle a variety of data requirements and even allow clients to specify the exact data they need. It has a tightly typed schema, which makes it simpler to comprehend and maintain big, complicated systems by providing clear documentation and validation of API actions.

GraphQL well suited for contemporary web development, where data requirements are frequently intricate and dynamic.

What is importance of Modularization in GraphQL?

When a huge, complex system is broken down into smaller, easier-to-manage sections, or modules, the process is called modularization. Modularization in GraphQL development refers to the division of a complex schema into simpler, easier to comprehend, and maintainable smaller, more focused schemas.

You can easily find faults and make changes without upsetting the entire system by segmenting your schema into modules. Additionally, by enabling you to add new functionality or data sources without disrupting already-existing portions of the schema, modularization can enhance scalability. In general, modularization can assist your GraphQL schema become more modular, understandable, and versatile.

What are GraphQL schema and how are they structured?

A GraphQL schema formally describes the data that a GraphQL API can give. In addition to detailing the actions that may be performed on the data, it also specifies the types of data that can be modified and queried. GraphQL schemas typically consist of three components:

Object Types - The GraphQL API models the system by utilising objects to represent the various system aspects. In an e-commerce platform, for example, there may be object types for Customers, Orders, and Items.

Fields - Object-type properties are represented by fields. A Product object, for example, may have properties for the name, price, and description.

Queries and Mutations - Queries are used to retrieve data from the API, where as mutations are used to change it. Queries and mutations are defined in respective "Root Query" and "Root Mutation" fields of a unique object type.

Here's an example of a GraphQL schema for a simple blog application:

    
# Define the Post type with its fields and relationships type Post { id: ID! # Non-nullable ID field for the post's unique identifier title: String! # Non-nullable String field for the post's title content: String! # Non-nullable String field for the post's content user: User! # Relationship field that specifies the user who created the post } # Define the User type with its fields and relationships type User { id: ID! # Non-nullable ID field for the user's unique identifier name: String! # Non-nullable String field for the user's name email: String! # Non-nullable String field for the user's email address posts: [Post]! # Relationship field that specifies the posts created by the user } # Define the Query type with its fields for retrieving data type Query { post(id: ID!): Post # Query field for retrieving a specific post by ID posts: [Post]! # Query field for retrieving all posts user(id: ID!): User # Query field for retrieving a specific user by ID users: [User]! # Query field for retrieving all users } # Define the Mutation type with its fields for modifying data type Mutation { createPost(title: String!, content: String!, userId: ID!): Post! # Mutation field for creating a new post updatePost(id: ID!, title: String, content: String): Post! # Mutation field for updating an existing post deletePost(id: ID!): Boolean! # Mutation field for deleting a post by ID }

The schema defines above can be used with a GraphQL API to query and edit blog data. It has three object types, including Post, User, and Query, along with two mutation operations. The fields for each object type are established on the basis of its properties and relationship with other object types. Note, Query and mutation fields are defined on the root object types.

Monitor your users in real time and optimize your digital experience with Zipy!

Get Started for free

Modularizing a GraphQL Schema

Working with GraphQL frequently involves dealing with complicated schema definitions, which can be intimidating and challenging to maintain as the project expands. Developers have developed a number of methods to modularize their schemas—to divide them into smaller, more manageable schemas—in order to avoid this issue.

We'll examine how to divide a GraphQL schema definition into individual files and then merge them to produce a complete schema object in this example. With this strategy, we can keep our schema structured and manageable over time while also facilitating developer collaboration.

First, let's create a file for the User type definition. We'll call it userTypeDefs.js:

    
// userTypeDefs.js export const userTypeDefs = ` type User { id: ID! firstName: String lastName: String organization: [Organization] } `;

Next, let's create a file for the Organization type definition. We'll call it organizationTypeDefs.js:

    
// organizationTypeDefs.js export const organizationTypeDefs = ` type Organization { id: ID! title: String user: User } `;

Now we can import these type definitions into our main schema file, schema.js:

    
// schema.js import { makeExecutableSchema } from 'graphql-tools'; import { userTypeDefs } from './userTypeDefs'; import { organizationTypeDefs } from './organizationTypeDefs'; const typeDefs = [userTypeDefs, organizationTypeDefs]; const resolvers = { // define resolvers here }; export const schema = makeExecutableSchema({ typeDefs, resolvers, });

Here, a complete schema object is created using the 'makeExecutableSchema' function from the 'graphql-tools' library. By importing them from their respective files and placing them in an array called "typeDefs," we are merging the type definitions for the "User" and "Organization" types. Any resolvers that we could have are also defined in the 'resolvers' object.

In order to use the whole schema object in our GraphQL server, we're finally exporting it as 'schema'.

With this method, we can break up a lengthy schema description into digestible, smaller parts that are simpler to keep up over time. Collaboration with other developers who could be working on various schema components is also made simpler.

Organizing Resolvers

As our schema grows, we may find it useful to split it into logical parts. However, we also want to keep our resolvers organized with their associated parts of the schema. Generally, it's best to keep resolvers for a particular type in the same file as the schema definition for that type.

In this example, let's assume that we have a schema.js file with some resolvers included. We have imported schema definitions from separate files user.js and organization.js.

To keep things organized, we can split up the resolvers object and put a piece of it in each of the imported files. Here's what user.js would look like with its respective resolvers object:

    
// user.js export const typeDef = ` type User { id: Int! firstName: String lastName: String organization: [Organization] } `; export const resolvers = { User: { organization: () => { ... }, } };

And here's organization.js:

    
// organization.js export const typeDef = ` type Organization { title: String user: User } `; export const resolvers = { Organization: { user: () => { ... }, } };

To combine everything in schema.js, we can import the typeDef and resolvers objects from each of the separate files. Then we can apply lodash.merge to combine the resolvers  objects into a single object:

    
import { merge } from 'lodash'; import { typeDef as User, resolvers as userResolvers, } from './user.js'; import { typeDef as Organization, resolvers as organizationResolvers, } from './organization.js'; const Query = ` type Query { user(id: Int!): User organization(id: Int!): Organization } `; const resolvers = { Query: { ..., } }; makeExecutableSchema({ typeDefs: [ Query, User, Organization ], resolvers: merge(resolvers, userResolvers, organizationResolvers), });

By splitting up our schema and resolvers and combining them with lodash.merge, we can keep our code organized and easy to manage.

Extend types in multiple files in GraphQL

To make our code more organized, we can use type extensions to define fields for a type in the same file where the type is defined. This approach allows us to keep our schema organized by associating each resolver with its corresponding part of the schema.

We can start by defining our Query type in schema.js with a fake _empty field. Then, we can use the extend keyword to add fields to this type in the user.js and organization.js files, along with their respective resolvers.

For example, in user.js, we can define the user field for the Query type and the User type, and their respective resolvers. The typeDef and resolvers for user.js would look like this:

    
export const typeDef = ` extend type Query { user(id: Int!): User } type User { id: Int! firstName: String lastName: String organization: [Organization] } `; export const resolvers = { Query: { user: () => { ... }, }, User: { organization: () => { ... }, } };

Similar to this, the organisation field for the Query type, the organisation type, and their respective resolvers can be defined in organization.js. The organization.js typeDef and resolvers would resemble the following:

    
export const typeDef = ` extend type Query { organization(id: Int!): Organization } type Organization { title: String user: User } `; export const resolvers = { Query: { organization: () => { ... }, }, Organization: { user: () => { ... }, } };

The final schema and resolver objects can be produced by importing these type definitions and resolvers into schema.js and merging them with lodash.merge. Here is how schema.js's finished code would appear:

    
import { merge } from 'lodash'; import { typeDef as User, resolvers as userResolvers, } from './user.js'; import { typeDef as Organization, resolvers as organizationResolvers, } from './organization.js'; const Query = ` type Query { user(id: Int!): User organization(id: Int!): Organization } `; const resolvers = { Query: { ..., } }; makeExecutableSchema({ typeDefs: [ Query, User, Organization ], resolvers: merge(resolvers, userResolvers, organizationResolvers), });

By splitting up our schema and resolvers and combining them with lodash.merge, we can keep our code organized and easy to manage.

Additional tips to modularize GraphQL schema

As, we've seen, breaking down your server code into modules can enhance its manageability and comprehensibility. To effectively modularize your codebase, consider the following tips:

  • For learning, prototyping, or building a proof of concept, having your entire schema in a single file is usually fine. It can be helpful to have an overview of the entire schema in one place.
  • Consider organizing your schema and resolvers by feature. For instance, if you're building an e-commerce site, you might want to keep the checkout-related code together.
  • To improve code maintainability and understanding, keep your resolvers in the same file as the schema definition for the fields they implement.
  • Use the graphql-tag package to wrap your SDL type definitions with a gql tag. This will give you syntax highlighting for SDL within your code editor, especially if you're using a GraphQL plugin or code formatter like Prettier.

With these tips, you should be able to structure your code in a way that makes it easy to work with.

Monitor your users in real time and optimize your digital experience with Zipy!

Get Started for free

Conclusion

In conclusion, GraphQL modules are a potent tool for organizing, simplifying, and modularizing your GraphQL work. It can enable developers control the structure of their GraphQL APIs, resulting in more effective and maintainable code, thanks to its intuitive design and straightforward implementation.

By segmenting your schema into modules, you can build APIs that are more focused, cohesive, simpler to comprehend, and update over time. Overall, adopting GraphQL modules can assist developers in streamlining their development process and creating better GraphQL APIs, that too without the headache of manually maintaining your code.

Before we wind up, I have one more trick for you to optimize performance of your GraphQL API - you can use GraphQL HTTP caching and reduce load on your servers.

Happy coding!

Call to Action

Feel free to comment or write to us in case you have any further questions at support@zipy.ai. We would be happy to help you. In case you want to explore for your app, you can sign up or book a demo.











Fix bugs faster with Zipy!

Get Started for Free
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Want to solve customer bugs even before they're reported?

The unified digital experience platform to drive growth with Product Analytics, Error Tracking, and Session Replay in one.

SOC 2 Type 2
Zipy is GDPR and SOC2 Type II Compliant
© 2023 Zipy Inc. | All rights reserved
with
by folks just like you
// open links in new tab script