Skip to content

v2.27.0

Latest
Compare
Choose a tag to compare
@github-actions github-actions released this 24 Sep 13:35
· 2 commits to refs/heads/main since this release
f38af1d

Summary

We're excited to announce an update to our Event Handler utility, featuring new middleware for CORS (Cross-Origin Resource Sharing) configuration and automatic response compression. We have also added support for Route Prefixes which allows you to avoid repeating a shared prefix in each route definition.

We've listened to your feedback and also made some slight changes to the type signature in the handlers to improve developer experience.

We have also made the error handlers more versatile by allowing a Response object or a JavaScript object to be returned from the error handler.

⭐ Congratulations @guillemcomerma for their first PR merged in the project, and thank you to @shrivarshapoojari and @dani-abib for their contributions 🎉

CORS Middleware

Docs

The CORS middleware ensures CORS headers are returned as part of the response when your functions match the path invoked and the Origin matches one of the allowed values.

import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
import { cors } from '@aws-lambda-powertools/event-handler/experimental-rest/middleware';
import type { Context } from 'aws-lambda';

const app = new Router();

app.use(
  cors({
    origin: 'https://example.com',
    maxAge: 300,
  })
);

app.get('/todos/:todoId', async ({ params: { todoId } }) => {
  const todo = await getTodoById(todoId);
  return { todo };
});

export const handler = async (event: unknown, context: Context) =>
  app.resolve(event, context);
  
/**
When invoked from a valid origin, this returns:
  
{
  "statusCode": 200,
  "headers": {
    "access-control-allow-credentials": "false",
    "access-control-allow-origin": "https://example.com",
    "content-type": "application/json"
  },
  "multiValueHeaders": {
    "access-control-allow-headers": [
      "Authorization",
      "Content-Type",
      "X-Amz-Date",
      "X-Api-Key",
      "X-Amz-Security-Token"
    ],
    "access-control-allow-methods": [
      "DELETE",
      "GET",
      "HEAD",
      "PATCH",
      "POST",
      "PUT"
    ]
  },
  "body": "{\"todoId\":\"123\",\"task\":\"Example task\",\"completed\":false}",
  "isBase64Encoded": false
}
**/

Compress Middleware

Docs

The compress middleware automatically compresses responses using gzip and base64 encodes them when the client indicates support via the Accept-Encoding header.

import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
import { compress } from '@aws-lambda-powertools/event-handler/experimental-rest/middleware';
import type { Context } from 'aws-lambda';

const app = new Router();

app.use(compress());

app.get('/todos/:todoId', async ({ params: { todoId } }) => {
  const todo = await getTodoById(todoId);
  return { todo };
});

export const handler = async (event: unknown, context: Context) =>
  app.resolve(event, context);
  
/**
When invoked with the following request,
{
  "headers": {
    "Accept-Encoding": "gzip"
  },
  "resource": "/todos/1",
  "path": "/todos/1",
  "httpMethod": "GET"
}

it would return,

{
  "statusCode": 200,
  "multiValueHeaders": {
    "Content-Type": [
      "application/json"
    ],
    "Content-Encoding": [
      "gzip"
    ]
  },
  "body": "H4sIAAAAAAACE42STU4DMQyFrxJl3QXln96AMyAW7sSDLCVxiJ0Kqerd8TCCUOgii1EmP/783pOPXjmw+N3L0TfB+hz8brvxtC5KGtHvfMCIkzZx0HT5MPmNnziViIr2dIYoeNr8Q1x3xHsjcVadIbkZJoq2RXU8zzQROLseQ9505NzeCNQdMJNBE+UmY4zbzjAJhWtlZ57sB84BWtul+rteH2HPlVgWARwjqXkxpklK5gmEHAQqJBMtFsGVygcKmNVRjG0wxvuzGF2L0dpVUOKMC3bfJNjJgWMrCuZk7cUp02AiD72D6WKHHwUDKbiJs6AZ0VZXKOUx4uNvzdxT+E4mLcMA+6G8nzrLQkaxkNEVrFKW2VGbJCoCY7q2V3+tiv5kGThyxfTecDWbgGz/NfYXhL6ePgF9PnFdPgMAAA==",
  "isBase64Encoded": true
}
**/

Route Prefixes

Docs

When defining multiple routes related to a specific resource, you can use the prefix constructor parameter when creating a new Router instance, and we'll automatically strip it from the request path before matching routes. This will prevent you to avoid repeating the prefix in each route definition.

import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
import type { Context } from 'aws-lambda';

const app = new Router({ prefix: '/todos' });

// matches POST /todos
app.post('/', async ({ req: { headers } }) => {
  const todos = await getUserTodos(headers.get('Authorization'));
  return { todos };
});

export const handler = async (event: unknown, context: Context) =>
  app.resolve(event, context);

Type Signature Changes

We’ve also made changes to some type signatures to reduce parameter complexity.

The path parameters in the route handlers have been moved to reqCtx

app.get('/todos/:todoId', async (reqCtx) => {
  return { id: reqCtx.params.todoId };
});

Previously middleware used positional arguments:

const middleware: Middleware = async (params, reqCtx, next) => {
  logger.info('Request processed');
  await next();
};

Now, we have changed it to be an object so customers can pick only the parameters that they want and also moved the params parameter to reqCtx

const middleware: Middleware = async ({ reqCtx: { params }, next }) => {
  logger.info('Request processed');
  await next();
};

We have also changed the request property to req in the RequestContext type to maintain consistency with the res property.

Improvements on Error Handling

Error handlers can now return a Response object or a JavaScript object that will be auto-serialized.

import {
  HttpStatusCodes,
  Router,
} from '@aws-lambda-powertools/event-handler/experimental-rest';
import { Logger } from '@aws-lambda-powertools/logger';
import type { Context } from 'aws-lambda/handler';

const logger = new Logger();
const app = new Router({ logger });

app.errorHandler(GetTodoError, async (error, reqCtx) => {
  logger.error('Unable to get todo', { error });

  return {
    statusCode: HttpStatusCodes.BAD_REQUEST,
    message: `Bad request: ${error.message} - ${reqCtx.req.headers.get('x-correlation-id')}`,
  };
});

app.get('/todos/:todoId', async ({ params: { todoId } }) => {
  const todo = await getTodoById(todoId); // May throw GetTodoError
  return { todo };
});

export const handler = async (event: unknown, context: Context) =>
  app.resolve(event, context);

Changes

  • chore: sanitize CI inputs via env var (#4528) by @dreamorosi
  • improv(event-handler): rename HttpErrorCodes to HttpStatusCodes (#4543) by @dreamorosi
  • docs(idempotency): fix dataKeywordArgument reference and remove unused test code (#4537) by @guillemcomerma
  • improv(event-handler): made error handler responses versatile (#4536) by @sdangol
  • chore(ci): add registry-url to setup-node steps to fix Scorecard packaging check (#4539) by @shrivarshapoojari
  • improv(event-handler): changed path parameter in middleware and routehandler signature (#4532) by @sdangol
  • improv(event-handler): changed the Middleware and RequestContext signatures (#4530) by @sdangol
  • fix(event-handler): fixed CORS behaviour not aligned with CORS spec (#4512) by @sdangol
  • feat(event-handler): implemented route prefixes in HTTP event handler (#4523) by @sdangol
  • feat(event-handler): throw error when middleware does not await next() (#4511) by @svozza
  • chore: always append PTEnv to UA string (#4522) by @dreamorosi
  • fix(event-handler): run global middleware on all requests for REST API (#4507) by @svozza
  • fix(batch): fixed the build issue with Batch processor due to missing dependencies (#4498) by @sdangol
  • feat(event-handler): added compress middleware for the REST API event handler (#4495) by @sdangol
  • feat(event-handler): add CORS middleware support (#4477) by @dcabib

📜 Documentation updates

🔧 Maintenance

This release was made possible by the following contributors:

@dcabib, @dependabot[bot], @dreamorosi, @github-actions[bot], @guillemcomerma, @sdangol, @shrivarshapoojari, @svozza, dependabot[bot] and github-actions[bot]