-
Notifications
You must be signed in to change notification settings - Fork 174
Description
Use case
As applications grow and the number of routes a Lambda function handles increases, it becomes natural to either break into smaller Lambda functions or split routes into separate files to ease maintenance.
Currently, the TypeScript event-handler's Router
class doesn't provide a way to compose multiple router instances, forcing developers to define all routes in a single file or manually merge route definitions.
This creates several challenges:
- Maintainability: Large route files become difficult to manage and navigate
- Team collaboration: Multiple developers working on different route groups can create merge conflicts
- Code organization: Related routes cannot be logically grouped in separate modules
- Reusability: Common route patterns cannot be easily shared across different Lambda functions
Solution/User Experience
Implement an includeRouter
method on the Router
class that allows merging routes, context, and middleware from one router instance into another. This would mirror the functionality available in the Python version of Powertools for AWS.
Basic usage
routes/todos.ts (separate route module):
import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
export const router = new Router();
const endpoint = 'https://jsonplaceholder.typicode.com/todos';
router.get('/todos', async (_, { request }) => {
const apiKey = request.headers['x-api-key'];
const response = await fetch(endpoint, {
headers: { 'X-Api-Key': apiKey }
});
const todos = await response.json();
return { todos: todos.slice(0, 10) };
});
router.get('/todos/{todoId}', async ({ todoId }, { request }) => {
const apiKey = request.headers['x-api-key'];
const response = await fetch(`${endpoint}/${encodeURIComponent(todoId)}`, {
headers: { 'X-Api-Key': apiKey }
});
const todo = await response.json();
return { todo };
});
handler.ts (main Lambda handler):
import { router as todosRouter } from './routes/todos.js';
import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
import { Logger } from '@aws-lambda-powertools/logger';
import type { Context } from 'aws-lambda';
const logger = new Logger();
const app = new Router({ logger });
// Include routes from separate module
app.includeRouter(todosRouter);
export const handler = async (event: unknown, context: Context) => {
logger.addContext(context);
return app.resolve(event, context);
};
Route prefix support
Allow prefixing routes when including a router to avoid repetitive path definitions:
// Remove '/todos' prefix from route definitions
router.get('/', async (_, { request }) => { /* get all todos */ });
router.get('/{todoId}', async ({ todoId }, { request }) => { /* get specific todo */ });
// Include with prefix
app.includeRouter(todosRouter, { prefix: '/todos' });
Context sharing with appendContext
Support sharing contextual data between router instances:
export const handler = async (event: unknown, context: Context) => {
app.appendContext({ isAdmin: true, userId: 'user123' });
return app.resolve(event, context);
};
Routes in included routers can access this context:
router.get('/todos', async (_, { request, reqCtx }) => {
const isAdmin = context.get('isAdmin', false);
// Use context in route logic
});
Method signature
class Router {
includeRouter(router: Router, options?: { prefix?: string }): void;
appendContext(data: Record<string, unknown>): void;
}
Alternative solutions
N/A
Open questions
The appendContext
method allows adding arbitrary key-value pairs to the context.
This is not the Lambda function's context
object but a shared request-specific context - hence the suggested reqCtx
name.
Is this clear enough, or should we consider a different name? Should we also rename the context
property currently used in the RequestContext
to something like lambdaContext
to avoid confusion?
Should we instead add the context directly to the RequestContext
object, making it accessible in route handlers as:
router.get('/todos', async (_, { isAdmin }) => {});
And if we do this, how do we handle potential naming conflicts with existing properties?
Also, I want to make sure this context is future proof in case as I want to make it easier to surface the context from Lambda Authorizers that is currently buried in the request.requestContext.authorizer
property.
Acknowledgment
- This feature request 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.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status