Skip to content

Commit aa76305

Browse files
author
Elad Ben-Israel
authored
feat(aws-apigateway): "LambdaRestApi" and "addProxy" routes (#867)
`LambdaRestApi` is a higher level construct for defining Lambda-backed REST APIs: const backend = new lambda.Function(...); const api = new apigw.LambdaRestApi({ handler: backend, proxyPath: '/' }); `resource.addProxy` allows mounting a {proxy+} route on a path, including an "ANY" method integration: const api = new apigw.RestApi(this, 'api'); api.root.addProxy({ defaultIntegration: new apigw.HttpIntegration('http://foo/bar') });
1 parent 2d63a35 commit aa76305

File tree

9 files changed

+482
-3
lines changed

9 files changed

+482
-3
lines changed

packages/@aws-cdk/aws-apigateway/README.md

+53
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,45 @@ book.addMethod('GET');
2929
book.addMethod('DELETE');
3030
```
3131

32+
### AWS Lambda-backed APIs
33+
34+
A very common practice is to use Amazon API Gateway with AWS Lambda as the
35+
backend integration. The `LambdaRestApi` construct makes it easy:
36+
37+
The following code defines a REST API that uses a greedy `{proxy+}` resource
38+
mounted under `/api/v1` and integrates all methods (`"ANY"`) with the specified
39+
AWS Lambda function:
40+
41+
```ts
42+
const backend = new lambda.Function(...);
43+
new apigateway.LambdaRestApi(this, 'myapi', {
44+
handler: backend,
45+
proxyPath: '/api/v1'
46+
});
47+
```
48+
49+
If `proxyPath` is not defined, you will have to explicitly define the API model:
50+
51+
```ts
52+
const backend = new lambda.Function(...);
53+
const api = new apigateway.LambdaRestApi(this, 'myapi', {
54+
handler: backend
55+
});
56+
57+
const items = api.root.addResource('items');
58+
items.addMethod('GET'); // GET /items
59+
items.addMethod('POST'); // POST /items
60+
61+
const item = items.addResource('{item}');
62+
item.addMethod('GET'); // GET /items/{item}
63+
64+
// the default integration for methods is "handler", but one can
65+
// customize this behavior per method or even a sub path.
66+
item.addMethod('DELETE', {
67+
integration: new apigateway.HttpIntegration('http://amazon.com')
68+
});
69+
```
70+
3271
### Integration Targets
3372

3473
Methods are associated with backend integrations, which are invoked when this
@@ -95,6 +134,20 @@ const book = books.addResource('{book_id}');
95134
book.addMethod('GET'); // integrated with `booksBackend`
96135
```
97136

137+
### Proxy Routes
138+
139+
The `addProxy` method can be used to install a greedy `{proxy+}` resource
140+
on a path. By default, this also installs an `"ANY"` method:
141+
142+
```ts
143+
const proxy = resource.addProxy({
144+
defaultIntegration: new LambdaIntegration(handler),
145+
146+
// "false" will require explicitly adding methods on the `proxy` resource
147+
anyMethod: true // "true" is the default
148+
});
149+
```
150+
98151
### Deployments
99152

100153
By default, the `RestApi` construct will automatically create an API Gateway

packages/@aws-cdk/aws-apigateway/lib/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export * from './integration';
66
export * from './deployment';
77
export * from './stage';
88
export * from './integrations';
9+
export * from './lambda-api';
910

1011
// AWS::ApiGateway CloudFormation Resources:
1112
export * from './apigateway.generated';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import lambda = require('@aws-cdk/aws-lambda');
2+
import cdk = require('@aws-cdk/cdk');
3+
import { LambdaIntegration } from './integrations';
4+
import { RestApi, RestApiProps } from './restapi';
5+
6+
export interface LambdaRestApiProps {
7+
/**
8+
* The default Lambda function that handles all requests from this API.
9+
*
10+
* This handler will be used as a the default integration for all methods in
11+
* this API, unless specified otherwise in `addMethod`.
12+
*/
13+
handler: lambda.Function;
14+
15+
/**
16+
* An API path for a greedy proxy with an "ANY" method, which will route all
17+
* requests under that path to the defined handler.
18+
*
19+
* If not defined, you will need to explicitly define the API model using
20+
* `addResource` and `addMethod` (or `addProxy`).
21+
*
22+
* @default undefined
23+
*/
24+
proxyPath?: string;
25+
26+
/**
27+
* Further customization of the REST API.
28+
*
29+
* @default defaults
30+
*/
31+
options?: RestApiProps;
32+
}
33+
34+
/**
35+
* Defines an API Gateway REST API with AWS Lambda proxy integration.
36+
*
37+
* Use the `proxyPath` property to define a greedy proxy ("{proxy+}") and "ANY"
38+
* method from the specified path. If not defined, you will need to explicity
39+
* add resources and methods to the API.
40+
*/
41+
export class LambdaRestApi extends RestApi {
42+
constructor(parent: cdk.Construct, id: string, props: LambdaRestApiProps) {
43+
if (props.options && props.options.defaultIntegration) {
44+
throw new Error(`Cannot specify "options.defaultIntegration" since Lambda integration is automatically defined`);
45+
}
46+
47+
super(parent, id, {
48+
defaultIntegration: new LambdaIntegration(props.handler),
49+
...props.options
50+
});
51+
52+
// if proxyPath is specified, add a proxy at the specified path
53+
// we will need to create all resources along the path.
54+
const proxyPath = props.proxyPath;
55+
if (proxyPath) {
56+
const route = proxyPath.split('/').filter(x => x);
57+
let curr = this.root;
58+
for (const part of route) {
59+
curr = curr.addResource(part);
60+
}
61+
curr.addProxy();
62+
}
63+
}
64+
}

packages/@aws-cdk/aws-apigateway/lib/method.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@ export class Method extends cdk.Construct {
117117
*/
118118
public get methodArn(): string {
119119
if (!this.restApi.deploymentStage) {
120-
throw new Error('There is no stage associated with this restApi. Either use `autoDeploy` or explicitly assign `deploymentStage`');
120+
throw new Error(
121+
`Unable to determine ARN for method "${this.id}" since there is no stage associated with this API.\n` +
122+
'Either use the `deploy` prop or explicitly assign `deploymentStage` on the RestApi');
121123
}
122124

123125
const stage = this.restApi.deploymentStage.stageName.toString();

packages/@aws-cdk/aws-apigateway/lib/resource.ts

+52
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ export interface IRestApiResource {
4545
*/
4646
addResource(pathPart: string, options?: ResourceOptions): Resource;
4747

48+
/**
49+
* Adds a greedy proxy resource ("{proxy+}") and an ANY method to this route.
50+
* @param options Default integration and method options.
51+
*/
52+
addProxy(options?: ResourceOptions): ProxyResource;
53+
4854
/**
4955
* Defines a new method for this resource.
5056
* @param httpMethod The HTTP method
@@ -132,6 +138,52 @@ export class Resource extends cdk.Construct implements IRestApiResource {
132138
public addMethod(httpMethod: string, integration?: Integration, options?: MethodOptions): Method {
133139
return new Method(this, httpMethod, { resource: this, httpMethod, integration, options });
134140
}
141+
142+
public addProxy(options?: ResourceOptions): ProxyResource {
143+
return new ProxyResource(this, '{proxy+}', { parent: this, ...options });
144+
}
145+
}
146+
147+
export interface ProxyResourceProps extends ResourceOptions {
148+
/**
149+
* The parent resource of this resource. You can either pass another
150+
* `Resource` object or a `RestApi` object here.
151+
*/
152+
parent: IRestApiResource;
153+
154+
/**
155+
* Adds an "ANY" method to this resource. If set to `false`, you will have to explicitly
156+
* add methods to this resource after it's created.
157+
*
158+
* @default true
159+
*/
160+
anyMethod?: boolean;
161+
}
162+
163+
/**
164+
* Defines a {proxy+} greedy resource and an ANY method on a route.
165+
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html
166+
*/
167+
export class ProxyResource extends Resource {
168+
/**
169+
* If `props.anyMethod` is `true`, this will be the reference to the 'ANY'
170+
* method associated with this proxy resource.
171+
*/
172+
public readonly anyMethod?: Method;
173+
174+
constructor(parent: cdk.Construct, id: string, props: ProxyResourceProps) {
175+
super(parent, id, {
176+
parent: props.parent,
177+
pathPart: '{proxy+}',
178+
defaultIntegration: props.defaultIntegration,
179+
defaultMethodOptions: props.defaultMethodOptions,
180+
});
181+
182+
const anyMethod = props.anyMethod !== undefined ? props.anyMethod : true;
183+
if (anyMethod) {
184+
this.anyMethod = this.addMethod('ANY');
185+
}
186+
}
135187
}
136188

137189
function validateResourcePathPart(part: string) {

packages/@aws-cdk/aws-apigateway/lib/restapi.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { cloudformation } from './apigateway.generated';
44
import { Deployment } from './deployment';
55
import { Integration } from './integration';
66
import { Method, MethodOptions } from './method';
7-
import { IRestApiResource, Resource, ResourceOptions } from './resource';
7+
import { IRestApiResource, ProxyResource, Resource, ResourceOptions } from './resource';
88
import { RestApiRef } from './restapi-ref';
99
import { Stage, StageOptions } from './stage';
1010

@@ -213,6 +213,9 @@ export class RestApi extends RestApiRef implements cdk.IDependable {
213213
addMethod: (httpMethod: string, integration?: Integration, options?: MethodOptions) => {
214214
return new Method(this, httpMethod, { resource: this.root, httpMethod, integration, options });
215215
},
216+
addProxy: (options?: ResourceOptions) => {
217+
return new ProxyResource(this, '{proxy+}', { parent: this.root, ...options });
218+
},
216219
defaultIntegration: props.defaultIntegration,
217220
defaultMethodOptions: props.defaultMethodOptions,
218221
resourceApi: this,

0 commit comments

Comments
 (0)