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