Welcome back to the second part of the Zero to Serverless Hero Series. In this blog post I will cover how you can configure your lambda to handle GraphQL queries.
If you are not yet familiar with how GraphQL works, I would recommend you to read the GraphQL Documentation.
Getting Started
For implementing the GraphQL schemas we will be using TypeGraphQL because this gives us an easy to understand and maintainable way to implement the GraphQL schema by using decorators. As of the time of writing the support for Apollo v4 version is still in beta, nevertheless, we will use the beta, because Apollo v3 is already deprecated. So let’s install it, along with the aws-lambda package which we will need npm i type-graphql@2.0.0-beta.1 aws-lambda
. For building and deploying the lambda we will additionally install the following package as dev dependencies: npm i -D @cubesoft/nx-lambda-build depcheck
.
Creating the Lambda Handler
In the last blog post we used inline code for our lambda handler. However, that should be only done only for testing or if you have pretty small functions, because you loose a lot of code maintainability there. So first we will put our lambda code into a separate file called serverless-api.ts
.
typescript
import { APIGatewayProxyEventV2, Context } from 'aws-lambda';
export async function handler(event: APIGatewayProxyEventV2, context: Context) {
console.log('Hello, CDK!');
return {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
}
Then we need to tell the cdk how to find the code. Therefore, we need to change the lambda part in our stack (serverless-stack.ts
):
typescript
const lambda = new Function(this, 'api-handler', {
runtime: Runtime.NODEJS_18_X,
handler: 'serverless-api.handler',
functionName: 'serverless-api',
code: Code.fromAsset(
join(
getWorkspaceRoot(),
'dist/packages/serverless-api/serverless-api',
'handler.zip'
)
),
memorySize: Size.gibibytes(1).toMebibytes(),
architecture: Architecture.ARM_64,
logRetention: RetentionDays.ONE_DAY,
timeout: Duration.seconds(5),
});
Additionally we also need to tell the nx-lambda-build plugin where our handler will be located. Therefore, we need to create a file called serverless-api/src/handlers.json
to create a mapping between the handler name and the code location:
{
"entrypoints": {
"serverless-api": "src/handler/serverless-api"
}
}
Before updating our lambda to be able to function as a GraphQL server, we first want to check if what we did so far is functioning. Because it is much easier to test smaller changes and fixing errors right after they occur, instead of searching for the problem in a whole stack of changes. For testing our lambda we first need to build it and then deploy it:
bash
nx build serverless-api
nx deploy serverless-cdk --profile <your-profile>
You need to confirm that you want to deploy the changes and afterwards you can check, that your /graphql endpoint still returns "Hello from Lambda!"
. If you go to your AWS Console and open your lambda function (Lambda > Functions > serverless-api), you will also see that your code is now deployed:
Configuring the Apollo Server
We need to install some dependency for using the Apollo server npm i -S typedi reflect-metadata @apollo/server graphql reflect-metadata @as-integrations/aws-lambda
. After installing these dependencies we can start creating our first resolver, which we can later pass to our Apollo Server, which we will create in the next step.
We will stick with our easy lambda example and for the beginning and start with an easy query that will just return a message.
typescript
import { Query, Resolver } from 'type-graphql';
import { Service } from 'typedi';
@Service()
@Resolver()
export class GreetingResolver {
@Query(() => String, {
description:
'Returns a greeting message. This is a sample query that returns a string',
})
async hello(): Promise<string> {
return 'world';
}
}
Make sure to add both the @Resolver
and the @Query
decorator to your class. The @Resolver
class is TypeGraphQL specific and is required to build the GraphQL schema from your implementation. The @Query
decorator marks the class method as a GraphQL query.
After we have created our resolver we can create the Apollo Server and a build a GraphQL schema including our resolver. For this we replace the content of our serverless.api.ts
file with the following code.
typescript
import 'reflect-metadata';
import { ApolloServer } from '@apollo/server';
import 'graphql';
import { buildSchemaSync } from 'type-graphql';
import {
handlers,
startServerAndCreateLambdaHandler,
} from '@as-integrations/aws-lambda';
import { GreetingResolver } from '../resolver/greeting.resolver';
const schema = buildSchemaSync({
resolvers: [GreetingResolver],
validate: { forbidUnknownValues: false },
dateScalarMode: 'timestamp',
});
const server = new ApolloServer({
schema: schema,
introspection: true,
csrfPrevention: true,
});
export const handler = startServerAndCreateLambdaHandler(
server,
handlers.createAPIGatewayProxyEventV2RequestHandler()
);
As you can see we tell our schema to use our newly created resolver. Afterward this schema is injected into the Apollo Server and as a last part we create the handler to tell our lambda how to execute our calls.
Deploy your stack
Finally, you can build and deploy your CDK stack using the command nx build serverless-api && nx deploy serverless-cdk --profile serverless-hero
. If you open your browser this time using the same url as last time (/graphql) you will no longer see the message "Hello from Lambda!" but you will see the apollo playground like in the image below. Now you have a GraphQL lambda hander, it only supports one simple query but this is a good starting point, isn’t it?
Next time we will setup a database for storing and retrieving data for your application.
You can find my sample repo here
Top comments (0)