Amazon API Gateway Construct Library
Amazon API Gateway is a fully managed service that makes it easy for developers
to publish, maintain, monitor, and secure APIs at any scale. Create an API to
access data, business logic, or functionality from your back-end services, such
as applications running on Amazon Elastic Compute Cloud (Amazon EC2), code
running on AWS Lambda, or any web application.
Defining APIs
APIs are defined as a hierarchy of resources and methods. addResource
and
addMethod
can be used to build this hierarchy. The root resource is
api.root
.
For example, the following code defines an API that includes the following HTTP
endpoints: ANY /, GET /books
, POST /books
, GET /books/{book_id}
, DELETE /books/{book_id}
.
const api = new apigateway.RestApi(this, 'books-api');
api.root.addMethod('ANY');
const books = api.root.addResource('books');
books.addMethod('GET');
books.addMethod('POST');
const book = books.addResource('{book_id}');
book.addMethod('GET');
book.addMethod('DELETE');
AWS Lambda-backed APIs
A very common practice is to use Amazon API Gateway with AWS Lambda as the
backend integration. The LambdaRestApi
construct makes it easy:
The following code defines a REST API that routes all requests to the
specified AWS Lambda function:
const backend = new lambda.Function(...);
new apigateway.LambdaRestApi(this, 'myapi', {
handler: backend,
});
You can also supply proxy: false
, in which case you will have to explicitly
define the API model:
const backend = new lambda.Function(...);
const api = new apigateway.LambdaRestApi(this, 'myapi', {
handler: backend,
proxy: false
});
const items = api.root.addResource('items');
items.addMethod('GET');
items.addMethod('POST');
const item = items.addResource('{item}');
item.addMethod('GET');
item.addMethod('DELETE', new apigateway.HttpIntegration('http://amazon.com'));
Integration Targets
Methods are associated with backend integrations, which are invoked when this
method is called. API Gateway supports the following integrations:
MockIntegration
- can be used to test APIs. This is the default
integration if one is not specified.LambdaIntegration
- can be used to invoke an AWS Lambda function.AwsIntegration
- can be used to invoke arbitrary AWS service APIs.HttpIntegration
- can be used to invoke HTTP endpoints.
The following example shows how to integrate the GET /book/{book_id}
method to
an AWS Lambda function:
const getBookHandler = new lambda.Function(...);
const getBookIntegration = new apigateway.LambdaIntegration(getBookHandler);
book.addMethod('GET', getBookIntegration);
Integration options can be optionally be specified:
const getBookIntegration = new apigateway.LambdaIntegration(getBookHandler, {
contentHandling: apigateway.ContentHandling.CONVERT_TO_TEXT,
credentialsPassthrough: true,
});
Method options can optionally be specified when adding methods:
book.addMethod('GET', getBookIntegration, {
authorizationType: apigateway.AuthorizationType.IAM,
apiKeyRequired: true
});
The following example shows how to use an API Key with a usage plan:
const hello = new lambda.Function(this, 'hello', {
runtime: lambda.Runtime.NODEJS_10_X,
handler: 'hello.handler',
code: lambda.Code.fromAsset('lambda')
});
const api = new apigateway.RestApi(this, 'hello-api', { });
const integration = new apigateway.LambdaIntegration(hello);
const v1 = api.root.addResource('v1');
const echo = v1.addResource('echo');
const echoMethod = echo.addMethod('GET', integration, { apiKeyRequired: true });
const key = api.addApiKey('ApiKey');
const plan = api.addUsagePlan('UsagePlan', {
name: 'Easy',
apiKey: key,
throttle: {
rateLimit: 10,
burstLimit: 2
}
});
plan.addApiStage({
stage: api.deploymentStage,
throttle: [
{
method: echoMethod,
throttle: {
rateLimit: 10,
burstLimit: 2
}
}
]
});
In scenarios where you need to create a single api key and configure rate limiting for it, you can use RateLimitedApiKey
.
This construct lets you specify rate limiting properties which should be applied only to the api key being created.
The API key created has the specified rate limits, such as quota and throttles, applied.
The following example shows how to use a rate limited api key :
const hello = new lambda.Function(this, 'hello', {
runtime: lambda.Runtime.NODEJS_10_X,
handler: 'hello.handler',
code: lambda.Code.fromAsset('lambda')
});
const api = new apigateway.RestApi(this, 'hello-api', { });
const integration = new apigateway.LambdaIntegration(hello);
const v1 = api.root.addResource('v1');
const echo = v1.addResource('echo');
const echoMethod = echo.addMethod('GET', integration, { apiKeyRequired: true });
const key = new apigateway.RateLimitedApiKey(this, 'rate-limited-api-key', {
customerId: 'hello-customer',
resources: [api],
quota: {
limit: 10000,
period: apigateway.Period.MONTH
}
});
Working with models
When you work with Lambda integrations that are not Proxy integrations, you
have to define your models and mappings for the request, response, and integration.
const hello = new lambda.Function(this, 'hello', {
runtime: lambda.Runtime.NODEJS_10_X,
handler: 'hello.handler',
code: lambda.Code.fromAsset('lambda')
});
const api = new apigateway.RestApi(this, 'hello-api', { });
const resource = api.root.addResource('v1');
You can define more parameters on the integration to tune the behavior of API Gateway
const integration = new LambdaIntegration(hello, {
proxy: false,
requestParameters: {
'integration.request.querystring.who': 'method.request.querystring.who'
},
allowTestInvoke: true,
requestTemplates: {
'application/json': JSON.stringify({ action: 'sayHello', pollId: "$util.escapeJavaScript($input.params('who'))" })
},
passthroughBehavior: PassthroughBehavior.NEVER,
integrationResponses: [
{
statusCode: "200",
responseTemplates: {
'application/json': JSON.stringify({ state: 'ok', greeting: '$util.escapeJavaScript($input.body)' })
},
responseParameters: {
'method.response.header.Content-Type': "'application/json'",
'method.response.header.Access-Control-Allow-Origin': "'*'",
'method.response.header.Access-Control-Allow-Credentials': "'true'"
}
},
{
selectionPattern: '(\n|.)+',
statusCode: "400",
responseTemplates: {
'application/json': JSON.stringify({ state: 'error', message: "$util.escapeJavaScript($input.path('$.errorMessage'))" })
},
responseParameters: {
'method.response.header.Content-Type': "'application/json'",
'method.response.header.Access-Control-Allow-Origin': "'*'",
'method.response.header.Access-Control-Allow-Credentials': "'true'"
}
}
]
});
You can define models for your responses (and requests)
const responseModel = api.addModel('ResponseModel', {
contentType: 'application/json',
modelName: 'ResponseModel',
schema: {
schema: JsonSchemaVersion.DRAFT4,
title: 'pollResponse',
type: JsonSchemaType.OBJECT,
properties: {
state: { type: JsonSchemaType.STRING },
greeting: { type: JsonSchemaType.STRING }
}
}
});
const errorResponseModel = api.addModel('ErrorResponseModel', {
contentType: 'application/json',
modelName: 'ErrorResponseModel',
schema: {
schema: JsonSchemaVersion.DRAFT4,
title: 'errorResponse',
type: JsonSchemaType.OBJECT,
properties: {
state: { type: JsonSchemaType.STRING },
message: { type: JsonSchemaType.STRING }
}
}
});
And reference all on your method definition.
resource.addMethod('GET', integration, {
requestParameters: {
'method.request.querystring.who': true
},
requestValidatorOptions: {
requestValidatorName: 'test-validator',
validateRequestBody: true,
validateRequestParameters: false
}
methodResponses: [
{
statusCode: '200',
responseParameters: {
'method.response.header.Content-Type': true,
'method.response.header.Access-Control-Allow-Origin': true,
'method.response.header.Access-Control-Allow-Credentials': true
},
responseModels: {
'application/json': responseModel
}
},
{
statusCode: '400',
responseParameters: {
'method.response.header.Content-Type': true,
'method.response.header.Access-Control-Allow-Origin': true,
'method.response.header.Access-Control-Allow-Credentials': true
},
responseModels: {
'application/json': errorResponseModel
}
}
]
});
Specifying requestValidatorOptions
automatically creates the RequestValidator construct with the given options.
However, if you have your RequestValidator already initialized or imported, use the requestValidator
option instead.
Default Integration and Method Options
The defaultIntegration
and defaultMethodOptions
properties can be used to
configure a default integration at any resource level. These options will be
used when defining method under this resource (recursively) with undefined
integration or options.
If not defined, the default integration is MockIntegration
. See reference
documentation for default method options.
The following example defines the booksBackend
integration as a default
integration. This means that all API methods that do not explicitly define an
integration will be routed to this AWS Lambda function.
const booksBackend = new apigateway.LambdaIntegration(...);
const api = new apigateway.RestApi(this, 'books', {
defaultIntegration: booksBackend
});
const books = new api.root.addResource('books');
books.addMethod('GET');
books.addMethod('POST');
const book = books.addResource('{book_id}');
book.addMethod('GET');
A Method can be configured with authorization scopes. Authorization scopes are
used in conjunction with an authorizer that uses Amazon Cognito user
pools.
Read more about authorization scopes
here.
Authorization scopes for a Method can be configured using the authorizationScopes
property as shown below -
books.addMethod('GET', new apigateway.HttpIntegration('http://amazon.com'), {
authorizationType: AuthorizationType.COGNITO,
authorizationScopes: ['Scope1','Scope2']
});
Proxy Routes
The addProxy
method can be used to install a greedy {proxy+}
resource
on a path. By default, this also installs an "ANY"
method:
const proxy = resource.addProxy({
defaultIntegration: new LambdaIntegration(handler),
anyMethod: true
});
Authorizers
API Gateway supports several different authorization types
that can be used for controlling access to your REST APIs.
IAM-based authorizer
The following CDK code provides 'execute-api' permission to an IAM user, via IAM policies, for the 'GET' method on the books
resource:
const getBooks = books.addMethod('GET', new apigateway.HttpIntegration('http://amazon.com'), {
authorizationType: apigateway.AuthorizationType.IAM
});
iamUser.attachInlinePolicy(new iam.Policy(this, 'AllowBooks', {
statements: [
new iam.PolicyStatement({
actions: [ 'execute-api:Invoke' ],
effect: iam.Effect.Allow,
resources: [ getBooks.methodArn() ]
})
]
}))
Lambda-based token authorizer
API Gateway also allows lambda functions to be used as authorizers.
This module provides support for token-based Lambda authorizers. When a client makes a request to an API's methods configured with such
an authorizer, API Gateway calls the Lambda authorizer, which takes the caller's identity as input and returns an IAM policy as output.
A token-based Lambda authorizer (also called a token authorizer) receives the caller's identity in a bearer token, such as
a JSON Web Token (JWT) or an OAuth token.
API Gateway interacts with the authorizer Lambda function handler by passing input and expecting the output in a specific format.
The event object that the handler is called with contains the authorizationToken
and the methodArn
from the request to the
API Gateway endpoint. The handler is expected to return the principalId
(i.e. the client identifier) and a policyDocument
stating
what the client is authorizer to perform.
See here for a detailed specification on
inputs and outputs of the Lambda handler.
The following code attaches a token-based Lambda authorizer to the 'GET' Method of the Book resource:
const authFn = new lambda.Function(this, 'booksAuthorizerLambda', {
});
const auth = new apigateway.TokenAuthorizer(this, 'booksAuthorizer', {
handler: authFn
});
books.addMethod('GET', new apigateway.HttpIntegration('http://amazon.com'), {
authorizer: auth
});
You can find a full working example here.
By default, the TokenAuthorizer
looks for the authorization token in the request header with the key 'Authorization'. This can,
however, be modified by changing the identitySource
property.
Authorizers can also be passed via the defaultMethodOptions
property within the RestApi
construct or the Method
construct. Unless
explicitly overridden, the specified defaults will be applied across all Method
s across the RestApi
or across all Resource
s,
depending on where the defaults were specified.
Lambda-based request authorizer
This module provides support for request-based Lambda authorizers. When a client makes a request to an API's methods configured with such
an authorizer, API Gateway calls the Lambda authorizer, which takes specified parts of the request, known as identity sources,
as input and returns an IAM policy as output. A request-based Lambda authorizer (also called a request authorizer) receives
the identity sources in a series of values pulled from the request, from the headers, stage variables, query strings, and the context.
API Gateway interacts with the authorizer Lambda function handler by passing input and expecting the output in a specific format.
The event object that the handler is called with contains the body of the request and the methodArn
from the request to the
API Gateway endpoint. The handler is expected to return the principalId
(i.e. the client identifier) and a policyDocument
stating
what the client is authorizer to perform.
See here for a detailed specification on
inputs and outputs of the Lambda handler.
The following code attaches a request-based Lambda authorizer to the 'GET' Method of the Book resource:
const authFn = new lambda.Function(this, 'booksAuthorizerLambda', {
});
const auth = new apigateway.RequestAuthorizer(this, 'booksAuthorizer', {
handler: authFn,
identitySources: [IdentitySource.header('Authorization')]
});
books.addMethod('GET', new apigateway.HttpIntegration('http://amazon.com'), {
authorizer: auth
});
You can find a full working example here.
By default, the RequestAuthorizer
does not pass any kind of information from the request. This can,
however, be modified by changing the identitySource
property, and is required when specifying a value for caching.
Authorizers can also be passed via the defaultMethodOptions
property within the RestApi
construct or the Method
construct. Unless
explicitly overridden, the specified defaults will be applied across all Method
s across the RestApi
or across all Resource
s,
depending on where the defaults were specified.
Deployments
By default, the RestApi
construct will automatically create an API Gateway
Deployment and a "prod" Stage which represent the API configuration you
defined in your CDK app. This means that when you deploy your app, your API will
be have open access from the internet via the stage URL.
The URL of your API can be obtained from the attribute restApi.url
, and is
also exported as an Output
from your stack, so it's printed when you cdk deploy
your app:
$ cdk deploy
...
books.booksapiEndpointE230E8D5 = https://6lyktd4lpk.execute-api.us-east-1.amazonaws.com/prod/
To disable this behavior, you can set { deploy: false }
when creating your
API. This means that the API will not be deployed and a stage will not be
created for it. You will need to manually define a apigateway.Deployment
and
apigateway.Stage
resources.
Use the deployOptions
property to customize the deployment options of your
API.
The following example will configure API Gateway to emit logs and data traces to
AWS CloudWatch for all API calls:
By default, an IAM role will be created and associated with API Gateway to
allow it to write logs and metrics to AWS CloudWatch unless cloudWatchRole
is
set to false
.
const api = new apigateway.RestApi(this, 'books', {
deployOptions: {
loggingLevel: apigateway.MethodLoggingLevel.INFO,
dataTraceEnabled: true
}
})
Access Logging
Access logging creates logs everytime an API method is accessed. Access logs can have information on
who has accessed the API, how the caller accessed the API and what responses were generated.
Access logs are configured on a Stage of the RestApi.
Access logs can be expressed in a format of your choosing, and can contain any access details, with a
minimum that it must include the 'requestId'. The list of variables that can be expressed in the access
log can be found
here.
Read more at Setting Up CloudWatch API Logging in API
Gateway
const prdLogGroup = new cwlogs.LogGroup(this, "PrdLogs");
const api = new apigateway.RestApi(this, 'books', {
deployOptions: {
accessLogDestination: new apigateway.LogGroupLogDestination(prdLogGroup),
accessLogFormat: apigateway.AccessLogFormat.jsonWithStandardFields()
}
})
const deployment = new apigateway.Deployment(stack, 'Deployment', {api});
const devLogGroup = new cwlogs.LogGroup(this, "DevLogs");
new apigateway.Stage(this, 'dev', {
deployment,
accessLogDestination: new apigateway.LogGroupLogDestination(devLogGroup),
accessLogFormat: apigateway.AccessLogFormat.jsonWithStandardFields({
caller: false,
httpMethod: true,
ip: true,
protocol: true,
requestTime: true,
resourcePath: true,
responseLength: true,
status: true,
user: true
})
});
The following code will generate the access log in the CLF format.
const logGroup = new cwlogs.LogGroup(this, "ApiGatewayAccessLogs");
const api = new apigateway.RestApi(this, 'books', {
deployOptions: {
accessLogDestination: new apigateway.LogGroupLogDestination(logGroup),
accessLogFormat: apigateway.AccessLogFormat.clf(),
}});
You can also configure your own access log format by using the AccessLogFormat.custom()
API.
AccessLogField
provides commonly used fields. The following code configures access log to contain.
const logGroup = new cwlogs.LogGroup(this, "ApiGatewayAccessLogs");
new apigateway.RestApi(this, 'books', {
deployOptions: {
accessLogDestination: new apigateway.LogGroupLogDestination(logGroup),
accessLogFormat: apigateway.AccessLogFormat.custom(
`${AccessLogFormat.contextRequestId()} ${AccessLogField.contextErrorMessage()} ${AccessLogField.contextErrorMessageString()}`);
})
};
You can use the methodOptions
property to configure
default method throttling
for a stage. The following snippet configures the a stage that accepts
100 requests per minute, allowing burst up to 200 requests per minute.
const api = new apigateway.RestApi(this, 'books');
const deployment = new apigateway.Deployment(this, 'my-deployment', { api });
const stage = new apigateway.Stage(this, 'my-stage', {
deployment,
methodOptions: {
'/*/*': {
throttlingRateLimit: 100,
throttlingBurstLimit: 200
}
}
});
Configuring methodOptions
on the deployOptions
of RestApi
will set the
throttling behaviors on the default stage that is automatically created.
const api = new apigateway.RestApi(this, 'books', {
deployOptions: {
methodOptions: {
'/*/*': {
throttlingRateLimit: 100,
throttlingBurstLimit: 1000
}
}
}
});
Deeper dive: invalidation of deployments
API Gateway deployments are an immutable snapshot of the API. This means that we
want to automatically create a new deployment resource every time the API model
defined in our CDK app changes.
In order to achieve that, the AWS CloudFormation logical ID of the
AWS::ApiGateway::Deployment
resource is dynamically calculated by hashing the
API configuration (resources, methods). This means that when the configuration
changes (i.e. a resource or method are added, configuration is changed), a new
logical ID will be assigned to the deployment resource. This will cause
CloudFormation to create a new deployment resource.
By default, old deployments are deleted. You can set retainDeployments: true
to allow users revert the stage to an old deployment manually.
Custom Domains
To associate an API with a custom domain, use the domainName
configuration when
you define your API:
const api = new apigw.RestApi(this, 'MyDomain', {
domainName: {
domainName: 'example.com',
certificate: acmCertificateForExampleCom,
},
});
This will define a DomainName
resource for you, along with a BasePathMapping
from the root of the domain to the deployment stage of the API. This is a common
set up.
To route domain traffic to an API Gateway API, use Amazon Route 53 to create an
alias record. An alias record is a Route 53 extension to DNS. It's similar to a
CNAME record, but you can create an alias record both for the root domain, such
as example.com
, and for subdomains, such as www.example.com
. (You can create
CNAME records only for subdomains.)
import * as route53 from '@aws-cdk/aws-route53';
import * as targets from '@aws-cdk/aws-route53-targets';
new route53.ARecord(this, 'CustomDomainAliasRecord', {
zone: hostedZoneForExampleCom,
target: route53.RecordTarget.fromAlias(new targets.ApiGateway(api))
});
You can also define a DomainName
resource directly in order to customize the default behavior:
new apigw.DomainName(this, 'custom-domain', {
domainName: 'example.com',
certificate: acmCertificateForExampleCom,
endpointType: apigw.EndpointType.EDGE,
securityPolicy: apigw.SecurityPolicy.TLS_1_2
});
Once you have a domain, you can map base paths of the domain to APIs.
The following example will map the URL https://example.com/go-to-api1
to the api1
API and https://example.com/boom to the api2
API.
domain.addBasePathMapping(api1, { basePath: 'go-to-api1' });
domain.addBasePathMapping(api2, { basePath: 'boom' });
You can specify the API Stage
to which this base path URL will map to. By default, this will be the
deploymentStage
of the RestApi
.
const betaDeploy = new Deployment(this, 'beta-deployment', {
api: restapi,
});
const betaStage = new Stage(this, 'beta-stage', {
deployment: betaDeploy,
});
domain.addBasePathMapping(restapi, { basePath: 'api/beta', stage: betaStage });
If you don't specify basePath
, all URLs under this domain will be mapped
to the API, and you won't be able to map another API to the same domain:
domain.addBasePathMapping(api);
This can also be achieved through the mapping
configuration when defining the
domain as demonstrated above.
If you wish to setup this domain with an Amazon Route53 alias, use the targets.ApiGatewayDomain
:
import * as route53 from '@aws-cdk/aws-route53';
import * as targets from '@aws-cdk/aws-route53-targets';
new route53.ARecord(this, 'CustomDomainAliasRecord', {
zone: hostedZoneForExampleCom,
target: route53.RecordTarget.fromAlias(new targets.ApiGatewayDomain(domainName))
});
Cross Origin Resource Sharing (CORS)
Cross-Origin Resource Sharing (CORS) is a mechanism
that uses additional HTTP headers to tell browsers to give a web application
running at one origin, access to selected resources from a different origin. A
web application executes a cross-origin HTTP request when it requests a resource
that has a different origin (domain, protocol, or port) from its own.
You can add the CORS preflight OPTIONS
HTTP method to any API resource via the defaultCorsPreflightOptions
option or by calling the addCorsPreflight
on a specific resource.
The following example will enable CORS for all methods and all origins on all resources of the API:
new apigateway.RestApi(this, 'api', {
defaultCorsPreflightOptions: {
allowOrigins: apigateway.Cors.ALL_ORIGINS,
allowMethods: apigateway.Cors.ALL_METHODS
}
})
The following example will add an OPTIONS method to the myResource
API resource, which
only allows GET and PUT HTTP requests from the origin https://amazon.com.
myResource.addCorsPreflight({
allowOrigins: [ 'https://amazon.com' ],
allowMethods: [ 'GET', 'PUT' ]
});
See the
CorsOptions
API reference for a detailed list of supported configuration options.
You can specify defaults this at the resource level, in which case they will be applied to the entire resource sub-tree:
const subtree = resource.addResource('subtree', {
defaultCorsPreflightOptions: {
allowOrigins: [ 'https://amazon.com' ]
}
});
This means that all resources under subtree
(inclusive) will have a preflight
OPTIONS added to them.
See #906 for a list of CORS
features which are not yet supported.
Endpoint Configuration
API gateway allows you to specify an
API Endpoint Type.
To define an endpoint type for the API gateway, use endpointConfiguration
property:
const api = new apigw.RestApi(stack, 'api', {
endpointConfiguration: {
types: [ apigw.EndpointType.EDGE ]
}
});
You can also create an association between your Rest API and a VPC endpoint. By doing so,
API Gateway will generate a new
Route53 Alias DNS record which you can use to invoke your private APIs. More info can be found
here.
Here is an example:
const someEndpoint: IVpcEndpoint =
const api = new apigw.RestApi(stack, 'api', {
endpointConfiguration: {
types: [ apigw.EndpointType.PRIVATE ],
vpcEndpoints: [ someEndpoint ]
}
});
By performing this association, we can invoke the API gateway using the following format:
https://{rest-api-id}-{vpce-id}.execute-api.{region}.amazonaws.com/{stage}
APIGateway v2
APIGateway v2 APIs are now moved to its own package named aws-apigatewayv2
. For backwards compatibility, existing
APIGateway v2 "CFN resources" (such as CfnApi
) that were previously exported as part of this package, are still
exported from here and have been marked deprecated. However, updates to these CloudFormation resources, such as new
properties and new resource types will not be available.
Move to using aws-apigatewayv2
to get the latest APIs and updates.
This module is part of the AWS Cloud Development Kit project.