![Dev Dependencies](https://david-dm.org/smartprix/gqutils/dev-status.svg)
gqutils
Utilities for GraphQL
Int
Float
String
StringOrInt
StringOriginal
: String
is automatically trimmed of whitespaces. If you want an untrimmed string use this.Boolean
ID
IntID
: this can be used where input is either an integer or a numeric string. value is casted as an integer.Email
URL
DateTime
UUID
JSON
JSONObject
: A valid JSON object (arrays and other json values are invalid), most of the times you'd want to use JSONObject
instead of JSON
Functions
makeSchemaFromModules(modules, opts)
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} = makeSchemaFromModules(modules, {
baseFolder: `${__dirname}/lib`,
schema: ['admin', 'public'],
allowUndefinedInResolve: false,
resolverValidationOptions: {},
});
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.
Concept of Schemas
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.
Example Schema
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,
},
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,
};
makeSchemaFromDirectory(directory, opts = {})
Create a graphQL schema from a directory. It'll automatically require all the schemas & resolvers from inside the directory and create a schema using that.
It'll require all the files with format:
export {schema}
export schema from
module.exports = {schema}
exports.schema =
Object.defineProperty(exports, "schema",
Example
const {schemas} = makeSchemaFromDirectory(`${__dirname}/lib`, {
schema: ['admin', 'public'],
allowUndefinedInResolve: false,
resolverValidationOptions: {},
});
makeSchemaFromConfig(opts = {})
Create a graphQL schema from config defined in package.json
(gqutils
key), gqutils.js
or sm-config.js
(gqutils
key) in the root directory.
"gqutils": {
"schemaDirectory": "dist/lib",
"schema": ["admin", "public"],
"allowUndefinedInResolve": false,
};
const {schemas} = makeSchemaFromConfig();
Gql Class
The Gql class provides a way to execute the schema and to construct queries.
Executable Schemas
There are two ways you can use Gql to get an executable schema:
Config:
const gql = Gql.fromConfig({
schema: ['admin', 'public'],
schemaName: 'admin',
...
});
Provide the same options you would provide to makeSchemaFromConfig under the config field in the constructor options.
Select the schema you want to execute against using the schemaName option (default is the default
schema)
Schemas:
If you have multiple schemas and would like to have multiple Gql instances each executing different schemas then use it this way. It takes the output of one of the makeSchema
functions plus some options as input.
const output = makeSchemaFromConfig();
const adminGql = Gql.fromSchemas({
...output,
schemaName: 'admin',
});
const publicGql = Gql.fromSchemas({
...output,
schemaName: 'public',
})
Execute against API
If you would like to use Gql against a GraphQL API:
const apiGql = Gql.fromApi({
endpoint: 'https://example.com/api',
headers: {},
cookies: {},
});
Client Side Use
Browser:
If you are using Gql against Api from browser then use it like:
import {GqlApi} from 'gqutils';
import postRequest from 'gqutils/dist/postRequestBrowser'
GqlApi.postRequest = postRequest;
const apiGql = new GqlApi({
endpoint: 'https://example.com/api',
headers: {},
cookies: {},
});
Server Side Client:
If you want to use the client without adding graphql
and graphql-tools
peer dependencies then import GqlApi directly from it's file like:
import GqlApi from 'gqutils/dist/GqlApi';
import postRequest from 'gqutils/dist/postRequestNode'
GqlApi.postRequest = postRequest;
const apiGql = new GqlApi({
endpoint: 'https://example.com/api',
headers: {},
cookies: {},
});
Query Building And Execution
gql.tag
This is a Tag function used to build GraphQL Queries, it'll automatically convert args and arg objects. Some examples:
const query = gql.tag`
query {
employee(name: ${'admin'}) {
id
}
}`;
const args = {
name: 'admin',
};
const query = gql.tag`
query {
employee(${args}) {
id
}
}`;
These all give us the query:
query {
employee(name: "admin") {
id
}
}
gql.enum
As the tag
function adds "
to all string args, enums need to be handled separately.
const query = gql.tag`
query {
employee(type: ${gql.enum('MANAGER'))}) {
id
}
}`;
This gives us the query:
query {
employee(type: MANAGER) {
id
}
}
gql.fragment
Note: This function is only available to use with execuatble schemas and not with API
Use this function to add a fragment declared in the schema to the query. eg.:
const employeeFragment = {
graphql: 'fragment',
type: 'Employee',
fields: [
'id',
'name',
'email',
'phone',
'createdAt',
],
};
const query = gql.tag`
query {
employee(name: ${'admin'}) {
${gql.fragment('employeeFragment')}
}
}`;
This will give us the query:
query {
employee(name: "admin") {
...employeeFragment
}
fragment employeeFragment on Employee {
id
name
email
phone
createdAt
}
}
gql.exec
gql.getAll
is an alias
Execute a query. Let's consider the following query example:
const query = gql.tag`
query($name: String) {
employee(name: $name) {
id
name
email
phone
}
}`;
async function getEmployeeByName(name) {
const res = await gql.exec(query, {
variables: {name},
});
return res.employee;
}
gql.get
For the previous query there was only one field in the result. To simplify that use case we have the get function that automatically gets the nested field if only one field was queried.
It goes one level deep if the nested field is nodes
and is the only field.
const query = gql.tag`
query($name: String) {
employee(name: $name) {
id
name
email
phone
}
}`;
async function getEmployeeByName(name) {
return gql.get(query, {
variables: {name},
});
}
Language Reference
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 subscriptionfragment
: for declaring common fragments (To be used with Gql)
These are available in the type definitions, so can be imported as 'GQUtilsSchema' and type checked.
Types
Defined with graphql: type
const Employee = {
graphql: 'type',
name: 'Employee',
description: 'An employee',
interfaces: ['Person'],
relayConnection: true,
schema: ['admin', 'public'],
fields: {
id: 'ID!',
name: 'String',
},
}
Input Types
Defined with graphql: input
Its denition is mostly same as type.
const EmployeeInput = {
graphql: 'input',
name: 'EmployeeInput',
description: 'An employee input',
schema: ['admin', 'public'],
fields: {
id: 'ID!',
name: 'String',
},
}
Unions
Defined with graphql: union
const User = {
graphql: 'union',
name: 'User',
description: 'An employee or a guest',
schema: ['admin', 'public'],
types: ['Employee', 'Guest'],
resolveType: (value, info) => 'Type',
}
Interface
Defined with graphql: interface
Interfaces in gqutils
work more like extends
, i.e. any type
that implements an interface automatically has the fields of that interface.
This can be used to have a set of default fields. (Along with default resolver implementations)
const Vehicle = {
graphql: 'interface',
name: 'Vehicle',
description: 'A vehicle (can be a car or bike or bus etc)',
schema: ['admin', 'public'],
extends: ['Transport'],
fields: {
id: 'ID!',
modelName: 'String',
variantName: 'String',
name: {
type: 'String',
resolve: (root) => {
return `${root.modelName} - ${root.variantName}`;
}
}
},
resolveType: (value, info) => 'Type',
}
Enum
Defined with graphql: enum
const Color = {
graphql: 'enum',
name: 'Color',
description: 'color you know C-O-L-O-R',
schema: ['admin', 'public'],
values: {
RED: 'RED',
WHITE: 'white',
BLACK: 0,
BLUE: {
value: 'blue',
description: 'the best color obviously',
deprecationReason: 'too much blue is happening',
schema: ['admin'],
},
},
resolveType: (value, info) => 'Type',
}
Scalar
Defined with graphql: scalar
You need to give either resolve
or serialize, parseValue, parseLiteral
const URL = {
graphql: 'scalar',
name: 'URL',
description: 'A url',
schema: ['admin', 'public'],
resolve: GraphQLURL
serialize: (value) => serializedValue,
parseValue: (value) => parsedValue,
parseLiteral: (ast) => parsedValue,
}
Query / Mutation / Subscription
- Defined as
graphql: query
=> for Query - Defined as
graphql: mutation
=> for Mutation - Defined as
graphql: subscription
=> for Subscription
const getEmployees = {
graphql: 'query',
name: 'employees',
description: 'Get employees',
type: 'EmployeeConnection',
schema: ['admin', 'public'],
resolve: (root, args, ctx, info) => {}
args: {
$default: ['id', '$paging'],
name: 'String',
email: 'String',
},
}
Fields / Args
const Employee = {
graphql: 'type',
name: 'Employee',
fields: {
id: 'ID!',
email: 'String',
emails: '[String!]',
teams: {
type: 'TeamConnection',
description: 'teams that the employee belongs to',
default: 'yo',
schema: ['admin'],
deprecationReason: 'teams are so old fashioned',
resolve: (root, args, ctx, info) => {}
args: {
$default: ['id', 'phone!', '$paging', '$sort'],
search: 'String',
status: {
type: 'String',
default: 'active',
schema: ['admin'],
},
},
},
}
}
Fragments
For use with Gql's fragment function while building queries.
const EmployeeFragment = {
graphql: 'fragment',
type: 'Employee',
fields: [
'id',
{
alias: 'contact',
name: 'email',
},
{
name: 'teams',
args: {
status: 'active',
},
fields: [
'id',
'phone',
]
},
]
};
getConnectionResolver(query, args, options = {})
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}`,
}
}
});
}
formatError
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,
}));
Generate type definitions from schema
Using https://github.com/dangcuuson/graphql-schema-typescript#readme to generate types. Read more about them here https://medium.com/@pongsatt/how-to-generate-typescript-types-from-graphql-schemas-8d63ed6cda2e
Pass the generated schema to generateTypesFromSchema
and it will output type definitions in 'typings/graphql' folder.
Or use the cli after creating gqutils config or adding to package.json
CLI
Usage: gqutils types [options]
Use to generate types from graphql schema
$ gqutils types
Only build specific schema:
$ gqutils types --schema admin
Options:
-v, --version output the version number
-s, --schema [name] Specify schema (default: all)
-d, --dest [dir] Specify Destination Directory (default: typings/graphql) (default: "typings/graphql")
-h, --help output usage information
Config
Have a file 'gqutils.js' in the projects root directory which exports the following options:
moudles.exports = {
modules: [
'Array',
'of',
'modules',
],
baseFolder: 'dist/lib',
schema: ['schemaNames', 'to', 'generate' 'types', 'for'],
contextType: 'any',
generateTypeOptions: {
tabSpaces: 4,
},
}
Or provide these properties in your package.json under the key 'gqutils'.