Skip to content

Commit 00ef295

Browse files
committed
add middleware docs pt1
1 parent d57a21a commit 00ef295

10 files changed

+381
-12
lines changed

docs/features/event-handler/rest.md

Lines changed: 156 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,166 @@ You can configure CORS at the router level via the `cors` middleware.
162162

163163
### Middleware
164164

165-
// TODO: @svozza
165+
```mermaid
166+
sequenceDiagram
167+
participant Request
168+
participant Router
169+
participant M1 as Middleware 1
170+
participant M2 as Middleware 2
171+
participant Handler as Route Handler
172+
173+
Request->>Router: Incoming Request
174+
Router->>M1: Execute (params, reqCtx, next)
175+
Note over M1: Pre-processing
176+
M1->>M2: Call next()
177+
Note over M2: Pre-processing
178+
M2->>Handler: Call next()
179+
Note over Handler: Execute handler
180+
Handler-->>M2: Return
181+
Note over M2: Post-processing
182+
M2-->>M1: Return
183+
Note over M1: Post-processing
184+
M1-->>Router: Return
185+
Router-->>Request: Response
186+
187+
```
188+
189+
Middleware are functions that execute during the request-response cycle, sitting between the
190+
incoming request and your route handler. They provide a way to implement cross-cutting
191+
concerns like authentication, logging, validation, and response transformation without
192+
cluttering your route handlers.
193+
194+
Each middleware function receives the following arguments:
195+
196+
* **params** Route parameters extracted from the URL path
197+
* **reqCtx** Request context containing the event, Lambda context, request, and response objects
198+
* **next** A function to pass control to the next middleware in the chain
199+
200+
Middleware can be applied globally, only on specific routes, or a combination of both.
201+
202+
#### Global middleware
203+
204+
You can use `app.use` to register middleware that should always run regardless of the route.
205+
206+
=== "index.ts"
207+
208+
```ts hl_lines="8-17"
209+
--8<-- "examples/snippets/event-handler/rest/gettingStarted_global_middleware.ts:3"
210+
```
211+
212+
#### Route specific middleware
213+
214+
You can apply middleware to specific routes by passing them as arguments before the route
215+
handler.
216+
217+
=== "index.ts"
218+
219+
```ts hl_lines="9-18 25"
220+
--8<-- "examples/snippets/event-handler/rest/gettingStarted_route_middleware.ts:3"
221+
```
222+
223+
#### Order of execution
224+
225+
```mermaid
226+
sequenceDiagram
227+
participant Request
228+
participant Router
229+
participant GM as Global Middleware
230+
participant RM as Route Middleware
231+
participant Handler as Route Handler
232+
233+
Request->>Router: Incoming Request
234+
Router->>GM: Execute (params, reqCtx, next)
235+
Note over GM: Pre-processing
236+
GM->>RM: Call next()
237+
Note over RM: Pre-processing
238+
RM->>Handler: Call next()
239+
Note over Handler: Execute handler
240+
Handler-->>RM: Return
241+
Note over RM: Post-processing
242+
RM-->>GM: Return
243+
Note over GM: Post-processing
244+
GM-->>Router: Return
245+
Router-->>Request: Response
246+
247+
```
248+
249+
Middleware follow an onion pattern where global middleware executes first in pre-processing,
250+
then route-specific middleware. After the handler executes, the order reverses for
251+
post-processing. When middleware modify the same response properties, the middleware that
252+
executes last in post-processing wins.
253+
254+
=== "index.ts"
255+
256+
```ts hl_lines="8-11 13-16 23"
257+
--8<-- "examples/snippets/event-handler/rest/gettingStarted_middleware_order.ts:3"
258+
```
259+
260+
=== "JSON Response"
261+
262+
```json hl_lines="5"
263+
--8<-- "examples/snippets/event-handler/rest/samples/gettingStarted_middleware_order.json"
264+
```
265+
266+
#### Returning early
267+
268+
```mermaid
269+
sequenceDiagram
270+
participant Request
271+
participant Router
272+
participant M1 as Middleware 1
273+
participant M2 as Middleware 2
274+
participant M3 as Middleware 3
275+
participant Handler as Route Handler
276+
277+
Request->>Router: Incoming Request
278+
Router->>M1: Execute (params, reqCtx, next)
279+
Note over M1: Pre-processing
280+
M1->>M2: Call next()
281+
Note over M2: Pre-processing
282+
M2->>M2: Return Response (early return)
283+
Note over M2: Post-processing
284+
M2-->>M1: Return Response
285+
Note over M1: Post-processing
286+
M1-->>Router: Return Response
287+
Router-->>Request: Response
288+
Note over M3,Handler: Never executed
289+
290+
```
291+
292+
There are cases where you may want to terminate the execution of the middleware chain early. To
293+
do so, middleware can short-circuit processing by returning a `Response` or JSON object
294+
instead of calling `next()`. Neither the handler nor any subsequent middleware will run
295+
but the post-processing of already executed middleware will.
296+
297+
=== "index.ts"
298+
299+
```ts hl_lines="12-17"
300+
--8<-- "examples/snippets/event-handler/rest/gettingStarted_early_return.ts:3"
301+
```
302+
303+
=== "JSON Response"
304+
305+
```json hl_lines="2"
306+
--8<-- "examples/snippets/event-handler/rest/samples/gettingStarted_early_return.json"
307+
```
166308

167309
### Fine grained responses
168310

169-
You can use the Web API's `Response` object to have full control over the response. For example, you might want to add additional headers, cookies, or set a custom content type.
311+
You can use the Web API's `Response` object to have full control over the response. For
312+
example, you might want to add additional headers, cookies, or set a custom content type.
170313

171-
// TODO: @svozza please add a code example + response sample
314+
=== "index.ts"
315+
316+
```ts hl_lines="9-16 21-26"
317+
--8<-- "examples/snippets/event-handler/rest/gettingStarted_fine_grained_responses.ts:3"
318+
```
319+
320+
=== "JSON Response"
321+
322+
```json hl_lines="4-6"
323+
--8<-- "examples/snippets/event-handler/rest/samples/gettingStarted_fine_grained_responses.json"
324+
```
172325

173326
### Response streaming
174327

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
declare function getAllTodos(): Promise<{ id: string; title: string }[]>;
2+
3+
import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
4+
import type { Middleware } from '@aws-lambda-powertools/event-handler/types';
5+
import { Logger } from '@aws-lambda-powertools/logger';
6+
import type { Context } from 'aws-lambda';
7+
8+
const logger = new Logger();
9+
const app = new Router({ logger });
10+
11+
// Authentication middleware - returns early if no auth header
12+
const authMiddleware: Middleware = async (params, reqCtx, next) => {
13+
const authHeader = reqCtx.request.headers.get('authorization');
14+
15+
if (!authHeader) {
16+
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
17+
status: 401,
18+
headers: { 'Content-Type': 'application/json' },
19+
});
20+
}
21+
22+
await next();
23+
};
24+
25+
// Logging middleware - never executes when auth fails
26+
const loggingMiddleware: Middleware = async (params, reqCtx, next) => {
27+
logger.info('Request processed');
28+
await next();
29+
};
30+
31+
app.use(authMiddleware);
32+
app.use(loggingMiddleware);
33+
34+
app.get('/todos', async () => {
35+
const todos = await getAllTodos();
36+
return { todos };
37+
});
38+
39+
export const handler = async (event: unknown, context: Context) => {
40+
return app.resolve(event, context);
41+
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
declare function getAllTodos(): Promise<{ id: string; title: string }[]>;
2+
declare function createTodo(
3+
title: string
4+
): Promise<{ id: string; title: string }>;
5+
6+
import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
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+
app.get('/todos', async () => {
14+
const todos = await getAllTodos();
15+
16+
return new Response(JSON.stringify({ todos }), {
17+
status: 200,
18+
headers: {
19+
'Content-Type': 'application/json',
20+
'Cache-Control': 'max-age=300',
21+
'X-Custom-Header': 'custom-value',
22+
},
23+
});
24+
});
25+
26+
app.post('/todos', async (params, reqCtx) => {
27+
const body = await reqCtx.request.json();
28+
const todo = await createTodo(body.title);
29+
30+
return new Response(JSON.stringify(todo), {
31+
status: 201,
32+
headers: {
33+
Location: `/todos/${todo.id}`,
34+
'Content-Type': 'application/json',
35+
},
36+
});
37+
});
38+
39+
export const handler = async (event: unknown, context: Context) => {
40+
return app.resolve(event, context);
41+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
declare function getAllTodos(): Promise<{ id: string; title: string }[]>;
2+
declare function putTodo<T>(todo: unknown): Promise<{ id: string } & T>;
3+
4+
import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
5+
import { Logger } from '@aws-lambda-powertools/logger';
6+
import type { Context } from 'aws-lambda';
7+
8+
const logger = new Logger();
9+
const app = new Router({ logger });
10+
11+
// Logging middleware - runs for all routes
12+
app.use(async (params, reqCtx, next) => {
13+
const start = Date.now();
14+
await next();
15+
const duration = Date.now() - start;
16+
logger.info('Request completed', {
17+
path: reqCtx.request.url,
18+
duration,
19+
});
20+
});
21+
22+
app.get('/todos', async () => {
23+
const todos = await getAllTodos();
24+
return { todos };
25+
});
26+
27+
app.post('/todos', async (params, { request }) => {
28+
const body = await request.json();
29+
const todo = await putTodo(body);
30+
return todo;
31+
});
32+
33+
export const handler = async (event: unknown, context: Context) => {
34+
return app.resolve(event, context);
35+
};
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
declare function getAllTodos(): Promise<{ id: string; title: string }[]>;
2+
declare function putTodo<T>(todo: unknown): Promise<{ id: string } & T>;
3+
4+
import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
5+
import type { Middleware } from '@aws-lambda-powertools/event-handler/types';
6+
import { Logger } from '@aws-lambda-powertools/logger';
7+
import type { Context } from 'aws-lambda';
8+
9+
const logger = new Logger();
10+
const app = new Router({ logger });
11+
12+
// Global middleware - executes first in pre-processing, last in post-processing
13+
app.use(async (params, reqCtx, next) => {
14+
reqCtx.res.headers.set('x-pre-processed-by', 'global-middleware');
15+
await next();
16+
reqCtx.res.headers.set('x-post-processed-by', 'global-middleware');
17+
});
18+
19+
// Route-specific middleware - executes second in pre-processing, first in post-processing
20+
const routeMiddleware: Middleware = async (params, reqCtx, next) => {
21+
reqCtx.res.headers.set('x-pre-processed-by', 'route-middleware');
22+
await next();
23+
reqCtx.res.headers.set('x-post-processed-by', 'route-middleware');
24+
};
25+
26+
app.get('/todos', async () => {
27+
const todos = await getAllTodos();
28+
return { todos };
29+
});
30+
31+
// This route will have:
32+
// x-pre-processed-by: route-middleware (route middleware overwrites global)
33+
// x-post-processed-by: global-middleware (global middleware executes last)
34+
app.post('/todos', [routeMiddleware], async (params, reqCtx) => {
35+
const body = await reqCtx.request.json();
36+
const todo = await putTodo(body);
37+
return todo;
38+
});
39+
40+
export const handler = async (event: unknown, context: Context) => {
41+
return app.resolve(event, context);
42+
};
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
declare function getAllTodos(): Promise<{ id: string; title: string }[]>;
2+
declare function putTodo<T>(todo: unknown): Promise<{ id: string } & T>;
3+
4+
import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
5+
import type { Middleware } from '@aws-lambda-powertools/event-handler/types';
6+
import { Logger } from '@aws-lambda-powertools/logger';
7+
import type { Context } from 'aws-lambda';
8+
9+
const logger = new Logger();
10+
const app = new Router({ logger });
11+
12+
// Add timestamp to todo items
13+
const addTimestamp: Middleware = async (params, reqCtx, next) => {
14+
const body = await reqCtx.request.json();
15+
body.createdAt = new Date().toISOString();
16+
reqCtx.request = new Request(reqCtx.request.url, {
17+
...reqCtx.request,
18+
body: JSON.stringify(body),
19+
});
20+
await next();
21+
};
22+
23+
app.get('/todos', async () => {
24+
const todos = await getAllTodos();
25+
return { todos };
26+
});
27+
28+
// Route with timestamp middleware
29+
app.post('/todos', [addTimestamp], async (params, reqCtx) => {
30+
const body = await reqCtx.request.json();
31+
const todo = await putTodo(body);
32+
return todo;
33+
});
34+
35+
export const handler = async (event: unknown, context: Context) => {
36+
return app.resolve(event, context);
37+
};
Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
11
import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
2-
import type {
3-
APIGatewayProxyEvent,
4-
APIGatewayProxyResult,
5-
Context,
6-
} from 'aws-lambda';
2+
import type { APIGatewayProxyResult, Context } from 'aws-lambda';
73

84
const app = new Router();
95

106
app.get('/ping', async () => {
117
return { message: 'pong' }; // (1)!
128
});
139

14-
export const handler = async (
15-
event: APIGatewayProxyEvent,
16-
context: Context
17-
): Promise<APIGatewayProxyResult> => {
10+
export const handler = async (event: unknown, context: Context) => {
1811
return app.resolve(event, context);
1912
};

0 commit comments

Comments
 (0)