Skip to content

Feature request: Split routes with Router - includeRouter method #4481

@dreamorosi

Description

@dreamorosi

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

Future readers

Please react with 👍 and your use case to help us understand customer demand.

Metadata

Metadata

Assignees

Labels

discussingThe issue needs to be discussed, elaborated, or refinedevent-handlerThis item relates to the Event Handler Utilityfeature-requestThis item refers to a feature request for an existing or new utility

Type

No type

Projects

Status

Backlog

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions