notes: blog.apollographql.com/modularizin…
As your GraphQL application grows from demo, to proof of concept, to production, the complexity of your schema and resolvers will grow in tandem. To organize your code, you’ll want to split up your schema types and the associated resolvers into multiple files.
We can arrange our schema and resolver code in separate files with just a few simple JavaScript concepts.
Schema
If you’re just starting out and have your entire schema defined in one file, it might look much like the below snippet. Here, we’ll call it schema.js:
// schema.js
const typeDefs = `
type Query {
author(id: Int!): Post
book(id: Int!): Post
}
type Author {
id: Int!
firstName: String
lastName: String
books: [Book]
}
type Book {
title: String
author: Author
}
`;
makeExecutableSchema({
typeDefs: typeDefs,
resolvers: {},
});
We’d like to put the schema types for Author and Book in files called author.js
and book.js
.At the end of the day, the schema definitions we’ve written in the Schema Definition Language (SDL) are just strings. Treating them as such, we have a simple way of importing type definitions across different files: Split up the string into multiple strings that we can combine later. This is what author.js would look like:
// author.js
export const typeDef = `
type Author {
id: Int!
firstName: String
lastName: String
books: [Book]
}
`;
Here’s book.js:
// book.js
export const typeDef = `
type Book {
title: String
author: Author
}
`;
Finally, we pull it all together in schema.js:
// schema.js
import { typeDef as Author } from './author.js';
import { typeDef as Book } from './book.js';
const Query = `
type Query {
author(id: Int!): Post
book(id: Int!): Post
}
`;
makeExecutableSchema({
typeDefs: [ Query, Author, Book ],
resolvers: {},
});
Resolvers
We want to be able to move each resolver with its associated part of the schema as well. In general, we want to keep resolvers for a certain type in the same file as the schema definition for that type.Expanding on our previous example, here’s our schema.js file with some resolvers added into the picture:
// schema.js
import { typeDef as Author } from './author.js';
import { typeDef as Book } from './book.js';
const Query = `
type Query {
author(id: Int!): Post
book(id: Int!): Post
}
`;
const resolvers = {
Query: {
author: () => { ... },
book: () => { ... },
},
Author: {
name: () => { ... },
},
Book: {
title: () => { ... },
},
};
makeExecutableSchema({
typeDefs: [ Query, Author, Book ],
resolvers,
});
We can split up the resolvers object. We can put a piece of it in author.js
, another in book.js
, and then import them and use the lodash.merge
function to put it all together in schema.js.Here’s what author.js
would look like in that case:
// author.js
export const typeDef = `
type Author {
id: Int!
firstName: String
lastName: String
books: [Book]
}
`;
export const resolvers = {
Author: {
books: () => { ... },
}
};
Here’s book.js:
// book.js
export const typeDef = `
type Book {
title: String
author: Author
}
`;
export const resolvers = {
Book: {
author: () => { ... },
}
};
Then, we apply lodash.merge
in schema.js
to put everything together:
import { merge } from 'lodash';
import {
typeDef as Author,
resolvers as authorResolvers,
} from './author.js';
import {
typeDef as Book,
resolvers as bookResolvers,
} from './book.js';
const Query = `
type Query {
author(id: Int!): Author
book(id: Int!): Book
}
`;
const resolvers = {
Query: {
...,
}
};
makeExecutableSchema({
typeDefs: [ Query, Author, Book ],
resolvers: merge(resolvers, authorResolvers, bookResolvers),
});
And that gives us the resolvers structure that we started out with!
Extending types in multiple files
We’re still defining authors and books as top-level fields on Query within schema.js, even though these are logically tied to Author and Book and should live in author.js and book.js.To accomplish that, we can use type extensions. We can define our existing Query type like this:
const Query = `
type Query {
_empty: String
}
extend type Query {
author(id: Int!): Author
}
extend type Query {
book(id: Int!): Book
}
`;
Note: In the current version of GraphQL, you can’t have an empty type even if you intend to extend it later. So we need to make sure the Query type has at least one field — in this case we can add a fake _empty field. Hopefully in future versions it will be possible to have an empty type to be extended later. Basically, the extend keyword lets us add fields to an already-defined type. We can use this keyword in order to define Query fields relevant to those types in book.js and author.js. We should then also move to defining the Query resolvers for those types in the same place.Here’s what author.js looks like with this approach:
// author.js
export const typeDef = `
extend type Query {
author(id: Int!): Author
}
type Author {
id: Int!
firstName: String
lastName: String
books: [Book]
}
`;
export const resolvers = {
Query: {
author: () => { ... },
},
Author: {
books: () => { ... },
}
};
This is what book.js looks like:
// book.js
export const typeDef = `
extend type Query {
book(id: Int!): Book
}
type Book {
title: String
author: Author
}
`;
export const resolvers = {
Query: {
book: () => { ... },
},
Book: {
author: () => { ... },
}
};
Just as before, we put it all together in schema.js:
import { merge } from 'lodash';
import {
typeDef as Author,
resolvers as authorResolvers,
} from './author.js';
import {
typeDef as Book,
resolvers as bookResolvers,
} from './book.js';
// If you had Query fields not associated with a
// specific type you could put them here
const Query = `
type Query {
_empty: String
}
`;
const resolvers = {};
makeExecutableSchema({
typeDefs: [ Query, Author, Book ],
resolvers: merge(resolvers, authorResolvers, bookResolvers),
});
Now, the schema and resolver definitions are properly co-located with the associated types!
Final tips
Wrap your SDL type definitions with a gql tag using graphql-tag. If you’re using a GraphQL plugin for your editor or formatting your code with Prettier, you’ll be able to get syntax highlighting for SDL within your code editor as long as you prefix it with the gql tag.