Skip to content

docs(api-gateway): add support for new router feature #767

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Nov 12, 2021
Merged
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 158 additions & 6 deletions docs/core/event_handler/api_gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Event handler for Amazon API Gateway REST/HTTP APIs and Application Loader Balan
* Integrates with [Data classes utilities](../../utilities/data_classes.md){target="_blank"} to easily access event and identity information
* Built-in support for Decimals JSON encoding
* Support for dynamic path expressions
* Router to allow for splitting up the handler accross multiple files

## Getting started

Expand Down Expand Up @@ -75,12 +76,11 @@ This is the sample infrastructure for API Gateway we are using for the examples

Outputs:
HelloWorldApigwURL:
Description: "API Gateway endpoint URL for Prod environment for Hello World Function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello"

HelloWorldFunction:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt HelloWorldFunction.Arn
Description: "API Gateway endpoint URL for Prod environment for Hello World Function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello"
HelloWorldFunction:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt HelloWorldFunction.Arn
```

### API Gateway decorator
Expand Down Expand Up @@ -853,6 +853,158 @@ You can instruct API Gateway handler to use a custom serializer to best suit you
}
```

### Splitting routes across multiple files

When building a larger application, sometimes to helps to split out your routes into multiple file. Also
there might be cases where you have some shared routes for multiple lambdas like a `health` status lambda
to be used with Application Load Balancer.

Below is an example project layout for AWS Lambda Functions using AWS SAM CLI that allows for relative path
imports (ie: `from .routers import health`).

!!! tip "See in `src/app/main.py`, when including a route we can add a prefix to those routes ie: `prefix="/health"`."
!!! tip "See in `src/app/routers/health.py`, when adding a child logger we use `Logger(child=True)`."

=== "Project layout"

```text
.
├── Pipfile
├── Pipfile.lock
├── src
│ ├── __init__.py
│ ├── requirements.txt # pipenv lock -r > src/requirements.txt
│ └── app
│ ├── __init__.py # this file makes "app" a "Python package"
│ ├── main.py # Main lambda handler (app.py, index.py, handler.py)
│ └── routers # routers module
│ ├── __init__.py # this file makes "routers" a "Python package"
│ ├── health.py # "health" submodule, e.g. from .routers import health
│ └── users.py # "users" submodule, e.g. from .routers import users
├── template.yaml # SAM template.yml
└── tests
├── __init__.py
├── unit
│ ├── __init__.py
│ └── test_health.py # unit tests for the health router
└── functional
├── __init__.py
├── conftest.py # pytest fixtures for the functional tests
└── test_app_main.py # functional tests for the main lambda handler
```

=== "template.yml"

```yaml hl_lines="22 23"
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Example service with multiple routes

Globals:
Function:
Timeout: 20
MemorySize: 512
Runtime: python3.9
Tracing: Active
Environment:
Variables:
LOG_LEVEL: INFO
POWERTOOLS_LOGGER_LOG_EVENT: true
POWERTOOLS_METRICS_NAMESPACE: MyServerlessApplication
POWERTOOLS_SERVICE_NAME: ServiceName

Resources:
AppFunction:
Type: AWS::Serverless::Function
Properties:
Handler: app.main.lambda_handler
CodeUri: src
Description: App function description
Events:
HealthPath:
Type: Api
Properties:
Path: /health/status
Method: GET
UserPath:
Type: Api
Properties:
Path: /users/{name}
Method: GET
Environment:
Variables:
PARAM1: VALUE
Tags:
LambdaPowertools: python
Outputs:
AppApigwURL:
Description: "API Gateway endpoint URL for Prod environment for App Function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/app"
AppFunction:
Description: "App Lambda Function ARN"
Value: !GetAtt AppFunction.Arn
```

=== "src/app/main.py"

```python hl_lines="9 14-16"
from typing import Dict

from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.event_handler import ApiGatewayResolver
from aws_lambda_powertools.event_handler.api_gateway import ProxyEventType
from aws_lambda_powertools.logging.correlation_paths import API_GATEWAY_HTTP
from aws_lambda_powertools.utilities.typing import LambdaContext

from .routers import health, users

tracer = Tracer()
logger = Logger()
app = ApiGatewayResolver(proxy_type=ProxyEventType.ALBEvent)
app.include_router(health.router, prefix="/health")
app.include_router(users.router)


@logger.inject_lambda_context(correlation_id_path=API_GATEWAY_HTTP)
@tracer.capture_lambda_handler
def lambda_handler(event: Dict, context: LambdaContext):
app.resolve(event, context)
```

=== "src/app/routers/health.py"

```python hl_lines="4 6-7 10 12"
from typing import Dict

from aws_lambda_powertools import Logger
from aws_lambda_powertools.event_handler.api_gateway import Router

logger = Logger(child=True)
router = Router()


@router.get("/status")
def health() -> Dict:
logger.debug("Health check called")
return {"status": "OK"}
```

=== "tests/functional/test_app_main.py"

```python hl_lines="3"
import json

from src.app import main


def test_lambda_handler(apigw_event, lambda_context):
ret = main.lambda_handler(apigw_event, lambda_context)
expected = json.dumps({"message": "hello universe"}, separators=(",", ":"))

assert ret["statusCode"] == 200
assert ret["body"] == expected
```

## Testing your code

You can test your routes by passing a proxy event request where `path` and `httpMethod`.
Expand Down