@fastify/aws-lambda: running a Fastify app behind API Gateway
Insipired by aws-serverless-express to work with Fastify with inject functionality
At a glance
- What is it?
- The Fastify project's own Lambda adapter avoids internal sockets entirely and drives the application through Fastify's inject function, which removes a layer of network plumbing and leaves a set of options whose defaults quietly decide how API Gateway will treat your responses.
- Who is it for?
- @fastify/aws-lambda is the right choice for a Fastify service that is deployed behind API Gateway or an Application Load Balancer and wants to keep the framework it already uses, and its inject based design avoids the socket handling that other adapters require. It is the wrong choice for a service that also consumes other event sources, and it is the wrong choice if you need to stream a response without opting into the streaming option.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 13 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Fastify's own adapter, in the Fastify organisation
@fastify/aws-lambda is a library that lets a Fastify application run behind AWS Lambda, and it is published under the @fastify scope, which is the scope the framework's own plugins use. The package is MIT licensed, the author is Adriano Raiano, the main entry is index.js and the type definitions are in types/index.d.ts.
The lineage is stated in the readme. It is inspired by the Code Genie serverless-express library, tailor made for the Fastify web framework, and the comparison the readme draws is with serverless-express and aws-serverless-fastify. The claim is that it seems faster, as the name implies, and that is the project's own claim rather than an independent measurement.
The version history is active. Version 6.4.0 was published on 2026-01-25, 6.4.1 on 2026-08-03 and 6.4.2 on 2026-10-10, and the last push to the default branch was on the same day.
For a team already using Fastify, the pitch is simple. You keep the framework, you keep the plugins, you keep the testing you wrote with inject, and you change one line in the handler file. What you gain is the avoidance of the socket layer, and what you inherit is a set of options that map Fastify's behaviour onto what API Gateway expects.
No internal sockets, everything runs through inject
The bolded line near the top of the readme is the design statement of the whole package: no use of internal sockets, it makes use of Fastify's inject function.
That is a real architectural choice rather than a detail. The usual way to run a Node web framework in a Lambda handler is to start a server, let it listen on a local port, and then forward the Lambda event into that socket. Every request therefore opens or reuses a local TCP connection, and the adapter becomes a proxy in the literal sense.
Using inject instead means the adapter hands the request to Fastify the same way the test suite does. The framework builds the request, runs the handler, produces a response, and the adapter converts that into the shape API Gateway or an Application Load Balancer expects. Nothing listens on a port, there is no port to allocate, and the behaviour of a request in production is the behaviour of a request in a test.
The trade-off is that this is a Node.js package, and the readme is explicit that the Lambda runtime waits for an empty event loop, with a callbackWaitsForEmptyEventLoop option that points at the official AWS documentation for the semantics. A framework that keeps a handle open, a connection pool or a background timer will change the behaviour of the function, and that option is where you would look.
There is a second mechanism for the same idea. The serializeLambdaArguments option puts the Lambda event and context into the request headers, x-apigateway-event and x-apigateway-context, and the decorateRequest option attaches the event and context to the request object at request.awsLambda.event and request.awsLambda.context, with the property name configurable through decorationPropertyName.
The options that decide what API Gateway sees
The options table is long, and most of the entries fall into three groups.
The first group is about how the request is interpreted. retainStage keeps the stage part of the API Gateway URL, which matters if your routes are written with the stage prefix. pathParameterUsedAsPath uses a defined path parameter as the path, for example a route written as proxy, which is the shape API Gateway uses for a catch-all proxy resource. parseCommaSeparatedQueryParams parses a query string with commas into an array of values, and the documentation is careful that this changes the behaviour of the query string parser when you are using Payload Format Version 2.0. albMultiValueHeaders is for a load balancer that presents multi value headers.
The second group is about the response body, and it is where the interesting defaults live. binaryMimeTypes is a list of Content-Type values treated as binary, and enforceBase64 is a function that decides whether a particular response should be base64 encoded. disableBase64Encoding turns the encoding off and omits the isBase64Encoded property, and the documentation notes that it is automatically enabled when payloadAsStream is true and the request does not come from an ALB.
The third group is about the response shape. payloadAsStream makes the response a stream that can be used with awslambda.streamifyResponse, which is the option that lets a Lambda response be delivered incrementally rather than as one buffered payload.
The options are passed with awsLambdaFastify(app, options), so the whole set is a single argument at the point where the handler is created.
Binary responses: two paths to base64
The binary response section is the most substantial part of the documentation, and it exists because API Gateway has to be told how to treat a response body.
The problem is that API Gateway needs to know when a response body is binary so that it can be base64-encoded over the wire, which it signals with the isBase64Encoded property set to true in the Lambda return payload. The adapter decides this through two complementary paths, and at least one match triggers the encoding.
The first path is binaryMimeTypes, which matches the response Content-Type against an allowlist. That is for the case where binary-ness is a property of the content type itself, for example application/octet-stream, image/png or application/zip.
The second path is enforceBase64, a function called with the response, for the case where binary-ness depends on something other than the Content-Type. The documentation's example is the important one: a body that is compressed is binary, but its content type is text-like, for example application/json with a Content-Encoding of br.
And when enforceBase64 is omitted, a built-in default kicks in that base64-encodes any response with a non-identity Content-Encoding, so gzip, br, deflate and zstd all work out of the box. The readme puts it plainly: compressed responses work without you writing a callback for the common case.
That default is the single most useful thing in this package, because the compressed JSON response is an extremely common case and an easy one to get wrong.
The compress example, and the trap in enforceBase64
The first example wires the compression plugin into the adapter and shows the automatic behaviour.
const fastify = require('fastify')
const compress = require('@fastify/compress')
const awsLambdaFastify = require('@fastify/aws-lambda')
const app = fastify()
app.register(compress, { global: true, encodings: ['br', 'gzip'] })
app.get('/', (req, reply) => reply.send({ hello: 'world' }))
// Responses gain Content-Encoding: br|gzip, and the default behaviour
// auto-base64-encodes them for API Gateway. No extra option needed.
exports.handler = awsLambdaFastify(app)The second example adds a content type rule, and the comment in it is the part to read twice.
exports.handler = awsLambdaFastify(app, {
binaryMimeTypes: ['application/octet-stream', 'image/png'],
// Custom rule for binary detection. Note: when you pass enforceBase64,
// Re-implement the compression check yourself if you still want it.
enforceBase64: (res) => {
if (res.headers['content-type'] === 'application/x-protobuf') return true
const enc = res.headers['content-encoding']
return !!enc && enc !== 'identity'
}
})Passing enforceBase64 replaces the built-in Content-Encoding default rather than composing with it, so the example re-implements the compression check by hand in order not to lose it. That is a sharp edge in an API where the two mechanisms are described as complementary, and it is the one thing to check when you write your own rule.
The alternative, in the same example, is to simply not pass enforceBase64 and let the default do the work.
The test and performance setup in the repository
The package manifest shows how the project checks itself, and there is a performance harness alongside the unit tests.
The test script runs the unit tests and then the TypeScript type tests.
"scripts": {
"lint": "eslint",
"lint:fix": "eslint --fix",
"test": "npm run test:unit && npm run test:typescript",
"test:unit": "node scripts/unit.js",
"test:typescript": "tstyche",
"performance": "npm run lint && node performanceTest/test"
},The unit tests run through a node script, and the type level tests use tstyche, which checks the public type definitions against usage, so the types directory is not just documentation but something the build verifies. The performance target lints first and then runs the performanceTest directory, and the repository has a performanceTest folder at the top level.
The development dependencies explain what the comparison is against. Both aws-serverless-express and aws-serverless-fastify are present, along with serverless-http, which are the socket based approaches the adapter is positioning itself against, and there is a benchmark package and an @h4ad/serverless-adapter. The fastify version in the development dependencies is 5.8.4, and the main entry is CommonJS.
For a team adopting the adapter, the useful inheritance is the fact that the response handling is covered by unit tests and the types are covered by type tests, since the options table is long and the defaults are subtle.
Where @fastify/aws-lambda is the wrong tool
Four limits, and the first is about what kind of Lambda function this is.
The adapter is built for the HTTP proxy integration of API Gateway and the Application Load Balancer. It is not a general purpose event handler, so a function that consumes SQS records, receives an S3 event or is invoked by a schedule is not what this package is for. The event and context are available on the request, which is exactly the shape of a proxy integration and nothing else.
The second is the response body. The default path produces a complete response payload, and delivering a response incrementally is opt in through payloadAsStream and the awslambda.streamifyResponse helper. A service whose value depends on streaming, a large file or a long lived connection, has to find that path and accept the isBase64Encoded semantics that come with it.
The third is the base64 contract, and it cuts both ways. The default behaviour is a good default, and it is also a behaviour you inherit: a compressed response is base64 encoded and marked as binary, and a client that did not expect binary content may treat it differently. The options exist to change that, and the one option that changes it, enforceBase64, replaces the default rather than extending it, so the configuration is not simply additive.
The fourth is the runtime model. A Node.js Lambda function with a framework that may hold timers or keep handles open has to work with the empty event loop semantics of the platform, and the callbackWaitsForEmptyEventLoop option exists precisely because that interaction is not always what you want. A Fastify service with plugins that open connections or schedule work will need that option understood before it goes to production.
Editorial conclusion
@fastify/aws-lambda is the right choice for a Fastify service that is deployed behind API Gateway or an Application Load Balancer and wants to keep the framework it already uses, and its inject based design avoids the socket handling that other adapters require. It is the wrong choice for a service that also consumes other event sources, and it is the wrong choice if you need to stream a response without opting into the streaming option. Before adopting, check the base64 defaults against your response types, in particular if you use compression, and read the enforceBase64 documentation, because passing that option replaces the built-in behaviour rather than adding to it.
Frequently asked questions
What does @fastify/aws-lambda do?
It is a Fastify adapter for AWS Lambda, published under the @fastify scope and inspired by the Code Genie serverless-express library. It makes no use of internal sockets and instead drives the application through Fastify's inject function, the same mechanism the framework's test suite uses.
How does @fastify/aws-lambda decide which responses to base64 encode?
Through two complementary paths, and a match on either triggers the encoding. binaryMimeTypes matches the response Content-Type against an allowlist, while enforceBase64 is a function that receives the response, for cases such as a compressed body with a text-like content type. When enforceBase64 is omitted, a built-in default base64-encodes any response with a non-identity Content-Encoding.
How do I install the Fastify AWS Lambda adapter?
With npm i @fastify/aws-lambda. The options are passed when the handler is created, by calling awsLambdaFastify(app, options), and the package ships its type definitions at types/index.d.ts with the CommonJS entry point at index.js.
Can I stream a response from a Fastify Lambda handler?
Yes, by setting the payloadAsStream option to true, which makes the response a stream that can be used with awslambda.streamifyResponse. Note that base64 encoding is then automatically enabled unless the request comes from an ALB.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/fastify-aws-lambda-fastify)Was this useful?