Share AWS API Gateway Resources in Serverless Framework

As your application grows, you will likely need to break it out into multiple, smaller services. By default, each Serverless project generates a new API Gateway. If you design backend system in microservices, there is really painful. Imagine that there are 10 API Gateways for 1 application. It is painful to monitor and increase the cost

By the time I writed this article, Serverless didn't support shared API Gateway. There is a Pull Request for this. However, it is so simple and limited that don't fit my needs. The critical point is, for example, I have a path /users in service A, and I want to define a subpath /user/{id}/jobs in service B. This PR don't work. So I decide to do another PR for this feature

Update Jan 2018: This PR was merged. You can officially use it from version 1.26.0 Update May 2018: Share Authorizer PR was marge

Serverless Template

You can share the same API Gateway between multiple projects by referencing its REST API ID and Root Resource ID in serverless.yml as follows:

service: service-name
provider:
name: aws
apiGateway:
restApiId: xxxxxxxxxx # REST API resource ID. Default is generated by the framework
restApiRootResourceId: xxxxxxxxxx # Root resource, represent as / path

functions:
...

If your application has many nested paths, you might also want to break them out into smaller services.

service: service-a
provider:
apiGateway:
restApiId: xxxxxxxxxx
restApiRootResourceId: xxxxxxxxxx

functions:
create:
handler: posts.create
events:
- http:
method: post
path: /posts
service: service-b
provider:
apiGateway:
restApiId: xxxxxxxxxx
restApiRootResourceId: xxxxxxxxxx

functions:
create:
handler: posts.createComment
events:
- http:
method: post
path: /posts/{id}/comments

The above example services both reference the same parent path /posts. However, Cloudformation will throw an error if we try to generate an existing path resource. To avoid that, we reference the resource ID of /posts:

service: service-a
provider:
apiGateway:
restApiId: xxxxxxxxxx
restApiRootResourceId: xxxxxxxxxx
restApiResources:
/posts: xxxxxxxxxx

functions:
...
service: service-b
provider:
apiGateway:
restApiId: xxxxxxxxxx
restApiRootResourceId: xxxxxxxxxx
restApiResources:
/posts: xxxxxxxxxx

functions:
...

You can define more than one path resource, but by default, Serverless will generate them from the root resource. restApiRootResourceId is optional if a path resource isn't required for the root (/).

service: service-a
provider:
apiGateway:
restApiId: xxxxxxxxxx
# restApiRootResourceId: xxxxxxxxxx # Optional
restApiResources:
/posts: xxxxxxxxxx
/categories: xxxxxxxxx


functions:
listPosts:
handler: posts.list
events:
- http:
method: get
path: /posts

listCategories:
handler: categories.list
events:
- http:
method: get
path: /categories

Manually Configuring shared API Gateway

Use AWS console on browser, navigate to the API Gateway console. Select your already existing API Gateway. Top Navbar should look like this

    APIs>apigateway-Name (xxxxxxxxxx)>Resources>/ (yyyyyyyyyy)

Here xxxxxxxxx is your restApiId and yyyyyyyyyy the restApiRootResourceId.

Note while using authorizers with shared API Gateway

AWS API Gateway allows only 1 Authorizer for 1 ARN, This is okay when you use conventional serverless setup, because each stage and service will create different API Gateway. But this can cause problem when using authorizers with shared API Gateway. If we use the same authorizer directly in different services like this.

service: service-c

provider:
apiGateway:
restApiId:
'Fn::ImportValue': apiGateway-restApiId
restApiRootResourceId:
'Fn::ImportValue': apiGateway-rootResourceId

functions:
deleteUser:
events:
- http:
path: /users/{userId}
authorizer:
arn: xxxxxxxxxxxxxxxxx #cognito/custom authorizer arn
service: service-d

provider:
apiGateway:
restApiId:
'Fn::ImportValue': apiGateway-restApiId
restApiRootResourceId:
'Fn::ImportValue': apiGateway-rootResourceId

functions:
deleteProject:
events:
- http:
path: /project/{projectId}
authorizer:
arn: xxxxxxxxxxxxxxxxx #cognito/custom authorizer arn

we encounter error from cloudformation as reported here.

A proper fix for this is work is using Share Authorizer or you can add a unique name attribute to authorizer in each function. This creates different API Gateway authorizer for each function, bound to the same API Gateway. However, there is a limit of 10 authorizers per RestApi, and they are forced to contact AWS to request a limit increase to unblock development.

Share Authorizer

Auto-created Authorizer is convenient for conventional setup. However, when you need to define your custom Authorizer, or use COGNITO_USER_POOLS authorizer with shared API Gateway, it is painful because of AWS limitation. Sharing Authorizer is a better way to do.

functions:
createUser:
...
events:
- http:
path: /users
...
authorizer:
# Provide both type and authorizerId
type: COGNITO_USER_POOLS # TOKEN or COGNITO_USER_POOLS, same as AWS Cloudformation documentation
authorizerId:
Ref: ApiGatewayAuthorizer # or hard-code Authorizer ID

deleteUser:
...
events:
- http:
path: /users/{userId}
...
# Provide both type and authorizerId
type: COGNITO_USER_POOLS # TOKEN or COGNITO_USER_POOLS, same as AWS Cloudformation documentation
authorizerId:
Ref: ApiGatewayAuthorizer # or hard-code Authorizer ID

resources:
Resources:
ApiGatewayAuthorizer:
Type: AWS::ApiGateway::Authorizer
Properties:
AuthorizerResultTtlInSeconds: 300
IdentitySource: method.request.header.Authorization
Name: Cognito
RestApiId:
Ref: YourApiGatewayName
Type: COGNITO_USER_POOLS
ProviderARNs:
- arn:aws:cognito-idp:${self:provider.region}:xxxxxx:userpool/abcdef