npm i -D @alma-cdk/openapix
Generate AWS Api Gateway REST APIs via OpenAPI (a.k.a. Swagger) Schema Definitions by consuming "clean" OpenAPI schemas and inject x-amazon-apigateway-
extensions with type-safety.
🚧 Project Stability
![Stability: Experimental experimental](https://img.shields.io/badge/stability-experimental-yellow)
This construct is still versioned with v0
major version and breaking changes might be introduced if necessary (without a major version bump), though we aim to keep the API as stable as possible (even within v0
development). We aim to publish v1.0.0
soon and after that breaking changes will be introduced via major version bumps.
Getting Started
-
First, let's create some integration points:
const fn = new lambda.Function(this, "fn", {
handler: "index.handler",
runtime: lambda.Runtime.NODEJS_14_X,
code: lambda.Code.fromInline('export function handler() { return { statusCode: 200, body: JSON.stringify("hello")} }'),
});
const pkName = 'item';
const table = new dynamodb.Table(this, 'table', {
partitionKey: {
type: dynamodb.AttributeType.STRING,
name: pkName,
}
});
const role = new iam.Role(this, "role", {
assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'),
});
table.grantReadData(role);
-
Next, let's inject the integrations into an existing OpenAPI schema:
import * as openapix from '@alma-cdk/openapix';
new openapix.Api(this, 'MyApi', {
upload: false,
source: './schema.yaml',
paths: {
'/mock': {
get: new openapix.MockIntegration(this),
},
'/message': {
post: new openapix.LambdaIntegration(this, fn),
},
'/ext': {
any: new openapix.HttpIntegration(this, "https://example.com"),
},
'/item': {
get: new openapix.AwsIntegration(this, {
service: 'dynamodb',
action: 'GetItem',
options: {
credentialsRole: role,
requestTemplates: {
'application/json': JSON.stringify({
"TableName": table.tableName,
"Key": {
[pkName]: {
"S": "$input.params('item')"
}
}
}),
},
},
}),
},
},
})
Validators
API Gateway REST APIs can perform request parameter and request body validation. You can provide both default validator and integration specific validator (which will override the default for given integration).
See /examples/todo-api
for complete example within a CDK application.
Given todo-api.yaml
OpenApi schema you may define the API Gateway validators for your integration in CDK:
new openapix.Api(this, 'MyApi', {
source: path.join(__dirname, '..', '/schema/todo-api.yaml'),
validators: {
'all': {
validateRequestBody: true,
validateRequestParameters: true,
default: true,
},
'params-only' : {
validateRequestBody: false,
validateRequestParameters: true,
},
},
paths: {
'/todos': {
post: new openapix.HttpIntegration(this, baseUrl, { httpMethod: 'post' }),
},
'/todos/{todoId}': {
get: new openapix.HttpIntegration(this, `${baseUrl}/{todoId}`, {
validator: 'params-only',
options: {
requestParameters: {
'integration.request.path.todoId': 'method.request.path.todoId',
},
},
}),
},
},
})
Authorizers
There are multiple ways to control & manages access to API Gateway REST APIs such as resource policies, IAM permissions and usage plans with API keys but this section focuses on Cognito User Pools and Lambda authorizers.
Cognito Authorizers
In this example we're defining a Congito User Pool based authorizer.
Given the following schema.yaml
OpenApi definition:
openapi: 3.0.1
paths:
/:
get:
security:
- MyAuthorizer: ["test/read"]
components:
securitySchemes:
MyCognitoAuthorizer:
type: apiKey
name: Authorization
in: header
You can define the Cognito Authorizer in CDK with:
const userPool: cognito.IUserPool;
new openapix.Api(this, 'MyApi', {
source: './schema.yaml',
authorizers: [
new openapix.CognitoUserPoolsAuthorizer(this, 'MyCognitoAuthorizer', {
cognitoUserPools: [userPool],
resultsCacheTtl: Duration.minutes(5),
})
],
})
Lambda Authorizers
In this example we're defining a custom Lambda authorizer. The authorizer function code is not relevant for the example but the idea in the example is that an API caller sends some "secret code" in query parameters (?code=example123456
) which then the authorizer function somehow evaluates.
Given the following schema.yaml
OpenApi definition:
openapi: 3.0.1
paths:
/:
get:
security:
- MyAuthorizer: []
components:
securitySchemes:
MyCustomAuthorizer:
type: apiKey
name: code
in: query
You can define the custom Lambda Authorizer in CDK with:
const authFn: lambda.IFunction;
new openapix.Api(this, 'MyApi', {
source: './schema.yaml',
authorizers: [
new openapix.LambdaAuthorizer(this, 'MyCustomAuthorizer', {
fn: authFn,
identitySource: apigateway.IdentitySource.queryString('code'),
type: 'request',
authType: 'custom',
resultsCacheTtl: Duration.minutes(5),
}),
],
})
Inject/Reject
You may modify the generated OpenAPI definition (which is used to define API Gateway REST API) by injecting or rejecting values from the source OpenAPI schema definition:
new openapix.Api(this, 'MyApi', {
source: './schema.yaml',
injections: {
"info.title": "FancyPantsAPI"
},
rejections: ['info.description'],
rejectionsDeep: ['example', 'examples'],
});
CORS
Using openapix.CorsIntegration
creates a Mock integration which responds with correct response headers:
new openapix.Api(this, 'MyApi', {
source: './schema.yaml',
paths: {
'/foo': {
options: new openapix.CorsIntegration(this, {
headers: CorsHeaders.from(this, 'Content-Type', 'X-Amz-Date', 'Authorization'),
origins: CorsOrigins.from(this, 'https://www.example.com'),
methods: CorsMethods.from(this, 'options','post','get'),
}),
},
'/bar': {
options: new openapix.CorsIntegration(this, {
headers: 'Content-Type,X-Amz-Date,Authorization',
origins: '*',
methods: 'options,get',
}),
},
'/baz': {
options: new openapix.CorsIntegration(this, {
headers: CorsHeaders.ANY,
origins: CorsOrigins.ANY,
methods: CorsMethods.ANY,
}),
},
},
});
When specifying multiple origins
the mock integration uses VTL magic to respond with the correct Access-Control-Allow-Origin
header.
Default CORS
If you wish to define same CORS options to every path, you may do so by providing a default cors
value:
new openapix.Api(this, 'MyApi', {
source: './schema.yaml',
defaultCors: new openapix.CorsIntegration(this, {
headers: CorsHeaders.ANY,
origins: CorsOrigins.ANY,
methods: CorsMethods.ANY,
}),
paths: {},
});
This will apply the given cors
configuration to every path as options
method. You may still do path specific overrides by adding an options
method to specific paths.
API Gateway EndpointType
AWS CDK API Gateway constructs default to Edge-optimized API endpoints by using EndpointType.EDGE
as the default.
This construct @alma-cdk/openapix
instead defaults to using Regional API endpoints by setting EndpointType.REGIONAL
as the default value. This is because we believe that in most cases you're better of by configuring your own CloudFront distribution in front the API. If you do that, you might also be interested in @alma-cdk/origin-verify
construct.
You MAY override this default in @alma-cdk/openapix
by providing your preferred endpoint types via restApiProps
:
new openapix.Api(this, 'MyApi', {
source: './schema.yaml',
paths: {},
restApiProps: {
endpointConfiguration: {
types: [ apigateway.EndpointType.EDGE ],
},
},
});