GRAM
GRAphQL Model builder is a utility to generate all necessary GraphQL query, mutation and subscription Types. It enforces a specific structure for your schema.
Installation
npm i gram
Features
- Automatic Interface generation
- Strict schema layout
- Easy adding of resolvers for attribute fields
- Connect a service and go
- Context dependent builds
Usage
Basic usage would let you generate a model by just defining it and adding some attributes. It requires you to have some service that provides necessary CRUD-style getter and setter functions (findMany
, findOne
, update
, create
, remove
).
If you have a service called Animals
you could create a schema like this:
// animal type like 'dog', 'cat' // field is required animal.attr'type', GraphQLString.isNonNull // animal name like 'Fluffy', 'Rex' // field is not required animal.attr'name', GraphQLString // is it a tame animal animal.attr'tame', GraphQLBoolean // age of the animal animal.attr'age', GraphQLInt
This will generate a graphql schema like:
type Animal implements Node { id: ID createdAt: String updatedAt: String deletedAt: String type: String! name: String tame: Boolean age: Int } input AnimalFilter { … } input AnimalPage { limit: Int offset: Int } type Animals implements List { page: Page nodes: [Animal!]! } enum AnimalSortOrder { … } input AnimalWhere { … } input CreateAnimalData { … } interface List { page: Page nodes: [Node!]! } type Mutation { createAnimal(data: CreateAnimalData!): Animal updateAnimal(data: UpdateAnimalData!, where: AnimalWhere!): [Animal!]! deleteAnimals(where: AnimalWhere!): [Animal!]! } interface Node { id: ID createdAt: String updatedAt: String deletedAt: String } type Page { page: Int limit: Int offset: Int } type Query { getAnimal(where: AnimalWhere!, order: AnimalSortOrder): Animal getAnimals(order: AnimalSortOrder, page: AnimalPage, where: AnimalWhere!): Animals } type Subscription { onCreateAnimal: Animal! onUpdateAnimal: [Animal!]! onDeleteAnimals: [Animal!]! } input UpdateAnimalData { … }
As you can see, some types and interfaces were added.
Gram is a little opinionated about Node
, Page
and List
.
It will generate the Node
and List
interfaces and apply them to you models by itself.
Your models will have to implement the Node
interface and the return value of the findMany
method will have to return a Page
type as the getAnimals
query returns Animals
which is of interface List
.
Gram will automatically assume all implemented methods on the service will work.
If you, for example, do not have a findMany method, Gram will remove the getAnimals
method and just generate the rest.
If you want to generate more than one schema from your models, there will be a feature implemented in the near future to enable and disable parts depending on the context given in the build method.
Model Resolvers
To add a resolver to the model we can use the .resolve(() => Resolver)
method on the model.
In this example the database does not have an age
column, and we need to calculate the age of the animal in the resolver.
// animal type like 'dog', 'cat' // field is required animal.attr'type', GraphQLString.isNonNull // animal name like 'Fluffy', 'Rex' // field is not required animal.attr'name', GraphQLString // is it a tame animal animal.attr'tame', GraphQLBoolean // age of the animal animal.attr'age', GraphQLInt animal.resolve
Interfaces
Of course our Animal
model is just an interface to help us build Cat
and Dog
models.
We will convert our Animal into an interface and generate a Cat
model that implements the interface.
There will be no use for that type
attribute any more, we will just skip it.
// animal name like 'Fluffy', 'Rex' // field is not required animal.attr'name', GraphQLString // is it a tame animal animal.attr'tame', GraphQLBoolean // age of the animal animal.attr'age', GraphQLInt cat.interface'Animal'
interface Animal { name: String tame: Boolean } type Cat implements Node & Animal { id: ID createdAt: Date updatedAt: Date deletedAt: Date name: String tame: Boolean } type Query { getCat(where: CatWhere!, order: CatSortOrder): Cat getCats(order: CatSortOrder, page: CatPage, where: CatWhere!): Cats }
As you can see it now added the attributes to the Cat
model and we can now easily add more animal-type models to our system.
We moved the service to the Cat
model, which is not quite right.
It would be best to have a Cat
specific service, which will use the Animal
service in turn, by adding some filters.
But we will want to fetch any Animals
as well.
// after defining all interfaces we setup the schema animal.attr'name', GraphQLString animal.attr'tame', GraphQLBoolean animal.attr'age', GraphQLInt cat.interface'Animal' dog.interface'Animal'
interface Animal { name: String tame: Boolean age: Int } type Cat implements Node & Animal { id: ID createdAt: Date updatedAt: Date deletedAt: Date name: String tame: Boolean age: Int } type Dog implements Node & Animal { id: ID createdAt: Date updatedAt: Date deletedAt: Date name: String tame: Boolean age: Int } type Query { getAnimal(where: AnimalWhere!, order: AnimalSortOrder): Animal getCat(where: CatWhere!, order: CatSortOrder): Cat getDog(where: DogWhere!, order: DogSortOrder): Dog }
Now we can find any animal with a getAnimal(where: { name: "Fluffy" }) { ... on Dog { … }}
or find our Dog directly getDog(where: { name: "Fluffy" }) { … }
.
Scalars
For use as DateTime
type inside this library @saeris/graphql-scalars
is used.
It is applied in the pagination system and allows more types to be setup, see the documentation for more information.
Sadly this library has no typing.
To install another type, simply attach it to the schemabuilder
.
builder.setScalar'EatingType', EatingType // animal name like 'Fluffy', 'Rex' // field is not required animal.attr'name', GraphQLString // is it a tame animal animal.attr'tame', GraphQLBoolean // age of the animal animal.attr'dateOfBirth', DateTime // feeding type of the animal animal.attr'feed', EatingType
"""What is this animal eating""" scalar EatingType type Animal implements Node { name: String tame: Boolean dateOfBirth: DateTime feed: EatingType id: ID createdAt: DateTime updatedAt: DateTime deletedAt: DateTime }
There is no filter strategy setup for the new scalar type yet. To add this we will to setup this.
builder.addFiltercheckFn, filterFn
input AnimalFilter { {…} feed: EatingType feed_not: EatingType feed_in: [EatingType!] feed_not_in: [EatingType!] {…} }
Direct Queries & Mutations
With gram
you could also just build up a graphQL schema by hand, it provides all necessary method to do so.
builder.addQuery'random', GraphQLFloat,Math.random
type Query { random: Float }
GraphQL Types or String
Since version 2.1.2
it is now possible to set attribute types as strings, and not as GraphQLType objects.
// animal name like 'Fluffy', 'Rex' // field is required animal.attr'name', 'String!' // parents animal.attr'mother', 'Animal' animal.attr'father', 'Animal' // children animal.attr'children', '[Animal!]!'
type Animal implements Node { name: String! mother: Animal father: Animal children: [Animal!]! id: ID createdAt: DateTime updatedAt: DateTime deletedAt: DateTime}
Context
The schema builder will allow you to create different schemas from one definition. When accessing a graphql endpoint we always need to keep in mind who and with what rights the access is done.
user.attr'email', GraphQLString builder.addQuery'context', GraphQLString,context builder.addQuery 'me', user,,
This will generate 2 different graphql schema.
The user-schema will be used for requests against the graphql system when the request is authenticated and identified to be a normal user.
The admin-schema will then be used for requests from the system administrators.
The build can also be generated as typeDefs
and resolvers
pair.
Contributing
If you want to help with this project, just leave a bug report or pull request. I'll try to come back to you as soon as possible
License
MIT