AWS API Gateway
AWS API Gateway is a fully managed service for creating, monitoring, and securing APIs at scale. It acts as a “front door” for REST and WebSocket applications that use backend services, and handles all the tasks necessary to accept and process up to hundreds of thousands of concurrent API calls, including traffic management, authorization and access control, monitoring, and API version management. API Gateway is inexpensive, has no minimum fees, and you only pay for the API calls you receive and the data transferred out.
Overview
Pulumi Crosswalk for Amazon Web Services (AWS) provides better AWS API management through significantly easier ways of programming an API Gateway. This includes using infrastructure as code techniques for simple, declarative APIs, including easy Lambda-based handlers.
Defining an AWS API Gateway Endpoint and Routes
AWS API Gateway creates REST APIs that:
- Are HTTP based.
- Adhere to the REST protocol.
- Implement standard HTTP methods such as
GET
,POST
,PUT
,PATCH
, andDELETE
.
Each API Gateway instance defines a new API endpoint and a collection of API routes, each of which has a distinct URL.
Internally the API Gateway resource uses a collection of supporting objects, like resources, methods, and more, however one of the benefits of Pulumi Crosswalk for AWS is that it hides these mechanics behind a simpler interface.
Each API Gateway deployment is associated with a so-called stage. A stage is simply a version of your API, such
as stage
, prod
, v1
, or v2
. For simple APIs, you will likely just have one. You can always define a custom
stage name, but if you leave it off, a default of stage
will be chosen.
API Gateway will auto-generate a domain name with built-in HTTPS support. The stage name will also be part of this URL. We will see later how to assign a custom domain, SSL certificate, and/or eliminate the stage name from the URL.
There are multiple ways to define APIs using Pulumi Crosswalk for AWS:
- Lambda Function Event Handler Route
- Static Route Served by S3
- Integration Route
- OpenAPI Specification for an Entire Endpoint
- OpenAPI Specification for a Single Route
Multiple endpoints on the same API Gateway can be defined using a combination of these techniques.
Defining a Lambda Function Event Handler Route
An Event Handler Route is an API that will map to a Lambda Function. You will specify the path, HTTP method, and the Lambda Function to invoke when the API is called. Pulumi offers multiple ways of defining the Lambda Function and it provisions the appropriate permissions so that API Gateway can communicate with it.
An easy way to define the Lambda Function, which mimics modern REST web application frameworks in style, is to
write the application code inline with the API Gateway definition itself. This example creates an AWS API Gateway
endpoint with a single API, listening at /
for GET
requests, which simply returns a 200 OK
for each call:
import * as awsx from "@pulumi/awsx";
// Define a new GET endpoint that just returns a 200 and "hello" in the body.
const api = new awsx.apigateway.API("example", {
routes: [{
path: "/",
method: "GET",
eventHandler: async (event) => {
// This code runs in an AWS Lambda anytime `/` is hit.
return {
statusCode: 200,
body: "Hello, API Gateway!",
};
},
}],
})
// Export the auto-generated API Gateway base URL.
export const url = api.url;
By running pulumi up
, we will provision the API Gateway, its routes, and we’ll get back the URL:
$ pulumi up -y
Updating (dev):
Type Name Status
+ pulumi:pulumi:Stack crosswalk-aws-dev created
+ └─ aws:apigateway:x:API example created
+ ├─ aws:iam:Role example4c238266 created
+ ├─ aws:iam:RolePolicyAttachment example4c238266-32be53a2 created
+ ├─ aws:lambda:Function example4c238266 created
+ ├─ aws:apigateway:RestApi example created
+ ├─ aws:apigateway:Deployment example created
+ ├─ aws:lambda:Permission example-fa520765 created
+ └─ aws:apigateway:Stage example created
Outputs:
url: "https://no90ji5v23.execute-api.us-west-2.amazonaws.com/stage/"
Resources:
+ 9 created
Duration: 25s
We can curl
the URL to see that it is up and running:
$ curl $(pulumi stack output url)
Hello, API Gateway!
This example uses Pulumi Crosswalk for AWS’s built-in Lambda Function support. In this example, we are specifying the code inline, and using the default Lambda property values. We can customize aspects of this Function such as, for example, increasing the memory size available to our Function to 256MB:
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";
// Define a new GET endpoint that just returns a 200 and "hello" in the body.
const api = new awsx.apigateway.API("example", {
routes: [{
path: "/",
method: "GET",
eventHandler: new aws.lambda.CallbackFunction("get-handler", {
memorySize: 256,
callback: async (event) => {
// This code runs in an AWS Lambda anytime `/` is hit.
return {
statusCode: 200,
body: "Hello, API Gateway!",
};
},
}),
}],
})
// Export the auto-generated API Gateway base URL.
export const url = api.url;
In many cases, you might want to define your application logic in a different set of files than your
API Gateway infrastructure. Using modules lets you do this easily. For instance, we might have a subdirectory
named app/
that contains a routes.js
file:
import * as awsx from "@pulumi/awsx";
// Define our routes, independent from the API Gateway itself.
export async function helloHandler(
event: awsx.apigateway.Request): Promise<awsx.apigateway.Response> {
return {
statusCode: 200,
body: "Hello, API Gateway!",
};
}
Then for our API Gateway infrastructure definition we can simply write:
import * as awsx from "@pulumi/awsx";
import * as routes from "./app/routes";
// Define a new GET endpoint that just returns a 200 and "hello" in the body.
const api = new awsx.apigateway.API("example", {
routes: [{ path: "/", method: "GET", eventHandler: routes.helloHandler }],
})
// Export the auto-generated API Gateway base URL.
export const url = api.url;
If you prefer to keep your application and infrastructure code in entirely separate projects, you can define
Lambdas in the usual way and consume them from the AWS API Gateway resources. For instance, if you already have a
Lambda Function provisioned in AWS, you can reference it using aws.lambda.Function.get
:
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";
// Define a new GET endpoint from an existing Lambda Function.
const api = new awsx.apigateway.API("example", {
routes: [{
path: "/",
method: "GET",
eventHandler: aws.lambda.Function.get("get-handler", "your_lambda_id"),
}],
})
// Export the auto-generated API Gateway base URL.
export const url = api.url;
For more complete information about creating Lambda Functions, see the Pulumi Crosswalk for AWS Lambda documentation. Any of the techniques described may be used in combination with the awsx.apigateway.API
class.
Defining a Static Route Served by S3
A Static Route serves static content from S3 at an API endpoint.
To ease population of the S3 bucket to serve from, and connecting it to the API Gateway, you specify the local path – either a file or an entire directory – and Pulumi will upload the contents as S3 objects.
Let’s say we have a directory www
containing a single index.html
file:
<h1>Hello, AWS API Gateway + S3!</h1>
The following program will create an AWS API Gateway that serves this content at the /
URL:
import * as awsx from "@pulumi/awsx";
// Define a GET endpoint that serves an entire directory of static content.
const api = new awsx.apigateway.API("example", {
routes: [{
path: "/",
localPath: "www",
}],
})
// Export the auto-generated API Gateway base URL.
export const url = api.url;
After running pulumi up
, we can curl
the resulting endpoint:
$ curl $(pulumi stack output url)
<h1>Hello, AWS API Gateway + S3!</h1>
By default, any index documents will be automatically served by S3 when directories are retrieved over HTTP. (See AWS: Configuring an Index Document.) To suppress this
behavior, simply pass index: false
as part of configuring your static route:
import * as awsx from "@pulumi/awsx";
// Define a GET endpoint that serves an entire directory of static content.
const api = new awsx.apigateway.API("example", {
routes: [{
path: "/",
localPath: "www",
index: false,
}],
})
// Export the auto-generated API Gateway base URL.
export const url = api.url;
After making this change, the earlier curl
command will return a 404:
$ curl $(pulumi stack output url)
{ "message": "404 Not found" }
We must instead request the index.html
document explicitly:
$ curl $(pulumi stack output url)/index.html
<h1>Hello, AWS API Gateway + S3!</h1>
Finally, the content type for all files in a path referencing a directory is inferred. If the local path instead
points to a single file, you can specify the content type explicitly with the contentType
property.
Defining an Integration Route
If neither of the above route types work for you, AWS API Gateway uses so-called integrations to hook up an API Gateway endpoint to backend services that will execute code in response to requests. The above examples use integrations internally, even if it’s not evident in the simple interface exposed.
Integrations give you full control over how HTTP requests are handled, and responses served, by an API Gateway
route. If you want more flexibility than the earlier methods, to proxy HTTP requests, to integrate with
AWS services other than Lambda Functions, or to mock your APIs, you can use an Integration Route simply by
specifying the target
property on your route.
An Integration Route is a route that will map an endpoint to a specified backend. The supported types are:
aws
: This type of integration lets an API expose AWS service actions, such as invoking Amazon Lambda Functions, Amazon DynamoDB, Amazon Simple Notification Service, or Amazon Simple Queue Service. You must set up the necessary data mappings between the HTTP and underlying AWS service requests/responses.aws_proxy
: This type of integration lets an API expose AWS service actions, much likeAWS
, and passes the HTTP request information, including request headers, URL path variables, query string parameters, and applicable body, directly to the underlying AWS service actions.http
: This type of integration lets an API expose HTTP endpoints with custom integration requests and responses. You must set up necessary data mappings between the HTTP and integration requests/responses.http_proxy
: This type of integration lets an API expose HTTP endpoints with a streamlined integration, without needing to perform custom data mappings as with theHTTP
integration type.mock
: This type of integration lets API Gateway return a response without sending a request further to the backend. This is useful for API testing without needing to configure any backend to service requests.
The following example sets up an http_proxy
integration type that simply passes requests/responses directly
through to another endpoint, in this case https://www.google.com
:
import * as awsx from "@pulumi/awsx";
// Define a GET endpoint that proxies HTTP requests to https://www.google.com.
const api = new awsx.apigateway.API("example", {
routes: [{
path: "/google",
target: {
type: "http_proxy",
uri: "https://www.google.com",
},
}],
});
// Export the auto-generated AWS API Gateway base URL.
export const url = api.url;
For more information AWS API Gateway Integrations, visit the AWS documentation.
Defining an OpenAPI Specification for an Entire Endpoint
AWS API Gateway supports the OpenAPI specification (formerly known as “Swagger”) for defining APIs. Using OpenAPI to define your APIs eases integration with other API authoring, modeling, and testing tools, at some added complexity cost as you will need to understand the mechanics of how API Gateway works and what HTTP headers it uses to accomplish its integrations.
To use an OpenAPI specification to initialize your API Gateway, supply an entire OpenAPI specification as a string
in the swaggerString
property. For example, this API invokes an existing Lambda whose ID is your_lambda_id
:
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";
// Look up an existing Lambda Function by ID, so that we can get its ARN. (If we knew the ARN ahead
// of time, this would not be necessary, we can just use it in the URI below.)
const handler = aws.lambda.Function.get("get-handler", "your_lambda_id");
// Define a GET endpoint that invokes our Lambda Function-based handler.
const api = new awsx.apigateway.API("example", {
swaggerString: handler.arn.apply(arn => JSON.stringify({
"swagger": "2.0",
"info": {
"title": "example",
"version": "1.0",
},
"paths": {
"/": {
"get": {
"x-amazon-apigateway-integration": {
"httpMethod": "POST",
"passthroughBehavior": "when_no_match",
"type": "aws_proxy",
"uri": `arn:aws:apigateway:us-west-2:lambda:path/2015-03-31/functions/${arn}/invocations`,
},
},
},
},
"x-amazon-apigateway-api-key-source": "HEADER",
"x-amazon-apigateway-binary-media-types": [ "*/*" ],
"x-amazon-apigateway-gateway-responses": {
"ACCESS_DENIED": {
"responseTemplates": {
"application/json": "{\"message\": \"404 Not found\" }",
},
"statusCode": 404,
},
"MISSING_AUTHENTICATION_TOKEN": {
"responseTemplates": {
"application/json": "{\"message\": \"404 Not found\" }",
},
"statusCode": 404,
},
},
})),
});
// Export the auto-generated AWS API Gateway base URL.
export const url = api.url;
This is more complex than the above examples, but this in an escape hatch that you can use to access any API Gateway features not yet supported by the easier abstractions in Pulumi Crosswalk for AWS API Gateway. You must manually provide permission for any route targets to be invoked by API Gateway when using this option.
For more information about AWS API Gateway’s support for OpenAPI, including exporting specifications from existing APIs for consumption from other tools, see Documenting a REST API in API Gateway
Defining an OpenAPI Specification for a Single Route
Being able to provide an OpenAPI specification for an entire API Gateway lets you take matters into your own
hands if you need to access a feature not supported directly by awsx.apigateway.API
. However, if you’d like to
define just a single API using OpenAPI, you can define a Raw Data Route, by supplying a data
property.
The data
property is just the x-amazon-apigateway-integration
object, which can be seen in the above example.
The route’s other parameters, such as its path and method, otherwise use the same approaches seen earlier.
For instance, the same API Gateway endpoint that invokes a Lambda Function can be authored as follows:
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";
// Look up an existing Lambda Function by ID, so that we can get its ARN. (If we knew the ARN ahead
// of time, this would not be necessary, we can just use it in the URI below.)
const handler = aws.lambda.Function.get("get-handler", "your_lambda_id");
// Define a GET endpoint that invokes our Lambda Function-based handler.
const api = new awsx.apigateway.API("example", {
routes: [{
path: "/",
method: "GET",
data: {
"x-amazon-apigateway-integration": {
"httpMethod": "POST",
"passthroughBehavior": "when_no_match",
"type": "aws_proxy",
"uri": handler.arn.apply(arn =>
`arn:aws:apigateway:us-west-2:lambda:path/2015-03-31/functions/${arn}/invocations`),
},
},
}],
});
// Export the auto-generated AWS API Gateway base URL.
export const url = api.url;
For full details on what the OpenAPI integration object may contain, refer to the full x-amazon-apigateway-integration Object documentation.
Performing AWS API Gateway Request Validation
API Gateway can perform basic validations against request parameters, a request payload or both. When a validation fails, a 400 error is returned immediately, without invoking the backend integration, and the validation results are published to the CloudWatch Logs, eliminating unnecessary calls to the backend.
For basic validation, API Gateway verifies either or both of these conditions:
- The required request parameters in the URI, query string, and headers of an incoming request are included and non-blank.
- The applicable request payload adheres to the configured JSON schema request model of the method.
When enabling validation, you will choose a validation scope, in addition to validation rules.
Assigning Validators to APIs and Methods
Validators can be assigned for an entire API or at the individual method level, such as only for POST
on a given
route. The validators defined at a method level override any validator set at the global API level.
To enable validation, pass the requestValidator
property on the API object or individual route. The following
validator values are available:
"ALL"
: Validate both the request body and request parameters."BODY_ONLY"
: Validate only the request body."PARAMS_ONLY"
: Validate only the request parameters.
For example, this enables parameter validation on all routes, and all validation on a specific route:
import * as awsx from "@pulumi/awsx";
const api = new awsx.apigateway.API("example", {
requestValidator: "PARAMS_ONLY",
routes: [
{
// ...
requestValidator: "ALL",
},
// ...
],
});
This enables validation already specified in the underlying models. The awsx.apigateway.API
class also
supports mechanisms to specify the validation rules in the API Gateway configuration.
Request Parameter Validation
To validate that a given request parameter is present in each request, use the requiredParams
route property.
This is an array where each entry defines a different parameter. Each entry specifies the parameter name
and where
it is expected to be found ("path"
, "query"
, or "header"
), using the in
property.
For example, this ensures that the key
querystring parameter is present on all requests:
import * as awsx from "@pulumi/awsx";
const api = new awsx.apigateway.API("example", {
routes: [{
// ...
requestValidator: "PARAMS_ONLY",
requiredParams: [{ name: "key", in: "query" }],
}],
})
For additional information about request validation, refer to Enable Request Validation in AWS API Gateway.
Request Body Validation
Request body validation is currently not supported. If you have a need for it, we would love to hear from you. Comment on this open issue with details about your use case.
Controlling and Managing Access to APIs
AWS API Gateway supports several mechanisms for controlling and managing access to your APIs. This includes authentication and authorization – e.g., resource policies, standard AWS IAM roles and policies, Cognito user pools, and Lambda authorizers – other access control tasks – e.g., cross-origin resource sharing (CORS), client-side SSL certificates, and Amazon Web Application Firewall (WAF) – and limiting access to authorized clients through usage plans and API keys.
The awsx.apigateway.API
class supports three specific methods of controlling access to your APIs:
- Amazon Cognito user pools let you create customizable authentication and authorization for your APIs.
- Lambda authorizers are Lambda Functions that control access to your APIs based on HTTP information available in headers, paths, query strings, or other request information, including bearer tokens.
- Usage plans let you provide API keys to customers, and then track and limit usage of your APIs.
Details on each is below. For those not directly supported, all of these capabilities are accessible to you in the AWS package, and are described in depth in the article Controlling and Managing Access to a REST API in AWS API Gateway.
Authorizing Requests using Cognito Authorizers
Cognito Authorizers allow you to use Amazon Cognito User Pools as an Authorizer for API Gateway. With a user pool, your users can sign into your web or mobile app through Amazon Cognito directly, or through social identity providers like Facebook or Amazon, or even through SAML identity providers. This enables your API Gateway to offload the difficult work of security to Cognito entirely.
To require users to sign in through Cognito, use the awsx.apigateway.getCognitoAuthorizer
function to get a
configured Authorizer object, and supply it to the authorizers
property on your route:
import * as awsx from "@pulumi/awsx";
// Create an Amazon Cognito User Pool.
const cognitoUserPool = new aws.cognito.UserPool("pool");
// Create an API that requires authorizes against the User Pool.
const apiWithCognitoAuthorizer = new awsx.apigateway.API("cognito-protected-api", {
routes: [{
path: "/www_old",
localPath: "www",
authorizers: [
awsx.apigateway.getCognitoAuthorizer({
providerARNs: [ cognitoUserPool ],
}),
],
}],
});
This will require that a user authenticate, obtain an identity/access token, and call your API with said token.
Authorizing Requests using Lambda Authorizers
Lambda Authorizers are AWS Lambda Functions that control access to an API. This allows you to use information in the request itself, including headers, paths, query parameters, or tokens, to decide whether a request is authorized to hit the backend.
You can define a Lambda Authorizer for an Event Handler Route or a Static Route. API Gateway supports request
or
token
type Lambda authorizers. A token
Lambda Authorizer uses an authorization token (i.e. a header in the form
Authorization: Token <token>
) to authorize the user, whereas a request
Lambda Authorizer uses the request
parameters (i.e. headers, path parameter or query parameters).
To define an Authorizer, you provide a Lambda that fulfills
aws.lambda.EventHandler<AuthorizerEvent, AuthorizerResponse>
or you provide information on a pre-existing Lambda
Authorizer. The example below shows defining the Authorizer Lambda directly inline. See
Pulumi Crosswalk for AWS Lambda for other ways you can define your Lambda for the Authorizer.
Creating a Lambda-based Request Authorizer
Below is an example of a custom request
Lambda Authorizer. This has access to all aspects of the request and can
make a decision based on whether to permit access based on any of it:
import * as awsx from "@pulumi/awsx";
const api = new awsx.apigateway.API("myapi", {
routes: [{
path: "/b",
method: "GET",
eventHandler: async () => {
return {
statusCode: 200,
body: "<h1>Hello world!</h1>",
};
},
authorizers: [
awsx.apigateway.getRequestLambdaAuthorizer({
queryParameters: [ "auth" ],
handler: async (event: awsx.apigateway.AuthorizerEvent) => {
// --- Add your own custom authorization logic here. ---
// Access the headers using event.headers, the query parameters using
// event.queryStringParameters or path parameters using event.pathParameters
return awsx.apigateway.authorizerResponse("user", "Deny", event.methodArn);
},
}),
],
}],
});
This example denies the request unconditionally, but you can experiment with adding your own logic that
validates the request before letting it proceed. Instead of returning "Deny"
, you should conditionally
return "Allow"
for requests that pass the custom verification criteria.
The above example used awsx.apigateway.getRequestLambdaAuthorizer
to simplify defining the authorizer. You can
instead define the authorizer by specifying all the property values explicitly:
import * as awsx from "@pulumi/awsx";
const api = new awsx.apigateway.API("myapi", {
routes: [{
...
authorizers: [{
authorizerName: "prettyAuthorizer",
parameterName: "auth",
parameterLocation: "query",
authType: "custom",
type: "request",
handler: async (event: awsx.apigateway.AuthorizerEvent) => {
// --- Add your own custom authorization logic here. ---
return awsx.apigateway.authorizerResponse(
"user",
"Deny",
event.methodArn);
},
identitySource: [ "method.request.querystring.auth" ],
}],
}],
});
If you wish to reuse an Authorizer across multiple routes, you can declare it in a variable.
For additional information about request-based AWS API Gateway Lambda Authorizers, see the AWS documentation.
Creating a Lambda-based Request Authorizer with an Existing Lambda
In the above examples, we created a new Lambda Function inline. Instead of writing a custom Lambda Authorizer, we can
use an existing Lambda elsewhere and then reference the required values. Or, if the function already exists, we can use
the aws.lambda.Function.get
function to look up the Lambda resource and use it:
import * as awsx from "@pulumi/awsx";
const apiWithAuthorizer = new awsx.apigateway.API("authorizer-api", {
routes: [{
...
authorizers: [{
authorizerName: "testing",
parameterName: "auth",
parameterLocation: "query",
authType: "custom",
type: "request",
handler: {
// Use aws.lambda.Function.get to look up an existing Lambda Function. The library
// will automatically fetch the invoke URI to pass to the Authorizer.
uri: aws.lambda.Function.get("get-handler", "your_lambda_id"),
credentials: gatewayRole.arn,
},
identitySource: [ "method.request.querystring.auth" ],
}],
}],
});
Complete example of defining the Lambda Authorizer.
Creating a Lambda-based Token Authorizer
In many cases, we will want to base our authorization decision on the absence, presence, or validity of an
Authorization: Token <token>
header instead, such as a JSON Web Token (JWT) or an OAuth token. To do that, we will
use the token
type of Lambda Authorizer.
Below is an example of a custom token
Lambda Authorizer that validates a request using such a token:
import * as awsx from "@pulumi/awsx";
const api = new awsx.apigateway.API("myapi", {
routes: [{
path: "/b",
method: "GET",
eventHandler: async () => {
return {
statusCode: 200,
body: "<h1>Hello world!</h1>",
};
},
authorizers: [
awsx.apigateway.getTokenLambdaAuthorizer({
handler: async (event: awsx.apigateway.AuthorizerEvent) => {
// --- Add your own custom authorization logic here. ---
const token = event.authorizationToken;
if (token === "Allow") {
return awsx.apigateway.authorizerResponse("user", "Allow", event.methodArn);
}
return awsx.apigateway.authorizerResponse("user", "Deny", event.methodArn);
},
}),
],
}],
});
In this case, our boolean condition token === "Allow"
is meant for illustration and should be customized
to suit your own application.
The above example used awsx.apigateway.getTokenLambdaAuthorizer
to simplify defining the authorizer. You can
instead define the authorizer by specifying all the property values explicitly:
import * as awsx from "@pulumi/awsx";
const api = new awsx.apigateway.API("myapi", {
routes: [{
path: "/b",
method: "GET",
eventHandler: async () => {
return {
statusCode: 200,
body: "<h1>Hello world!</h1>",
};
},
authorizers: [{
parameterName: "Authorization",
parameterLocation: "header",
authType: "oauth2",
type: "request",
handler: async (event: awsx.apigateway.AuthorizerEvent) => {
// Add your own custom authorization logic here.
return awsx.apigateway.authorizerResponse("user", "Allow", event.methodArn);
},
}],
}],
});
For additional information about token-based AWS API Gateway Lambda Authorizers, see the AWS documentation.
Specifying Your Authorizer’s IAM Role
If your Authorizer requires access to other AWS resources to make its authorization decisions, such as looking up entries in a database, you will need to provision the appropriate role for your AWS Lambda Function.
Instead of using inline Lambda Functions, you can do this by using new aws.lambda.CallbackFunction
:
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";
const callbackRole = new aws.iam.Role("callbackRole", {
// Specify appropriate AWS permissions here.
});
const callbackFxn = new aws.lambda.CallbackFunction("callbackFxn", {
role: callbackRole,
callback: async (event: awsx.apigateway.AuthorizerEvent) => {
// --- Add custom authorization logic here. ---
return awsx.apigateway.authorizerResponse("user", "Allow", event.methodArn);
},
});
const api = new awsx.apigateway.API("myapi", {
routes: [{
path: "/b",
method: "GET",
eventHandler: async () => {
return {
statusCode: 200,
body: "<h1>Hello world!</h1>",
};
},
authorizers: [{
parameterName: "Authorization",
parameterLocation: "header",
authType: "oauth2",
type: "request",
handler: callbackFxn,
}],
}],
});
For more information about creating and managing IAM Roles, refer to the Pulumi Crosswalk for AWS IAM documentation.
Generating Authorizer Responses Easily
A helper function awsx.apigateway.authorizerResponse
has been created to simplify generating the authorizer
response. All of the above examples use it. This can be used when defining the authorizer handler as follows:
import * as awsx from "@pulumi/awsx";
const api = new awsx.apigateway.API("myapi", {
routes: [{
...
authorizers: [{
header: "Authorization",
handler: async (event: awsx.apigateway.AuthorizerEvent) => {
// Add your own custom authorization logic here.
const token = event.authorizationToken;
if (token === "Allow") {
return awsx.apigateway.authorizerResponse("user", "Allow", event.methodArn);
}
return awsx.apigateway.authorizerResponse("user", "Deny", event.methodArn);
},
}],
}],
});
If you prefer, you can return the AuthorizerResponse data structure explicitly.
Tracking and Limiting Requests with Usage Plans with API Keys
After you create, test, and deploy your APIs, you can use AWS API Gateway usage plans to make them available to your customers. These usage plans and API keys allow customers to use your API at agreed-upon request rates and quotas that meet their business requirements and budget constraints. If desired, you can set API-level throttling limits.
To require an API Key for an API Gateway route you set the apiKeyRequired
property equal to true
. At the API level,
you can choose if you want the API Key source to be HEADER
(i.e. client includes a x-api-key
header with the API
Key) or AUTHORIZER
(i.e. a Lambda authorizer sends the API Key as part of the authorization response). If the API
Key source is not set, then the source will default to HEADER
:
import * as awsx from "@pulumi/awsx";
const api = new awsx.apigateway.API("myapi", {
routes: [{
path: "/a",
method: "GET",
eventHandler: async () => {
return {
statusCode: 200,
body: "<h1>Hello world!</h1>",
};
},
apiKeyRequired: true,
}],
apiKeySource: "AUTHORIZER",
});
You will also need to create a usage plan (new aws.apigateway.UsagePlan
) and an API key (new aws.apigateway.ApiKey
)
and then associate the key with the usage plan (new aws.apigateway.UsagePlanKey
). To simplify the creation of API
Keys associated with your API you can use awsx.apigateway.createAssociatedAPIKeys
, which create a Usage Plan, API
Keys and associates the API Keys by creating a UsagePlanKey. Below is an example of using this helper function:
import * as awsx from "@pulumi/awsx";
const apikeys = awsx.apigateway.createAssociatedAPIKeys("my-api-keys", {
apis: [api],
apiKeys: [{
name: "test-key",
}],
});
// Export the API Key if desired
export const apiKeyValue = apikeys.keys[0].apikey.value;
awsx.apigateway.createAssociatedAPIKeys
will return an object that contains the Usage Plan, API Keys and Usage Plan
Keys. Instead of providing the APIs, you can also specify the api stages for the Usage Plan as follows:
import * as awsx from "@pulumi/awsx";
const apikeys = awsx.apigateway.createAssociatedAPIKeys("my-api-keys", {
usagePlan: {
apiStages: [{
apiId: api.restAPI.id,
stage: api.stage.stageName,
}],
},
apiKeys: [{
name: "test-key",
}],
});
For more information about Usage Plans and API Keys, refer to Create and Use Usage Plans with API Keys.
Configuring AWS API Gateway Custom Domains and SSL using Route53 and ACM
AWS API Gateway will automatically provision and assign a domain name, URL that contains the stage, and SSL
support. It will look something like https://no90ji5v23.execute-api.us-west-2.amazonaws.com/stage/
. The host
portion of the URL refers to an API endpoint which can be edge-optimized or regional.
Although it’s a great convenience to have a URL automatically created with SSL support that just works right
away, the resulting URL isn’t user-friendly or very easy to remember, and may not be suitable for business
scenarios that require using company domains. To provide a simpler and more intuitive URL for your API users,
you can configure a custom domain name (e.g., api.acmecorp.com
) as the API’s host name, and customize the
base path of the URL to map to an alternative URL (e.g., one that does not include the /stage
at the end).
For example, we may map https://no90ji5v23.execute-api.us-west-2.amazonaws.com/stage/
instead to
https://api.acmecorp.com/web-ordering
. In doing so, API Gateway will also set up an edge-optimized
Amazon CloudFront Content Distribution Network (CDN).
We now will walk through the various steps required to set this up.
First, we provision an awsx.apigateway.API
in any of the ways we’ve seen above. We’ll use something simple:
import * as awsx from "@pulumi/awsx";
// Define a new GET endpoint that just returns a 200 and "hello" in the body.
const api = new awsx.apigateway.API("example", {
routes: [{
path: "/",
method: "GET",
eventHandler: async (event) => {
// This code runs in an AWS Lambda anytime `/` is hit.
return {
statusCode: 200,
body: "Hello, API Gateway!",
};
},
}],
})
Let us assume we are hard-coding the domain name (although Pulumi’s configuration system can be used instead):
const domain = "acmecorp.com";
Although API Gateway will create a regional domain name for our API, we must now explicitly configure a DNS zone and record to map the custom domain name to the regional domain name for API requests. For this example, we will use Amazon Route53’s Domain Name System (DNS) Service for this task.
Add the creation of a Route53 DNS zone:
// Create a DNS zone for our custom domain.
const webDnsZone = new aws.route53.Zone("webDnsZone", {
name: domain,
});
Next we will provision an SSL/TLS certificate to use for our custom domain name. We will use
AWS Certificate Manager (ACM) to generate
a new certificate. We could instead import one into ACM that has been issued by a third-party certificate authority.
Note that we must create this certificate in the us-east-1
(Northern Virginia) region, per AWS rules.
// Provision an SSL certificate to enable SSL -- ensuring to do so in us-east-1.
const awsUsEast1 = new aws.Provider("usEast1", { region: "us-east-1" });
const sslCert = new aws.acm.Certificate("sslCert", {
domainName: domain,
validationMethod: "DNS",
}, { provider: awsUsEast1 });
// Create the necessary DNS records for ACM to validate ownership, and wait for it.
const sslCertValidationRecord = new aws.route53.Record("sslCertValidationRecord", {
zoneId: webDnsZone.id,
name: sslCert.domainValidationOptions[0].resourceRecordName,
type: sslCert.domainValidationOptions[0].resourceRecordType,
records: [ sslCert.domainValidationOptions[0].resourceRecordValue ],
ttl: 10 * 60 /* 10 minutes */,
});
const sslCertValidationIssued = new aws.acm.CertificateValidation("sslCertValidationIssued", {
certificateArn: sslCert.arn,
validationRecordFqdns: [ sslCertValidationRecord.fqdn ],
}, { provider: awsUsEast1 });
This code snippet not only provisions the certificate, but also uses DNS validation to eliminate any manual steps, and to wait until the certificate is ready.
After this, we will direct API Gateway to use our new domain and a different base path than stage/
. Without
such a mapping, API requests bound for the custom domain name cannot reach our API Gateway.
// Configure an edge-optimized domain for our API Gateway. This will configure a Cloudfront CDN
// distribution behind the scenes and serve our API Gateway at a custom domain name over SSL.
const webDomain = new aws.apigateway.DomainName("webCdn", {
certificateArn: sslCertValidationIssued.certificateArn,
domainName: domain,
});
const webDomainMapping = new aws.apigateway.BasePathMapping("webDomainMapping", {
restApi: web.restAPI,
stageName: web.stage.stageName,
domainName: webDomain.id,
});
There is one step remaining, which is to create the A record in our DNS zone that allows external traffic to reach out new custom domain. After doing so, our AWS API Gateway will be reachable at our custom URL.
// Finally create an A record for our domain that directs to our custom domain.
const webDnsRecord = new aws.route53.Record("webDnsRecord", {
name: webDomain.domainName,
type: "A",
zoneId: webDnsZone.id,
aliases: [{
evaluateTargetHealth: true,
name: webDomain.cloudfrontDomainName,
zoneId: webDomain.cloudfrontZoneId,
}],
}, { dependsOn: sslCertValidationIssued });
For more information about the options and levels of customizability available for edge-optimized AWS API Gateways and custom domains, refer to Set up Custom Domain Name for an API in API Gateway.
Additional API Gateway Resources
For more details about AWS API Gateway and REST APIs, see the following resources: