Introducing Envelop - The GraphQL Plugin System
Tags: graphql, server, node, plugins
Published: 2021-07-29 10:17:09

> This article was published on Thursday, July 22, 2021 by [Dotan Simha](https://twitter.com/dotansimha) @ [The Guild Blog](https://the-guild.dev/)

**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](https://the-guild.dev/blog-assets/envelop/definition.png)

## TL;DR

*   Envelop aims to be **The GraphQL Plugin system** ([envelop.dev](https://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](https://github.com/dotansimha/envelop/pull/311), Tracing with
    [Prometheus](https://envelop.dev/plugins/use-prometheus)/DataDog/[NewRelic](https://envelop.dev/plugins/use-newrelic)/[Sentry](https://envelop.dev/plugins/use-sentry)/[OpenTelemetry](https://envelop.dev/plugins/use-open-telemetry)/[ApolloTracing](https://envelop.dev/plugins/use-apollo-tracing),
    [Loggers](https://envelop.dev/plugins/use-logger),
    [GraphQL-Jit](https://envelop.dev/plugins/use-graphql-jit),
    [Persisted Operations](https://envelop.dev/plugins/use-persisted-operations), Security with
    [rate-limit](https://envelop.dev/plugins/use-rate-limiter)/[depth-limit](https://envelop.dev/plugins/use-depth-limit)/[Auth0](https://envelop.dev/plugins/use-auth0)
    and many others from the [Plugins Hub](https://envelop.dev/plugins))
*   **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](https://community.redwoodjs.com/t/using-graphql-envelop-helix-in-redwood-v0-35/2276),
    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](https://producthunt.com/posts/envelop-3)!

## Overview

![Envelop](https://the-guild.dev/static/shared-logos/products/envelop.svg)

**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](https://the-guild.dev/blog-assets/envelop/envelop.png)

> 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](https://envelop.dev/assets/home-communication.png)

## 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`](https://github.com/contrawork/graphql-helix) for that.

[You can also find more in-depth article and technical documentation here](https://envelop.dev/docs/getting-started)

To get started quickly, start by installing only `@envelop/core` package in your project:

```sh
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:

```ts
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

![Plugins](https://envelop.dev/assets/features-pluggable.png)

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:

```ts
const getEnveloped = envelop({
  plugins: [useSchema(schema), useParserCache(), useValidationCache(), useGraphQLJit()]
})
```

> 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](https://github.com/zalando-incubator/graphql-jit).

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](https://envelop.dev/plugins) : 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](https://envelop.dev/assets/features-modern.png)

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.

```ts
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](https://envelop.dev/docs/plugins)**

## Sharing `envelop`s

![Share](https://envelop.dev/assets/features-performant.png)

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.

```ts
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](https://envelop.dev/docs/composing-envelop)**

## "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`](https://envelop.dev/plugins/use-extended-validation)) 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](https://github.com/graphql/graphql-js/pull/2485), and almost
    [available in envelop](https://github.com/dotansimha/envelop/pull/183).
*   `@oneOf`: suggested in [this PR](https://github.com/graphql/graphql-spec/pull/825), and now
    [available in envelop](https://github.com/dotansimha/envelop/blob/main/packages/plugins/extended-validation/README.md#union-inputs-oneof).
*   [Migrate operations](https://github.com/dotansimha/envelop/pull/94) - a new suggestion for
    migration GraphQL operation during `parse`, which allows simpler flow for introducing breaking
    changes.
*   [Public schema filter](https://github.com/n1ru4l/graphql-public-schema-filter) - for creating a
    simple GraphQL schema that can be used for public APIs based on existing GraphQL schema.
*   [`useOperationFieldPermissions`](https://envelop.dev/plugins/use-operation-field-permissions) - 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](https://redwoodjs.com) 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](https://community.redwoodjs.com/t/migrate-your-redwood-graphql-api-with-graphql-helix-and-envelop/2264)
!

[Here is a thread about why Redwood now gives you the option](https://community.redwoodjs.com/t/using-graphql-envelop-helix-in-redwood-v0-35/2276)
to replace Apollo Server with [GraphQL-Helix](https://github.com/contrawork/graphql-helix) +
`Envelop`.

During that process, we also start to work with other frameworks and support them with that:
[Loopback](https://github.com/loopbackio/loopback-next/pull/7634),
[NestJS](https://github.com/PabloSzx/nestjs-graphql/pull/1),
[Parse](https://github.com/parse-community/parse-server/pull/7394),
[Apollo Server](https://github.com/apollographql/apollo-server/discussions/5541) 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](https://github.com/the-guild-org), [email](/contact) 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](https://envelop.dev/docs/integrations)

## 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!