与我们一起参加10月8日至10日在纽约市的活动,了解有关 GraphQL 联盟和 API 平台工程的最新的技巧、趋势和新闻。参加2024年纽约市 GraphQL 大会
文档
免费开始

与 AWS Lambda 部署

如何使用 AWS Lambda 部署 Apollo Server


先决条件

在继续本指南之前,请确保您已完成以下步骤

⚠️ AWS最佳实践警告称,不建议在无需此类密钥的情况下使用您的AWS账户根用户密钥(例如,不要使用这些密钥来配置AWS CLI)。相反,创建一个IAM用户账户并且分配满足部署应用程序需求的最低权限分配权限,以及使用该账户。

设置您的项目

在此示例中,我们将从头开始,以展示所有组件是如何组合在一起的。

首先,安装用于使用Apollo Server及其AWS Lambda集成所需的包:

npm install @apollo/server graphql @as-integrations/aws-lambda
npm install -D typescript

接下来,我们将创建一个包含基本Apollo Server设置的文件。注意文件的名称和位置;我们将在后续步骤中用到这些信息:

src/server.ts
import { ApolloServer } from '@apollo/server';
// The GraphQL schema
const typeDefs = `#graphql
type Query {
hello: String
}
`;
// A map of functions which return data for the schema.
const resolvers = {
Query: {
hello: () => 'world',
},
};
// Set up Apollo Server
const server = new ApolloServer({
typeDefs,
resolvers,
});
src/server.js
import { ApolloServer } from '@apollo/server';
// The GraphQL schema
const typeDefs = `#graphql
type Query {
hello: String
}
`;
// A map of functions which return data for the schema.
const resolvers = {
Query: {
hello: () => 'world',
},
};
// Set up Apollo Server
const server = new ApolloServer({
typeDefs,
resolvers,
});

现在我们可以导入startServerAndCreateLambdaHandler函数和从@as-integrations/aws-lambda中获取的handlers对象,传入我们的ApolloServer实例:

src/server.ts
import { ApolloServer } from '@apollo/server';
import { startServerAndCreateLambdaHandler, handlers } from '@as-integrations/aws-lambda';
const typeDefs = `#graphql
type Query {
hello: String
}
`;
const resolvers = {
Query: {
hello: () => 'world',
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
// This final export is important!
export const graphqlHandler = startServerAndCreateLambdaHandler(
server,
// We will be using the Proxy V2 handler
handlers.createAPIGatewayProxyEventV2RequestHandler(),
);
src/server.js
import { ApolloServer } from '@apollo/server';
import { startServerAndCreateLambdaHandler, handlers } from '@as-integrations/aws-lambda';
const typeDefs = `#graphql
type Query {
hello: String
}
`;
const resolvers = {
Query: {
hello: () => 'world',
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
// This final export is important!
export const graphqlHandler = startServerAndCreateLambdaHandler(
server,
// We will be using the Proxy V2 handler
handlers.createAPIGatewayProxyEventV2RequestHandler(),
);

以上代码片段的最后一行创建了一个名为graphqlHandler的导出,它是Lambda函数处理器。我们稍后将回到这个函数!

使用Serverless框架部署

Serverless是一个框架,可以帮助将无服务器应用程序部署到AWS Lambda等平台。

安装CLI

我们将使用Serverless CLI来部署我们的应用程序。您可以直接将软件包安装到项目中,或者全局安装Serverless CLI:

npm install -g serverless

Serverless CLI可以访问您之前配置的AWS CLI的凭据。所以现在我们只需要告诉Serverless我们想要部署哪个服务。

AWS最佳实践建议定期更换访问密钥,适用于需要长期凭证的场景(例如,托管应用程序)。

配置服务

您可以使用serverless.yml文件配置Serverless,让它知道要部署哪些服务以及处理程序的存储位置。

如果您使用TypeScript,请下载serverless-plugin-typescript包以启用Serverless使用您的TS文件:

npm install -D serverless-plugin-typescript

您可以使用下面的示例serverless.yml配置;请注意,确保您使用的文件路径指向导出处理程序的文件:

serverless.yml
service: apollo-lambda
provider:
name: aws
runtime: nodejs16.x
httpApi:
cors: true
functions:
graphql:
# Make sure your file path is correct!
# (e.g., if your file is in the root folder use server.graphqlHandler )
# The format is: <FILENAME>.<HANDLER>
handler: src/server.graphqlHandler
events:
- httpApi:
path: /
method: POST
- httpApi:
path: /
method: GET
# Omit the following lines if you aren't using TS!
plugins:
- serverless-plugin-typescript

本地运行

在部署之前,我们可以使用Serverless CLI在本地调用我们的处理程序,以确保一切正常。我们将通过使用 来模拟HTTP请求。

您可以通过创建一个query.json文件来在本地存储模拟的HTTP请求,如下所示:

query.json
{
"version": "2",
"headers": {
"content-type": "application/json",
},
"isBase64Encoded": false,
"rawQueryString": "",
"requestContext": {
"http": {
"method": "POST",
},
// Other requestContext properties omitted for brevity
},
"rawPath": "/",
"routeKey": "/",
"body": "{\"operationName\": null, \"variables\": null, \"query\": \"{ hello }\"}"
}

现在我们可以使用 serverless 调用上面的 来触发我们的处理函数:

serverless invoke local -f graphql -p query.json

您的响应应该类似下面的样子

{
"statusCode": 200,
"headers": {
"cache-control": "no-store",
"content-type": "application/json; charset=utf-8",
"content-length": "27"
},
"body": "{\"data\":{\"hello\":\"world\"}}\n"
}

如果本地一切正常,我们可以继续进行部署!

部署

如我们前面所提到的Serverless 已经获取了您的 AWS CLI 凭据,因此要部署,您只需运行以下命令:

serverless deploy

如果成功, serverless 将会输出类似以下的输出:

> serverless deploy
> Deploying apollo-lambda to stage dev (us-east-1)
> ✔ Service deployed to stack apollo-lambda-dev (187s)
> ..............
> endpoints:
> POST - https://ujt89xxyn3.execute-api.us-east-1.amazonaws.com/dev/
> GET - https://ujt89xxyn3.execute-api.us-east-1.amazonaws.com/dev/
> functions:
> graphql: apollo-lambda-dev-graphql
> Monitor all your API routes with Serverless Console: run "serverless --console"

现在您可以通过 Apollo 沙箱 进入您的端点,并使用 query 查询您新部署的服务器。

serverless 做了什么?

首先,它构建了函数,将文件归档并上传到新的 S3 桶中。然后,它创建了一个 Lambda 函数并使用这些归档文件,如果在一切正常的情况下,它还将在控制台输出 HTTP 端点 URL。

管理生成的服务

您可以通过 AWS 控制台 访问生成的 S3 桶和 Lambda 函数。AWS 控制台也可以让查看您之前创建的 IAM 用户

要找到Serverless创建的S3桶,在亚马逊列出的服务中搜索S3,然后查找您的桶名(例如,apollo-lambda-dev-serverlessdeploymentbucket-1s10e00wvoe5f 是我们桶的名字)。

要找到Serverless创建的Lambda函数,在亚马逊列出的服务中搜索Lambda。如果您的Lambda函数列表为空或缺少您的新函数,请检查屏幕右上角的区域。Serverless部署的默认区域是us-east-1(弗吉尼亚州北)。

如果您想删除Serverless创建的S3桶或Lambda函数,可以运行以下命令:

serverless remove

中间件

为了实现事件和结果,可以将类型安全的中间件传递给startServerAndCreateLambdaHandler调用。此API如下所示:

import { middleware, startServerAndCreateLambdaHandler, handlers } from '@as-integrations/aws-lambda';
import { server } from './server';
const requestHandler = handlers.createAPIGatewayProxyEventV2RequestHandler();
// Middleware is an async function whose type is based on your request handler. Middleware
// can read and mutate the incoming event. Additionally, returning an async function from your
// middleware allows you to read and mutate the result before it's sent.
const middlewareFn: middleware.MiddlewareFn<typeof requestHandler> = async (event) => {
// read or update the event here
// optionally return a callback to access the result
return async (result) => {
// read or update the result here
};
};
startServerAndCreateLambdaHandler(server, requestHandler, {
middleware: [middlewareFn],
});
import { startServerAndCreateLambdaHandler, handlers } from '@as-integrations/aws-lambda';
import { server } from './server';
const requestHandler = handlers.createAPIGatewayProxyEventV2RequestHandler();
// Middleware is an async function whose type is based on your request handler. Middleware
// can read and mutate the incoming event. Additionally, returning an async function from your
// middleware allows you to read and mutate the result before it's sent.
const middlewareFn = async (event) => {
// read or update the event here
// optionally return a callback to access the result
return async (result) => {
// read or update the result here
};
};
startServerAndCreateLambdaHandler(server, requestHandler, {
middleware: [middlewareFn],
});

中间件的一个用例是Cookie修改。包含一个名为cookies的可推送属性的APIGatewayProxyStructuredResultV2类型可以将多个set-cookie头部设置到响应中。

import {
startServerAndCreateLambdaHandler,
middleware,
handlers,
} from '@as-integrations/aws-lambda';
import { server } from './server';
import { refreshCookie } from './cookies';
const requestHandler = handlers.createAPIGatewayProxyEventV2RequestHandler();
// Utilizing typeof
const cookieMiddleware: middleware.MiddlewareFn<typeof requestHandler> = async (
event,
) => {
// Access existing cookies and produce a refreshed one
const cookie = refreshCookie(event.cookies);
return async (result) => {
// Ensure proper initialization of the cookies property on the result
result.cookies = result.cookies ?? [];
// Result is mutable so it can be updated here
result.cookies.push(cookie);
};
};
export default startServerAndCreateLambdaHandler(server, requestHandler, {
middleware: [
cookieMiddleware,
],
});

有关更多用例和API信息,请参阅库的README

事件扩展

在许多情况下,API网关事件将有一个授权器在前,它包含将在GraphQL解析期间用于授权的定制状态。该库所包含的所有处理程序都包含一个通用类型,它允许您显式扩展基础事件类型。通过传递包含授权信息的事件,该事件类型将在创建contextValuemiddleware时使用。以下是一个示例,更多信息请参阅库的README

import { startServerAndCreateLambdaHandler, middleware, handlers } from '@as-integrations/aws-lambda';
import type { APIGatewayProxyEventV2WithLambdaAuthorizer } from 'aws-lambda';
import { server } from './server';
export default startServerAndCreateLambdaHandler(
server,
handlers.createAPIGatewayProxyEventV2RequestHandler<
APIGatewayProxyEventV2WithLambdaAuthorizer<{
myAuthorizerContext: string;
}>
>(),
);
import { startServerAndCreateLambdaHandler, handlers } from '@as-integrations/aws-lambda';
import { server } from './server';
export default startServerAndCreateLambdaHandler(server, handlers.createAPIGatewayProxyEventV2RequestHandler());

自定义请求处理

为了支持从AWS Lambda(包括自定义事件)传递的所有事件类型,公开了一个请求处理程序创建实用程序handlers.createHandler(eventParser, resultGenerator)。此函数返回一个完全类型化的请求处理程序,该处理程序可以作为传递到startServerAndCreateLambdaHandler的调用中。下面是一个示例,具体API在该库的README中记录

import { startServerAndCreateLambdaHandler, handlers } from '@as-integrations/aws-lambda';
import type { APIGatewayProxyEventV2 } from 'aws-lambda';
import { HeaderMap } from '@apollo/server';
import { server } from './server';
type CustomInvokeEvent = {
httpMethod: string;
queryParams: string;
headers: Record<string, string>;
body: string;
};
type CustomInvokeResult =
| {
success: true;
body: string;
}
| {
success: false;
error: string;
};
const requestHandler = handlers.createRequestHandler<CustomInvokeEvent, CustomInvokeResult>(
{
parseHttpMethod(event) {
return event.httpMethod;
},
parseHeaders(event) {
const headerMap = new HeaderMap();
for (const [key, value] of Object.entries(event.headers)) {
headerMap.set(key, value);
}
return headerMap;
},
parseQueryParams(event) {
return event.queryParams;
},
parseBody(event) {
return event.body;
},
},
{
success({ body }) {
return {
success: true,
body: body.string,
};
},
error(e) {
if (e instanceof Error) {
return {
success: false,
error: e.toString(),
};
}
console.error('Unknown error type encountered!', e);
throw e;
},
},
);
export default startServerAndCreateLambdaHandler(server, requestHandler);
import { startServerAndCreateLambdaHandler, handlers } from '@as-integrations/aws-lambda';
import { HeaderMap } from '@apollo/server';
import { server } from './server';
const requestHandler = handlers.createRequestHandler(
{
parseHttpMethod(event) {
return event.httpMethod;
},
parseHeaders(event) {
const headerMap = new HeaderMap();
for (const [key, value] of Object.entries(event.headers)) {
headerMap.set(key, value);
}
return headerMap;
},
parseQueryParams(event) {
return event.queryParams;
},
parseBody(event) {
return event.body;
},
},
{
success({ body }) {
return {
success: true,
body: body.string,
};
},
error(e) {
if (e instanceof Error) {
return {
success: false,
error: e.toString(),
};
}
console.error('Unknown error type encountered!', e);
throw e;
},
},
);
export default startServerAndCreateLambdaHandler(server, requestHandler);

使用事件信息

您可以使用context函数从原始Lambda数据结构中获取有关当前操作的信息。

您的context函数可以通过包含eventcontext对象的参数访问此信息:

const server = new ApolloServer<MyContext>({
typeDefs,
resolvers,
});
// This final export is important!
export const graphqlHandler = startServerAndCreateLambdaHandler(server, handlers.createAPIGatewayProxyEventV2RequestHandler(), {
context: async ({ event, context }) => {
return {
lambdaEvent: event,
lambdaContext: context,
};
},
});
const server = new ApolloServer({
typeDefs,
resolvers,
});
// This final export is important!
export const graphqlHandler = startServerAndCreateLambdaHandler(server, handlers.createAPIGatewayProxyEventV2RequestHandler(), {
context: async ({ event, context }) => {
return {
lambdaEvent: event,
lambdaContext: context,
};
},
});

具有API网关事件的event对象(HTTP头、HTTP方法、主体、路径等)。context对象(不要与context函数混淆)包含当前Lambda上下文(函数名称、函数版本、awsRequestId、剩余时间等)。

如果您已经更改了您的配置以使用@vendia/serverless-express您的context函数将接收reqres选项,它们分别是express.Requestexpress.Response对象:

const { ApolloServer } = require('@apollo/server');
const { expressMiddleware } = require('@apollo/server/express4');
const serverlessExpress = require('@vendia/serverless-express');
const express = require('express');
const cors = require('cors');
const server = new ApolloServer({
typeDefs: 'type Query { x: ID }',
resolvers: { Query: { x: () => 'hi!' } },
});
server.startInBackgroundHandlingStartupErrorsByLoggingAndFailingAllRequests();
const app = express();
app.use(
cors(),
express.json(),
expressMiddleware(server, {
// The Express request and response objects are passed into
// your context initialization function
context: async ({ req, res }) => {
// Here is where you'll have access to the
// API Gateway event and Lambda Context
const { event, context } = serverlessExpress.getCurrentInvoke();
return {
expressRequest: req,
expressResponse: res,
lambdaEvent: event,
lambdaContext: context,
};
},
}),
);
exports.handler = serverlessExpress({ app });
const { ApolloServer } = require('@apollo/server');
const { expressMiddleware } = require('@apollo/server/express4');
const serverlessExpress = require('@vendia/serverless-express');
const express = require('express');
const cors = require('cors');
const server = new ApolloServer({
typeDefs: 'type Query { x: ID }',
resolvers: { Query: { x: () => 'hi!' } },
});
server.startInBackgroundHandlingStartupErrorsByLoggingAndFailingAllRequests();
const app = express();
app.use(
cors(),
express.json(),
expressMiddleware(server, {
// The Express request and response objects are passed into
// your context initialization function
context: async ({ req, res }) => {
// Here is where you'll have access to the
// API Gateway event and Lambda Context
const { event, context } = serverlessExpress.getCurrentInvoke();
return {
expressRequest: req,
expressResponse: res,
lambdaEvent: event,
lambdaContext: context,
};
},
}),
);
exports.handler = serverlessExpress({ app });

自定义HTTP路由行为

如果您想自定义HTTP路由行为,可以将Apollo Server's Express集成(即expressMiddleware)与@vendia/serverless-express包结合起来。该@vendia/serverless-express库在Lambda事件和Express请求之间进行转换。尽管它们的名称相似,但Serverless CLI与@vendia/serverless-express包并无关联。

您可以将您的Apollo Server设置更新如下,以获得一个完全运行的Lambda服务器,该服务器在AWS的各种功能中工作:

const { ApolloServer } = require('@apollo/server');
const { expressMiddleware } = require('@apollo/server/express4');
const serverlessExpress = require('@vendia/serverless-express');
const express = require('express');
const cors = require('cors');
const server = new ApolloServer({
typeDefs: 'type Query { x: ID }',
resolvers: { Query: { x: () => 'hi!' } },
});
server.startInBackgroundHandlingStartupErrorsByLoggingAndFailingAllRequests();
const app = express();
app.use(cors(), express.json(), expressMiddleware(server));
exports.graphqlHandler = serverlessExpress({ app });
const { ApolloServer } = require('@apollo/server');
const { expressMiddleware } = require('@apollo/server/express4');
const serverlessExpress = require('@vendia/serverless-express');
const express = require('express');
const cors = require('cors');
const server = new ApolloServer({
typeDefs: 'type Query { x: ID }',
resolvers: { Query: { x: () => 'hi!' } },
});
server.startInBackgroundHandlingStartupErrorsByLoggingAndFailingAllRequests();
const app = express();
app.use(cors(), express.json(), expressMiddleware(server));
exports.graphqlHandler = serverlessExpress({ app });

该设置允许您根据需要自定义HTTP行为。

上一页
代理配置
下一页
Heroku
评估文章评估在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,商用名Apollo GraphQL。

隐私政策

公司