DEV Community

Cover image for Introducing Envelop - The GraphQL Plugin System
TheGuildBot for The Guild

Posted on • Edited on • Originally published at the-guild.dev

Introducing Envelop - The GraphQL Plugin System

This article was published on Thursday, July 22, 2021 by Dotan Simha @ The Guild Blog

Today we are super excited to share with you a new open-source library we've been working on for
the past few months!

Envelop

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

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, operation DocumentNode, 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.

Envelop

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 does console.log.
  • useTiming - hooks into “before” and “after” of all phases, measures times, and then prints it.
  • useParserCache - hooks into before and after the parse phase and implements caching based on the operation string.
  • useGraphQLJit - hooks into execute phase and replaces the execute function with GraphQL-Jit's executor.
  • usePersistedOperations - hooks into parse and replaces the parse 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 the execute 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.

Envelop

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
Enter fullscreen mode Exit fullscreen mode

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...`)
})
Enter fullscreen mode Exit fullscreen mode

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

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()]
})
Enter fullscreen mode Exit fullscreen mode

By using useParserCache we make sure to parse every unique operation only once. By using
useValidationCache we make sure to validate every unique operation only once. By using
useGraphQLJit 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

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
  ]
})
Enter fullscreen mode Exit fullscreen mode

You can find here the complete plugins documentation

Sharing envelops {/* eslint-disable-line mdx/remark */}

Share

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()]
})
Enter fullscreen mode Exit fullscreen mode

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:

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!

Top comments (0)