如何基于express-graphql、Mongoose拆分大型GraphQL Schema文件
Great question! When your GraphQL schema starts getting unwieldy, splitting it into modular, single-responsibility files makes your code way easier to maintain and scale. Here’s a straightforward approach that fits your current setup (using express-graphql, Mongoose, no buildSchema or Apollo):
Step 1: Set up a modular directory structure
First, reorganize your GraphQL-related code into a dedicated folder with subdirectories for different components:
your-project/ models/ book.js author.js graphql/ types/ # For your object types (BookType, AuthorType) BookType.js AuthorType.js queries/ # For root queries RootQuery.js mutations/ # For mutation definitions Mutations.js schema.js # Main schema entry point app.js
Step 2: Split your Object Types
Break out each GraphQLObjectType into its own file in the types/ directory. We handle circular references (Book ↔ Author) by requiring the dependent type inside the fields function (since it’s executed lazily):
graphql/types/BookType.js
const graphql = require('graphql'); const Book = require('../../models/book'); const { GraphQLObjectType, GraphQLID, GraphQLString } = graphql; const BookType = new GraphQLObjectType({ name: 'Book', fields: () => ({ id: { type: GraphQLID }, name: { type: GraphQLString }, genre: { type: GraphQLString }, author: { type: require('./AuthorType').AuthorType, resolve(parent, args) { return Book.findById(parent.authorID); } } }) }); module.exports = { BookType };
graphql/types/AuthorType.js
const graphql = require('graphql'); const Author = require('../../models/author'); const { GraphQLObjectType, GraphQLID, GraphQLString, GraphQLInt, GraphQLList } = graphql; const AuthorType = new GraphQLObjectType({ name: 'Author', fields: () => ({ id: { type: GraphQLID }, name: { type: GraphQLString }, age: { type: GraphQLInt }, books: { type: new GraphQLList(require('./BookType').BookType), resolve(parent, args) { return Author.find({ authorID: parent.id }); } } }) }); module.exports = { AuthorType };
Step 3: Extract Root Queries
Move your root query definitions into their own file:
graphql/queries/RootQuery.js
const graphql = require('graphql'); const Book = require('../../models/book'); const Author = require('../../models/author'); const { GraphQLObjectType, GraphQLID, GraphQLList } = graphql; const { BookType } = require('../types/BookType'); const { AuthorType } = require('../types/AuthorType'); const RootQuery = new GraphQLObjectType({ name: 'RootQueryType', fields: { book: { type: BookType, args: { id: { type: GraphQLID } }, resolve(parent, args) { return Book.findById(args.id); } }, author: { type: AuthorType, args: { id: { type: GraphQLID } }, resolve(parent, args) { return Author.findById(args.id); } }, books: { type: new GraphQLList(BookType), resolve(parent, args) { return Book.find({}); } }, authors: { type: new GraphQLList(AuthorType), resolve(parent, args) { return Author.find({}); } } } }); module.exports = { RootQuery };
Step 4: Extract Mutations
Do the same for your mutation definitions:
graphql/mutations/Mutations.js
const graphql = require('graphql'); const Book = require('../../models/book'); const Author = require('../../models/author'); const { GraphQLObjectType, GraphQLString, GraphQLID, GraphQLInt, GraphQLNonNull } = graphql; const { BookType } = require('../types/BookType'); const { AuthorType } = require('../types/AuthorType'); const Mutation = new GraphQLObjectType({ name: 'Mutation', fields: { addAuthor: { type: AuthorType, args: { name: { type: new GraphQLNonNull(GraphQLString) }, age: { type: new GraphQLNonNull(GraphQLInt) } }, resolve(parent, args) { let author = new Author({ name: args.name, age: args.age }); return author.save(); } }, addBook: { type: BookType, args: { name: { type: new GraphQLNonNull(GraphQLString) }, genre: { type: new GraphQLNonNull(GraphQLString) }, authorID: { type: new GraphQLNonNull(GraphQLID) } }, resolve(parent, args) { let book = new Book({ name: args.name, genre: args.genre, authorID: args.authorID }); return book.save(); } } } }); module.exports = { Mutation };
Step 5: Assemble the Main Schema
Update your root schema.js to import and combine all the modular components:
graphql/schema.js
const graphql = require('graphql'); const { GraphQLSchema } = graphql; const { RootQuery } = require('./queries/RootQuery'); const { Mutation } = require('./mutations/Mutations'); module.exports = new GraphQLSchema({ query: RootQuery, mutation: Mutation });
Step 6: Update app.js (if needed)
If your schema path changed, just adjust the require statement—otherwise, your existing code works exactly as before:
const schema = require('./graphql/schema'); // Verify the path matches your new structure app.use('/api', graphqlHTTP(async req => ({ schema, graphiql: { headerEditorEnabled: true } })));
Key Notes
- Circular Reference Handling: By requiring dependent types inside the
fieldsfunction (which runs lazily), we avoid the "undefined" errors that come with mutual references between Book and Author types. - Maintainability: Each file now focuses on one specific part of your schema—you can update Book fields, add a new mutation, or tweak a resolver without digging through a massive single file.
- Scalability: Adding new types (like
Publisher) or features later just means adding new files to the appropriate directories, rather than cluttering your main schema.
内容的提问来源于stack exchange,提问作者mitchldtn

