Utilities for GraphQL
String
,Int
,Float
,Boolean
,ID
JSON
StringOrInt
Email
URL
DateTime
UUID
StringOriginal
IntID
: this can be used where input is either an integer or a numeric string. value is casted as an integer.
String
is automatically trimmed of whitespaces. If you want an untrimmed string use StringOriginal
.
IntID
Create a graphQL schema from various modules. If the module is a folder, it'll automatically require it.
const modules = [
'employee/Employee',
'Category',
];
const {schemas} = makeSchemasFromModules(modules, {
baseFolder: `${__dirname}/lib`,
schema: ['admin', 'public'],
allowUndefinedInResolve: false,
resolverValidationOptions: {},
});
// schemas will be {default: GraphqlSchema, admin: GraphqlSchema, public: GraphqlSchema}
This function returns {schemas, pubsub}
, you can ignore pubsub if you're not using graphql subscriptions.
Each module can either export {schema, resolvers}
or the {schema}
can contain resolvers in itself.
makeSchemasFromModules
, returns multiple graphql schemas. You have to list all possible schema names in the schema
option. Each graphql schema will only contain the types/queries/mutations etc, that have listed that schema name in their schema
option.
To be included in a particular schema, the following must be true:
- The schema name is defined in the
schema
option - The schema name is also defined in the parent's
schema
option
eg. if a query returns a particular type, then it'll not be included in a schema the that type doesn't have the schema name in its schema
option. In short, it works like intersection of schema
options of parent and child.
In case of fields, args & values, if you haven't defined schema
option, it'll be included in all schemas. So, generally speaking in case of args & values, only define schema
when you want them to exclude from a particular schema and that schema is listed in its parent's schema
.
Regardless of the schema option, a default schema named default
contains all the types/fields.
const Employee = {
graphql: 'type',
fields: {
id: 'ID!',
smartprixId: 'ID',
name: 'String',
email: 'String',
phone: 'String',
createdAt: 'DateTime',
updatedAt: 'DateTime',
},
schema: ['admin', 'public'],
relayConnection: true,
};
const getEmployee = {
graphql: 'query',
name: 'employee',
type: 'Employee',
args: {
$default: ['id', 'email'],
},
schema: ['admin', 'public'],
};
const getEmployees = {
graphql: 'query',
name: 'employess',
type: 'EmployeeConnection',
args: {
$default: ['name', '$paging'],
},
schema: ['admin', 'public'],
};
const saveEmployee = {
graphql: 'mutation',
args: {
$default: ['id', 'name', 'email', 'phone'],
smartprixId: {
type: 'ID',
default: 0,
schema: ['admin'],
},
},
schema: ['admin'],
};
const deleteEmployee = {
graphql: 'mutation',
args: {
id: 'ID!',
},
schema: ['admin'],
};
const employeeAdded = {
graphql: 'subscription',
type: 'Employee',
};
const employeeChanged = {
graphql: 'subscription',
type: 'Employee',
args: {
'id': 'ID!',
},
};
const resolvers = {
Query: {
employee: getEmployee,
employees: getEmployees,
},
Mutation: {
saveEmployee,
deleteEmployee,
},
// You can also declare Subscription
// For Subscription Related Things
// Every resolver can contain {subscribe, filter, resolve}
// Only subscribe is required. Rest are optional.
// subscribe: return an async iterator that will contain data to be returned to the client
// filter: Filter events from pubsub async iterator
// resolve: Modify event data before sending to client
Subscription: {
employeeAdded: {
subscribe() {
return pubsub.asyncIterator('employeeAdded');
},
resolve(employee) {
if (employee.password) employee.password = '******';
return employee;
},
},
employeeChanged: {
subscribe() {
return pubsub.asyncIterator('employeeChanged');
},
filter(employee, args) {
return employee.id === args.id;
},
resolve(employee) {
if (employee.password) employee.password = '******';
return employee;
},
},
},
};
export {
schema: {
Employee,
getEmployee,
getEmployeees,
saveEmployee,
deleteEmployee,
employeeAdded,
employeeChanged,
},
resolvers,
};
graphql option reference
type
: for object typeinput
: for input object typeunion
: for unioninterface
: for interfaceenum
: for enumscalar
: for scalarsquery
: for root querymutation
: for root mutationsubscription
: for root subscription
Defined with graphql: type
const Employee = {
// graphql = type means it's a graphql type
graphql: 'type',
// name (optional): name of the type
// if name is not given it'll be taken from the object where it is exported
// eg. export {schema: {Employee}}
name: 'Employee',
// description (optional): description that'll displayed in docs
description: 'An employee',
// interfaces (optional): interfaces this type implements
interfaces: ['Person'],
// relayConnection (optional, default=false): generate a relay connection type automatically
// if this is true, a connection type (EmployeeConnection here) will be added to the schema
// relayConnection can also be an object with fields {edgeFields, fields}
// edgeFields and fields will be merged with EmployeeEdge and EmployeeConnection respectively
// eg. relayConnection: {
// edgeFields: {title: 'String!'},
// fields: {timeTaken: 'Int!'}
// }
relayConnection: true,
// schema (required): schemas that this type is available in
// if schema is not given, it won't be available in any schema
schema: ['admin', 'public'],
// fields (required): fields of the type
// see Fields definition for more details
fields: {
id: 'ID!',
name: 'String',
},
}
Defined with graphql: input
Its denition is mostly same as type.
const EmployeeInput = {
// graphql = input means it's a graphql input type
graphql: 'input',
// name (optional): name of the input type
// if name is not given it'll be taken from the object where it is exported
// eg. export {schema: {EmployeeInput}}
name: 'EmployeeInput',
// description (optional): description that'll displayed in docs
description: 'An employee input',
// schema (required): schemas that this input type is available in
// if schema is not given, it won't be available in any schema
schema: ['admin', 'public'],
// fields (required): fields of the input type
// see Fields definition for more details
fields: {
id: 'ID!',
name: 'String',
},
}
Defined with graphql: union
const User = {
// graphql = union means it's a graphql union
graphql: 'union',
// name (optional): name of the union
// if name is not given it'll be taken from the object where it is exported
// eg. export {schema: {User}}
name: 'User',
// description (optional): description that'll displayed in docs
description: 'An employee or a guest',
// schema (required): schemas that this union is available in
// if schema is not given, it won't be available in any schema
schema: ['admin', 'public'],
// types (required): types that this union contains
types: ['Employee', 'Guest'],
// resolveType (optional): function for determining which type is actually used when the value is resolved
resolveType: (value, info) => 'Type',
}
Defined with graphql: interface
const Vehicle = {
// graphql = interface means it's a graphql iterface
graphql: 'interface',
// name (optional): name of the interface
// if name is not given it'll be taken from the object where it is exported
// eg. export {schema: {Vehicle}}
name: 'Vehicle',
// description (optional): description that'll displayed in docs
description: 'A vehicle (can be a car or bike or bus etc)',
// schema (required): schemas that this interface is available in
// if schema is not given, it won't be available in any schema
schema: ['admin', 'public'],
// fields (required): fields of the interface
// see Fields definition for more details
fields: {
id: 'ID!',
name: 'String',
},
// resolveType (optional): function for determining which type is actually used when the value is resolved
resolveType: (value, info) => 'Type',
}
Defined with graphql: enum
const Color = {
// graphql = enum means it's a graphql enum
graphql: 'enum',
// name (optional): name of the enum
// if name is not given it'll be taken from the object where it is exported
// eg. export {schema: {Vehicle}}
name: 'Color',
// description (optional): description that'll displayed in docs
description: 'color you know C-O-L-O-R',
// schema (required): schemas that this enum is available in
// if schema is not given, it won't be available in any schema
schema: ['admin', 'public'],
// values (required): enum values
// see Field definition for more details
values: {
// both name and value are RED,
RED: 'RED',
// name is WHITE, value is white
WHITE: 'white',
// name is BLACK, value is 0
BLACK: 0,
// you can also define this as an object
BLUE: {
// value (optional): if value is not given, name is used as value
value: 'blue',
// description (optional): description that'll displayed in docs
description: 'the best color obviously',
// deprecationReason (optional): reason for deprecation
deprecationReason: 'too much blue is happening',
// schema (optional): schemas that this value is available in
// if schema is not given, it will be available in its parent's schemas
schema: ['admin'],
},
},
// resolveType (optional): function for determining which type is actually used when the value is resolved
resolveType: (value, info) => 'Type',
}
Defined with graphql: scalar
You need to give either resolve
or serialize, parseValue, parseLiteral
const URL = {
// graphql = scalar means it's a graphql scalar
graphql: 'scalar',
// name (optional): name of the scalar
// if name is not given it'll be taken from the object where it is exported
// eg. export {schema: {URL}}
name: 'URL',
// description (optional): description that'll displayed in docs
description: 'A url',
// schema (required): schemas that this scalar is available in
// if schema is not given, it won't be available in any schema
schema: ['admin', 'public'],
// resolve (required/optional): Already defined graphql scalar you can resolve it with
// if resolve is not given then, serialize, parseValue, parseLiteral must be given
resolve: GraphQLURL
// serialize (optional, default=identity function): send value to client
serialize: (value) => serializedValue,
// parseValue(optional, default=identity function): parse value coming from client
parseValue: (value) => parsedValue,
// parseLiteral (required/optional): parse ast tree built after value coming from client
parseLiteral: (ast) => parsedValue,
}
- Defined as
graphql: query
=> for Query - Defined as
graphql: mutation
=> for Mutation - Defined as
graphql: subscription
=> for Subscription
const getEmployees = {
// graphql = query means it's a graphql query
graphql: 'query',
// name (optional): name of the query
// if name is not given it'll be taken from the object where it is exported
// eg. export {schema: {employees: getEmployees}}
name: 'employees',
// description (optional): description that'll displayed in docs
description: 'Get employees',
// type (required): type that this query returns
type: 'EmployeeConnection',
// schema (optional): schemas that this type is available in
// if schema is not given, it will be available in its parent's schemas (Employee's)
schema: ['admin', 'public'],
// resolve (optional): resolver for this query
// this can also be defined in resolvers
resolve: (root, args, ctx, info) => {}
// args (optional): arguments of the query
// see Fields / Args definition for more details
args: {
$default: ['id', '$paging'],
name: 'String',
email: 'String',
},
}
const Employee = {
graphql: 'type',
name: 'Employee',
// fields
fields: {
// key is field's name, value is field's type
id: 'ID!',
// name is email, type is String
email: 'String',
// you can use ! for non null, and [] for list same as graphql
emails: '[String!]',
// you can also define it as an object
teams: {
// type (required): type of the field
type: 'TeamConnection',
// description (optional): description that'll displayed in docs
description: 'teams that the employee belongs to',
// default (optional): default value of the field
default: 'yo',
// schema (optional): schemas that this type is available in
// if schema is not given, it will be available in its parent's schemas (Employee's)
schema: ['admin'],
// deprecationReason (optional): reason why this field was deprecated
deprecationReason: 'teams are so old fashioned',
// resolve (optional): resolver for this field
// this can also be defined in resolvers
resolve: (root, args, ctx, info) => {}
// args (optional): arguments that this field takes
// NOTE: args are defined as the same way fields are
args: {
// $default is special
// fields defined in $default will be taken from parent's (TeamConnection's) fields
// fields in $default will not have required condition even if mentioned in the type
// to enforce required condition add `!` to the field's name
// $paging is used for paging parameters (first, after, last, before)
// $order is used for order parameters (orderBy & orderDirection)
$default: ['id', 'phone!', '$paging', '$order'],
// rest of the parameters are defined in same way as field definition
search: 'String',
status: {
type: 'String',
default: 'active',
schema: ['admin'],
},
},
},
}
}
Given a query (xorm query) and its arguments, it'll automatically generate a resolver for a relay connection.
options can be {resolvers: { fields }}
if you want to override default resolvers or specify any extra resolver.
async function getEmployees(root, args) {
const query = Employee.query();
if (args.name) {
query.where('name', 'like', `%${args.name}%`);
}
return getConnectionResolver(query, args);
}
async function getReviews(root, args) {
const query = Review.query();
if (args.name) {
query.where('name', 'like', `%${args.name}%`);
}
return getConnectionResolver(query, args, {
resolvers: {
totalCount: 0,
edges: {
format: (node, i, {offset}) => `${offset + i}. ${node.title}`,
}
}
});
}
Use this function to format the errors sent to the client, so that you can display them in a user friendly way.
It'll add fields
to each error, which you can use to display errors on front end.
import {formatError} from 'gqutils';
route.post('/api', apolloKoa({
schema: graphqlSchema,
formatError: formatError,
}));