Separating GraphQL Resolvers by Domain
Recently, while revisiting an old project, I noticed several areas for improvement.
One issue that stood out was that multiple queries and mutations were all being resolved within a single file.
Hereβs a comparison of the current state (as-is) and the intended state (to-be).
1. As-is
const resolvers: Resolvers = { Query: { concertList: async (parent, args, ctx) => { // Implementation }, concert: async (parent, args, ctx) => { // Implementation }, user: async (parent, args, ctx) => { // Implementation }, }, Mutation: { createConcert: async (parent, args, ctx) => { // Implementation }, updateConcert: async (parent, args, ctx) => { // Implementation }, removeConcert: async (parent, args, ctx) => { // Implementation }, updateConcertTicket: async (parent, args, ctx) => { // Implementation }, createConcertPoster: async (parent, args, ctx) => { // Implementation }, updateConcertPoster: async (parent, args, ctx) => { // Implementation }, createEmailAuthRequest: async (parent, args) => { // Implementation }, authenticateEmailAuthRequest: async (parent, args) => { // Implementation }, }, }
As shown above, resolvers for various domains were being handled in a single file.
To address this, I reorganized the structure as shown in to-be.
2. To-be
/resolvers /user.ts /staff.ts /concert.ts /concertCategory.ts /auth.ts /emailAuth.ts index.ts
Example -
staffResolvers.ts
import { Resolvers } from 'your-types-path'; const staffResolvers: Resolvers = { Query: { // Add staff-related queries here }, Mutation: { // Add staff-related mutations here }, }; export default staffResolvers;
Example -
concertResolvers.ts
import { Resolvers } from 'your-types-path'; const concertResolvers: Resolvers = { Query: { concertList: async (parent, args, ctx) => { // Implementation }, concert: async (parent, args, ctx) => { // Implementation }, }, Mutation: { createConcert: async (parent, args, ctx) => { // Implementation }, updateConcert: async (parent, args, ctx) => { // Implementation }, removeConcert: async (parent, args, ctx) => { // Implementation }, updateConcertTicket: async (parent, args, ctx) => { // Implementation }, createConcertPoster: async (parent, args, ctx) => { // Implementation }, updateConcertPoster: async (parent, args, ctx) => { // Implementation }, }, }; export default concertResolvers;
Final Step: Combining Resolvers in the Host File
In the main
resolvers.ts
file, simply combine the separated resolvers as follows:import userResolvers from './user'; import staffResolvers from './staff'; import concertResolvers from './concert'; import concertCategoryResolvers from './concertCategory'; import authResolvers from './auth'; import emailAuthResolvers from './emailAuth'; const resolvers = { Query: { ...userResolvers.Query, ...staffResolvers.Query, ...concertResolvers.Query, ...concertCategoryResolvers.Query, }, Mutation: { ...userResolvers.Mutation, ...authResolvers.Mutation, ...emailAuthResolvers.Mutation, ...concertResolvers.Mutation, ...concertCategoryResolvers.Mutation, }, }; export default resolvers;
This setup effectively organizes your resolver code by domain, making it more maintainable and scalable.