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 two in the "Intro to PostGraphile V5" series, which started with Part 1: Replacing the Foundations. This week, Benjie takes a look at PostGraphile V5's new unified plugin and preset system.
In PostGraphile V4, we had two different types of plugins: schema plugins (the original plugins; they control how the GraphQL schema is built: adding types, fields, arguments, etc) and server plugins (these came later and allowed you to manipulate the HTTP request lifecycle, add validation rules, customize GraphiQL, add CLI arguments and all that good stuff). These two different types of plugins worked in completely different ways and were added to the system in four different ways — two for the CLI, and two in API mode. Confusing, right?
Worse, as PostGraphile V4 advanced and we learned alongside the community what the best practices were, the recommended settings would evolve. But we couldn't change the defaults because that would break all existing user's schemas. To me, a major release is a major thing — we shouldn't have one every time I want to change a default — so I kept a list of recommended options up to date in the documentation and hoped that new users would use them. We managed to do 41 releases of PostGraphile 4.x over the course of 5 years, all without a major breaking change! But the situation with default options was not ideal.
And worst of all, there were those 4 different ways of configuring PostGraphile. You could use the CLI, in which case you could pass CLI flags. If you started getting too many CLI flags, you might switch to using the RC file, which would get merged in with the parsed CLI flags before being passed to the library. The RC file didn’t line up with the library options; nor did it quite line up with the CLI flags because, while it was derived from them, it used a format of its own — initially a quick tweak to enable greater flexibility, the RC file became a separate beast which needed its own documentation. Then we also had the library options for when you were using PostGraphile as middleware inside your existing Express/Koa/Fastify/etc server. And finally we had the schema–only options, which were a stripped down set from the library options applicable when using the schema with no server.
Ugh. So much to document!
It's clear this system evolved over time, and was patched together in the dribs and drabs of time I had available. Well, no more! With a significant amount of sponsorship behind me this time, I could do it right.
It was obvious I wanted a system that could evolve the defaults over time without major breaking changes. A "preset" system, where you could pull down the Jan2023 preset, or the Sept2025 preset, and stick with that as your base for the lifetime of your project. Clearly presets would need to be able to "extend" other presets, we don't want to have to populate every single option in every preset. And, in fact, why not make it so the user's own config is a preset — whether they're using CLI, library or schema–only modes?
Though I've long believed that GraphQL APIs aren’t a one–size–fits–all problem space — hence making PostGraphile so configurable and extensible — through experience with users and sponsors it's clear there are certain classes of use cases that are common:
- Some expose their GraphQL APIs directly over the web. For them it's critical that their schema is small and straightforward with nothing that could cause performance or security issues.
- Some leverage persisted operations to guarantee that only a preset list of queries will be accepted by the server. For these the schemas can be larger and expose more capabilities, trusting the frontend engineers to make sure the queries are performant.
- Some use PostGraphile to share data internally within their organization in an easy to query format. These users typically want to expose as much of Postgres' power as possible without having to give actual SQL access to the database, typically enabling features that would be a poor fit for most consumer–facing APIs.
- Some are in the prototyping phase and want to see what's possible, before they make their decisions and narrow down their schema later.
And on top of these "feature" decisions, there's also "style" to consider. Powerful and extensible connections, or simple and performant lists? Relay-focused global object identifiers, or straightforward database primary keys? Start with an empty schema and add things as you need them, or start with a expansive schema and remove things you don't need?
With this experience and these needs in mind, I started planning the new system. To simplify things for users there would be just one type of plugin. Presets would compose other presets, plugins, and scoped configuration options. These new presets and plugins would be enabled via the new
graphile-config module which would handle common concerns such as merging presets and ordering plugins. Plugins and presets would contain "scopes" that relate to different parts of the system, so a plugin could choose to affect the schema, or the server, or both. And if a plugin details a scope that is not relevant to the current situation, for example a "server" scope when using “schema–only”, then that would simply be ignored thanks to the system using declarative objects rather than the previous procedural-style hooks.
Thanks to the declarative and strongly-typed nature of the new system, I've expanded our new
graphile command (a toolbelt for handy utilities) with configuration-based subcommands. One command,
graphile config print, outputs the resolved preset (after merging in all the other presets that your preset extends) which is very useful for debugging and getting an understanding of all of the features that your combination of presets have enabled.
graphile config options, outputs a reference to all of the configuration options that you have available to you. Importantly, this is specific to your project, based on the modules that you're importing in your
graphile.config.* file. Via the magic of TypeScript it can even extract the documentation for each option from the various modules' TSDoc comments! And furthermore, the output is in markdown, so you can write it to a file to act as configuration documentation for your project. Honestly, I love it!
Having now built V5's unified plugins and presets system, I'm extremely pleased with it! I'm so happy, in fact, that I'm looking forward to integrating it with Graphile's other tools such as Graphile Worker (our Postgres-backed job queue) and Graphile Migrate (a lightweight SQL-based migration framework that focuses on DX) once V5 is out and stable.
But standardization of the configuration of PostGraphile across all the usage modes is a minor feature compared to some of the other things coming in V5…
Check back next week forNext, check out Part 3: Introspection and TypeScript; 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! Find out more at graphile.org/sponsor