Envelop is a lightweight JavaScript library for wrapping GraphQL execution layer and flow, allowing developers to develop, share and collaborate on GraphQL-related plugins, while filling the missing pieces in GraphQL implementations.
Envelop is created and maintained by The Guild, and used in production by our clients.
Our goal is to allow developers keep the original GraphQL interfaces, while adding plugins to enrich the feature set of you application quickly, while sharing ideas, code and plugins with other developers.
Envelop is agnostic to the HTTP server you use, so it's not a traditional server. We do not aim to provide a complete server, you can use Envelop with any environment (NodeJS or browser) and any type of GraphQL workflow (client / server, client-side execute, or server to server). So any piece of code that uses GraphQL's execute can benfit from that layer.
The core of Envelop is zero-dependency, and will only apply changes to your GraphQL execution based on the plugins you wish to use. It can be integrated with any GraphQL server that follows the execution phase, as defined in the GraphQL spec and let you provide your own lifecycle methods.
Separating the execution workflow and the logic that it runs in each phase allow you to write reusable piece of code, like logging, metric collection, error handling, custom validations, resolvers tracing (and integration with any external), authentication, authorization and much more, without the needs to write it explictly every time, or in any project or microservice. With Envelop, you can easily create a wrapper around your common logics, and share it with others.
Start by adding the core of Envelop to your codebase:
yarn add graphql @envelop/core
Then, create a simple Envelop based on your GraphQL schema:
import { envelop, useSchema } from '@envelop/core';
const mySchema = buildSchema( ... ); // GraphQLSchema
const getEnveloped = envelop({
plugins: [
useSchema(mySchema)
],
});The result of envelop is a function, that allow you to get everything you need for the GraphQL execution: parse, validate, contextBuilder and execute. Use that to run the client's GraphQL queries. Here's a psuedo-code of how it should looks like:
const httpServer = createServer();
httpServer.on('request', (req, res) => {
// Here you get the alternative methods that are bundled with your plugins
const { parse, validate, contextFactory, execute, schema } = getEnveloped();
// Parse the initial request and validate it
const { query, variables } = JSON.parse(req.payload);
const document = parse(query);
const validationErrors = validate(schema, document);
if (validationErrors.length > 0) {
return res.end(JSON.stringify({ errors: validationErrors }));
}
// Build the context and execute
const context = await contextFactory(req);
const result = await execute({
document,
schema,
variableValues: variables,
contextValue: context,
});
// Send the response
res.end(JSON.stringify(result));
});
httpServer.listen(3000);Behind the scenes, this simple workflow allow you to use Envelop plugins and hook into the entire request handling flow.
Here's a simple example for collecting metrics and log all incoming requests, using the built-in plugins:
const getEnveloped = envelop({
plugins: [useSchema(schema), useLogger(), useTiming()],
});Envelop
pluginsare based on a simple event-based contact, that allow you easily to add more logic to your app. You can easily share and collaborate on plugins that you find generic.
Envelop provides a low-level API at consumpion of the output, but a rich API while using it with plugins. Based on that, it's possible to integrate Envelop with many tools.
We recommend on using graphql-helix as request pipeline orchestrator, as it allows the maximum flexibility and you can easily override/manage every part of the pipeline with Envelop.
Here's a list of integrations and examples:
| Server/Framework | Fully supported? | Example |
|---|---|---|
Node's http |
V | basic-http |
| GraphQL-Helix | V | graphql-helix |
| Apollo-Server | Almost | apollo-server |
Since Envelop is not a HTTP server, and just a wrapper around GraphQL request pipeline - it's possible to integrate it with any server/framework, if it's flexible enough and allow you to specify the pipeline methods*.
We provide a few built-in plugins within the @envelop/core, and many more plugins as standalone packages.
| Name | Package | Description |
|---|---|---|
| useSchema | @envelop/core |
Simplest plugin to provide your GraphQL schema. |
| useErrorHandler | @envelop/core |
Get notified when any execution error occurs. |
| useExtendContext | @envelop/core |
Extend execution context based on your needs. |
| useLogger | @envelop/core |
Simple, yet powerful logging for GraphQL execution. |
| usePayloadFormatter | @envelop/core |
Format, clean and customize execution result. |
| useTiming | @envelop/core |
Simple timing/tracing mechanism for your execution. |
| useGraphQLJit | @envelop/graphql-jit |
Custom executor based on GraphQL-JIT. |
| useParserCache | @envelop/parser-cache |
Simple LRU for caching parse results. |
| useValidationCache | @envelop/validation-cache |
Simple LRU for caching validate results. |
| useDepthLimit | @envelop/depth-limit |
Limits the depth of your GraphQL selection sets. |
| useDataLoader | @envelop/dataloader |
Simply injects a DataLoader instance into your context. |
| useApolloTracing | @envelop/apollo-tracing |
Integrates timing with Apollo-Tracing format (for GraphQL Playground) |
| useSentry | @envelop/sentry |
Tracks performance, timing and errors and reports it to Sentry. |
| useOpenTelemetry | @envelop/opentelemetry |
Tracks performance, timing and errors and reports in OpenTelemetry structure. |
| useGenericAuth | @envelop/generic-auth |
Super flexible authentication, also supports @auth directive . |
| useAuth0 | @envelop/auth0 |
Validates Auth0 JWT tokens and injects the authenticated user to your context. |
| useGraphQLModules | @envelop/graphql-modules |
Integrates the execution lifecycle of GraphQL-Modules. |
| useGraphQLMiddleware | @envelop/graphql-middleware |
Integrates middlewares written for graphql-middleware |
| useRateLimiter | @envelop/rate-limiter |
Loimoit request rate via @rateLimit diractive |
After an envelop has been created, you can share it with others as a complete layer of plugins. This is useful if you wish to create a predefined layer of plugins, and share it with others. You can use the as a shell and as a base for writing sharable pieces of servers.
Here's a small exmple for sharing envelops:
// 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({
extends: [myBaseEnvelop],
plugins: [useSchema(myServerSchema), useMyCustomPlugin()],
});This approach allow developers to create a base Envelop and share it across the organization: you can define your monitoring setup, logging, authentication, etc, only once in a common package, and share it with others without losing the ability to extend it.
Envelop plugins are just object with functions, that provides contextual implementation for before/after of each phase, with a flexible API.
Here's a simple example that allow you print the execution params:
const myPlugin = {
onExecute({ args }) {
console.log('Execution started!', { args });
return {
onExecuteDone: ({ result }) => {
console.log('Execution done!', { result });
},
};
},
};
const getEnveloped = envelop({
plugins: [
/// ... other plugins ...,
myPlugin,
],
});Feel free to share you plugin with us, or with the community. Sharing = Caring!
You can find it here: https://github.com/dotansimha/envelop/blob/main/packages/types/src/index.ts#L50
By extending the GraphQL execution pipeline, we allow developers to write reusable plugins, that can be shared with others easily, as NPM packages. So instead of delivering a bloated GraphQL server with tons of features, we allow you to choose the HTTP server you prefer, the request pipeline you prefer, and the feautures you prefer.
We wrap the execution pipeline of GraphQL operation, and allow Envelop plugins to do the following:
parse- Hook into the before/after of this phase
- Overide the parse function
- Access to the parsed result
- Modify the parsed result
validate- Hook into the before/after of this phase
- Overide the validation function
- Access to the validation error results
- Modify the validation results
- Add custom validation rules
contextFactory- Hook into the before/after of this phase
- Access to the initial HTTP request context
- Extend the context with custom data
- Replace the context object
execute- Hook into the before/after of this phase
- Extend the execution context
- Access to all execution parameters
- Replace the execute function
- Access to the results / error of the execution
- Access to before / after resolvers calls
- Extend resolvers behaviour
- Access resolvers parameters
- Replace / modify the execution result
subscribe- Hook into the before/after of this phase
- Extend the execution context
- Access to all execution parameters
- Replace the execute function
- Access to the results / error of the execution
- Access to before / after resolvers calls
- Extend resolvers behaviour
- Access resolvers parameters
- Replace / modify the subscription result
We also allow you to change the GraphQL schema during execution - so if your server has a schema that could change dynamically, you can always update it. As a result, we trigger schemaChange event that allow plugins respond accordingly.
If this is your first time contributing to this project, please do read our Contributor Workflow Guide before you get started off.
Feel free to open issues and pull requests. We're always welcome support from the community.
For a contribution guide specific to this project, please refer to: http://graphql-code-generator.com/docs/custom-codegen/contributing
Help us keep GraphQL Codegenerator open and inclusive. Please read and follow our Code of Conduct as adopted from Contributor Covenant
MIT
