Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(aws-apigateway): "LambdaRestApi" and "addProxy" routes #867

Merged
merged 1 commit into from
Oct 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'
eladb marked this conversation as resolved.
Show resolved Hide resolved
});
```

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