Skip to content

Commit b4acd18

Browse files
matthew2564svozza
andauthored
docs(event-handler): add http router decorator examples (#4950)
Co-authored-by: Stefano Vozza <[email protected]>
1 parent af4e846 commit b4acd18

5 files changed

Lines changed: 218 additions & 1 deletion

File tree

docs/features/event-handler/http.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,19 @@ If you need to accept multiple HTTP methods in a single function, or support an
167167
!!! tip
168168
We recommend defining separate route handlers for each HTTP method within your Lambda function, as the functionality typically differs between operations such as `GET`, `POST`, `PUT`, `DELETE` etc
169169

170+
#### Using decorators
171+
172+
If you prefer to use the decorator syntax, you can use the same methods on a class method to register your route handlers. Learn more about how Powertools for TypeScript supports [decorators](../../getting-started/usage-patterns.md).
173+
174+
=== "Decorator syntax"
175+
176+
```ts hl_lines="10 21 32"
177+
--8<-- "examples/snippets/event-handler/http/gettingStarted_decorators_basic.ts"
178+
```
179+
180+
!!! tip
181+
We recommend passing a reference to `this` to ensure the correct class scope is propagated to the route handler functions.
182+
170183
### Data validation
171184

172185
!!! note "Coming soon"
@@ -195,6 +208,12 @@ Error handlers receive the error object and the request context as arguments, an
195208
--8<-- "examples/snippets/event-handler/http/gettingStarted_error_handling.ts:4"
196209
```
197210

211+
=== "Using decorators"
212+
213+
```ts hl_lines="16"
214+
--8<-- "examples/snippets/event-handler/http/gettingStarted_error_handling_decorators.ts"
215+
```
216+
198217
### Built-in Error Handlers
199218

200219
We provide built-in error handlers for common routing errors so you don't have to specify the Error type explicitly.
@@ -209,6 +228,12 @@ By default, we return a `404 Not Found` response for unmatched routes.
209228
--8<-- "examples/snippets/event-handler/http/gettingStarted_built_in_error_handler.ts"
210229
```
211230

231+
=== "Using decorators"
232+
233+
```ts hl_lines="15 31"
234+
--8<-- "examples/snippets/event-handler/http/gettingStarted_built_in_error_handler_decorators.ts"
235+
```
236+
212237
### Throwing HTTP errors
213238

214239
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.
@@ -295,14 +320,20 @@ executes last in post-processing wins.
295320

296321
You can use `app.use()` to register middleware that should always run regardless of the route
297322
and you can apply middleware to specific routes by passing them as arguments before the route
298-
handler.
323+
handler or as a second argument to the route decorator.
299324

300325
=== "index.ts"
301326

302327
```ts hl_lines="9-14 16-21 31"
303328
--8<-- "examples/snippets/event-handler/http/advanced_mw_middleware_order.ts:3"
304329
```
305330

331+
=== "Using decorators"
332+
333+
```ts hl_lines="18-22 34"
334+
--8<-- "examples/snippets/event-handler/http/advanced_mw_middleware_order_decorators.ts:3"
335+
```
336+
306337
=== "Response"
307338

308339
```json hl_lines="6-7"
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
declare function getAllTodos(): Promise<{ id: string; title: string }[]>;
2+
declare function putTodo<T>(todo: unknown): Promise<{ id: string } & T>;
3+
4+
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
5+
import { Router } from '@aws-lambda-powertools/event-handler/http';
6+
import type { Middleware } from '@aws-lambda-powertools/event-handler/types';
7+
import { Logger } from '@aws-lambda-powertools/logger';
8+
import type { Context } from 'aws-lambda';
9+
10+
const logger = new Logger();
11+
const app = new Router({ logger });
12+
13+
// Global middleware - executes first in pre-processing, last in post-processing
14+
app.use(async ({ reqCtx, next }) => {
15+
reqCtx.res.headers.set('x-pre-processed-by', 'global-middleware');
16+
await next();
17+
reqCtx.res.headers.set('x-post-processed-by', 'global-middleware');
18+
});
19+
20+
// Route-specific middleware - executes second in pre-processing, first in post-processing
21+
const routeMiddleware: Middleware = async ({ reqCtx, next }) => {
22+
reqCtx.res.headers.set('x-pre-processed-by', 'route-middleware');
23+
await next();
24+
reqCtx.res.headers.set('x-post-processed-by', 'route-middleware');
25+
};
26+
27+
class Lambda implements LambdaInterface {
28+
@app.get('/todos')
29+
public async getTodos() {
30+
const todos = await getAllTodos();
31+
return { todos };
32+
}
33+
34+
// This route will have:
35+
// x-pre-processed-by: route-middleware (route middleware overwrites global)
36+
// x-post-processed-by: global-middleware (global middleware executes last)
37+
@app.post('/todos', [routeMiddleware])
38+
public async createTodo({ req }: { req: Request }) {
39+
const body = await req.json();
40+
const todo = await putTodo(body);
41+
return todo;
42+
}
43+
44+
async handler(event: unknown, context: Context) {
45+
return app.resolve(event, context, { scope: this });
46+
}
47+
}
48+
49+
const lambda = new Lambda();
50+
export const handler = lambda.handler.bind(lambda);
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
2+
import {
3+
HttpStatusCodes,
4+
type MethodNotAllowedError,
5+
type NotFoundError,
6+
Router,
7+
} from '@aws-lambda-powertools/event-handler/http';
8+
import { Logger } from '@aws-lambda-powertools/logger';
9+
import type { Context } from 'aws-lambda';
10+
11+
const logger = new Logger();
12+
const app = new Router({ logger });
13+
14+
class Lambda implements LambdaInterface {
15+
@app.notFound()
16+
public async handleNotFound(
17+
error: NotFoundError,
18+
reqCtx: { req: { headers: { get: (key: string) => string | null } } }
19+
) {
20+
logger.error('Unable to get todo', { error });
21+
22+
return {
23+
statusCode: HttpStatusCodes.IM_A_TEAPOT,
24+
body: "I'm a teapot!",
25+
headers: {
26+
'x-correlation-id': reqCtx.req.headers.get('x-correlation-id'),
27+
},
28+
};
29+
}
30+
31+
@app.methodNotAllowed()
32+
public async handleMethodNotAllowed(error: MethodNotAllowedError) {
33+
logger.error('Method not allowed', { error });
34+
35+
return {
36+
body: 'This method is not allowed',
37+
};
38+
}
39+
40+
async handler(event: unknown, context: Context) {
41+
return app.resolve(event, context, { scope: this });
42+
}
43+
}
44+
45+
const lambda = new Lambda();
46+
export const handler = lambda.handler.bind(lambda);
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
2+
import { Router } from '@aws-lambda-powertools/event-handler/http';
3+
import { Logger } from '@aws-lambda-powertools/logger';
4+
import type { Context } from 'aws-lambda';
5+
6+
const logger = new Logger();
7+
const app = new Router({ logger });
8+
9+
class Lambda implements LambdaInterface {
10+
@app.get('/todos/:todoId')
11+
public async getTodo({ params }: { params: { todoId: string } }) {
12+
logger.debug('Getting todo', { todoId: params.todoId });
13+
14+
return {
15+
id: params.todoId,
16+
title: 'Todo Title',
17+
completed: false,
18+
};
19+
}
20+
21+
@app.post('/todos')
22+
public async createTodo({ body }: { body: { title: string } }) {
23+
logger.debug('Creating todo', { title: body.title });
24+
25+
return {
26+
id: 'new-todo-id',
27+
title: body.title,
28+
completed: false,
29+
};
30+
}
31+
32+
@app.delete('/todos/:todoId')
33+
public async deleteTodo({ params }: { params: { todoId: string } }) {
34+
logger.debug('Deleting todo', { todoId: params.todoId });
35+
36+
return {
37+
message: `Todo ${params.todoId} deleted`,
38+
};
39+
}
40+
41+
async handler(event: unknown, context: Context) {
42+
return app.resolve(event, context, { scope: this });
43+
}
44+
}
45+
46+
const lambda = new Lambda();
47+
export const handler = lambda.handler.bind(lambda);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
2+
import {
3+
HttpStatusCodes,
4+
Router,
5+
} from '@aws-lambda-powertools/event-handler/http';
6+
import { Logger } from '@aws-lambda-powertools/logger';
7+
import type { Context } from 'aws-lambda';
8+
9+
declare function getTodoById<T>(todoId: unknown): Promise<{ id: string } & T>;
10+
declare class GetTodoError extends Error {}
11+
12+
const logger = new Logger();
13+
const app = new Router({ logger });
14+
15+
class Lambda implements LambdaInterface {
16+
@app.errorHandler(GetTodoError)
17+
public async handleGetTodoError(
18+
error: GetTodoError,
19+
reqCtx: {
20+
req: { headers: { get: (key: string) => string | null } };
21+
}
22+
) {
23+
logger.error('Unable to get todo', { error });
24+
25+
return {
26+
statusCode: HttpStatusCodes.BAD_REQUEST,
27+
message: `Bad request: ${error.message} - ${reqCtx.req.headers.get('x-correlation-id')}`,
28+
};
29+
}
30+
31+
@app.get('/todos/:todoId')
32+
public async getTodo({ params }: { params: { todoId: string } }) {
33+
const todo = await getTodoById(params.todoId); // May throw GetTodoError
34+
return { todo };
35+
}
36+
37+
async handler(event: unknown, context: Context) {
38+
return app.resolve(event, context, { scope: this });
39+
}
40+
}
41+
42+
const lambda = new Lambda();
43+
export const handler = lambda.handler.bind(lambda);

0 commit comments

Comments
 (0)