Introducing Envelop - The GraphQL Plugin System
Today we are super excited to share with you a new open-source library we’ve been working on for the past few months!
TL;DR
- Envelop aims to be The GraphQL Plugin system (envelop.dev)
- Envelop is not a GraphQL server, it’s just a wrapper on top of the GraphQL engine.
- Make “hard” GraphQL capabilities easy by installing powerful plugins (Caching, Tracing with Prometheus/DataDog/NewRelic/Sentry/OpenTelemetry/ApolloTracing, Loggers, GraphQL-Jit, Persisted Operations, Security with rate-limit/depth-limit/Auth0 and many others from the Plugins Hub)
- Solve once and share across the ecosystem - Each plugin works with any HTTP server or deployment (Express/Fastify/Netlify/Vercel/AWS Lambda/Azure Functions/Cloudflare Workers/Google Cloud Functions) and any schema builder (SDL, Apollo Federation, Nexus, TypeGraphQL and others)
- Framework for Frameworks - Envelop will become the new basis for GraphQL Frameworks. It’s already available if you are using RedwoodJS, and we have PRs open for Loopback, NestJS, Parse and others.
- “Babel for GraphQL” - Envelop also aims to be the “enrichment layer” for GraphQL. You can use
any new GraphQL Features today (
@defer
/@stream
,@live
queries,@oneOf
and any open RFC already today, even if graphql-js has not yet implemented or released it) envelop
is also available on ProductHunt!
Overview
Envelop is a lightweight library that allows developers to create plugins that enriches the GraphQL execution layer with new features. It’s the plugin system for your GraphQL layer.
Envelop’s core is based on hooks and plugins - we believe that developers should share and open-source small pieces of implementation and logics that can help others, while still keeping their codebase customized to their needs with full control and power.
Envelop is schema-agnostic and HTTP-server agnostic, meaning that it can be integrated with any kind of setup. We do not aim to provide a complete, vendor-locking suite, since we believe that the developer should be able to adjust any part of their application, at any time, without major implications.
As with any open-source created and maintained by The Guild - we created Envelop based on real-life use-cases, coming from our clients (startups, enterprises and our own products) and from the GraphQL community. We strive to keep our open-source modern, well maintained and always up-to-date, and support the community around it.
Background
While working with many clients on GraphQL projects, we noticed a major gap in collaboration across projects, and a gap in knowledge sharing.
Things were overcomplicated, and GraphQL servers just kept reinventing the wheel.
We believe these gaps were created because many GraphQL frameworks are focused on creating a “whole” experience, sometimes to promote their stack/product, rather than introducing real flexibility for developers.
Also, as GraphQL keeps evolving with new capabilities and solutions, it seems like the GraphQL
frameworks are making it hard or even impossible to use these new features like @defer
/
@stream
, @live
queries, @oneOf
and other new GraphQL features.
We tried to locate the core of that issue, and from our point-of-view, it seemed like GraphQL was missing a robust, simple and flexible plugin system. That’s why we created Envelop.
While most existing implementations of GraphQL servers/frameworks introduce feature-rich environments, Envelop aims to introduce only hooks on top of the original GraphQL functions, without modifying the signature, and allow you to choose the features that you need, by adding Envelop plugins.
Most existing GraphQL servers are implemented in a way that implements schema building and HTTP server integration, meaning the features that are only relevant to the GraphQL layer “leaks” and creates a very opinionated product.
We believe that the Network Transport <> GraphQL Engine <> GraphQL Schema
coupling should be
separated, and each part should take care of it’s role, without mixing these features. Each layer
has its own responsibility.
That’s why we decided to create an agnostic library where you can choose your transport (HTTP / WebSocket / anything else), choose your schema (any schema builder works with Envelop), and Envelop will take care of the extra features.
We also felt that for too long things haven’t been moving on the server area when it comes to GraphQL - most servers are in maintenance/support mode and don’t bring anything new.
Many extra features of GraphQL are straightforward, but not available for developers since it’s not open-source (or, bundled into specific frameworks/servers), or not transparent enough (like, tracing, metrics, auditing, fine-grained permissions and more). We aim to change that.
The envelop
Approach
One of the goals of Envelop is to allow developers to modify/enrich their GraphQL execution layer.
In most implementations, running a GraphQL operation consists of the following actions:
parse
- takes raw GraphQL operation string and converts it into an executable DocumentNode.validate
- AST based validations, that checks the DocumentNode against the GraphQL schema.contextBuilding
- builds a GraphQL execution context, based on the incoming request, and prepares for the execution.variables
- parses the input variables and builds the variables object.execute
- takes a GraphQL schema, operationDocumentNode
, variables and context and runs your resolvers.
There are more phases, and more workflows - It’s dropped only for brevity ;)
Envelop allows developers to create plugins that hook into any phase, and change the behaviour of
it, based on the feature it implements. The output of envelop
are the GraphQL functions, with the
injected behaviour based on the plugins you use.
Very initial draft of what Envelop is.
By creating these plugins, you can create custom behaviour in a very easy way.
Let’s try to break a few plugins and understand how it works:
useLogger
- hooks into the “before” of all phases, and just doesconsole.log
.useTiming
- hooks into “before” and “after” of all phases, measures times, and then prints it.useParserCache
- hooks into before and after theparse
phase and implements caching based on the operation string.useGraphQLJit
- hooks intoexecute
phase and replaces theexecute
function with GraphQL-Jit’s executor.usePersistedOperations
- hooks intoparse
and replaces theparse
function with a function that maps a hash into a DocumentNode.useGenericAuth
- hooks into context building and resolves the current user from the GraphQL request, then hooks into theexecute
phase to verify the user authentication.useOpenTelemetry
- hooks into all phases, execution and resolvers, and creates Spans for OpenTelemetry tracing.
Makes sense, right? Because if you have control of all the execution pipeline, you can easily create very sophisticated plugins that implement things that were missing before with GraphQL, without changing/forking GraphQL.
Getting Started
To get started with Envelop, make sure you understand the other requirements that you need:
- You need a GraphQL schema - it doesn’t matter how you created it (either with GraphQL core library, makeExecutableSchema, or any code-first / schema-first frameworks)
- You need a HTTP server - like express, Fastify, Koa AWS Lambda or others
- You need a request normalization and GraphQL request pipeline - we recommend
graphql-helix
for that.
You can also find more in-depth article and technical documentation here
To get started quickly, start by installing only @envelop/core
package in your project:
yarn add @envelop/core
Now, take a look at the following code snippet - it creates a /graphql
endpoint, normalizes the
incoming request with graphql-helix
, creates the GraphQL functions with Envelop and runs the
operation:
import fastify from 'fastify'
import { getGraphQLParameters, processRequest } from 'graphql-helix'
import { envelop, useLogger, useSchema } from '@envelop/core'
// This creates the `getEnveloped` function for us. Behind the scene the wrapped functions are created once, here.
const getEnveloped = envelop({
plugins: [useSchema(schema), useLogger()]
})
const app = fastify()
app.route({
method: ['POST'],
url: '/graphql',
async handler(req, res) {
// Here we can pass the request and make available as part of the "context".
// The return value is the GraphQL-proxy that exposes all the functions.
const { parse, validate, contextFactory, execute, schema } = getEnveloped({
req
})
const request = {
body: req.body,
headers: req.headers,
method: req.method,
query: req.query
}
const { operationName, query, variables } = getGraphQLParameters(request)
// Here, we pass our custom functions to Helix, and it will take care of the rest.
const result = await processRequest({
operationName,
query,
variables,
request,
schema,
parse,
validate,
execute,
contextFactory
})
if (result.type === 'RESPONSE') {
res.status(result.status)
res.send(result.payload)
} else {
// You can find a complete example with Subscriptions and stream/defer here:
// https://github.com/contrawork/graphql-helix/blob/master/examples/fastify/server.ts
res.send({ errors: [{ message: 'Not Supported in this demo' }] })
}
}
})
app.listen(3000, () => {
console.log(`GraphQL server is running...`)
})
With that example, we only used useLogger
, so while executing GraphQL operations, you should see
that everything you do should be printed to the log.
Use Plugins
But logging is not everything possible with Envelop. By adding more plugins, you can add more features to your GraphQL execution, based on your app needs.
For example, here’s a cool snippet for boosting things in your execution layer:
const getEnveloped = envelop({
plugins: [useSchema(schema), useParserCache(), useValidationCache(), useGraphQLJit()]
})
By using
useParserCache
we make sure to parse every unique operation only once. By usinguseValidationCache
we make sure to validate every unique operation only once. By usinguseGraphQLJit
we replace the default execute function with a just-in-time implementation.
While working with our clients, we saw that many pieces of code can be moved into an Envelop plugin, and shared with the community. That created tons of plugins that you can now use quickly, without implementing it on your own for that specific project!
We also created Envelop Plugins Hub : a place where you can find all the plugins that are available for Envelop, with their documentation, versions, and some stats. Plugin Hub is open and available for the community to add their own.
Write Your Own Plugins
Writing plugins for Envelop is super simple. We allow you to write code that connects to the phases that you need, and we’ll make sure to run your functions at the right time.
Plugins can either live as internal plugins that are relevant only to your project, or you can share it with the community as NPM package.
To get started with a custom plugin, choose what phases you need, and create functions that handle what you need. Envelop will provide a low-level, flexible api in each phase, so you can communicate with the core pipeline.
import { Plugin } from '@envelop/types'
const myPlugin: Plugin = {
onParse({ params }) {
console.log('Parse started!', { args })
return result => {
console.log('Parse done!', { result })
}
},
onExecute({ args }) {
console.log('Execution started!', { args })
return {
onExecuteDone: ({ result }) => {
console.log('Execution done!', { result })
}
}
}
}
const getEnveloped = envelop({
plugins: [
/// ... other plugins ...,
myPlugin
]
})
You can find here the complete plugins documentation
Sharing envelop
s
In many cases, developers are looking for a way to reuse their server setup, as a boilerplate/template. Envelop allows you to create Envelops instances and later share it with others.
import { envelop, useEnvelop, useSchema } from '@envelop/core'
// Somewhere where you wish to create the basics of what you wish to share
// This defined the base plugins you wish to use as base.
const myBaseEnvelop = envelop({
plugins: [useOrgAuth(), useOrgTracing(), useOrgLogsCollector()]
})
// Later, when you create your own Envelop, you can extend that and add custom plugins.
// You can also specify the schema only at this point
const myEnvelop = envelop({
plugins: [useEnvelop(myBaseEnvelop), useSchema(myServerSchema), useMyCustomPlugin()]
})
So if you are working in a microservices’ environment, or in an enterprise that has many servers - you can now share the entire base GraphQL setup in a single variable, and extend it based on your needs.
You can read more about sharing/composing envelops here
”Babel for GraphQL” - New Features for the GraphQL Engine
Since we allow developers to take part in any phase of the execution, it means that you can easily add new features for the GraphQL engine, and not just features that come on top of GraphQL.
For example, one of the Envelop plugins
(useExtendedValidation
) allows developers
now to write and run GraphQL validations, with access to the operation variables. That means you can
write simple validations now without making it part of your schema.
One of the things that is also possible now is @oneOf
- a spec suggestion that is still in
discussion for adding input unions, but already available for you if you use Envelop, because
extended validations can access variables and can do additional things that was difficult to do
before.
Here are some additional examples for cool new plugins:
perEventContextResolver
: suggested in this PR, and almost available in envelop.@oneOf
: suggested in this PR, and now available in envelop.- Migrate operations - a new suggestion for
migration GraphQL operation during
parse
, which allows simpler flow for introducing breaking changes. - Public schema filter - for creating a simple GraphQL schema that can be used for public APIs based on existing GraphQL schema.
useOperationFieldPermissions
- a plugin that allows you to check if the fields queried in an operation are allowed for a user before execution starts.
Adoption and Migration Path / Framework for Frameworks
If you are already using GraphQL, you are probably using a server that comes with all the features built-in. This is great in some cases, but if you wish to have that extra flexibility, you can migrate to Envelop. You can even use Envelop with other server frameworks without migrating the entire pipeline (see examples section below).
GraphQL is also widely adopted in the JAMStack world - and libraries that offer GraphQL out of the box migrate to Envelop to simplify parts of the code, and to allow their users to extend the GraphQL layer in a simple way.
Redwood is a great example. We start with a small suggestion PR, and the Redwood team was open for new ideas - so now you can use envelop if you are a Redwood user !
Here is a thread about why Redwood now gives you the option
to replace Apollo Server with GraphQL-Helix +
Envelop
.
During that process, we also start to work with other frameworks and support them with that: Loopback, NestJS, Parse, Apollo Server and others.
We are also helping with that, so if you are migrating to Envelop and not sure what it includes/means for your project - feel free to reach out to us (through GitHub, email or the chat box in our website) and we would love to help you with that.
Examples
Since we understand that Envelop doesn’t come as a whole server, we create tons of examples you can
use for reference. We added examples for using several HTTP servers (express/fastify), running
different Functions/Lambda cloud providers, different schema providers (Type-GraphQL, Nexus)
subscriptions transports (SSE / GraphQL-WS), new GraphQL features like @stream
/ @defer
and
more.
You can find all examples and demos here
What’s Next?
We are constantly working on improving the low-level API of Envelop, so if something is missing, you can always reach out and report an issue. We are also adding more plugins based on our use-cases.
Like with any other open-source maintained by The Guild, we always welcome you to share your thoughts, ideas, feedback, questions and issues. We also encourage developers to take an active part in the development of the products/libraries they are using - so if you think something you wrote can benefit others - we can help with making it a reality!
Join our newsletter
Want to hear from us when there's something new?
Sign up and stay up to date!
*By subscribing, you agree with Beehiiv’s Terms of Service and Privacy Policy.