创建 Apollo Server 插件
通过自定义功能扩展 Apollo Server
所有插件生命周期方法是async
,除了 willResolveField
和 schemaDidLoadOrUpdate
。
你可以创建自己的Apollo Server 插件来执行自定义 操作以响应某些事件。例如,一个基本的日志记录插件可能会记录与 Apollo Server 相关联的 GraphQL 查询 字符串。
插件的结构
如果你使用TypeScript来创建插件,你的插件应实现ApolloServerPlugin
接口。
插件是JavaScript对象,它们实现一个或多个响应事件的功能。以下是一个基本的插件,用于响应serverWillStart
事件:
const myPlugin = {async serverWillStart() {console.log('Server starting up!');},};
const myPlugin = {async serverWillStart() {console.log('Server starting up!');},};
你可以在初始化 contextValue
的地方定义插件,或者将其作为单独的模块导出:
export default {async serverWillStart() {console.log('Server starting up!');},};
export default {async serverWillStart() {console.log('Server starting up!');},};
为了创建一个接受选项的插件,创建一个接受一个options
对象并返回一个正确结构的插件对象的函数,如下所示:
export default (options: { logMessage: string }) => {return {async serverWillStart() {console.log(options.logMessage);},};};
export default (options) => {return {async serverWillStart() {console.log(options.logMessage);},};};
如果你的插件需要读取请求的contextValue
(即requestContext.contextValue
),你必须将你的插件声明为ApolloServerPlugin<YourContextType>
,如下所示:
interface MyContext {token: string;}export default function (): ApolloServerPlugin<MyContext> {return {async requestDidStart({ contextValue }) {// token is properly inferred as a stringconsole.log(contextValue.token);},};}
export default function () {return {async requestDidStart({ contextValue }) {// token is properly inferred as a stringconsole.log(contextValue.token);},};}
响应事件
插件通过实现与这些事件对应的功能来精确指定它响应哪些事件。上述示例中的插件响应的是 serverWillStart
事件,当 Apollo Server 准备启动时将触发该事件。几乎所有插件事件都是 async
函数(即返回 Promise
的函数)。唯一的例外是 willResolveField
和 schemaDidLoadOrUpdate
。
插件可以响应任何组合的 受支持事件。
响应请求生命周期事件
插件可以响应以下与 GraphQL 请求生命周期相关的事件:
didResolveSource
parsingDidStart
validationDidStart
didResolveOperation
responseForOperation
executionDidStart
didEncounterErrors
willSendResponse
然而,您定义这些函数的方式与上面给出的 serverWillStart
示例略有不同。首先,您的插件必须定义 requestDidStart
函数:
const myPlugin = {async requestDidStart() {console.log('Request started!');},};
const myPlugin = {async requestDidStart() {console.log('Request started!');},};
每当 Apollo Server 收到一个 GraphQL 请求时,就会触发 requestDidStart
事件,这在该列表中列出的其他生命周期事件之前。您可以像响应 serverWillStart
事件一样响应此事件,但您还可以使用该函数来定义响应请求生命周期事件的逻辑,如下所示:
const myPlugin = {async requestDidStart(requestContext) {console.log('Request started!');return {async parsingDidStart(requestContext) {console.log('Parsing started!');},async validationDidStart(requestContext) {console.log('Validation started!');},};},};
const myPlugin = {async requestDidStart(requestContext) {console.log('Request started!');return {async parsingDidStart(requestContext) {console.log('Parsing started!');},async validationDidStart(requestContext) {console.log('Validation started!');},};},};
如图所示,requestDidStart
函数可以返回一个对象,该对象定义了响应请求生命周期事件的函数。这种结构可以组织和封装所有插件请求生命周期逻辑,使其更容易理解。
请求生命周期事件流程
以下图表说明了每个请求发生的事件序列。每个事件都在 Apollo Server 插件事件 中有详细的文档。
重要:以下任何可能导致“成功”的事件也可能导致错误。每当发生错误时,都会触发 didEncounterErrors
事件,并且“成功”路径的其余部分都不会执行。
结束钩子
以下事件的处理程序可以选择返回一个在相关生命周期阶段 结束时 调用的函数:
与 executionDidStart
钩子一样,executionDidStart
钩子返回一个包含 executionDidEnd
函数的对象,而不是仅作为结束钩子返回一个函数。这是因为返回的对象还可以包含 willResolveField
。
与其他事件处理程序一样,这些结束钩子都是异步函数(除了 willResolveField
的结束钩子)。
生命周期阶段执行过程中发生的任何错误都会通过端钩传递。例如,以下插件会记录上述任何生命周期事件中发生的任何错误。
const myPlugin = {async requestDidStart() {return {async parsingDidStart() {return async (err) => {if (err) {console.error(err);}};},async validationDidStart() {// This end hook is unique in that it can receive an array of errors,// which will contain every validation error that occurred.return async (errs) => {if (errs) {errs.forEach((err) => console.error(err));}};},async executionDidStart() {return {async executionDidEnd(err) {if (err) {console.error(err);}},};},};},};
const myPlugin = {async requestDidStart() {return {async parsingDidStart() {return async (err) => {if (err) {console.error(err);}};},async validationDidStart() {// This end hook is unique in that it can receive an array of errors,// which will contain every validation error that occurred.return async (errs) => {if (errs) {errs.forEach((err) => console.error(err));}};},async executionDidStart() {return {async executionDidEnd(err) {if (err) {console.error(err);}},};},};},};
请注意,validationDidStart
端钩会收到一个包含发生过的所有验证错误的数组(如果有)。willResolveField
端钩收到解析器抛出的错误作为第一个参数,并将解析器的结果作为第二个参数。每个端钩的参数在请求生命周期事件中的类型定义中有说明。
检查请求和响应详情
如上例所示,requestDidStart
和请求生命周期函数接收一个requestContext
参数。此参数类型为GraphQLRequestContext
,它包括一个request
(类型为GraphQLRequest
),还有可用的response
(类型为GraphQLResponse
)。
这些类型及其相关子类型都在@apollo/server
中定义。
安装自定义插件
通过向ApolloServer
构造函数提供一个plugins
配置选项,将您的插件添加到Apollo Server,如下所示:
import { ApolloServer } from '@apollo/server';import ApolloServerOperationRegistry from '@apollo/server-plugin-operation-registry';/* This example doesn't provide `typeDefs` or `resolvers`,both of which are required to start the server. */import { typeDefs, resolvers } from './separatelyDefined';const server = new ApolloServer({typeDefs,resolvers,// You can import plugins or define them in-line, as shown:plugins: [/* This plugin is from a package that's imported above. */ApolloServerOperationRegistry({/* options */}),/* This plugin is imported in-place. */require('./localPluginModule'),/* This plugin is defined in-line. */{async serverWillStart() {console.log('Server starting up!');},},],});
import { ApolloServer } from '@apollo/server';import ApolloServerOperationRegistry from '@apollo/server-plugin-operation-registry';/* This example doesn't provide `typeDefs` or `resolvers`,both of which are required to start the server. */import { typeDefs, resolvers } from './separatelyDefined';const server = new ApolloServer({typeDefs,resolvers,// You can import plugins or define them in-line, as shown:plugins: [/* This plugin is from a package that's imported above. */ApolloServerOperationRegistry({/* options */}),/* This plugin is imported in-place. */require('./localPluginModule'),/* This plugin is defined in-line. */{async serverWillStart() {console.log('Server starting up!');},},],});