mirror of
https://github.com/vale981/apollo-server
synced 2025-03-06 10:11:40 -05:00
Add support for AWS Lambda
Create an integration that will return a lambda handler for a graphql or graphiql server. This integration requires an API Gateway with Lambda Proxy Integration. The test runner expects req and res objects for testing. We have to mock these for the tests to execute properly.
This commit is contained in:
parent
7db522be9b
commit
cd1c19d033
8 changed files with 245 additions and 0 deletions
5
packages/graphql-server-lambda/.npmignore
Executable file
5
packages/graphql-server-lambda/.npmignore
Executable file
|
@ -0,0 +1,5 @@
|
|||
*
|
||||
!dist
|
||||
!dist/**/*
|
||||
dist/**/*.test.*
|
||||
!package.json
|
3
packages/graphql-server-lambda/README.md
Executable file
3
packages/graphql-server-lambda/README.md
Executable file
|
@ -0,0 +1,3 @@
|
|||
# graphql-server-lambda
|
||||
|
||||
This is the AWS Lambda integration for the Apollo community GraphQL Server. [Read the docs.](http://dev.apollodata.com/tools/apollo-server/index.html)
|
43
packages/graphql-server-lambda/package.json
Normal file
43
packages/graphql-server-lambda/package.json
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"name": "graphql-server-lambda",
|
||||
"version": "0.5.1",
|
||||
"description": "Production-ready Node.js GraphQL server for AWS Lambda",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"compile": "tsc",
|
||||
"prepublish": "npm run compile"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/apollostack/graphql-server/tree/master/packages/graphql-server-lambda"
|
||||
},
|
||||
"keywords": [
|
||||
"GraphQL",
|
||||
"Apollo",
|
||||
"Server",
|
||||
"Lambda",
|
||||
"Javascript"
|
||||
],
|
||||
"author": "Jonas Helfer <jonas@helfer.email>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/apollostack/graphql-server/issues"
|
||||
},
|
||||
"homepage": "https://github.com/apollostack/graphql-server#readme",
|
||||
"dependencies": {
|
||||
"graphql-server-core": "^0.5.1",
|
||||
"graphql-server-module-graphiql": "^0.4.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/aws-lambda": "0.0.5",
|
||||
"@types/graphql": "^0.8.6",
|
||||
"graphql-server-integration-testsuite": "^0.5.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^0.8.0 || ^0.9.0"
|
||||
},
|
||||
"typings": "dist/index.d.ts",
|
||||
"typescript": {
|
||||
"definition": "dist/index.d.ts"
|
||||
}
|
||||
}
|
6
packages/graphql-server-lambda/src/index.ts
Executable file
6
packages/graphql-server-lambda/src/index.ts
Executable file
|
@ -0,0 +1,6 @@
|
|||
export {
|
||||
LambdaHandler,
|
||||
IHeaders,
|
||||
graphqlLambda,
|
||||
graphiqlLambda
|
||||
} from './lambdaApollo';
|
64
packages/graphql-server-lambda/src/lambdaApollo.test.ts
Executable file
64
packages/graphql-server-lambda/src/lambdaApollo.test.ts
Executable file
|
@ -0,0 +1,64 @@
|
|||
import { graphqlLambda, graphiqlLambda } from './lambdaApollo';
|
||||
import testSuite, { schema as Schema, CreateAppOptions } from 'graphql-server-integration-testsuite';
|
||||
import { expect } from 'chai';
|
||||
import { GraphQLOptions } from 'graphql-server-core';
|
||||
import 'mocha';
|
||||
import * as url from 'url';
|
||||
|
||||
function createLambda(options: CreateAppOptions = {}) {
|
||||
let handler,
|
||||
callback,
|
||||
event,
|
||||
context;
|
||||
|
||||
options.graphqlOptions = options.graphqlOptions || { schema: Schema };
|
||||
if (options.graphiqlOptions ) {
|
||||
handler = graphiqlLambda( options.graphiqlOptions );
|
||||
} else {
|
||||
handler = graphqlLambda( options.graphqlOptions );
|
||||
}
|
||||
|
||||
return function(req, res) {
|
||||
let body = '';
|
||||
req.on('data', function (chunk) {
|
||||
body += chunk;
|
||||
});
|
||||
req.on('end', function() {
|
||||
let urlObject = url.parse(req.url, true);
|
||||
event = {
|
||||
httpMethod: req.method,
|
||||
body: body,
|
||||
path: req.url,
|
||||
queryStringParameters: urlObject.query,
|
||||
};
|
||||
context = {};
|
||||
callback = function(error, result) {
|
||||
res.statusCode = result.statusCode;
|
||||
for (let key in result.headers) {
|
||||
if (result.headers.hasOwnProperty(key)) {
|
||||
res.setHeader(key, result.headers[key]);
|
||||
}
|
||||
}
|
||||
res.write(result.body);
|
||||
res.end();
|
||||
};
|
||||
|
||||
handler(event, context, callback);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
describe('lambdaApollo', () => {
|
||||
it('throws error if called without schema', function(){
|
||||
expect(() => graphqlLambda(undefined as GraphQLOptions)).to.throw('Apollo Server requires options.');
|
||||
});
|
||||
|
||||
it('throws an error if called with more than one argument', function(){
|
||||
expect(() => (<any>graphqlLambda)({}, {})).to.throw(
|
||||
'Apollo Server expects exactly one argument, got 2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('integration:Lambda', () => {
|
||||
testSuite(createLambda);
|
||||
});
|
106
packages/graphql-server-lambda/src/lambdaApollo.ts
Executable file
106
packages/graphql-server-lambda/src/lambdaApollo.ts
Executable file
|
@ -0,0 +1,106 @@
|
|||
import * as lambda from 'aws-lambda';
|
||||
import { GraphQLOptions, runHttpQuery } from 'graphql-server-core';
|
||||
import * as GraphiQL from 'graphql-server-module-graphiql';
|
||||
|
||||
export interface LambdaGraphQLOptionsFunction {
|
||||
(event: any, context: lambda.Context): GraphQLOptions | Promise<GraphQLOptions>;
|
||||
}
|
||||
|
||||
// Design principles:
|
||||
// - there is just one way allowed: POST request with JSON body. Nothing else.
|
||||
// - simple, fast and secure
|
||||
//
|
||||
|
||||
export interface LambdaHandler {
|
||||
(event: any, context: lambda.Context, callback: lambda.Callback): void;
|
||||
}
|
||||
|
||||
export interface IHeaders {
|
||||
[header: string]: string | number;
|
||||
}
|
||||
|
||||
export function graphqlLambda( options: GraphQLOptions | LambdaGraphQLOptionsFunction ): LambdaHandler {
|
||||
if (!options) {
|
||||
throw new Error('Apollo Server requires options.');
|
||||
}
|
||||
|
||||
if (arguments.length > 1) {
|
||||
throw new Error(`Apollo Server expects exactly one argument, got ${arguments.length}`);
|
||||
}
|
||||
|
||||
return async (event, lambdaContext: lambda.Context, callback: lambda.Callback) => {
|
||||
let query = (event.httpMethod === 'POST') ? event.body : event.queryStringParameters,
|
||||
statusCode: number = null,
|
||||
gqlResponse = null,
|
||||
headers: {[headerName: string]: string} = {};
|
||||
|
||||
if (query && typeof query === 'string') {
|
||||
query = JSON.parse(query);
|
||||
}
|
||||
|
||||
try {
|
||||
gqlResponse = await runHttpQuery([event, lambdaContext], {
|
||||
method: event.httpMethod,
|
||||
options: options,
|
||||
query: query,
|
||||
});
|
||||
headers['Content-Type'] = 'application/json';
|
||||
statusCode = 200;
|
||||
} catch (error) {
|
||||
if ( 'HttpQueryError' !== error.name ) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
headers = error.headers;
|
||||
statusCode = error.statusCode;
|
||||
gqlResponse = error.message;
|
||||
} finally {
|
||||
callback(
|
||||
null,
|
||||
{
|
||||
'statusCode': statusCode,
|
||||
'headers': headers,
|
||||
'body': gqlResponse,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/* This Lambda Function Handler returns the html for the GraphiQL interactive query UI
|
||||
*
|
||||
* GraphiQLData arguments
|
||||
*
|
||||
* - endpointURL: the relative or absolute URL for the endpoint which GraphiQL will make queries to
|
||||
* - (optional) query: the GraphQL query to pre-fill in the GraphiQL UI
|
||||
* - (optional) variables: a JS object of variables to pre-fill in the GraphiQL UI
|
||||
* - (optional) operationName: the operationName to pre-fill in the GraphiQL UI
|
||||
* - (optional) result: the result of the query to pre-fill in the GraphiQL UI
|
||||
*/
|
||||
|
||||
export function graphiqlLambda(options: GraphiQL.GraphiQLData) {
|
||||
return (event, lambdaContext: lambda.Context, callback: lambda.Callback) => {
|
||||
const q = event.queryStringParameters || {};
|
||||
const query = q.query || '';
|
||||
const variables = q.variables || '{}';
|
||||
const operationName = q.operationName || '';
|
||||
|
||||
const graphiQLString = GraphiQL.renderGraphiQL({
|
||||
endpointURL: options.endpointURL,
|
||||
query: query || options.query,
|
||||
variables: q.variables && JSON.parse(variables) || options.variables,
|
||||
operationName: operationName || options.operationName,
|
||||
passHeader: options.passHeader,
|
||||
});
|
||||
callback(
|
||||
null,
|
||||
{
|
||||
'statusCode': 200,
|
||||
'headers': {
|
||||
'Content-Type': 'text/html',
|
||||
},
|
||||
'body': graphiQLString,
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
17
packages/graphql-server-lambda/tsconfig.json
Normal file
17
packages/graphql-server-lambda/tsconfig.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
],
|
||||
"types": [
|
||||
"@types/node"
|
||||
]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
|
@ -7,4 +7,5 @@ require('../packages/graphql-server-express/dist/connectApollo.test');
|
|||
require('../packages/graphql-server-hapi/dist/hapiApollo.test');
|
||||
require('../packages/graphql-server-koa/dist/koaApollo.test');
|
||||
require('../packages/graphql-server-restify/dist/restifyApollo.test');
|
||||
require('../packages/graphql-server-lambda/dist/lambdaApollo.test');
|
||||
require('../packages/graphql-server-express/dist/apolloServerHttp.test');
|
||||
|
|
Loading…
Add table
Reference in a new issue