GRAMOD
Modularize graphql app with ease
Installation
$ npm i gramod --save
# or
$ yarn add gramod
Features
- Modularizing supported
- Middlewares supported
- Overriding resolver supported
- Reusing resolvers for REST API
Getting started
Let's say we have 2 modules: user, post
const typeDefs = `
type Query {
users: [User]
user(id: ID!): User
post(id: ID!): Post
}
type User {
id: ID!
name: String
posts: [Post]
}
type Post {
id: ID!
title: String
body: String
}
`;
const resolvers = {
Query: {
users: () => {},
user: () => {},
post: () => {},
},
User: {
posts: () => {},
},
};
We can modularize our app like this with gramod
/modules/main.js
const user = require("./user");
module.exports = {
require: [user],
};
/modules/user.js
const post = require("./post");
module.exports = {
require: [post],
types: {
Query: `
users: [User]
user(id: ID!): User
`,
User: `
id: ID!
name: String
posts: [Post]
`,
},
resolvers: {
Query: {
users: () => {},
user: () => {},
},
User: {
posts: () => {},
},
},
};
/modules/post.js
module.exports = {
types: {
Query: `
post(id: ID!): Post
`,
Post: `
id: ID!
title: String
body: String
`,
},
resolvers: {
Query: {
post: () => {},
},
},
};
Finally, we use gramod to generate typeDefs and resolvers for GraphQL server
const main = require("./modules/main");
const { typeDefs, resolvers } = gramod(main);
const server = new ApolloServer({ typeDefs, resolvers });
Defining extensible module
In this example, the user module has no posts property like previous one.
/modules/user.js
module.exports = {
types: {
Query: `
users: [User]
user(id: ID!): User
`,
User: `
id: ID!
name: String
`,
},
resolvers: {
Query: {
users: () => {},
user: () => {},
},
},
};
And the post module looks like this:
/modules/post.js
const user = require("./user");
module.exports = {
require: user,
types: {
Query: `
post(id: ID!): Post
`,
Post: `
id: ID!
title: String
body: String
`,
// the posts property will be appended to User type declaration
User: `
posts: [Post]
`,
},
resolvers: {
Query: {
post: () => {},
},
User: {
// this resolver will bed injected to User type of user module
posts: () => {},
},
},
};
/modules/main.js
module.exports = {
require: [user], // user module has no post extension
require: [user, post], // user module includes post extension
};
App Level Middleware
const logInput = async (resolve, root, args, context, info) => {
console.log(`1. logInput: ${JSON.stringify(args)}`);
const result = await resolve(root, args, context, info);
console.log(`3. logInput`);
return result;
};
const helloModule = {
resolvers: {
Query: {
hello: (root, args, context, info) => {
console.log(`2. resolver: hello`);
return `Hello ${args.name ? args.name : "world"}!`;
},
},
},
};
const { resolvers, typeDefs } = gramod(helloModule, logInput);
Module Level Middleware
const logInput = async (resolve, root, args, context, info) => {
console.log(`1. logInput: ${JSON.stringify(args)}`);
const result = await resolve(root, args, context, info);
console.log(`3. logInput`);
return result;
};
const helloModule = {
middlewares: logInput,
resolvers: {
Query: {
hello: (root, args, context, info) => {
console.log(`2. resolver: hello`);
return `Hello ${args.name ? args.name : "world"}!`;
},
},
},
};
const { resolvers, typeDefs } = gramod(helloModule);
Overriding Resolver
const helloModule = {
resolvers: {
Query: {
hello: () => "Hello World !!!",
},
},
};
const wrapHelloModule = {
resolvers: {
Query: {
hello: () => (context) => {
const result = context.next();
return `*** ${result} ***`;
},
},
},
};
gramod([helloModule, wrapHelloModule]);
Using resolver with REST API
const helloModule = {
resolvers: {
Query: {
hello: (parent, { name }) => `Hello ${name}`,
},
Mutation: {
createPost: (parent, post) => {},
},
},
};
const { query, mutate } = gramod(helloModule);
app.get("/hello", (req, res) =>
res.send(query("hello", { name: req.query.name }))
);
app.post("/posts", (req, res) => {
mutate("createPost", req.body);
res.send("OK");
});
App Level Context Factory
const helloModule = {
resolvers: {
Query: {
hello: (parent, { name }, context) =>
`Hello ${name || context.defaultName}`,
},
},
};
const { typeDefs, resolvers, context } = gramod(helloModule, {
context: () => ({ defaultName: "World" }),
});
const server = new ApolloServer({ typeDefs, resolvers, context });
Module Level Context Factory
const helloModule = {
context: () => ({ defaultName: 'World' })
resolvers: {
Query: {
hello: (parent, { name }, context) =>
`Hello ${name || context.defaultName}`,
},
},
};
const { typeDefs, resolvers, context } = gramod(helloModule);
const server = new ApolloServer({ typeDefs, resolvers, context });