Skip to content

Commit 2b35573

Browse files
authored
Merge branch 'main' into bug/cors-behaviour
2 parents 49cb123 + b0b43e8 commit 2b35573

35 files changed

+842
-288
lines changed

docs/features/event-handler/rest.md

Lines changed: 270 additions & 174 deletions
Large diffs are not rendered by default.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { readFile } from 'node:fs/promises';
2+
import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
3+
import { compress } from '@aws-lambda-powertools/event-handler/experimental-rest/middleware';
4+
import type { Context } from 'aws-lambda';
5+
6+
const app = new Router();
7+
8+
app.get('/logo', [compress()], async () => {
9+
const logoFile = await readFile(`${process.env.LAMBDA_TASK_ROOT}/logo.png`);
10+
return {
11+
body: logoFile.toString('base64'),
12+
isBase64Encoded: true,
13+
headers: {
14+
'Content-Type': 'image/png',
15+
},
16+
statusCode: 200,
17+
};
18+
});
19+
20+
export const handler = async (event: unknown, context: Context) =>
21+
app.resolve(event, context);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
declare function getTodoById<T>(todoId: unknown): Promise<{ id: string } & T>;
2+
3+
import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
4+
import { compress } from '@aws-lambda-powertools/event-handler/experimental-rest/middleware';
5+
import type { Context } from 'aws-lambda';
6+
7+
const app = new Router();
8+
9+
app.use(compress());
10+
11+
app.get('/todos/:todoId', async ({ todoId }) => {
12+
const todo = await getTodoById(todoId);
13+
return { todo };
14+
});
15+
16+
export const handler = async (event: unknown, context: Context) =>
17+
app.resolve(event, context);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
declare function getTodoById<T>(todoId: unknown): Promise<{ id: string } & T>;
2+
3+
import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
4+
import { cors } from '@aws-lambda-powertools/event-handler/experimental-rest/middleware';
5+
import type { Context } from 'aws-lambda';
6+
7+
const app = new Router();
8+
9+
app.use(
10+
cors({
11+
origin: 'https://example.com',
12+
maxAge: 300,
13+
})
14+
);
15+
16+
app.get('/todos/:todoId', async ({ todoId }) => {
17+
const todo = await getTodoById(todoId);
18+
return { todo };
19+
});
20+
21+
app.get('/health', [cors({ origin: '*' })], async () => {
22+
return { status: 'ok' };
23+
});
24+
25+
export const handler = async (event: unknown, context: Context) =>
26+
app.resolve(event, context);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
declare function getTodoById<T>(todoId: unknown): Promise<{ id: string } & T>;
2+
3+
import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
4+
import { cors } from '@aws-lambda-powertools/event-handler/experimental-rest/middleware';
5+
import type { Context } from 'aws-lambda';
6+
7+
const app = new Router();
8+
9+
app.use(
10+
cors({
11+
origin: 'https://example.com',
12+
maxAge: 300,
13+
})
14+
);
15+
16+
app.get('/todos/:todoId', async ({ todoId }) => {
17+
const todo = await getTodoById(todoId);
18+
return { todo };
19+
});
20+
21+
export const handler = async (event: unknown, context: Context) =>
22+
app.resolve(event, context);

examples/snippets/event-handler/rest/advanced_fine_grained_responses.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ app.get('/todos', async () => {
2323
});
2424
});
2525

26-
app.post('/todos', async (params, reqCtx) => {
26+
app.post('/todos', async (_, reqCtx) => {
2727
const body = await reqCtx.request.json();
2828
const todo = await createTodo(body.title);
2929

@@ -36,6 +36,5 @@ app.post('/todos', async (params, reqCtx) => {
3636
});
3737
});
3838

39-
export const handler = async (event: unknown, context: Context) => {
40-
return app.resolve(event, context);
41-
};
39+
export const handler = async (event: unknown, context: Context) =>
40+
app.resolve(event, context);

examples/snippets/event-handler/rest/advanced_mw_compose_middleware.ts

Lines changed: 0 additions & 57 deletions
This file was deleted.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
declare const getAllTodos: () => Promise<Record<string, string>[]>;
2+
declare const putTodo: (body: unknown) => Promise<Record<string, string>>;
3+
4+
import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
5+
import type { Context } from 'aws-lambda';
6+
import { apiMiddleware } from './advanced_mw_compose_middleware_shared.js';
7+
8+
const app = new Router();
9+
10+
app.use(apiMiddleware);
11+
12+
app.get('/todos', async () => {
13+
const todos = await getAllTodos();
14+
return { todos };
15+
});
16+
17+
app.post('/todos', async (_, { request }) => {
18+
const body = await request.json();
19+
const todo = await putTodo(body);
20+
return todo;
21+
});
22+
23+
export const handler = async (event: unknown, context: Context) =>
24+
app.resolve(event, context);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { composeMiddleware } from '@aws-lambda-powertools/event-handler/experimental-rest';
2+
import { cors } from '@aws-lambda-powertools/event-handler/experimental-rest/middleware';
3+
import type { Middleware } from '@aws-lambda-powertools/event-handler/types';
4+
import { Logger } from '@aws-lambda-powertools/logger';
5+
6+
const logger = new Logger();
7+
8+
const logging: Middleware = async (_, reqCtx, next) => {
9+
logger.info(`Request: ${reqCtx.request.method} ${reqCtx.request.url}`);
10+
await next();
11+
logger.info(`Response: ${reqCtx.res.status}`);
12+
};
13+
14+
const rateLimit: Middleware = async (_, reqCtx, next) => {
15+
// Rate limiting logic would go here
16+
reqCtx.res.headers.set('X-RateLimit-Limit', '100');
17+
await next();
18+
};
19+
20+
// Reusable composed middleware
21+
const apiMiddleware = composeMiddleware([logging, cors(), rateLimit]);
22+
23+
export { apiMiddleware };
Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,57 @@
1-
declare const compresssBody: (body: string) => Promise<string>;
1+
declare const getUserTodos: (
2+
userId: string
3+
) => Promise<Record<string, string>[]>;
4+
declare const jwt: {
5+
verify(token: string, secret: string): { sub: string; roles: string[] };
6+
};
27

3-
import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
8+
import { getStringFromEnv } from '@aws-lambda-powertools/commons/utils/env';
9+
import {
10+
Router,
11+
UnauthorizedError,
12+
} from '@aws-lambda-powertools/event-handler/experimental-rest';
413
import type { Middleware } from '@aws-lambda-powertools/event-handler/types';
14+
import { Logger } from '@aws-lambda-powertools/logger';
515
import type { Context } from 'aws-lambda';
616

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();
17+
const jwtSecret = getStringFromEnv({
18+
key: 'JWT_SECRET',
19+
errorMessage: 'JWT_SECRET is not set',
20+
});
1621

17-
// Check if response should be compressed
18-
const body = await reqCtx.res.text();
19-
const threshold = options.threshold || 1024;
22+
const logger = new Logger({});
23+
const app = new Router();
24+
const store: { userId: string; roles: string[] } = { userId: '', roles: [] };
2025

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+
// Factory function that returns middleware
27+
const verifyToken = (options: { jwtSecret: string }): Middleware => {
28+
return async (_, { request }, next) => {
29+
const auth = request.headers.get('Authorization');
30+
if (!auth || !auth.startsWith('Bearer '))
31+
return new UnauthorizedError('Missing or invalid Authorization header');
32+
33+
const token = auth.slice(7);
34+
try {
35+
const payload = jwt.verify(token, options.jwtSecret);
36+
store.userId = payload.sub;
37+
store.roles = payload.roles;
38+
} catch (error) {
39+
logger.error('Token verification failed', { error });
40+
return new UnauthorizedError('Invalid token');
2641
}
42+
43+
await next();
2744
};
2845
};
2946

30-
const app = new Router();
31-
3247
// Use custom middleware globally
33-
app.use(compress({ threshold: 500 }));
48+
app.use(verifyToken({ jwtSecret }));
3449

35-
app.get('/data', async () => {
36-
return {
37-
message: 'Large response data',
38-
data: new Array(100).fill('content'),
39-
};
50+
app.post('/todos', async (_) => {
51+
const { userId } = store;
52+
const todos = await getUserTodos(userId);
53+
return { todos };
4054
});
4155

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-
};
56+
export const handler = async (event: unknown, context: Context) =>
57+
app.resolve(event, context);

0 commit comments

Comments
 (0)