From cb072cf1a7012d5229254e2d229a30ccdf4263a6 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Wed, 21 Nov 2018 17:30:06 +0200 Subject: [PATCH] docs(plugins): Beginnings of the documentation for plugins. This is still WIP, but aims to demonstrate some of the new functionality and flexibility introduced in Apollo Server 2.2's new request pipeline (See https://github.com/apollographql/apollo-server/pull/1795 for implementation details). --- docs/_config.yml | 1 + docs/source/features/plugins.md | 229 ++++++++++++++++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 docs/source/features/plugins.md diff --git a/docs/_config.yml b/docs/_config.yml index 6d007ec2205..e0b17a8c9d0 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -27,6 +27,7 @@ sidebar_categories: - features/creating-directives - features/authentication - features/testing + - features/plugins # Schema stitching: # - features/schema-stitching # - features/remote-schemas diff --git a/docs/source/features/plugins.md b/docs/source/features/plugins.md new file mode 100644 index 00000000000..5b4ddcfa985 --- /dev/null +++ b/docs/source/features/plugins.md @@ -0,0 +1,229 @@ +--- +title: Plugins +description: Extending Apollo Server through the use of plugins. +--- + +> **Note:** Plugins are available in Apollo Server 2.2.x or higher. + +## Overview + +The default Apollo Server installation is designed for a reliable out-of-the-box experience with as little configuration as possible. In order to provide additional features, Apollo Server supports _plugins_ which can interface with various stages of server operation and each request/response cycle. + +## Usage + +Plugins for Apollo Server can be specified using the `plugins` configuration parameter on the Apollo Server be provided as a module (and optionally published to a registry — e.g. npm) or defined in-line within the `ApolloServer` contructor. + +> **Types:** To facilitate plugin development, the `apollo-server-plugin-base` module exports the plugin interface for plugins as `ApolloServerPlugin`. Advanced users may want to directly explore `ApolloServerPlugin` by [reviewing its source](https://github.com/apollographql/apollo-server/blob/master/packages/apollo-server-plugin-base/src/index.ts), but we recommend TypeScript users implement plugins using the `ApolloServerPlugin` interface. + +For example, defining an in-line plugin (one which isn't published to a registry) which implements a single `onServerStart` event might look like this: + +```js +const { ApolloServer } = require('apollo-server'); + +/* Note: This example doesn't provide `typeDefs` or `resolvers`, + both of which are necessary to start the server. */ +const { typeDefs, resolvers } = require('./separatelyDefined'); + +const server = new ApolloServer({ + typeDefs, + resolvers, + + /* Plugins are defined within this array and initialized sequentially. */ + plugins: [ + { + onServerStart() { + console.debug('The server has started!'); + }, + }, + ], +}) +``` + +In addition to supporting in-line configuration for ad-hoc integration points, plugins which are provided as a stand-alone module or installed from a registry using `npm install` (or `yarn add`) can be listed as members of the `plugins` configuration: + +```js + /* ... Existing, required ApolloServer configuration. ... */ + + plugins: [ + + /* A plugin could be installed and provided from npm. */ + require('apollo-server-plugin-name')(), + + /* And another plugin might reside locally. */ + require('./localPluginModule')({ + /* plugin configuration, when necessary! */ + }) + + ] + + /* ... Any remaining ApolloServer configuration. ... */ +``` + +## Events + +There are two main categories of events: server life-cycle events and request life-cycle events. + +As the names imply, server life-cycle events are those which might not be directly related to a specific request. Instead, these events are more generally aimed at providing integrations for the server as a whole, rather than integrations which apply per request. + +On the other hand, request life-cycle events are those which are specifically coupled to a specific request. The organization of plugin event registration aims to make it simple to couple request life-cycle events with server life-cycle events by nesting the request life-cycle events within appropriate server life-cycle events. For example, the definition of request life-cycle events is done as an extension of the `requestDidStart` server life-cycle event. This will be explained further below. + +### Server life-cycle events + +Server life-cycle events are custom integration points which generally cover the life-cycle of the server, rather than focusing on a specific request. Specific server life-cycle events may expose additional events which are relevant to that portion of their life-cycle, but these are intended to be the most high-level events which represent the super-set of all events which occur will occur within Apollo Server. + +In the case that an event exposes additional events, the additional events are coupled to the server life-cycle event in order to provide a focused context which allows developers to couple related logic together. This will be explored more concisely in the `requestDidStart` server life-cycle event below. + +### `onServerStart` + +The `onServerStart` event is fired when the GraphQL server is preparing to start. If this is defined as an `async` function (or if it returns a `Promise`) the server will not start until the asynchronous behavior is resolved. Any rejection in this event will cause the server to not start, which provides a technique to ensure particular behavior is met before starting (for example, confirming that an underlying dependency is ready). + +#### Example + +```js +const server = new ApolloServer({ + /* ... other necessary configuration ... */ + + plugins: [ + { + onServerStart() { + + } + } + ] +}) +``` + +### `requestDidStart` + +This event is emitted when the server has begun fulfilling a request. This life-cycle may return an object which implements request life-cycle events, as necessary. + +By including these as a subset of `requestDidStart`, scope can be created +and used by more specific events — e.g. `parsingDidStart`, `didResolveOperation` `willSendResponse`, etc. The request-life-cycle events are defined in [request life-cycle events](#TODO) below. + +```js +const server = new ApolloServer({ + /* ... other necessary configuration ... */ + + plugins: [ + { + requestDidStart(requestContext) { + /* Request-specific scope can be created here and + used in more granular life-cycle events below. */ + return { + + parsingDidStart(requestContext) { + + }, + + } + } + } + ], + + /* */ +}) +``` + +If there are no more specific events to implement, `requestDidStart` need not return any value. + +### Request life-cycle events + +Request life-cycle events must be implemented by returning an object which defines their behavior to the `requestDidStart` server life-cycle event. By maintaining this structure, coupling logic and maintaining control over where scope is created and destroyed within a request becomes more semantic. + +For example, to implement any of the request life-cycle events, an object should be returned from `requestDidStart` as such: + +```js +const server = new ApolloServer({ + /* ... other necessary configuration ... */ + + plugins: [ + { + requestDidStart(requestContext) { + /* Request-specific scope can be created here and + used in more granular life-cycle events below. */ + return { + + parsingDidStart(requestContext) { + + }, + + validationDidStart(requestContext) { + + }, + + didResolveOperation(requestContext) { + + }, + + /* ... any additional request life-cycle events... */ + + } + } + } + ], + + /* */ +}) +``` + +### `parsingDidStart` + +The `parsingDidStart` request life-cycle event will receive the request context as the first argument. At this stage, the `document` AST may not be defined since the parsing may not succeed. + +#### TypeScript signature + +```typescript +parsingDidStart?( + requestContext: GraphQLRequestContext, +): (err?: Error) => void | void; +``` + +### `validationDidStart` + +The `validationDidStart` request life-cycle event will receive the request context as the first argument. Since parsing would have been successful prior to validation, the `document` AST will be present. + +#### TypeScript signature + +```typescript +validationDidStart?( + requestContext: WithRequired, 'document'>, +): (err?: ReadonlyArray) => void | void; +``` + +### `didResolveOperation` + +The `didResolveOperation` request life-cycle event is triggered after the operation to be executed has been successfully retrieved from the `document` AST by `graphql`'s `getOperationAST`. This focusing of execution which identifies the correct operation is important when a `document` contains multiple operations. At this stage, in addition to the `document` the `operationName` (`String`) and `operation` (AST) will be present on the context. + +If the operation is anonymous (e.g. the operation is `query { ... }` rather than `query NamedQuery { ... }`, then `operationName` will be `null`. + +```typescript +didResolveOperation?( + requestContext: WithRequired< + GraphQLRequestContext, + 'document' | 'operationName' | 'operation' + >, +): ValueOrPromise; +``` + +### `executionDidStart` + +> TODO + +```typescript +executionDidStart?( + requestContext: WithRequired< + GraphQLRequestContext, + 'document' | 'operationName' | 'operation' + >, +): (err?: Error) => void | void; +``` + +### `willSendResponse` + +> TODO + +```typescript +willSendResponse?( + requestContext: WithRequired, 'response'>, +): ValueOrPromise; +```