Benjie is the community–funded maintainer of the Graphile suite — a collection of MIT–licensed Node.js developer tooling for GraphQL and/or PostgreSQL. A key project in this suite is PostGraphile, a high performance, customizable and extensible GraphQL schema generator for APIs backed primarily (but not exclusively!) by PostgreSQL.
This is part six in the "Intro to PostGraphile V5" series, which started with Part 1: Replacing the Foundations. This week, Benjie introduces another completely new feature coming in PostGraphile Version 5, available in pre–release now for all Graphile sponsors, find out more at postgraphile.org
There was a time when barely a week would go by where someone didn't ask how to export their PostGraphile V4 schema so that they could run it in a different system, or merge it into another schema, or other such desires. PostGraphile V4 schemas are built on the fly, and their resolvers are built on the fly — they only exist in memory, not as real code. Furthermore, the resolvers are tied deeply into the lookahead system, which stores data into objects that are linked to (but not from) the GraphQL schema types. If you start trying to take a PostGraphile V4 schema apart and squeeze it into other things, everything quickly falls apart and it doesn't work, or at least doesn't work right. This wasn't deliberate, it's just an artifact of the way the system was built, and of the capabilities of GraphQL.js at the time V4 was conceived.
But since we're rebuilding the entire system from scratch, can we solve this problem? I think so!
Enter graphile-export
— an npm module that can be used to export executable code from an in–memory GraphQL schema in Node.js. Of course, to do this the code needs to be built in a particular way, and to help you do that I've built eslint-plugin-graphile-export
, an ESLint plugin based significantly on the React hooks ESLint plugin. This allows you to wrap code for resolvers, plan resolvers, isTypeOf and other callbacks in an EXPORTABLE()
call (a little like React's useCallback()
call) and the system will automatically determine the variables that each function is using from the parent scope so that they can be exported along with the function body (which is extracted via the magic of func.toString()
😁).
Importantly, this exported schema (which can be exported as a JavaScript file either using pure GraphQL.js class constructors for optimal performance, or more readably as a traditional typeDefs/resolvers–style object with plans instead of resolvers) does not depend on graphile-build
, graphile-build-pg
or any other tooling used purely during the schemas construction — it only contains dependencies on its runtime needs, typically graphql
, grafast
and @dataplan/pg
which contains all the step classes to help Grafast deal with PostgreSQL.
Why should you care? Well, a number of reasons!
Perfect for Serverless
Building a PostGraphile schema takes a moment, and in serverless contexts you don't want to incur that overhead every time you boot up your serverless function. Cold start times can now be reduced by just running the final schema without any build steps. For a non-trivial schema consisting of 201 different GraphQL types, this results in just 186ms to start node, import the schema, count the types, and shut down again:
$ time node --input-type=module -e 'import { schema } from "./exported-schema.mjs"; console.log(Object.keys(schema.getTypeMap()).length)'
201
real 0m0.186s
user 0m0.213s
sys 0m0.011s
For reference, it takes 29ms to import a file just containing the statement 1 + 2;
:
$ echo "1 + 2;" > one_plus_two.mjs
$ time node --input-type=module -e 'import "./one_plus_two.mjs"; console.log("done")'
done
real 0m0.029s
user 0m0.029s
sys 0m0.000s
If you webpack the resulting file, then you can eliminate the filesystem overhead of all the imports, and the time comes down even further to just 115ms (only 86ms longer than the one_plus_two.mjs
baseline file!):
$ time node -e 'console.log(Object.keys(require("./exported-schema.webpacked.js").schema.getTypeMap()).length)'
201
real 0m0.115s
user 0m0.097s
sys 0m0.021s
Even better, because the number of dependencies have been significantly reduced, you also end up with a much smaller bundle size when webpacked than you would with V4, which means it's faster to unzip, reducing cold start time even further!
Improved Visibility
It can sometimes be hard to understand what an automagically built GraphQL schema is doing, which makes modifying it challenging. By exporting your schema, you can read through the final plan resolver code — code which we've put a significant effort into making as straightforward as we can reasonably make it! This can help you to grok the system, and then extend or customize it based on your own needs.
Eject Button
For those who are worried about adopting PostGraphile because they don't want to be locked into an auto–generated GraphQL schema forever, you now have a way to export the code from your generated schema and then manage that for yourself from then onwards, with no dependency on graphile-build
, graphile-build-pg
or even postgraphile
itself!
It's not just for PostGraphile
A wonderful feature of this module is that it isn't limited to PostGraphile — you could use this with other systems that build a GraphQL schema dynamically (even if that schema is generated by a "builder API" rather than auto–generation) in order to make your GraphQL schema start as fast as possible in production! Long term, I'd like to make it so we can even use babel plugins or similar rather than having to add EXPORTABLE()
wrappers, but that's not a priority right now 🙂
And speaking of compatibility…
Check back next week when Benjie will be talking about compatibility, and remember: to get your hands on a pre–release version of PostGraphile V5, all you need to do is sponsor Graphile at any tier and then drop us an email or DM! Find out more at graphile.org/sponsor
Top comments (0)