Skip to content

Commit dd9b722

Browse files
authored
docs(event-handler): updated docs for split router, catch all routes and error handler (#4779)
1 parent d548f80 commit dd9b722

File tree

7 files changed

+143
-19
lines changed

7 files changed

+143
-19
lines changed

docs/features/event-handler/rest.md

Lines changed: 76 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,34 @@ All dynamic route parameters will be available as typed object properties in the
9191

9292
You can also nest dynamic paths, for example `/todos/:todoId/comments/:commentId`, where both `:todoId` and `:commentId` will be resolved at runtime.
9393

94+
#### Catch-all routes
95+
96+
For scenarios where you need to handle arbitrary or deeply nested paths, you can use regex patterns directly in your route definitions. These are particularly useful for proxy routes or when dealing with file paths.
97+
98+
**We recommend** having explicit routes whenever possible; use catch-all routes sparingly.
99+
100+
##### Using Regex Patterns
101+
102+
You can use standard [regular expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions){target="_blank" rel="nofollow"} in your route definitions, for example:
103+
104+
| Pattern | Description | Examples |
105+
|-----------|------------------------------------------|-------------------------------------------------------------|
106+
| `/.+/` | Matches one or more characters (greedy) | `/\/proxy\/.+/` matches `/proxy/any/deep/path` |
107+
| `/.*/` | Matches zero or more characters (greedy) | `/\/files\/.*/` matches `/files/` and `/files/deep/path` |
108+
| `/[^/]+/` | Matches one or more non-slash characters | `/\/api\/[^\/]+/` matches `/api/v1` but not `/api/v1/users` |
109+
| `/\w+/` | Matches one or more word characters | `/\/users\/\w+/` matches `/users/john123` |
110+
111+
=== "gettingStarted_dynamic_routes_catch_all.ts"
112+
113+
```python hl_lines="7 10 13 20"
114+
--8<-- "examples/snippets/event-handler/rest/gettingStarted_dynamic_routes_catch_all.ts"
115+
```
116+
117+
???+ warning "Route Matching Priority"
118+
- For non-regex routes, routes are matched in **order of specificity**, not registration order
119+
- More specific routes (exact matches) take precedence over regex patterns
120+
- Among regex routes, registration order determines matching precedence, therefore, always place catch-all routes `/.*/` last
121+
94122
### HTTP Methods
95123

96124
You can use dedicated methods to specify the HTTP method that should be handled in each resolver. That is, `app.<httpMethod>()`, where the HTTP method could be `delete`, `get`, `head`, `patch`, `post`, `put`, `options`.
@@ -130,18 +158,6 @@ Please [check this issue](https://github.com/aws-powertools/powertools-lambda-ty
130158

131159
You can access request details such as headers, query parameters, and body using the `Request` object provided to your route handlers and middleware functions via `reqCtx.req`.
132160

133-
### Handling not found routes
134-
135-
By default, we return a `404 Not Found` response for any unmatched routes.
136-
137-
You can use the `notFound()` method as a higher-order function or class method decorator to override this behavior, and return a custom response.
138-
139-
=== "index.ts"
140-
141-
```ts hl_lines="11"
142-
--8<-- "examples/snippets/event-handler/rest/gettingStarted_handle_not_found.ts"
143-
```
144-
145161
### Error handling
146162

147163
You can use the `errorHandler()` method as a higher-order function or class method decorator to define a custom error handler for errors thrown in your route handlers or middleware.
@@ -158,6 +174,20 @@ Error handlers receive the error object and the request context as arguments, an
158174
--8<-- "examples/snippets/event-handler/rest/gettingStarted_error_handling.ts:4"
159175
```
160176

177+
### Built-in Error Handlers
178+
179+
We provide built-in error handlers for common routing errors so you don't have to specify the Error type explicitly.
180+
181+
You can use the `notFound()` and `methodNotAllowed()` methods as higher-order functions or class method decorators to customize error responses for unmatched routes and unsupported HTTP methods.
182+
183+
By default, we return a `404 Not Found` response for unmatched routes.
184+
185+
=== "index.ts"
186+
187+
```ts hl_lines="11 23"
188+
--8<-- "examples/snippets/event-handler/rest/gettingStarted_built_in_error_handler.ts"
189+
```
190+
161191
### Throwing HTTP errors
162192

163193
You can throw HTTP errors in your route handlers to stop execution and return specific HTTP status codes and messages. Event Handler provides a set of built-in HTTP error classes that you can use to throw common HTTP errors.
@@ -439,11 +469,10 @@ For convenience, these are the default CORS settings applied when you register t
439469
| Key | Default Value | Description |
440470
| --------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
441471
| `origin` | `*` | Specifies the allowed origin(s) that can access the resource. Use `*` to allow all origins. |
442-
| `methods` | `GET,HEAD,PUT,PATCH,POST,DELETE` | Specifies the allowed HTTP methods. |
472+
| `methods` | `['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT']` | Specifies the allowed HTTP methods. |
443473
| `allowHeaders` | `[Authorization, Content-Type, X-Amz-Date, X-Api-Key, X-Amz-Security-Token]` | Specifies the allowed headers that can be used in the actual request. |
444474
| `exposeHeaders` | `[]` | Any additional header beyond the [safe listed by CORS specification](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_response_header){target="_blank"}. |
445475
| `credentials` | `false` | Only necessary when you need to expose cookies, authorization headers or TLS client certificates. |
446-
| `maxAge` | `0` | Indicates how long the results of a preflight request can be cached. Value is in seconds. |
447476

448477
#### Per-route overrides
449478

@@ -565,15 +594,43 @@ Please [check this issue](https://github.com/aws-powertools/powertools-lambda-ty
565594

566595
### Split routers
567596

568-
!!! note "Coming soon"
569-
570597
As applications grow and the number of routes a Lambda function handles increases, it becomes natural to either break it into smaller Lambda functions or split routes into separate files to ease maintenance.
571598

572-
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.
599+
The `Router` class provide an `includeRouter` method to compose multiple router instances allowing developers to define routes in multiple files and merge route definitions. You will be able to define routes in separate files and import them into a main router file, improving code organization and maintainability.
600+
601+
!!! note "Merging with Global Middleware"
602+
When merging two `Router` instances together, if you have a global middleware defined in one of your instances, the global middleware gets applied to the all the merged routes.
603+
604+
Let's assume you have `index.ts` as your Lambda function entrypoint and routes in `split_route.ts`. This is how you'd use the `includeRouter` feature.
605+
606+
=== "split_route.ts"
607+
608+
```typescript
609+
--8<-- "examples/snippets/event-handler/rest/split_route.ts"
610+
```
611+
612+
=== "index.ts"
613+
614+
```typescript hl_lines="8"
615+
--8<-- "examples/snippets/event-handler/rest/split_route_index.ts"
616+
```
617+
618+
#### Route Prefix
619+
620+
In the previous example, `split_route.ts` routes had a `/todos` prefix. This might grow over time and become repetitive.
573621

574-
Once this feature is available, you will be able to define routes in separate files and import them into a main router file, improving code organization and maintainability.
622+
When necessary, you can set a prefix when including a `Router` instance. This means you can remove `/todos` prefix altogether.
575623

576-
Please [check this issue](https://github.com/aws-powertools/powertools-lambda-typescript/issues/4481) for more details and examples, and add 👍 if you would like us to prioritize it.
624+
=== "split_route_prefix.ts"
625+
626+
```typescript
627+
--8<-- "examples/snippets/event-handler/rest/split_route_prefix.ts"
628+
```
629+
=== "index.ts"
630+
631+
```typescript hl_lines="8"
632+
--8<-- "examples/snippets/event-handler/rest/split_route_prefix_index.ts"
633+
```
577634

578635
### Considerations
579636

examples/snippets/event-handler/rest/gettingStarted_handle_not_found.ts renamed to examples/snippets/event-handler/rest/gettingStarted_built_in_error_handler.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,13 @@ app.notFound(async (error, reqCtx) => {
2020
};
2121
});
2222

23+
app.methodNotAllowed(async (error) => {
24+
logger.error('Method not allowed', { error });
25+
26+
return {
27+
body: 'This method is not allowed',
28+
};
29+
});
30+
2331
export const handler = async (event: unknown, context: Context) =>
2432
app.resolve(event, context);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
2+
import type { APIGatewayProxyEvent, Context } from 'aws-lambda';
3+
4+
const app = new Router();
5+
6+
// File path proxy
7+
app.get(/\/files\/.+/, () => 'Catch any GET method under /files');
8+
9+
// API versioning with any format
10+
app.get(/\/api\/v\d+\/.*/, () => 'Catch any GET method under /api/vX');
11+
12+
// Mixed: dynamic parameter + regex catch-all
13+
app.get(/\/users\/:userId\/files\/.+/, (reqCtx) => {
14+
return {
15+
userId: reqCtx.params.userId,
16+
};
17+
});
18+
19+
// Catch all route
20+
app.get(/.+/, () => 'Catch any GET method');
21+
22+
export const handler = async (event: APIGatewayProxyEvent, context: Context) =>
23+
app.resolve(event, context);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
2+
3+
const router = new Router();
4+
router.get('/todos', () => 'Get all todos');
5+
router.get('/todos/:id', () => 'Get a single todo item');
6+
7+
export { router };
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
2+
import type { APIGatewayProxyEvent, Context } from 'aws-lambda';
3+
import { router } from './split_route';
4+
5+
const app = new Router();
6+
7+
// Split Routers
8+
app.includeRouter(router);
9+
10+
export const handler = async (event: APIGatewayProxyEvent, context: Context) =>
11+
app.resolve(event, context);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
2+
3+
const router = new Router();
4+
router.get('/', () => 'Get all todos');
5+
router.get('/:id', () => 'Get a single todo item');
6+
7+
export { router };
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
2+
import type { APIGatewayProxyEvent, Context } from 'aws-lambda';
3+
import { router } from './split_route';
4+
5+
const app = new Router();
6+
7+
// Split Routers
8+
app.includeRouter(router, { prefix: '/todos' });
9+
10+
export const handler = async (event: APIGatewayProxyEvent, context: Context) =>
11+
app.resolve(event, context);

0 commit comments

Comments
 (0)