RFC: Event Handler for REST APIs · aws-powertools/powertools-lambda-typescript · Discussion #3500 (original) (raw)
Note
The RFC has concluded, a big thank you to everyone who took the time to read and engage with it. You can follow the progress of the implementation in this milestone and here (#3251).
Is this related to an existing feature request or issue?
Which area does this RFC relate to?
Event Handler
Summary
Resolvers handle request resolution, including one or more routers, and give access to the current event via typed properties.
While the code samples in the RFC will use mainly APIGatewayRestResolver
, the first iteration of the feature will have in scope also the APIGatewayHttpResolver
and LambdaFunctionUrlResolver
resolvers. Other resolvers present in the Python version of Powertools for AWS will be added in subsequent releases and based on demand.
In terms of patterns of usage, Powertools for AWS Lambda (TypeScript) generally supports three patterns:
- manual usage - most verbose, but usually adoptable in any codebase with minimal changes
- class method decorator usage - least verbose, but requires adopting an experimental TypeScript feature, and usage of OOP patterns for
- Middy.js middleware usage - moderately verbose but requires extra 3rd party dependency (@middy/core)
The first iteration of the feature will include only 1/ and 2/, while Middy.js middleware usage will be added later if there's demand.
Use case
Powertools for AWS customers have been asking us to provide a utility similar to the one present in Powertools for AWS Lambda (Python) that allows them to easily build REST APIs backed by an AWS Lambda function.
While there are already many popular frameworks in the Node.js ecosystem such as Express.js, Fastify, and Hono, none of them targets specifically and uniquely Lambda customers, and many enterprise customers who have already adopted Powertools for AWS in their workloads, would prefer a solution delivered by us rather than relying on a 3rd party dependency.
For this reason we are continuing our work to improve feature parity between Powertools for AWS versions and will offer an Event Handler utility in this version. We don't necessarily expect customers who have had success and are happy with other frameworks to migrate to Event Handler; this utility is rather for those who appreciate our commitment to supply chain security and our efforts to build lightweight utilities specifically focused on Lambda.
The goal of this RFC is to propose a feature set that matches the one present in Powertools for AWS Lambda (Python) while also being idiomatic with the Node.js ecosystem and adding selected features that make sense in this version.
Proposal
Response auto-serialization
For your convenience, you can return a plain object response and we will automatically:
- Auto-serialize
object
(Record<k, v>
) responses to JSON - Include the response under each resolver’s equivalent of a body
- Set
Content-Type
toapplication/json
- Set
statusCode
to 200 (OK)
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers';
const app = new APIGatewayResolver();
app.get('/ping', () => { return { message: 'pong' }; });
export const handler = async (event, context) => app.resolve(event, context);
/* response { "statusCode": 200, "multiValueHeaders": { "Content-Type": [ "application/json" ] }, "body": "{'message':'pong'}", "isBase64Encoded": false } */
If you want full control of the response, headers, and status code you can also return a Response object (more on this later).
Dynamic routes
You can use /todos/:todoId
to configure dynamic URL paths, where :todoId
will be resolved at runtime.
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers';
const app = new APIGatewayResolver();
class Lambda {
@app.get('/todos/:todoId')
public async getTodoById({ params }) {
const { todoId } = params;
const todos = await fetch(https://jsonplaceholder.typicode.com/todos/${todoId}
);
return { todos: await todos.json() }
} }
export const handler = async (event, context) => app.resolve(event, context);
Dynamic paths can also be nested and used at different levels, i.e. /todos/:todoId?/comments/:commentId/hidden/:isHidden
and in all cases, the parameters should be strongly typed and customers should get type hints in their IDE like this:
Query Strings
Within app.currentEvent
property, you can access all available query strings as an object via query
.
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers';
const app = new APIGatewayResolver();
app.get('/search', () => { const query = app.currentEvent.query('q'); // access one at the time
const { limit, offset } = app.currentEvent.query(); // access multiple at once
return { message: 'Hello, World!' }; });
export const handler = async (event, context) => app.resolve(event, context);
Payload
You can access the raw payload via the body
property, or if it's a JSON string you can quickly deserialize it via the json()
method.
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers';
const app = new APIGatewayResolver();
app.post('/todo', async () => { const rawBody = app.currentEvent.body;
const body = await app.currentEvent.json();
return { message: 'Hello, World!' }; });
export const handler = async (event, context) => app.resolve(event, context);
Headers
Similarly to query strings, you can access headers as dictionary via app.currentEvent.headers
. Specifically for headers, it's a case-insensitive dictionary, so all lookups are case-insensitive.
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers';
const app = new APIGatewayResolver();
app.post('/todo', async () => { const apiKey = app.currentEvent.headers.get('X-Api-Key');
return { message: 'Hello, World!' }; });
export const handler = async (event, context) => app.resolve(event, context);
Catch-all routes
You can also create things like catch-all routes, for example the .+ expression allows you to handle an arbitrary number of paths within a request.
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers';
const app = new APIGatewayResolver();
app.get('.+', () => { return { message: 'Hello, World!' }; });
export const handler = async (event, context) => app.resolve(event, context);
HTTP Methods
You can use named methods to specify the HTTP method that should be handled in your functions. That is, app.<http_method>
, where the HTTP method could be get()
, post()
, put()
, patch()
, delete()
, head()
.
If you need to accept multiple HTTP methods in a single function, or support a custom HTTP method for which no method exists (e.g. TRACE
), you can use the route()
method and pass an array of HTTP methods.
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers';
const app = new APIGatewayResolver();
app.route('/todos/', () => {
const data = JSON.parse(app.currentEvent.body || '{}');
const todos = await fetch(https://jsonplaceholder.typicode.com/todos
, {
body: JSON.stringify(data),
});
return { todo: await todos.json(), }; }, { method: ['PUT', 'POST'] });
export const handler = async (event, context) => app.resolve(event, context);
Below you'll find sections about how we handle routes not found and method not allowed, as well as how to customize the default behavior.
Data validation
All resolvers can optionally coerce and validate incoming requests by setting enableValidation: true
.
With this feature, we can now express how we expect our incoming data and response to look like. This moves data validation responsibilities to Event Handler resolvers, reducing a ton of boilerplate code.
You can pass an output schema to signal our resolver what shape you expect your data to be.
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers'; import { z } from 'zod';
const app = new APIGatewayResolver({ enableValidation: true, });
const todoSchema = z.object({ userId: z.number(), title: z.string(), completed: z.boolean() });
app.get('/todo/:todoId', async ({ todoId }) => { const response = await fetch(https://jsonplaceholder.typicode.com/todos/${todoId}`); const todo = await response.json();
return todo; }, { validation: { output: todoSchema } });
Handling validation errors
Any incoming request that fails validation will lead to a HTTP 422: Unprocessable Entity
error response that will look similar to this:
{ "statusCode": 422, "body": "[{"type": "int_parsing", "loc": ["path", "todo_id"]}]}", "isBase64Encoded": false, "headers": { "Content-Type": "application/json" }, "cookies": [] }
You can customize the error message by catching the RequestValidationError
exception. This is useful when you might have a security policy to return opaque validation errors, or have a company standard for API validation errors.
Here's an example where we catch validation errors, log all details for further investigation, and return the same HTTP 422
with an opaque error.
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers'; import { Logger } from '@aws-lambda-powertools/logger'; import { z } from 'zod';
const app = new APIGatewayResolver({ enableValidation: true, }); const logger = new Logger();
app.errorHandler(RequestValidationError, (error) => { logger.error('Request validation failed', { path: app.currentEvent.path, error, }) });
const todoSchema = z.object({ userId: z.number(), title: z.string(), completed: z.boolean() });
app.get('/todo/:todoId', async ({ todoId }) => { const response = await fetch(https://jsonplaceholder.typicode.com/todos/${todoId}`); const todo = await response.json();
return todo; }, { validation: { output: todoSchema } });
Validating payloads
You can do something similar with the incoming payload as well, in this case you can pass a schema to the validation.input
property.
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers'; import { z } from 'zod';
const app = new APIGatewayResolver({ enableValidation: true, });
const todoSchema = z.object({ userId: z.number(), title: z.string(), completed: z.boolean() });
app.post('/todo', async ({ title, userId, completed }) => { // TODO: create todo
return true; }, { validation: { input: todoSchema, output: z.boolean() } });
We will automatically parse and validate the outer envelope of the event according to the resolver you are using, so that you can focus on providing the schema for the body only.
Enabling SwaggerUI
Since Event Handler supports OpenAPI, you can use SwaggerUI to visualize and interact with your API.
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers';
const app = new APIGatewayResolver({ enableValidation: true, });
app.enableSwagger({ path: '/swagger', });
TODO: routes w/ validation
export const handler = async (event, context) => app.resolve(event, context);
We will implement this feature as a complete opt-in feature and with as little overhead as possible. Because of this we will return the HTML needed to render the SwaggerUI but you will have to bundle your own swagger-ui-bundle.min.js together with your function or provide an url to a CDN-hosted version of it (i.e. this).
Accessing request details
Event Handler exposes the resolver request and Lambda context under convenient properties like: app.currentEvent
and app.lambdaContext
, this is why you see app.resolve(event, context)
in every example.
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers'; import type { APIGatewayProxyEvent, Context } from 'aws-lambda';
const app = new APIGatewayResolver();
app.route('/todos/', () => { const event: APIGatewayProxyEvent = app.currentEvent; // event const context: Context = app.lambdaContext; // context
return { todo: await todos.json(), }; });
export const handler = async (event, context) => app.resolve(event, context);
Handling not found routes
By default, we return 404
for any unmatched route.
You can use the notFound()
method or class method decorator to override this behavior, and return a custom response.
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers'; import { Logger } from '@aws-lambda-powertools/logger';
const logger = new Logger(); const app = new APIGatewayResolver();
app.notFound(async (error: Error) => {
logger.info('Not found route', { path: app.currentEvent.path });
return {
statusCode: 302,
headers: {
Location: '/login'
}
}
});
class Lambda {
@app.get('/todos/:todoId')
public async getTodoById({ params }) {
const { todoId } = params;
const todos = await fetch(https://jsonplaceholder.typicode.com/todos/${todoId}
);
return { todos: await todos.json() }
} }
export const handler = async (event, context) => app.resolve(event, context);
Handling methods not allowed
By default, we return 405 for any request that matches a route but is using a method with no resolver.
You can use the methodNotAllowed()
method or class method decorator to override this behavior, and return a custom response.
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers'; import { Logger } from '@aws-lambda-powertools/logger';
const logger = new Logger(); const app = new APIGatewayResolver();
app.methodNotAllowed(async (error: Error) => { logger.info('Method not allowed', { path: app.currentEvent.path, method: app.currentEvent.method, });
return {
statusCode: 403,
}
});
class Lambda {
@app.get('/todos/:todoId')
public async getTodoById({ params }) {
const { todoId } = params;
const todos = await fetch(https://jsonplaceholder.typicode.com/todos/${todoId}
);
return { todos: await todos.json() }
} }
export const handler = async (event, context) => app.resolve(event, context);
Error handling
To keep your route handlers as focused as possible, you can use the errorHandler()
method or class method decorator with any Error
class or children. This allows you to handle common errors outside of your route, for example validation errors.
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers';
import { Logger } from '@aws-lambda-powertools/logger';
const logger = new Logger(); const app = new APIGatewayResolver();
class Lambda { @app.errorHandler(ZodError) public async handleInvalidRequest(error: ZodError) { logger.error('Malformed request', { path: app.currentEvent.path, });
return {
statusCode: 400,
headers: {
'Content-Type': 'text/plain',
},
body: 'Invalid request parameters.'
}
}
@app.get('/todos/:todoId')
public async getTodoById({ params }) {
const { todoId } = params;
const todos = await fetch(https://jsonplaceholder.typicode.com/todos/${todoId}
);
return { todos: await todos.json() }
} }
export const handler = async (event, context) => app.resolve(event, context);
The errorHandler()
method also supports passing a list of error classes you want to handle with a single handler.
Internally, we’ll test the error being thrown using error instanceof YourError
first, and then error.name === YourError.name
, this allows us to ensure equality even when the imports or the bundle might be polluted with double imports.
Raising HTTP errors
You can easily raise any HTTP Error back to the client using one of the handy prebuilt error responses. This ensures your Lambda function doesn’t fail but return the correct HTTP response signalling the error.
We provide pre-defined errors for the most popular ones such as HTTP 400, 401, 404, 500, etc.
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers'; import { BadRequestError, InternalServerError, NotFoundError, ServiceError, UnauthorizedError, } from '@aws-lambda-powertools/event-handler/errors';
const app = new APIGatewayResolver();
app.get('/bad-request-error', () => { throw BadRequestError('Missing required parameter'); // HTTP 400 });
app.get('/unauthorized-error', () => { throw UnauthorizedError('Unauthorized'); // HTTP 401 });
app.get('/not-found-error', () => { throw NotFoundError(); // HTTP 404 });
app.get('/interanal-server-error', () => { throw InternalServerError('Internal server error'); // HTTP 500 });
app.get('/service-error', () => { throw ServiceError('Something went wrong!', { code: 502 }); // HTTP 502 });
export const handler = async (event, context) => app.resolve(event, context);
Custom Domain API Mappings
When using the Custom Domain API Mappings feature in Amazon API Gateway, you must use the stripPrefixes
parameter in the APIGatewayRestResolver
constructor.
Scenario: You have a custom domain api.mydomain.dev
. Then you set a /payment
mapping to forward any payment requests to your Payments API.
Challenge: This means you path value for any API requests will always contain /payment/<actual_request>
, leading to HTTP 404 as Event Handler is trying to match what’s after payment/
. This gets further complicated with an arbitrary level of nesting.
Solution: To address this, we use the stripPrefixes
parameter to remove these prefixes before trying to match the routes you defined in your application.
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers';
const app = new APIGatewayResolver({ stripPrefixes: ['/payment'] });
/**
- Handles this:
- {
- "resource": "/subscriptions/{subscription}",
- "path": "/payment/subscriptions/123",
- "httpMethod": "GET"
- } */ app.get('/subscription/:subscriptionId', async ({ params }) => { return { subscriptionId: params.subscriptionId, }; });
export const handler = async (event, context) => app.resolve(event, context);
After removing a path prefix with stripPrefixes
, the new root path will automatically be mapped to the path argument of /
.
For example. when using the stripPrefixes
value of /pay
, there is no difference between a request path of /pay
and /pay/
; and the path argument would be defined as just /
.
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers';
/**
- This will support:
- /v1/dev/subscriptions/
- /v1/stg/subscriptions/
- /v1/qa/subscriptions/
- /v2/dev/subscriptions/ */ const app = new APIGatewayResolver({ stripPrefixes: [new RegExp(//v[1-3]+/(dev|stg|qa)/)] });
app.get('/subscription/:subscriptionId', async ({ params }) => { return { subscriptionId: params.subscriptionId, }; });
export const handler = async (event, context) => app.resolve(event, context);
Advanced use cases
CORS
You can configure CORS in each resolver constructor via the cors
parameter with a CORSConfig
class.
This will ensure that CORS headers are returned as part of the response when your functions match the path invoked and the Origin
matches one of the allowed values.
Optionally, you can disable CORS on a per path basis with the cors: false
option.
Pre-flight
Pre-flight (OPTIONS
) calls are typically handled by API Gateway or Lambda Function URL. For ALB instead, you are expected to handle these requests yourself.
For convenience, we automatically handle them for you as long as you enable CORS in the constructor.
Defaults
For convenience, these are the default values when using CORSConfig
to enable CORS:
Key | Value | Note |
---|---|---|
allowOrigin | * | Only use the default value for development. Never use * for production unless your use case strictly requires it |
extraOrigins | [] | Additional origins to be allowed, in addition to the one specified in allowOrigin |
allowHeaders | ["Authorization", "Content-Type", "X-Amz-Date", "X-Api-Key", "Amz-Security-Token"] | Additional headers that will be appended to the default list for your convenience |
exposeHeaders | [] | Any additional header beyond the safe ones listed by the CORS specification |
maxAge | `` | Only for pre-flight requests if you choose to have your function handle it instead of API Gateway |
allowCredentials | false | Only necessary when working with cookies, authorization headers, or TLS client certificates |
Compress
You can compress with gzip and base64 encode your responses via the compress
parameter. You have the option to pass the compress
parameter when working with a specific route.
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers';
const app = new APIGatewayResolver();
app.get('/subscription/:subscriptionId', async ({ params }) => { return { subscriptionId: params.subscriptionId, }; }, { compress: true, });
export const handler = async (event, context) => app.resolve(event, context);
The client must send the Accept-Encoding
header, otherwise we will send a normal response.
Binary responses
For convenience, we automatically base64 encode binary responses. You can also use in combination with compress
parameter if your client supports gzip.
Similar to the compress
feature, the client must send the Accept
header with the correct media type.
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers';
const app = new APIGatewayResolver();
app.get('/logo', async () => { TODO: response object });
export const handler = async (event, context) => app.resolve(event, context);
Notes:
- Lambda Function URLs handle binary media types automatically
- Amazon API Gateway does not support the
*/*
binary media type when CORS is also configured
Debug mode
You can enable debug mode via the POWERTOOLS_DEV
or POWERTOOLS_EVENT_HANDLER_DEBUG
environment variables.
This will enable full traceback errors in the response, log any request and response, and set CORS to allow any origin (*
).
Since this might reveal sensitive information in your logs and relax CORS restrictions, we recommend you to use this only for local development and as sparingly as possible.
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers';
const app = new APIGatewayResolver();
app.get('/subscription/:subscriptionId', async ({ params }) => { return { subscriptionId: params.subscriptionId, }; });
export const handler = async (event, context) => app.resolve(event, context);
OpenAPI
When you enable data validation, we use a combination of standard-schema compatible schemas (Zod, Valibot, Arktypes) and OpenAPI type annotations to add constraints to your API's parameters.
In OpenAPI documentation tools like SwaggerUI, these annotations become readable descriptions, offering a self-explanatory API interface. This reduces boilerplate code while improving functionality and enabling auto-documentation.
Customizing OpenAPI parameters
Whenever you use OpenAPI parameters to validate query strings or path parameters, you can enhance validation and OpenAPI documentation by using any of these parameters:
Field name | Type | Description |
---|---|---|
alias | string | Alternative name for a field, used when serializing and deserializing data |
validation_alias | string | Alternative name for a field during validation (but not serialization) |
serialization_alias | string | Alternative name for a field during serialization (but not during validation) |
description | string | Human-readable description |
gt | number | Greater than. If set, value must be greater than this. Only applicable to numbers |
ge | number | Greater than or equal. If set, value must be greater than or equal to this. Only applicable to numbers |
lt | number | Less than. If set, value must be less than this. Only applicable to numbers |
le | number | Less than or equal. If set, value must be less than or equal to this. Only applicable to numbers |
min_length | number | Minimum length for strings |
max_length | number | Maximum length for strings |
pattern | string | A regular expression that the string must match. |
strict | boolean | If true, strict validation is applied to the field. See Strict Mode for details |
multiple_of | number | Value must be a multiple of this. Only applicable to numbers |
allow_inf_nan | boolean | Allow inf, -inf, nan. Only applicable to numbers |
max_digits | number | Maximum number of allow digits for strings |
decimal_places | number | Maximum number of decimal places allowed for numbers |
examples | Array | List of examples of the field |
deprecated | boolean | Marks the field as deprecated |
include_in_schema | boolean | If false the field will not be part of the exported OpenAPI schema |
json_schema_extra | JsonDict | Any additional JSON schema data for the schema property |
Customizing API operations
Customize your API endpoints by adding metadata to endpoint definitions.
Here's a breakdown of various customizable fields:
Field Name | Type | Description |
---|---|---|
summary | string | A concise overview of the main functionality of the endpoint. This brief introduction is usually displayed in autogenerated API documentation and helps consumers quickly understand what the endpoint does. |
description | string | A more detailed explanation of the endpoint, which can include information about the operation's behavior, including side effects, error states, and other operational guidelines. |
responses | Record<number, Record<string, OpenAPIResponse>> | A dictionary that maps each HTTP status code to a Response Object as defined by the OpenAPI Specification. This allows you to describe expected responses, including default or error messages, and their corresponding schemas or models for different status codes. |
response_description | str | Provides the default textual description of the response sent by the endpoint when the operation is successful. It is intended to give a human-readable understanding of the result. |
tags | Array | Tags are a way to categorize and group endpoints within the API documentation. They can help organize the operations by resources or other heuristic. |
operation_id | string | A unique identifier for the operation, which can be used for referencing this operation in documentation or code. This ID must be unique across all operations described in the API. |
include_in_schema | boolean | A boolean value that determines whether or not this operation should be included in the OpenAPI schema. Setting it to False can hide the endpoint from generated documentation and schema exports, which might be useful for private or experimental endpoints. |
deprecated | boolean | A boolean value that determines whether or not this operation should be marked as deprecated in the OpenAPI schema. |
To implement these customizations, include extra parameters when defining your routes:
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers';
const app = new APIGatewayResolver();
app.get('/subscription/:subscriptionId', async ({ params }) => {
return {
subscriptionId: params.subscriptionId,
};
}, {
openApi: {
summary: 'Retrieves a subscription',
description: 'Loads a subscription identified by a subscriptionId
',
responseDescription: 'The subscription object',
responses: {
200: { description: 'subscription found' },
404: { description: 'subscription not found' }
},
tags: ['Subscription']
}
});
export const handler = async (event, context) => app.resolve(event, context);
Split routes with Router
As you grow the number of routes a given Lambda function should handle, it is natural to either break into smaller Lambda functions, or split routes into separate files to ease maintenance - that's where the Router
feature is useful.
Let's assume you have index.ts
as your Lambda function entrypoint and routes in todos.ts
. This is how you'd use the Router feature.
todos.ts
import { Router } from '@aws-lambda-powertools/event-handler';
const todosRouter = new Router();
todosRouter.get('/todos/', () => {
const headers = todosRouter.currentEvent.headers; // currentEvent is available on the router
const todos = await fetch(https://jsonplaceholder.typicode.com/todos/${todoId}
);
return {
todo: await todos.json(),
};
});
export { todosRouter };
index.ts
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers'; import { todosRouter } from './todos.js';
const app = new APIGatewayResolver(); app.includeRouter({ router: todosRouter });
export const handler = async (event, context) => app.resolve(event, context);
Route prefix
When necessary, you can set a prefix when including a router object. This means you could remove /todos
prefix altogether.
todos.ts
import { Router } from '@aws-lambda-powertools/event-handler';
const todosRouter = new Router();
todosRouter.get('/', () => {
const headers = todosRouter.currentEvent.headers; // currentEvent is available on the router
const todos = await fetch(https://jsonplaceholder.typicode.com/todos/${todoId}
);
return {
todo: await todos.json(),
};
});
export { todosRouter };
index.ts
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers'; import { todosRouter } from './todos.js';
const app = new APIGatewayResolver(); app.includeRouter({ router: todosRouter, prefix: '/todos' });
export const handler = async (event, context) => app.resolve(event, context);
Specialized routers
You can use specialized router classes according to the type of event that you are resolving. This way you'll get type hints from your IDE as you access the currentEvent
property.
Router | Resolver | curentEvent type |
---|---|---|
APIGatewayRouter | APIGatewayRestResolver | APIGatewayProxyEvent |
APIGatewayHttpRouter | APIGatewayHttpResolver | APIGatewayProxyEventV2 |
LambdaFunctionUrlRouter | LambdaFunctionUrlResolver | LambdaFunctionUrlEvent |
Sharing contextual data
You can use appendContext
when you want to share data between your App and Router instances. Any data you share will be available via the context object available in your App or Router context.
We always clear data available in context after each invocation.
index.ts
import { APIGatewayResolver } from '@aws-lambda-powertools/event-handler/resolvers'; import { todosRouter } from './todos.js';
const app = new APIGatewayResolver(); app.includeRouter({ router: todosRouter, prefix: '/todos' });
export const handler = async (event, context) => app.appendContext({ isAdmin: true }); app.resolve(event, context);
import { Router } from '@aws-lambda-powertools/event-handler';
const todosRouter = new Router();
todosRouter.get('/', () => {
const { isAdmin } = router.context;
const headers = todosRouter.currentEvent.headers; // currentEvent is available on the router
const todos = await fetch(https://jsonplaceholder.typicode.com/todos/${todoId}
);
return {
todo: await todos.json(),
};
});
export { todosRouter };
Out of scope
The following items are to be considered out of scope for the first iteration of the feature:
- ALBResolver - Resolver for Amazon Application Load Balancer (ALB)
- VPCLatticeResolver - Resolver for Amazon VPC Lattice
- Middy.js compatible middleware
- Middleware engine for routes
- Custom serializers for responses
If you are interested in any of them and would like us to prioritize them right after the first version, please let us know under this RFC.
Potential challenges
TBD
Dependencies and Integrations
No response
Alternative solutions
Acknowledgment
- This RFC meets Powertools for AWS Lambda (TypeScript) Tenets
- Should this be considered in other Powertools for AWS Lambda languages? i.e. Python, Java, and .NET
Future readers
Please react with 👍 and your use case to help us understand customer demand.