Edit on github

#API Gateway

Are you looking for tutorials on using API Gateway? Check out the following resources:

To create HTTP endpoints as Event sources for your AWS Lambda Functions, use the Serverless Framework's easy AWS API Gateway Events syntax.

There are five ways you can configure your HTTP endpoints to integrate with your AWS Lambda Functions:

  • lambda-proxy / aws-proxy / aws_proxy (Recommended)
  • lambda / aws
  • http
  • http-proxy / http_proxy
  • mock

The Framework uses the lambda-proxy method (i.e., everything is passed into your Lambda) by default unless another method is supplied by the user

The difference between these is lambda-proxy (alternative writing styles are aws-proxy and aws_proxy for compatibility with the standard AWS integration type naming) automatically passes the content of the HTTP request into your AWS Lambda function (headers, body, etc.) and allows you to configure your response (headers, status code, body) in the code of your AWS Lambda Function. Whereas, the lambda method makes you explicitly define headers, status codes, and more in the configuration of each API Gateway Endpoint (not in code). We highly recommend using the lambda-proxy method if it supports your use-case, since the lambda method is highly tedious.

Use http for integrating with an HTTP back end, http-proxy for integrating with the HTTP proxy integration or mock for testing without actually invoking the back end.

#Lambda Proxy Integration

#Simple HTTP Endpoint

This setup specifies that the hello function should be run when someone accesses the API gateway at hello via a GET request.

Here's an example:

# serverless.yml

functions:
  index:
    handler: handler.hello
    events:
      - http: GET hello
// handler.js

'use strict';

module.exports.hello = function(event, context, callback) {

    console.log(event); // Contains incoming request data (e.g., query params, headers and more)

    const response = {
      statusCode: 200,
      headers: {
        "x-custom-header" : "My Header Value"
      },
      body: JSON.stringify({ "message": "Hello World!" })
    };

    callback(null, response);
};

Note: When the body is a JSON-Document, you must parse it yourself:

JSON.parse(event.body);

#Example "LAMBDA-PROXY" event (default)

{
    "resource": "/",
    "path": "/",
    "httpMethod": "POST",
    "headers": {
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "Accept-Encoding": "gzip, deflate, br",
        "Accept-Language": "en-GB,en-US;q=0.8,en;q=0.6,zh-CN;q=0.4",
        "cache-control": "max-age=0",
        "CloudFront-Forwarded-Proto": "https",
        "CloudFront-Is-Desktop-Viewer": "true",
        "CloudFront-Is-Mobile-Viewer": "false",
        "CloudFront-Is-SmartTV-Viewer": "false",
        "CloudFront-Is-Tablet-Viewer": "false",
        "CloudFront-Viewer-Country": "GB",
        "content-type": "application/x-www-form-urlencoded",
        "Host": "j3ap25j034.execute-api.eu-west-2.amazonaws.com",
        "origin": "https://j3ap25j034.execute-api.eu-west-2.amazonaws.com",
        "Referer": "https://j3ap25j034.execute-api.eu-west-2.amazonaws.com/dev/",
        "upgrade-insecure-requests": "1",
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
        "Via": "2.0 a3650115c5e21e2b5d133ce84464bea3.cloudfront.net (CloudFront)",
        "X-Amz-Cf-Id": "0nDeiXnReyHYCkv8cc150MWCFCLFPbJoTs1mexDuKe2WJwK5ANgv2A==",
        "X-Amzn-Trace-Id": "Root=1-597079de-75fec8453f6fd4812414a4cd",
        "X-Forwarded-For": "50.129.117.14, 50.112.234.94",
        "X-Forwarded-Port": "443",
        "X-Forwarded-Proto": "https"
    },
    "queryStringParameters": null,
    "pathParameters": null,
    "stageVariables": null,
    "requestContext": {
        "path": "/dev/",
        "accountId": "125002137610",
        "resourceId": "qdolsr1yhk",
        "stage": "dev",
        "requestId": "0f2431a2-6d2f-11e7-b75152aa497861",
        "identity": {
            "cognitoIdentityPoolId": null,
            "accountId": null,
            "cognitoIdentityId": null,
            "caller": null,
            "apiKey": "",
            "sourceIp": "50.129.117.14",
            "accessKey": null,
            "cognitoAuthenticationType": null,
            "cognitoAuthenticationProvider": null,
            "userArn": null,
            "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
            "user": null
        },
        "resourcePath": "/",
        "httpMethod": "POST",
        "apiId": "j3azlsj0c4"
    },
    "body": "postcode=LS17FR",
    "isBase64Encoded": false
}

#HTTP Endpoint with Extended Options

Here we've defined an POST endpoint for the path posts/create.

# serverless.yml

functions:
  create:
    handler: posts.create
    events:
      - http:
          path: posts/create
          method: post

#Enabling CORS

To set CORS configurations for your HTTP endpoints, simply modify your event configurations as follows:

# serverless.yml

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: get
          cors: true

Setting cors to true assumes a default configuration which is equivalent to:

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: get
          cors:
            origin: '*'
            headers:
              - Content-Type
              - X-Amz-Date
              - Authorization
              - X-Api-Key
              - X-Amz-Security-Token
              - X-Amz-User-Agent
            allowCredentials: false

Configuring the cors property sets Access-Control-Allow-Origin, Access-Control-Allow-Headers, Access-Control-Allow-Methods,Access-Control-Allow-Credentials headers in the CORS preflight response.

To enable the Access-Control-Max-Age preflight response header, set the maxAge property in the cors object:

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: get
          cors:
            origin: '*'
            maxAge: 86400

If you want to use CORS with the lambda-proxy integration, remember to include the Access-Control-Allow-* headers in your headers object, like this:

// handler.js

'use strict';

module.exports.hello = function(event, context, callback) {

    const response = {
      statusCode: 200,
      headers: {
        "Access-Control-Allow-Origin" : "*", // Required for CORS support to work
        "Access-Control-Allow-Credentials" : true // Required for cookies, authorization headers with HTTPS
      },
      body: JSON.stringify({ "message": "Hello World!" })
    };

    callback(null, response);
};

#HTTP Endpoints with AWS_IAM Authorizers

If you want to require that the caller submit the IAM user's access keys in order to be authenticated to invoke your Lambda Function, set the authorizer to AWS_IAM as shown in the following example:

functions:
  create:
    handler: posts.create
    events:
      - http:
          path: posts/create
          method: post
          authorizer: aws_iam

Which is the short hand notation for:

functions:
  create:
    handler: posts.create
    events:
      - http:
          path: posts/create
          method: post
          authorizer:
            type: aws_iam

#HTTP Endpoints with Custom Authorizers

Custom Authorizers allow you to run an AWS Lambda Function before your targeted AWS Lambda Function. This is useful for Microservice Architectures or when you simply want to do some Authorization before running your business logic.

You can enable Custom Authorizers for your HTTP endpoint by setting the Authorizer in your http event to another function in the same service, as shown in the following example:

functions:
  create:
    handler: posts.create
    events:
      - http:
          path: posts/create
          method: post
          authorizer: authorizerFunc
  authorizerFunc:
    handler: handler.authorizerFunc

Or, if you want to configure the Authorizer with more options, you can turn the authorizer property into an object as shown in the following example:

functions:
  create:
    handler: posts.create
    events:
      - http:
          path: posts/create
          method: post
          authorizer:
            name: authorizerFunc
            resultTtlInSeconds: 0
            identitySource: method.request.header.Authorization
            identityValidationExpression: someRegex
            type: token
  authorizerFunc:
    handler: handler.authorizerFunc

If the Authorizer function does not exist in your service but exists in AWS, you can provide the ARN of the Lambda function instead of the function name, as shown in the following example:

functions:
  create:
    handler: posts.create
    events:
      - http:
          path: posts/create
          method: post
          authorizer: xxx:xxx:Lambda-Name

Or, if you want to configure the Authorizer with more options, you can turn the authorizer property into an object as shown in the following example:

functions:
  create:
    handler: posts.create
    events:
      - http:
          path: posts/create
          method: post
          authorizer:
            arn: xxx:xxx:Lambda-Name
            resultTtlInSeconds: 0
            identitySource: method.request.header.Authorization
            identityValidationExpression: someRegex

You can also use the Request Type Authorizer by setting the type property. In this case, your identitySource could contain multiple entries for your policy cache. The default type is 'token'.

functions:
  create:
    handler: posts.create
    events:
      - http:
          path: posts/create
          method: post
          authorizer:
            arn: xxx:xxx:Lambda-Name
            resultTtlInSeconds: 0
            identitySource: method.request.header.Authorization, context.identity.sourceIp
            identityValidationExpression: someRegex
            type: request

You can also configure an existing Cognito User Pool as the authorizer, as shown in the following example:

functions:
  create:
    handler: posts.create
    events:
      - http:
          path: posts/create
          method: post
          authorizer:
            arn: arn:aws:cognito-idp:us-east-1:xxx:userpool/us-east-1_ZZZ

If you are using the default lambda-proxy integration, your attributes will be exposed at event.requestContext.authorizer.claims.

If you want more control over which attributes are exposed as claims you can switch to integration: lambda and add the following configuration. The claims will be exposed at events.cognitoPoolClaims.

functions:
  create:
    handler: posts.create
    events:
      - http:
          path: posts/create
          method: post
          integration: lambda
          authorizer:
            arn: arn:aws:cognito-idp:us-east-1:xxx:userpool/us-east-1_ZZZ
            claims:
              - email
              - nickname

#Catching Exceptions In Your Lambda Function

In case an exception is thrown in your lambda function AWS will send an error message with Process exited before completing request. This will be caught by the regular expression for the 500 HTTP status and the 500 status will be returned.

#Setting API keys for your Rest API

You can specify a list of API keys to be used by your service Rest API by adding an apiKeys array property to the provider object in serverless.yml. You'll also need to explicitly specify which endpoints are private and require one of the api keys to be included in the request by adding a private boolean property to the http event object you want to set as private. API Keys are created globally, so if you want to deploy your service to different stages make sure your API key contains a stage variable as defined below. When using API keys, you can optionally define usage plan quota and throttle, using usagePlan object.

Here's an example configuration for setting API keys for your service Rest API:

service: my-service
provider:
  name: aws
  apiKeys:
    - myFirstKey
    - ${opt:stage}-myFirstKey
    - ${env:MY_API_KEY} # you can hide it in a serverless variable
  usagePlan:
    quota:
      limit: 5000
      offset: 2
      period: MONTH
    throttle:
      burstLimit: 200
      rateLimit: 100
functions:
  hello:
    events:
      - http:
          path: user/create
          method: get
          private: true

Please note that those are the API keys names, not the actual values. Once you deploy your service, the value of those API keys will be auto generated by AWS and printed on the screen for you to use. The values can be concealed from the output with the --conceal deploy option.

Clients connecting to this Rest API will then need to set any of these API keys values in the x-api-key header of their request. This is only necessary for functions where the private property is set to true.

#Configuring endpoint types

API Gateway supports regional endpoints for associating your API Gateway REST APIs with a particular region. This can reduce latency if your requests originate from the same region as your REST API and can be helpful in building multi-region applications.

By default, the Serverless Framework deploys your REST API using the EDGE endpoint configuration. If you would like to use the REGIONAL or PRIVATE configuration, set the endpointType parameter in your provider block.

Here's an example configuration for setting the endpoint configuration for your service Rest API:

service: my-service
provider:
  name: aws
  endpointType: REGIONAL
functions:
  hello:
    events:
      - http:
          path: user/create
          method: get

#Request Parameters

To pass optional and required parameters to your functions, so you can use them in API Gateway tests and SDK generation, marking them as true will make them required, false will make them optional.

functions:
  create:
    handler: posts.create
    events:
      - http:
          path: posts/create
          method: post
          request:
            parameters:
              querystrings:
                url: true
              headers:
                foo: false
              paths:
                bar: false

In order for path variables to work, API Gateway also needs them in the method path itself, like so:

functions:
  create:
    handler: posts.post_detail
    events:
      - http:
          path: posts/{id}
          method: get
          request:
            parameters:
              paths:
                id: true

#Lambda Integration

This method is more complicated and involves a lot more configuration of the http event syntax.

#Example "LAMBDA" event (before customization)

Refer to this only if you're using the non-default LAMBDA integration method

{
    "body": {},
    "method": "GET",
    "principalId": "",
    "stage": "dev",
    "cognitoPoolClaims": {
        "sub": ""
    },
    "enhancedAuthContext": {},
    "headers": {
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "Accept-Encoding": "gzip, deflate, br",
        "Accept-Language": "en-GB,en-US;q=0.8,en;q=0.6,zh-CN;q=0.4",
        "CloudFront-Forwarded-Proto": "https",
        "CloudFront-Is-Desktop-Viewer": "true",
        "CloudFront-Is-Mobile-Viewer": "false",
        "CloudFront-Is-SmartTV-Viewer": "false",
        "CloudFront-Is-Tablet-Viewer": "false",
        "CloudFront-Viewer-Country": "GB",
        "Host": "ec5ycylws8.execute-api.us-east-1.amazonaws.com",
        "upgrade-insecure-requests": "1",
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
        "Via": "2.0 f165ce34daf8c0da182681179e863c24.cloudfront.net (CloudFront)",
        "X-Amz-Cf-Id": "l06CAg2QsrALeQcLAUSxGXbm8lgMoMIhR2AjKa4AiKuaVnnGsOFy5g==",
        "X-Amzn-Trace-Id": "Root=1-5970ef3e249c0321b2eef14aa513ae",
        "X-Forwarded-For": "94.117.120.169, 116.132.62.73",
        "X-Forwarded-Port": "443",
        "X-Forwarded-Proto": "https"
    },
    "query": {},
    "path": {},
    "identity": {
        "cognitoIdentityPoolId": "",
        "accountId": "",
        "cognitoIdentityId": "",
        "caller": "",
        "apiKey": "",
        "sourceIp": "94.197.120.169",
        "accessKey": "",
        "cognitoAuthenticationType": "",
        "cognitoAuthenticationProvider": "",
        "userArn": "",
        "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
        "user": ""
    },
    "stageVariables": {}
}

#Request templates

#Default Request Templates

Serverless ships with the following default request templates you can use out of the box:

  1. application/json
  2. application/x-www-form-urlencoded

Both templates give you access to the following properties you can access with the help of the event object:

  • body
  • method
  • principalId
  • stage
  • headers
  • queryStringParameters
  • path
  • identity
  • stageVariables

#Custom Request Templates

However you can define and use your own request templates as follows (you can even overwrite the default request templates by defining a new request template for an existing content type):

functions:
  create:
    handler: posts.create
    events:
      - http:
          method: get
          path: whatever
          integration: lambda
          request:
            template:
              text/xhtml: '{ "stage" : "$context.stage" }'
              application/json: '{ "httpMethod" : "$context.httpMethod" }'

Note: The templates are defined as plain text here. However you can also reference an external file with the help of the ${file(templatefile)} syntax.

Note 2: In .yml, strings containing :, {, }, [, ], ,, &, *, #, ?, |, -, <, >, =, !, %, @, ` must be quoted.

If you want to map querystrings to the event object, you can use the $input.params('hub.challenge') syntax from API Gateway, as follows:

functions:
  create:
    handler: posts.create
    events:
      - http:
          method: get
          path: whatever
          integration: lambda
          request:
            template:
              application/json: '{ "foo" : "$input.params(''bar'')" }'

Note: Notice when using single-quoted strings, any single quote ' inside its contents must be doubled ('') to escape it. You can then access the query string https://example.com/dev/whatever?bar=123 by event.foo in the lambda function. If you want to spread a string into multiple lines, you can use the > or | syntax, but the following strings have to be all indented with the same amount, read more about > syntax.

#Pass Through Behavior

API Gateway provides multiple ways to handle requests where the Content-Type header does not match any of the specified mapping templates. When this happens, the request payload will either be passed through the integration request without transformation or rejected with a 415 - Unsupported Media Type, depending on the configuration.

You can define this behavior as follows (if not specified, a value of NEVER will be used):

functions:
  create:
    handler: posts.create
    events:
      - http:
          method: get
          path: whatever
          integration: lambda
          request:
            passThrough: NEVER

There are 3 available options:

Value Passed Through When Rejected When
NEVER Never No templates defined or Content-Type does not match a defined template
WHEN_NO_MATCH Content-Type does not match defined template Never
WHEN_NO_TEMPLATES No templates were defined One or more templates defined, but Content-Type does not match

See the api gateway documentation for detailed descriptions of these options.

Notes:

  • A missing/empty request Content-Type is considered to be the API Gateway default (application/json)
  • API Gateway docs refer to "WHEN_NO_TEMPLATE" (singular), but this will fail during creation as the actual value should be "WHEN_NO_TEMPLATES" (plural)

#Responses

Serverless lets you setup custom headers and a response template for your http event.

#Custom Response Headers

Here's an example which shows you how you can setup a custom response header:

functions:
  create:
    handler: posts.create
    events:
      - http:
          method: get
          path: whatever
          integration: lambda
          response:
            headers:
              Content-Type: integration.response.header.Content-Type
              Cache-Control: "'max-age=120'"

Note: You're able to use the integration response variables for your header values. Headers are passed to API Gateway exactly like you define them. Passing the Cache-Control header as "'max-age=120'" means API Gateway will receive the value as 'max-age=120' (enclosed with single quotes).

#Custom Response Templates

Sometimes you'll want to define a custom response template API Gateway should use to transform your lambdas output. Here's an example which will transform the return value of your lambda so that the browser renders it as HTML:

functions:
  create:
    handler: posts.create
    events:
      - http:
          method: get
          path: whatever
          integration: lambda
          response:
            headers:
              Content-Type: "'text/html'"
            template: $input.path('$')

Note: The template is defined as plain text here. However you can also reference an external file with the help of the ${file(templatefile)} syntax.

#Status Codes

Serverless ships with default status codes you can use to e.g. signal that a resource could not be found (404) or that the user is not authorized to perform the action (401). Those status codes are regex definitions that will be added to your API Gateway configuration.

Note: Status codes as documented in this chapter relate to lambda integration method (as documented at the top of this page). If using default integration method lambda-proxy object with status code and message should be returned as in the example below:

module.exports.hello = (event, context, callback) => {
  callback(null, { statusCode: 404, body: "Not found", headers: { "Content-Type": "text/plain" } });
}

#Available Status Codes

Status Code Meaning
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
422 Unprocessable Entity
500 Internal Server Error
502 Bad Gateway
504 Gateway Timeout

#Using Status Codes

To return a given status code you simply need to add square brackets with the status code of your choice to your returned message like this: [401] You are not authorized to access this resource!.

Here's an example which shows you how you can raise a 404 HTTP status from within your lambda function.

module.exports.hello = (event, context, callback) => {
  callback(new Error('[404] Not found'));
}

#Custom Status Codes

You can override the defaults status codes supplied by Serverless. You can use this to change the default status code, add/remove status codes, or change the templates and headers used for each status code. Use the pattern key to change the selection process that dictates what code is returned.

If you specify a status code with a pattern of '' that will become the default response code. See below on how to change the default to 201 for post requests.

If you omit any default status code. A standard default 200 status code will be generated for you.

functions:
  create:
    handler: posts.create
    events:
      - http:
          method: post
          path: whatever
          integration: lambda
          response:
            headers:
              Content-Type: "'text/html'"
            template: $input.path('$')
            statusCodes:
                201:
                    pattern: '' # Default response method
                409:
                    pattern: '.*"statusCode":409,.*' # JSON response
                    template: $input.path("$.errorMessage") # JSON return object
                    headers:
                      Content-Type: "'application/json+hal'"

You can also create varying response templates for each code and content type by creating an object with the key as the content type

functions:
  create:
    handler: posts.create
    events:
      - http:
          method: post
          path: whatever
          integration: lambda
          response:
            headers:
              Content-Type: "'text/html'"
            template: $input.path('$')
            statusCodes:
                201:
                    pattern: '' # Default response method
                409:
                    pattern: '.*"statusCode":409,.*' # JSON response
                    template:
                      application/json: $input.path("$.errorMessage") # JSON return object
                      application/xml: $input.path("$.body.errorMessage") # XML return object
                    headers:
                      Content-Type: "'application/json+hal'"

#Setting an HTTP Proxy on API Gateway

To set up an HTTP proxy, you'll need two CloudFormation templates, one for the endpoint (known as resource in CF), and one for method. These two templates will work together to construct your proxy. So if you want to set your-app.com/serverless as a proxy for serverless.com, you'll need the following two templates in your serverless.yml:

service: service-name
provider: aws
functions:
  ...

resources:
  Resources:
    ProxyResource:
      Type: AWS::ApiGateway::Resource
      Properties:
        ParentId:
          Fn::GetAtt:
            - ApiGatewayRestApi # our default Rest API logical ID
            - RootResourceId
        PathPart: serverless # the endpoint in your API that is set as proxy
        RestApiId:
          Ref: ApiGatewayRestApi
    ProxyMethod:
      Type: AWS::ApiGateway::Method
      Properties:
        ResourceId:
          Ref: ProxyResource
        RestApiId:
          Ref: ApiGatewayRestApi
        HttpMethod: GET # the method of your proxy. Is it GET or POST or ... ?
        MethodResponses:
          - StatusCode: 200
        Integration:
          IntegrationHttpMethod: POST
          Type: HTTP
          Uri: http://serverless.com # the URL you want to set a proxy to
          IntegrationResponses:
            - StatusCode: 200

There's a lot going on in these two templates, but all you need to know to set up a simple proxy is setting the method & endpoint of your proxy, and the URI you want to set a proxy to.

Now that you have these two CloudFormation templates defined in your serverless.yml file, you can simply run serverless deploy and that will deploy these custom resources for you along with your service and set up a proxy on your Rest API.

#Share API Gateway and API Resources

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. However, 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

#Easiest and CI/CD friendly example of using shared API Gateway and API Resources.

You can define your API Gateway resource in one of the former service and export the restApiId and restApiRootResourceId using cloudformation cross-stack references.

service: service-a

resources:
  Resources:
    YourApiGateway:
      Type: AWS::ApiGateway::RestApi 
      Properties:
        Name: YourApiGatewayName

  Outputs:
    apiGatewayRestApiId:
      Value:
        Ref: YourApiGateway
      Export:
        Name: apiGateway-restApiId

    apiGatewayRestApiRootResourceId:
      Value:
         Fn::GetAtt:
          - YourApiGateway
          - RootResourceId 
      Export:
        Name: apiGateway-rootResourceId

  provider:
    apiGateway:
      restApiId: 
        Ref: YourApiGateway
      restApiResources:
        Fn::GetAtt:
            - YourApiGateway
            - RootResourceId

functions: ......

This creates API gateway and then exports the restApiId and rootResourceId values using cloudformation cross stack output. We will import this and reference in future services.

service: service-b

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

You can use this method to share your API Gateway across services in same region. Read about this limitation here.

#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 

#Resource Policy

Resource policies are policy documents that are used to control the invocation of the API. Find more use cases from the Apigateway Resource Policies documentation.

provider:
  name: aws
  runtime: nodejs6.10

  resourcePolicy:
    - Effect: Allow
      Principal: "*"
      Action: execute-api:Invoke
      Resource:
        - execute-api:/*/*/*
      Condition:
        IpAddress:
          aws:SourceIp:
            - "123.123.123.123"

Made with love in San Francisco + Atlanta, Austria, Germany, Pakistan, Poland, Nebraska & Thailand

Serverless, Inc. © 2018

Join our newsletter and get the latest news about Serverless products and happenings. #noSpamWePromise