Skip to content

Commit b8bedc7

Browse files
committed
add custom middleware section
1 parent 4e66712 commit b8bedc7

File tree

3 files changed

+109
-0
lines changed

3 files changed

+109
-0
lines changed

docs/features/event-handler/rest.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,36 @@ middleware. Event handler provides many [built-in HTTP errors](#throwing-http-er
397397
you can use or you can throw a custom error of your own. As noted above, this means
398398
that no post-processing of your request will occur.
399399

400+
#### Custom middleware
401+
402+
A common pattern to create reusable middleware is to implement a factory functions that
403+
accepts configuration options and returns a middleware function.
404+
405+
=== "index.ts"
406+
407+
```ts hl_lines="8-30 35 38"
408+
--8<-- "examples/snippets/event-handler/rest/advanced_mw_custom_middleware.ts:3"
409+
```
410+
411+
In this example we have a middleware that acts only in the post-processing stage as all
412+
the logic occurs after the `next` function has been called. This is so as to ensure that
413+
the handler has run and we have access to request body.
414+
415+
#### Avoiding destructuring pitfalls
416+
417+
!!! warning "Critical: Never destructure the response object"
418+
When writing middleware, always access the response through `reqCtx.res` rather than destructuring `{ res }` from the request context. Destructuring captures a reference to the original response object, which becomes stale when middleware replaces the response.
419+
420+
=== "index.ts"
421+
422+
```ts hl_lines="8 15"
423+
--8<-- "examples/snippets/event-handler/rest/advanced_mw_destructuring_problem.ts:3"
424+
```
425+
426+
During the middleware execution chain, the response object (`reqCtx.res`) can be replaced by
427+
other middleware or the route handler. When you destructure the request context, you capture
428+
a reference to the response object as it existed at that moment, not the current response.
429+
400430
### Fine grained responses
401431

402432
You can use the Web API's `Response` object to have full control over the response. For
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
declare const compresssBody: (body: string) => Promise<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 type { Context } from 'aws-lambda';
6+
7+
interface CompressOptions {
8+
threshold?: number;
9+
level?: number;
10+
}
11+
12+
// Factory function that returns middleware
13+
const compress = (options: CompressOptions = {}): Middleware => {
14+
return async (params, reqCtx, next) => {
15+
await next();
16+
17+
// Check if response should be compressed
18+
const body = await reqCtx.res.text();
19+
const threshold = options.threshold || 1024;
20+
21+
if (body.length > threshold) {
22+
const compressedBody = await compresssBody(body);
23+
const compressedRes = new Response(compressedBody, reqCtx.res);
24+
compressedRes.headers.set('Content-Encoding', 'gzip');
25+
reqCtx.res = compressedRes;
26+
}
27+
};
28+
};
29+
30+
const app = new Router();
31+
32+
// Use custom middleware globally
33+
app.use(compress({ threshold: 500 }));
34+
35+
app.get('/data', async () => {
36+
return {
37+
message: 'Large response data',
38+
data: new Array(100).fill('content'),
39+
};
40+
});
41+
42+
app.get('/small', async () => {
43+
return { message: 'Small response' };
44+
});
45+
46+
export const handler = async (event: unknown, context: Context) => {
47+
return await app.resolve(event, context);
48+
};
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
2+
import type { Middleware } from '@aws-lambda-powertools/event-handler/types';
3+
import type { Context } from 'aws-lambda';
4+
5+
const app = new Router();
6+
7+
// ❌ WRONG: Using destructuring captures a reference to the original response
8+
const badMiddleware: Middleware = async (params, { res }, next) => {
9+
res.headers.set('X-Before', 'Before');
10+
await next();
11+
// This header will NOT be added because 'res' is a stale reference
12+
res.headers.set('X-After', 'After');
13+
};
14+
15+
// ✅ CORRECT: Always access response through reqCtx
16+
const goodMiddleware: Middleware = async (params, reqCtx, next) => {
17+
reqCtx.res.headers.set('X-Before', 'Before');
18+
await next();
19+
// This header WILL be added because we get the current response
20+
reqCtx.res.headers.set('X-After', 'After');
21+
};
22+
23+
app.use(goodMiddleware);
24+
25+
app.get('/test', async () => {
26+
return { message: 'Hello World!' };
27+
});
28+
29+
export const handler = async (event: unknown, context: Context) => {
30+
return await app.resolve(event, context);
31+
};

0 commit comments

Comments
 (0)