Apollo Server
In the following section, you will see how to integrate any cloud with your Apollo Server application.
We only support HTTP Requests, subscriptions is not supported yet.
Requirements
First, you need to ensure you have the libs installed, so run this code:
npm i --save @apollo/server
Also, to be able to handle JSON requests, we can use JsonBodyParserFramework, so let's install it:
npm i --save body-parser http-errors
npm i --save-dev @types/body-parser
Need to deal with CORS? See CorsFramework which helps you to add correct headers.
Usage
Then, you just need to use the ApolloServerFramework when you create your adapter, like:
import { ServerlessAdapter } from '@h4ad/serverless-adapter';
import { CorsFramework } from '@h4ad/serverless-adapter/frameworks/cors';
import { ApolloServerFramework } from '@h4ad/serverless-adapter/frameworks/apollo-server';
import { JsonBodyParserFramework } from '@h4ad/serverless-adapter/frameworks/body-parser';
import { ApolloServer } from '@apollo/server';
export const app = new ApolloServer({
typeDefs: `type Query { message: String }`,
resolvers: {
Query: {
message: () => 'Hello World!',
}
},
});
// json-body is needed to handle JSON content type
const framework = new JsonBodyParserFramework(
new ApolloServerFramework(),
);
// you always need to start your application
app.startInBackgroundHandlingStartupErrorsByLoggingAndFailingAllRequests();
export const handler = ServerlessAdapter.new(app)
.setFramework(framework)
// continue to set the other options here.
// .setHandler(new DefaultHandler())
// .setResolver(new PromiseResolver())
// .addAdapter(new ApiGatewayV1Adapter())
// .addAdapter(new ApiGatewayV2Adapter())
// after adding all the necessary methods, just call the build method.
.build();
Need more examples? See more examples here.
Is your application instance creation an asynchronous process? If so, you might want to consider using the LazyFramework, which can help with asynchronous startup.
Support for AWS SQS, SNS and others.
Well, this framework will work great for adapters like ApiGatewayV1Adapter, ApiGatewayV2Adapter, HttpTriggerV4Adapter and others based on pure HTTP requests.
But adapters like SQSAdapter, SNSAdapter and others by default cannot send a request with the correct format that Apollo Server understands, it's not in your control how the request is constructed, the adapter that construct the request to Apollo.
To address this issue, i created the ApolloServerMutationAdapter, an adapter for other adapters. It follows the same principle of helper frameworks, it wraps the adapter you are using and customize the behavior to support sending valid requests to Apollo.
Let's see how to integrate with AWS SQS and AWS SNS, first, let's create the schema.
const schema = `
type Query { message: String }
type AWSResult {
result: String
}
type Mutation {
sqs (event: String): AWSResult
sns (event: String): AWSResult
}
`;
In this schema, you define a mutation with the name you want to give to AWS SQS, I just put sqs
but you can put whatever you want.
About the mutation parameters and the result, you MUST
define the parameter as event: String
, but you CAN
change the AWSResult
if you want.
I explain further below about the decision to have this event: String
.
Ok, after define the schema, let's create the apollo server instance.
import { ApolloServer } from '@apollo/server';
import { GraphQLError } from 'graphql/error/GraphQLError';
import { DefaultServerlessApolloServerContext } from '@h4ad/serverless-adapter/frameworks/apollo-server';
import type { SNSEvent, SQSEvent } from 'aws-lambda';
export const app = new ApolloServer<DefaultServerlessApolloServerContext>({
typeDefs: schema,
resolvers: {
Query: {
message: () => 'Hello World!',
},
Mutation: {
sqs: (_, data, context) => {
// security measures: http://serverless-adapter.viniciusl.com.br/docs/main/adapters/aws/sqs#security
if (context.request.headers['host'] !== 'sqs.amazonaws.com')
throw new GraphQLError('Your host is not allowed to call this mutation.');
// here, you can manipulate the event data and do whatever you want with it.
const event = JSON.parse(data.event) as SQSEvent;
// I will just return the event data to better debugging
return ({
result: JSON.stringify(event),
});
},
sns: (_, data, context) => {
// security measures: http://serverless-adapter.viniciusl.com.br/docs/main/adapters/aws/sqs#security
if (context.request.headers['host'] !== 'sns.amazonaws.com')
throw new GraphQLError('Your host is not allowed to call this mutation.');
// here, you can manipulate the event data and do whatever you want with it.
const event = JSON.parse(data.event) as SNSEvent;
// I will just return the event data to better debugging
return ({
result: JSON.stringify(event),
});
},
},
},
});
In the code above, we created the Apollo Server Instance with the mutations that will handle the events from SQS and SNS.
Because of the nature of GraphQL, it is too hard to create strict schema definitions for each event source, so I just serialize in JSON the event and send it as string
inside data
with the type of { event: string }
.
About the result of each mutation (AWSResult
), you can customize it to return whatever you want, like the name, but you will need to specify the return inside ApolloServerMutationAdapter
,
we will see this configuration in next section.
Well, now we only need to expose the Apollo Server Instance using Serverless Adapter:
import { ServerlessAdapter } from '@h4ad/serverless-adapter';
import { ApolloServerMutationAdapter } from '@h4ad/serverless-adapter/adapters/apollo-server';
import { ApiGatewayV2Adapter, SNSAdapter, SQSAdapter } from '@h4ad/serverless-adapter/adapters/aws';
import { DefaultHandler } from '@h4ad/serverless-adapter/handlers/default';
import { PromiseResolver } from '@h4ad/serverless-adapter/resolvers/promise';
import { CorsFramework } from '@h4ad/serverless-adapter/frameworks/cors';
import { ApolloServerFramework, DefaultServerlessApolloServerContext } from '@h4ad/serverless-adapter/frameworks/apollo-server';
import { JsonBodyParserFramework } from '@h4ad/serverless-adapter/frameworks/body-parser';
const framework = new ApolloServerFramework<DefaultServerlessApolloServerContext>();
const jsonBodyFramework = new JsonBodyParserFramework(framework);
// optional: you can configure cors using this guy here, if you don't want, just erase
const corsFramework = new CorsFramework(jsonBodyFramework, {
origin: '*',
maxAge: 30,
});
// needed to start the application, without this, the apollo server will throw an error
app.startInBackgroundHandlingStartupErrorsByLoggingAndFailingAllRequests();
// let's add support for api gateway v2
const apiGatewayV2Adapter = new ApiGatewayV2Adapter();
// let's add support for sqs
const sqsAdapter = new SQSAdapter();
const wrappedSqsAdapter = new ApolloServerMutationAdapter(
sqsAdapter,
{
mutationName: 'sqs', // remember the scheme? This is the name of the mutation
mutationResultQuery: '{ result }', // we specify the return as `AWSResult` and here we specify which properties we want to return of that type
// if we dont specify nothing, `{ __typename }` will be returned.
}
);
// let's add support for sns
const snsAdapter = new SNSAdapter();
const wrappedSnsAdapter = new ApolloServerMutationAdapter(
snsAdapter,
{
mutationName: 'sns', // remember the scheme? This is the name of the mutation
mutationResultQuery: '{ result }', // we specify the return as `AWSResult` and here we specify which properties we want to return of that type
// if we dont specify nothing, `{ __typename }` will be returned.
}
);
export const handler = ServerlessAdapter.new(app)
.setFramework(corsFramework)
.setHandler(new DefaultHandler())
.setResolver(new PromiseResolver())
.addAdapter(apiGatewayV2Adapter)
.addAdapter(wrappedSqsAdapter)
.addAdapter(wrappedSnsAdapter)
.build();
Need more examples? See more examples here.
That's it! Now you are able to receive API Gateway V2
requests and also integrate AWS SQS
and AWS SNS
into the same lambda function. Great, right?
Customizing the Context
By default, the context will be DefaultServerlessApolloServerContext, but you can customize
the creation of the context by passing context
variable inside ApolloServerFramework
, like:
import { ApolloServerFramework, DefaultServerlessApolloServerContext } from '@h4ad/serverless-adapter/frameworks/apollo-server';
// I want the date when it's started, and also, i always recommend including the default context
type MyCustomContext = { startedAt: Date } & DefaultServerlessApolloServerContext;
const framework = new ApolloServerFramework<MyCustomContext>({
context: async ({ request, response }) => {
return {
startedAt: new Date(),
request,
response,
};
},
});