Generating Schema
SchemaComposer
allows to build a GraphQLSchema
instance. The Schema obtained calling the buildSchema()
method may be used in express-graphql
, apollo-server
and other libs that use GraphQL.js
under the hood for query execution at runtime.
Create Schema
SchemaComposer
provides three basic root types: Query
, Mutation
and Subscription
. It's imperative to initialize at least one of those, otherwise our Schema would not build.
import { schemaComposer } from 'graphql-compose';
import { AuthorTC } from './author';
schemaComposer.Query.addFields({
// add field with regular FieldConfig
currentTime: {
type: 'Date',
resolve: () => Date.now(),
},
// Assume that `AuthorTC` build with `graphql-compose-mongoose` which has CRUD resolvers
// in such case we can use pre-generated Resolvers as a FieldConfig
authorById: AuthorTC.getResolver('findById'),
authorMany: AuthorTC.getResolver('findMany'),
// ...
});
schemaComposer.Mutation.addNestedFields({
// also it may be very useful define nested fields
// Mutation will have `author` field, `author` will have `create` and `update` fields inside
'author.create': AuthorTC.getResolver('createOne'),
'author.update': AuthorTC.getResolver('updateById'),
// ...
});
export default schemaComposer.buildSchema(); // exports GraphQLSchema
Restrict access
GraphQL.js does not provide any access rights checks, so a developer would need to implemented them manually in the resolve
methods. With graphql-compose
it can be done by wrapping Resolvers:
// rootMutation.js
import { schemaComposer } from 'graphql-compose';
import { CommentTC } from './comment';
import { UserTC } from './user';
schemaComposer.Mutation.addNestedFields({
commentCreate: CommentTC.getResolver('createOne'), // may anybody
...adminAccess({
// only for admins
'user.create': UserTC.getResolver('createOne'),
'user.update': UserTC.getResolver('updateById'),
'user.remove': UserTC.getResolver('removeById'),
}),
});
function adminAccess(resolvers) {
Object.keys(resolvers).forEach(k => {
resolvers[k] = resolvers[k].wrapResolve(next => rp => {
if (!rp.context.isAdmin) {
throw new Error('You should be admin, to have access to this action.');
}
return next(rp);
});
});
return resolvers;
}
The isAdmin
property from the above example must be defined in express-graphql
or apollo-server
, in order to retrieve it from context
:
import express from 'express';
import graphqlHTTP from 'express-graphql';
import { schema } from './schema';
const PORT = 4000;
const app = express();
app.use(
'/graphql',
graphqlHTTP(async (request, response, graphQLParams) => {
return {
schema,
graphiql: true,
context: {
req: request,
isAdmin: someMethodForCheckingCookiesOrHeaders(request),
},
};
})
);
app.listen(PORT, () => {
console.log(`The server is running at http://localhost:${PORT}/graphql`);
});
Multiple Schemas
In some complex scenarios we may need several GraphQL Schemas within a single app. graphql-compose
by default exports the following classes/instances for single schema mode:
import { schemaComposer } from 'graphql-compose';
The equivalent class for multi-schema mode is called SchemaComposer
(with a capital S
). Unlike with single-schema where we have a static class, SchemaComposer
has a constructor and allows creating multiple instances:
import { SchemaComposer } from 'graphql-compose';
const schemaComposer1 = new SchemaComposer();
const ObjectTypeComposer1 = schemaComposer1.createObjectTC(...);
const schemaComposer2 = new SchemaComposer();
const ObjectTypeComposer2 = schemaComposer2.createObjectTC(...);
const InputTypeComposer2 = schemaComposer2.createInputTC(..);
const EnumTypeComposer2 = schemaComposer2.createEnumTC(...);
const UnionTypeComposer2 = schemaComposer2.createUnionTC(...);
const InterfaceTypeComposer2 = schemaComposer2.createInterfaceTC(...);
const ScalarTypeComposer2 = schemaComposer2.createScalarTC(...);
const Resolver2 = schemaComposer2.createResolver(...);
Types created via ObjectTypeComposer1
and ObjectTypeComposer2
will not be visible to each other: name-clashing and overriding would not be isssues, and multiple definitions for types with the same name are allowed, as long as they live in separate SchemaComposer
instances.