Skip to main content

Getting started

Installation

Grafast is an alternative execution layer for GraphQL; we still need GraphQL.js for building the schema, and parsing and validating requests. So the first thing you need to do to get started is to install grafast and graphql:

npm install --save grafast@beta graphql
note

We intend to write up a specification so that other languages may implement the Grafast execution strategy, but for now Grafast is JavaScript/TypeScript only.

If you have an existing GraphQL.js schema, you can run it through Grafast ─ see using with an existing schema.

TypeScript v5.0.0+ (optional)

We recommend that you use TypeScript for the best experience - auto-completion, inline documentation, etc.

You do not need to use TypeScript to use Grafast, but if you do then you must use a version from TypeScript v5.0.0 upward and configure it to support the exports property in package.json, you can do so by adding this to your TypeScript configuration:

    "moduleResolution": "node16", // Or "nodenext"
note

Our adherence to semver does not cover types - we may make breaking changes to TypeScript types in patch-level updates. The reason for this is that TypeScript itself is ever-changing, and the libraries we depend on often make breaking type changes, forcing us to do so too. Further, improvements to types are generally a good thing for developer experience, even if it might mean you have to spend a couple minutes after updating to address any issues.

However, we try and keep the TypeScript types as stable as possible, only making breaking changes when their benefits outweigh the costs (as determined by our maintainer), and we do our best to detail in the release notes how to deal with these changes (if any action is necessary).

My first plan

Let's build a simple GraphQL schema powered by Grafast plans and query it.

info

There are many ways to build a GraphQL schema, we're going to use the "schema first" approach in this example, but there's no reason that a Grafast schema couldn't be produced "code first" or "database first" or any other approach.

tip

We have a playground you can use for experimenting with Grafast without having to install any software.

First, lets define our GraphQL schema. We're going to go with an incredibly simple schema with a single field that adds together its two arguments:

const typeDefs = /* GraphQL */ `
type Query {
addTwoNumbers(a: Int!, b: Int!): Int
}
`;

Now we need to define the plans for the schema. The plan for our Query.addTwoNumbers field is to read the arguments, then use the lambda step to add them together. The lambda step takes a list of other steps, and then determines the result by calling the given callback for each set of resulting values.

const { lambda } = require("grafast");

const plans = {
Query: {
addTwoNumbers(_, fieldArgs) {
const $a = fieldArgs.get("a");
const $b = fieldArgs.get("b");
return lambda([$a, $b], ([a, b]) => a + b);
},
},
};
tip

lambda is a bit of an escape hatch ─ it enables one-by-one processing of values rather than the batched processing that Grafast prefers for efficiency. It can be handy as a utility function when batching would confer no benefit, but in general you should pick a more suitable step.

tip

Making the callback function to lambda a global (defined once) function would enable Grafast to potentially detect multiple uses of it and deduplicate them. This is important for performance if a similar lambda callback is used in lots of places in a query.

To turn this into a schema, we can use the makeGrafastSchema helper which will stitch the typeDefs and the plans together:

const { makeGrafastSchema } = require("grafast");

const schema = makeGrafastSchema({
typeDefs,
plans,
});

Finally, we can run our query:

const { grafastSync } = require("grafast");

const result = grafastSync({
schema,
source: /* GraphQL */ `
{
addTwoNumbers(a: 40, b: 2)
}
`,
});

The result is what we'd expect:

{
"data": {
"addTwoNumbers": 42
}
}
info

Our schema is so simple the query could be ran synchronously with grafastSync(...), but most queries in the real world should be executed through grafast(...) and will return either the result, a promise to the result, or even an async iterable for @stream, @defer, subscriptions and similar!

We could then serve this schema over HTTP using a server such as grafserv or any envelop-capable server.

My first step class

The building blocks of an operation plan are "steps." Steps are instances of "step classes," and Grafast makes available a modest range of standard steps that you can use; but when these aren't enough you can write your own. Step classes extend the ExecutableStep class. The only required method to define is execute, however most steps will also have a constructor in which they accept their arguments (some of which may be dependencies) and may also have the various lifecycle methods.

Full details for doing so can be found in Step classes, but let's build ourselves a simple one now to replace the lambda usage above:

const { ExecutableStep } = require("grafast");

class AddStep extends ExecutableStep {
constructor($a, $b) {
super();
this.addDependency($a);
this.addDependency($b);
}

execute({ indexMap, values: [aDep, bDep] }) {
return indexMap((i) => {
const a = aDep.at(i);
const b = bDep.at(i);
return a + b;
});
}
}

By convention, we always define a function that constructs an instance of our class:

function add($a, $b) {
return new AddStep($a, $b);
}
note

There's multiple reasons for this, a simple one is to make the plan code easier to read: we won't see the new calls in our plan resolver functions, nor the redundant Step wording, resulting in a higher signal-to-noise ratio. More importantly, though, is that the small layer of indirection allows us to do some minor manipulations before handing off to the class constructor, and makes the APIs more future-proof since we can have the function return something different in future without having to refactor our plans in the schema. And remember that this cost is only incurred at planning time (which is generally cached and can be re-used for similar future requests), and each field is only planned once, so the overhead of an additional function call is negligible.

Now we can use this function to add our numbers, rather than the lambda plan:

 const plans = {
Query: {
addTwoNumbers(_, args) {
const $a = args.get("a");
const $b = args.get("b");
- return lambda([$a, $b], ([a, b]) => a + b);
+ return add($a, $b);
},
},
};

You may well be able to write an entire Grafast schema using off-the-shelf step classes, but it's worth being aware of how step classes work in case you want to push your optimizations further. Read more about step classes, or continue through the documentation.