-
-
Notifications
You must be signed in to change notification settings - Fork 818
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
What is the best practice for modularizing GraphQL schema? #750
Comments
I am also thinking about this topic recently, what bothers me more is |
Recently I was trying graphql and before I discovered other packages, I made a similar thing to load schemas from files, here the code with an example. The interface that define the one-file graphql exports interface GraphQLExport {
inputs?: String
types?: String
queries?: String
mutations?: String
subscriptions?: String
resolvers?: Object
} An example Message.js schema export const types = `
type Message {
id: ID
chat: ID!
message: String!
author: User!
timestamp: String!
}
`
const queries = `
message(id: ID!): Message
messages(chat: ID): [Message]
`
const mutations = `
addMessage(message: MessageInput!): Message!
removeMessage(id: ID!): Message!
`
const subscriptions = `
onMessage(chat: ID!): Message
`
const resolvers = {
Query: {
message: async (_, { id }, { Models }) => {},
messages: async (_, args, { Models }) => {}
}
}
export {
types,
queries,
mutations,
subscriptions,
resolvers
} The schema build file, which takes all the partials and merge them in a single schema // Import schemas
import * as Message from './Message'
const partials: Array<GraphQLExport> = [
Message
]
const inputs = []
const types = []
const queries = []
const mutations = []
const subscriptions = []
const merge = (target, source) => {
for (let key of Object.keys(source)) {
if (source[key] instanceof Object && target[key]) {
Object.assign(source[key], merge(target[key], source[key]))
}
}
Object.assign(target || {}, source)
return target
}
// Concat all partial strings
partials.forEach(p => {
p.inputs && inputs.push(p.inputs)
p.types && types.push(p.types)
p.queries && queries.push(p.queries)
p.mutations && mutations.push(p.mutations)
p.subscriptions && subscriptions.push(p.subscriptions)
})
const typeDefs = `
${inputs.join('\n')}
${types.join('\n')}
${queries.length ? `
type Query {
${queries.join('\n')}
}` : ''
}
type Mutation {
${mutations.join('\n')}
}
${subscriptions.length ? `
type Subscription {
${subscriptions.join('\n')}
}` : ''
}
`
const resolvers = partials.reduce((obj, p) => {
return merge(obj, p.resolvers || {})
}, {
Query: {},
Mutation: {},
Subscription: {}
})
export default {
typeDefs,
resolvers
} I wrote it very quickly, I know is not elegant and probably does not handle all cases, but it worked for what I needed. I'm thinking about switching to a dedicated module such as merge-graphql-schemas but the idea was to define a interface for the schema exports than put all the logic in the file (queries, resolvers, subscriptions, ...) the difference is in case of Query, Mutation, Subscription you just have to export the strings without the full declaration: So this Query type Query {
message(id: ID!): Message
messages(chat: ID): [Message]
} in the export file becomes: const queries = `
message(id: ID!): Message
messages(chat: ID): [Message]
` Any feedback about it? |
Full Example Graphql, Mongo, Express, Apollo-Server |
@hajocava excellent work! |
Link is not working. |
Closing, see resources above. |
Here is the example ScreenshotCode example in server fileconst express = require('express');
const glob = require("glob");
const {graphqlHTTP} = require('express-graphql');
const {makeExecutableSchema, mergeResolvers, mergeTypeDefs} = require('graphql-tools');
const app = express();
//iterate through resolvers file in the folder "graphql/folder/folder/whatever*-resolver.js"
let resolvers = glob.sync('graphql/*/*/*-resolver.js')
let registerResolvers = [];
for (const resolver of resolvers){
// add resolvers to array
registerResolvers = [...registerResolvers, require('./'+resolver),]
}
//iterate through resolvers file in the folder "graphql/folder/folder/whatever*-type.js"
let types = glob.sync('graphql/*/*/*-type.js')
let registerTypes = [];
for (const type of types){
// add types to array
registerTypes = [...registerTypes, require('./'+type),]
}
//make schema from typeDefs and Resolvers with "graphql-tool package (makeExecutableSchema)"
const schema = makeExecutableSchema({
typeDefs: mergeTypeDefs(registerTypes),//merge array types
resolvers: mergeResolvers(registerResolvers,)//merge resolver type
})
// mongodb connection if you prefer mongodb
require('./helpers/connection');
// end mongodb connection
//Make it work with express "express and express-graphql packages"
app.use('/graphql', graphqlHTTP({
schema: schema,
graphiql: true,//test your query or mutation on browser (Development Only)
}));
app.listen(4000);
console.log('Running a GraphQL API server at http://localhost:4000/graphql'); |
These days I am looking for the best practice to split schemas into files. My thought is:
@stubailo told me a new, simpler approach here: https://dev-blog.apollodata.com/modularizing-your-graphql-schema-code-d7f71d5ed5f2, which uses the type extension syntax:
But I have another way to discuss here, which is more simpler and semantic.
Convention
I assume each schema file can export 3 properties(not necessary):
schemas
,resolvers
,directives
:Here I put the directive schema and the type schema together for simplicity. In production, I suggest to split directive schema, type schema and scalar schema into multiple files.
Merge
The schema definitions above are pretty simple and semantic:A schema has its type definition or directive declaring and resolvers in its own schema scope. Maybe the schema also has dependencies, but are not in its scope.
Now we need a way to merge those
schemas
,resolvers
anddirectives
.I would like to write a function to load the files together:
Then maybe we could call the api
makeExecutableSchema
to get a root schema with resolvers, but it will throw error shows"type Query defined more than once"
.That is because
makeExecutableSchema
will not merge the multiply defined schemas by the same type name.Here I hope the this feature would be implemented later. I have opened an issue here #708 for this feature.
Finally I find a npm module merge-graphql-schemas which could smartly merge the multiply defined schemas:
We call
flatten
here in case the item oftypeDefs
is an Array.Now, we've got an Merged Root Schema.
The text was updated successfully, but these errors were encountered: