Skip to content

Commit

Permalink
feat(aws-apigateway): "LambdaRestApi" and "addProxy" routes (#867)
Browse files Browse the repository at this point in the history
`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')
    });
  • Loading branch information
Elad Ben-Israel authored Oct 8, 2018
1 parent 2d63a35 commit aa76305
Show file tree
Hide file tree
Showing 9 changed files with 482 additions and 3 deletions.
53 changes: 53 additions & 0 deletions packages/@aws-cdk/aws-apigateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,45 @@ 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 uses a greedy `{proxy+}` resource
mounted under `/api/v1` and integrates all methods (`"ANY"`) with the specified
AWS Lambda function:

```ts
const backend = new lambda.Function(...);
new apigateway.LambdaRestApi(this, 'myapi', {
handler: backend,
proxyPath: '/api/v1'
});
```

If `proxyPath` is not defined, you will have to explicitly define the API model:

```ts
const backend = new lambda.Function(...);
const api = new apigateway.LambdaRestApi(this, 'myapi', {
handler: backend
});

const items = api.root.addResource('items');
items.addMethod('GET'); // GET /items
items.addMethod('POST'); // POST /items

const item = items.addResource('{item}');
item.addMethod('GET'); // GET /items/{item}

// the default integration for methods is "handler", but one can
// customize this behavior per method or even a sub path.
item.addMethod('DELETE', {
integration: new apigateway.HttpIntegration('http://amazon.com')
});
```

### Integration Targets

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

### 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:

```ts
const proxy = resource.addProxy({
defaultIntegration: new LambdaIntegration(handler),

// "false" will require explicitly adding methods on the `proxy` resource
anyMethod: true // "true" is the default
});
```

### Deployments

By default, the `RestApi` construct will automatically create an API Gateway
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-apigateway/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './integration';
export * from './deployment';
export * from './stage';
export * from './integrations';
export * from './lambda-api';

// AWS::ApiGateway CloudFormation Resources:
export * from './apigateway.generated';
64 changes: 64 additions & 0 deletions packages/@aws-cdk/aws-apigateway/lib/lambda-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import lambda = require('@aws-cdk/aws-lambda');
import cdk = require('@aws-cdk/cdk');
import { LambdaIntegration } from './integrations';
import { RestApi, RestApiProps } from './restapi';

export interface LambdaRestApiProps {
/**
* The default Lambda function that handles all requests from this API.
*
* This handler will be used as a the default integration for all methods in
* this API, unless specified otherwise in `addMethod`.
*/
handler: lambda.Function;

/**
* An API path for a greedy proxy with an "ANY" method, which will route all
* requests under that path to the defined handler.
*
* If not defined, you will need to explicitly define the API model using
* `addResource` and `addMethod` (or `addProxy`).
*
* @default undefined
*/
proxyPath?: string;

/**
* Further customization of the REST API.
*
* @default defaults
*/
options?: RestApiProps;
}

/**
* Defines an API Gateway REST API with AWS Lambda proxy integration.
*
* Use the `proxyPath` property to define a greedy proxy ("{proxy+}") and "ANY"
* method from the specified path. If not defined, you will need to explicity
* add resources and methods to the API.
*/
export class LambdaRestApi extends RestApi {
constructor(parent: cdk.Construct, id: string, props: LambdaRestApiProps) {
if (props.options && props.options.defaultIntegration) {
throw new Error(`Cannot specify "options.defaultIntegration" since Lambda integration is automatically defined`);
}

super(parent, id, {
defaultIntegration: new LambdaIntegration(props.handler),
...props.options
});

// if proxyPath is specified, add a proxy at the specified path
// we will need to create all resources along the path.
const proxyPath = props.proxyPath;
if (proxyPath) {
const route = proxyPath.split('/').filter(x => x);
let curr = this.root;
for (const part of route) {
curr = curr.addResource(part);
}
curr.addProxy();
}
}
}
4 changes: 3 additions & 1 deletion packages/@aws-cdk/aws-apigateway/lib/method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@ export class Method extends cdk.Construct {
*/
public get methodArn(): string {
if (!this.restApi.deploymentStage) {
throw new Error('There is no stage associated with this restApi. Either use `autoDeploy` or explicitly assign `deploymentStage`');
throw new Error(
`Unable to determine ARN for method "${this.id}" since there is no stage associated with this API.\n` +
'Either use the `deploy` prop or explicitly assign `deploymentStage` on the RestApi');
}

const stage = this.restApi.deploymentStage.stageName.toString();
Expand Down
52 changes: 52 additions & 0 deletions packages/@aws-cdk/aws-apigateway/lib/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ export interface IRestApiResource {
*/
addResource(pathPart: string, options?: ResourceOptions): Resource;

/**
* Adds a greedy proxy resource ("{proxy+}") and an ANY method to this route.
* @param options Default integration and method options.
*/
addProxy(options?: ResourceOptions): ProxyResource;

/**
* Defines a new method for this resource.
* @param httpMethod The HTTP method
Expand Down Expand Up @@ -132,6 +138,52 @@ export class Resource extends cdk.Construct implements IRestApiResource {
public addMethod(httpMethod: string, integration?: Integration, options?: MethodOptions): Method {
return new Method(this, httpMethod, { resource: this, httpMethod, integration, options });
}

public addProxy(options?: ResourceOptions): ProxyResource {
return new ProxyResource(this, '{proxy+}', { parent: this, ...options });
}
}

export interface ProxyResourceProps extends ResourceOptions {
/**
* The parent resource of this resource. You can either pass another
* `Resource` object or a `RestApi` object here.
*/
parent: IRestApiResource;

/**
* Adds an "ANY" method to this resource. If set to `false`, you will have to explicitly
* add methods to this resource after it's created.
*
* @default true
*/
anyMethod?: boolean;
}

/**
* Defines a {proxy+} greedy resource and an ANY method on a route.
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html
*/
export class ProxyResource extends Resource {
/**
* If `props.anyMethod` is `true`, this will be the reference to the 'ANY'
* method associated with this proxy resource.
*/
public readonly anyMethod?: Method;

constructor(parent: cdk.Construct, id: string, props: ProxyResourceProps) {
super(parent, id, {
parent: props.parent,
pathPart: '{proxy+}',
defaultIntegration: props.defaultIntegration,
defaultMethodOptions: props.defaultMethodOptions,
});

const anyMethod = props.anyMethod !== undefined ? props.anyMethod : true;
if (anyMethod) {
this.anyMethod = this.addMethod('ANY');
}
}
}

function validateResourcePathPart(part: string) {
Expand Down
5 changes: 4 additions & 1 deletion packages/@aws-cdk/aws-apigateway/lib/restapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { cloudformation } from './apigateway.generated';
import { Deployment } from './deployment';
import { Integration } from './integration';
import { Method, MethodOptions } from './method';
import { IRestApiResource, Resource, ResourceOptions } from './resource';
import { IRestApiResource, ProxyResource, Resource, ResourceOptions } from './resource';
import { RestApiRef } from './restapi-ref';
import { Stage, StageOptions } from './stage';

Expand Down Expand Up @@ -213,6 +213,9 @@ export class RestApi extends RestApiRef implements cdk.IDependable {
addMethod: (httpMethod: string, integration?: Integration, options?: MethodOptions) => {
return new Method(this, httpMethod, { resource: this.root, httpMethod, integration, options });
},
addProxy: (options?: ResourceOptions) => {
return new ProxyResource(this, '{proxy+}', { parent: this.root, ...options });
},
defaultIntegration: props.defaultIntegration,
defaultMethodOptions: props.defaultMethodOptions,
resourceApi: this,
Expand Down
Loading

0 comments on commit aa76305

Please sign in to comment.