A package to convert your mongoose schema to graphql schema
- Mongql
- TOC
- Features
- Motivation
- Usage
- Without initial typedef and resolvers
- With initial typedef and resolvers
- Output SDL and AST
- Using Schema config typedefs and resolvers
- FieldSchema Configs
- Fine grain Mutation configuration
- Fine grain Query configuration
- Fine grain Type configuration
- generating Models
- Using local folders
- Controlling nullability
- Concept
- API
- TODO
- Create graphql schema (typedef and resolvers) from mongoose schema
- Stitch already created typedef and resolvers
- Easily configurable (any of the typedef and resolvers can be turned off)
- Output the generated SDL
- Auto addition of graphql validators with mongoose
- Auto generation of fragments and operations
- Automatic integration with custom various scalar types from graphql-scalars
- Helper methods for dealing with graphql ast nodes
- Auto generation of fragments and operations
- Auto generation of basic crud operations
- Creating a graphql SDL is not a difficult task, but things get really cumbersome after a while, especially since a lot of the typedefs and resolvers are being repeated.
- Automating the schema generation helps to avoid errors regarding forgetting to define something in the schema thats been added to the resolver or vice versa.
- Creating resolvers for subtypes in a PITA, especially if all of them just refers to the same named key in parent
- Generating fragments and operation manually is quite difficult and error prone, typing duplicate stuffs and figuring out the interrelationship between each fragment.
// User.schema.js
const mongoose = require('mongoose');
const UserSchema = mongoose.Schema({
name: {
type: String,
mongql: {
nullable: {
object: [true]
} // field level config
}
}
});
UserSchema.mongql = {
resource: 'user'
}; // schema level config
module.exports = UserSchema;
// index.js
const {
makeExecutableSchema
} = require('@graphql-tools/schema');
const {
ApolloServer
} = require('apollo-server-express');
const Mongql = require('MonGql');
const UserSchema = require('./User.schema.js');
(async function() {
const mongql = new Mongql({
Schemas: [UserSchema], // Global level config
});
// Calling the generate method generates the typedefs and resolvers
const {
TransformedResolvers,
TransformedTypedefs, // Contains both arr and obj representation
} = await mongql.generate();
const GRAPHQL_SERVER = new ApolloServer({
schema: makeExecutableSchema({
typeDefs: TransformedTypedefs.arr,
resolvers: TransformedResolvers.arr,
}),
context: () => ({
user: req.user
})
});
})();
// user.typedef.js
module.exports = gql `
type BasicUserType{
name:String!
}
`;
// user.resolver.js
module.exports = {
Mutation: {
updateUserSettings: ...
}
}
const UserAST = require('./user.typedef.js');
const UserResolver = require('./user.resolver.js');
const PreTransformedTypeDefsASTs = {
user: UserAST // This has to match with the resource name added in the mongoose schema
}
const PreTransformedResolvers = {
user: UserResolver
}
const mongql = new Mongql({
Schemas: [UserSchema, SettingsSchema],
Typedefs: {
init: PreTransformedTypeDefsASTs
},
Resolvers: {
init: PreTransformedResolvers
}
});
const mongql = new Mongql({
Schemas: [],
output: {
SDL: path.resolve(__dirname, "./SDL"),
AST: path.resolve(__dirname, "./AST")
}
});
await mongql.generate()
const UserSchema = new mongoose.model({
name: String
});
UserSchema.mongql = {
TypeDefs: `type userinfo{
name: String!
}`,
Resolvers: {
Query: {
getUserInfo() {}
}
}
}
const mongql = new Mongql({
Schemas: [UserSchema],
});
await mongql.generate()
const NestedSchema = new mongoose.model({
nested: Boolean
});
NestedSchema.mongql = {
// FieldSchema configs
}
const UserSchema = new mongoose.model({
name: String,
nested: NestedSchema
});
UserSchema.mongql = {
TypeDefs: `type userinfo{
name: String!
}`,
Resolvers: {
Query: {
getUserInfo() {}
}
}
}
const mongql = new Mongql({
Schemas: [UserSchema],
});
await mongql.generate()
const mongql = new Mongql({
Schemas: [UserSchema, SettingsSchema],
generate: {
mutation: false, // will not generate any mutation typedef and resolver,
mutation: {
create: false, // Will not generate any create mutation typedef and resolver,
update: {
multi: false // Will not generate any update multi mutation typedef and resolver
},
single: false // Will not generate any single mutation typedef and resolver
}
}
});
const mongql = new Mongql({
Schemas: [UserSchema, SettingsSchema],
generate: {
query: false,
query: {
all: false
},
query: {
paginated: {
self: false
}
},
query: {
filtered: {
others: {
whole: false
}
}
},
query: {
self: false, // remove all self related typedefs and resolvers,
self: {
whole: false // remove all selfwhole related typedefs and resolvers,
},
count: false, // remove all count related typedefs and resolvers,
}
}
});
const mongql = new Mongql({
Schemas: [UserSchema, SettingsSchema],
generate: {
input: {
update: false
},
interface: false,
enum: false,
union: false,
object: {
self: false
}
}
});
const {
makeExecutableSchema
} = require('@graphql-tools/schema');
const Mongql = require('mongql');
const {
ApolloServer
} = require('apollo-server');
(async function() {
const mongql = new Mongql({
Schemas: [
/* Your schema array here */
],
});
const {
TransformedTypedefs,
TransformedResolvers
} = await mongql.generate();
const server = new ApolloServer({
schema: makeExecutableSchema({
typeDefs: TransformedTypedefs.arr,
resolvers: TransformedResolvers.arr,
}),
context: mongql.generateModels()
});
await server.listen();
})();
const Mongql = require('mongql');
const {
ApolloServer
} = require('apollo-server');
(async function() {
const mongql = new Mongql({
Schemas: path.resolve(__dirname, './schemas'),
output: {
dir: __dirname + '\\SDL'
},
Typedefs: {
init: path.resolve(__dirname, './typedefs')
},
Resolvers: {
init: path.resolve(__dirname, './resolvers')
}
});
const server = new ApolloServer({
schema: makeExecutableSchema({
typeDefs: TransformedTypedefs.arr,
resolvers: TransformedResolvers.arr,
}),
context: mongql.generateModels()
});
await server.listen();
})();
const UserSchema = new mongoose.model({
name: {
type: String,
mongql {
nullable: {
object: [true]
} // name: String
}
},
age: {
type: [Number],
mongql: {
nullable: {
input: [false, true]
} // age: [Int!]
}
}
});
Mongql contains 4 levels of configs
- Constructor/global level config: passed to the ctor during Mongql instantiation
- Schema level config: Attached to the schema via mongql key
- Field level config: Attached to the field via mongql key
- FieldSchema level config: Contains both field and schema configs
Precedence of same config option is global < Schema < FieldSchema < field. That is for the same option the one with the highest precedence will be used.
During the generation of schema, a few concepts are followed
-
Each Resource query object type contains four parts
-
Range(Input):
- All: Gets all the resource
- Paginated : Used to get resource through pagination inpu
- Filtered : Used to get resource through filter input
- ID: Used to get a resource by id
-
Auth:
- Self: Used to indicate logged in users resource
- Others: Used to indicate other users resource (when current user is authenticated)
- Mixed: Used to incicate others users resource (when user is unauthenticated)
-
Resource: Name of the resource (capitalized & pluralized form)
-
Part(Output):
- Whole: Get the whole data along with sub/extra types
- Count: get the count of resource
-
Generated Query Examples: getSelfSettingsWhole, getOthersSettingsCount
;
NOTE: Count part is not generate in paginated and id range as for paginated query the user already knows the count and id returns just one
-
Each resource mutation object type contains 2 parts
- Action: One of create|update|delete
- Target: resource for targeting single resource, resources for targeting multiple resources
-
Specific named functions pre|post(action) attached to the model's static is called pre and post of each action
Generated Mutation Examples: createSetting, updateSettings
-
Each resource types contains the following parts
- For each schema (base and nested), based on the permitted auth, object will be created, and based on generate config interface, input and union will be created
-
Each object type gets converted into 3 differnet kinds of fragment
- RefsNone: A fragment which excludes all the Refs Object type
- ScalarsOnly: A fragment which includes its and all included object types scalar fields
- ObjectsNone: A fragment which includes only scalar fields
-
All the custom fragments are generated from all the schemas
- All the generated operations have various kinds based on the generated fragments of that schema
All of the methods and configs have been commented along with their types
- Add more well rounded tests
- Provide ES modules to make the library tree-shakabl
PRS are more than welcome and highly appreciated!!!!