I am using Apollo Server on AWS lambda. I followed the documentation to bootstrap it:
const { ApolloServer } = require('apollo-server-lambda');
// [...]
const server = new ApolloServer({ typeDefs, resolvers });
exports.graphql = server.createHandler();
This version works just fine. Yet, I need to get a database to pass to the GraphQL context. Hence, I need an asynchrounous handler. In this case, I can't make it work simply:
export const graphql = async (event, context, callback) => {
const db = await getDb();
const server = new ApolloServer({
typeDefs: myTypeDefs,
resolvers: myResolvers,
context: { db },
});
return server.createHandler()(event, context, callback);
};
It seems the lambda function is closed before the handler executes. Diving into the code and adding some console.log, it looks like the following part is causing the issue:
graphqlLambda(async () => {
// In a world where this `createHandler` was async, we might avoid this
// but since we don't want to introduce a breaking change to this API
// (by switching it to `async`), we'll leverage the
// `GraphQLServerOptions`, which are dynamically built on each request,
// to `await` the `promiseWillStart` which we kicked off at the top of
// this method to ensure that it runs to completion (which is part of
// its contract) prior to processing the request.
await promiseWillStart;
return this.createGraphQLServerOptions(event, context);
})(event, context, callbackFilter);
I don't fully understand this part of the code. Any idea of what is going on? Am I missing something?
No change is needed to apollo-server, you need to create an async function that creates the server.
const createHandler = async () => {
const db = await getDb();
const server = new ApolloServer({
typeDefs: myTypeDefs,
resolvers: myResolvers,
context: { db },
});
return server.createHandler();
};
export const graphql = (event, context, callback) => {
createHandler().then(handler => handler(event, context, callback));
};
@carbonrobot Thanks, but we've tried that and it doesn't work. As soon as it reaches const db = await getDb();, the lambda returns.
This is the code we are using in production, albeit a bit simplified. If you remove the callback param, you should see it timing out, if not then check for an extra async on your handler.
Thanks it worked, I wasn't executing the handler properly
No change is needed to apollo-server, you need to create an async function that creates the server.
const createHandler = async () => { const db = await getDb(); const server = new ApolloServer({ typeDefs: myTypeDefs, resolvers: myResolvers, context: { db }, }); return server.createHandler(); }; export const graphql = (event, context, callback) => { createHandler().then(handler => handler(event, context, callback)); };
You saved me man. Thank you soooo much
in the newer node versions the signature of handler looks like:
export const handler = async (event, context) { ... }
Shouldn't we update the package to support the newer syntax? So we can do something like:
export const handler = (event, context) => {
const server = new ApolloServer({
typeDefs: myTypeDefs,
resolvers: myResolvers,
context: { db },
});
return server.createHandler();
}
The solutions mentioned above no longer work with nodejs14 lambdas. This is breaking change.
I have gotten it working by using promisify but it's not ideal.
const { promisify } = require('util');
export const handler = (event, context) => {
const server = new ApolloServer({
typeDefs: myTypeDefs,
resolvers: myResolvers,
context: { db },
});
const apolloHandler = promisify(server.createHandler());
return apolloHandler(event, context);
}
Can we reopen this ticket?
@adikari Would it make sense to add a new method to server like server.createHandler instead? Otherwise it seems like a serious backwards-compatibility concern, right?
If so, that seems like the sort of thing that would make a reasonable PR.
@glasser i am not sure what you mean. This is the breaking change on aws lambda node 14 runtime. Could you provide an example?
I'm not a Lambda expert. Are you saying that the signature of the main handler is different in Lambda Node 12 vs Lambda Node 14, in that one should accept a callback and the other should return a promise? If so, it would seem that instead of changing what server.createHandler returns, we should make a new function that returns the new type of handler?
Correct. In node 12 you had choice to use either async handler or non async. Non async handler will accept 3 parameters, event, context and callback. Apollo create handler requires callback to be passed. If you are using async handler, you don't get the callback. In node 12 lambda you could either use async or non async handler. So, you could do similar things suggested in the thread. Please check my previous comment for example. In node 14 handler you can only use async handler, hence you don't have the callback. Unless if you use promisify from node util as I posted in my example, create handler from this package won't work.
OK, but if we change the handler to be always async, won't that break folks on the Node 12 runtime who are configured to use the non-async mode, or on older runtimes that maybe don't support async at all?
It seems like the best fix is adding a server.createAsyncHandler function that returns the new kind of handler.
Or should the handler automatically adapt based on how many arguments it receives? Sounds like it'll lead to vaguer TypeScript types but maybe it's worth the tradeoff.
I have looked into the implementation of create handler. We can simply do a conditional where if the callback is undefined, then assume its a async handler. There is no need to change the existing signature and it will be backwards compatible.
I am happy to create a PR when I can if we agree to the proposed changes.
Ah, that makes sense. Sure, send a PR! Make sure it has appropriate tests and docs.
Since async/await functions are generally easier to read, I'd encourage you to port the function to being an async function, and then having createHandler return a little wrapper which runs the async function in the proper style.
You may want to fix #3999 while you're at it.
Sure. I will try to out together a PR sometimes this week.
@glasser I have created PR for the changes.
@adikari Hi! I think it's a lot easier to maintain this code if it's async, so I appreciated your PR and extended it to make the bulk of the Lambda-related code async rather than callback-based.
I'm pretty sure that (pending review) I'm excited to release this, but I'd like to make sure it's documented appropriately.
Specifically, you told me that the current callback-based code just doesn't work on the Node 14 runtime.
But I went in myself to test this by following this Lambda/API Gateway tutorial. I used the Node 14 runtime and wrote this handler
exports.handler = (event, context, callback) => {
callback(null, {
statusCode: 200,
body: 'Hello from Lambda.',
});
};
and it worked fine. So while I do think it's an improvement for the code to be written async, are you sure that it's required for Node 14 runtime compatibility? I just want to make sure that our release notes are accurate..
I recently deployed a node 14 lambda in AWS and ran into this problem and landed on this thread again and thought to revive it. I did log the callback and found it to be undefined then resorted in using promisify. I can try it again tonight and confirm it. Just to confirm you did deploy your lambda to aws and it worked fine?
Yep, I did deploy that. Maybe it is an APIGateway vs something else difference?
I will try this again to confirm the behaviour. May be tomorrow. I am using API Gateway lambda proxy integration. So in theory it should be same as yours.
@adikari I'd like to move forward with releasing this in the next day or two. Maybe I should just change the CHANGELOG to say that we've refactored it into an async function which may make it easier to wrap it inside your own async handler, but that it doesn't affect version compatibility?
@glasser i have been but busy last week so didn't get chance to follow up. I will do this today and get back to you.
@glasser I can confirm the callback is available in nodejs 14 lambda runtime. I am not sure why I got the wrong impression with my earlier investigation. Here is the screenshot with a logged callback.

I've published a release with this to version 2.21.2-alpha.0. My plan is to release 2.21.2 in a day or two. If you want to test that this fix works before then, try running the prerelease yourself, and provide feedback on #5037. (I have not significantly QAed my changes in real Lambda contexts, so positive feedback on #5037 saying that the release works for your app would be great! It is intended to be a largely no-op change.)
I have been using this in one of my projects a so far it's been fine.
Most helpful comment
No change is needed to apollo-server, you need to create an async function that creates the server.