Reference to a external swagger api definition

Hello,
I would like to reference inside the serverless.yml to an external swagger api definition.

Swagger is a widely spread way to define an api and it’s also supported from the api gateway to import external swagger definitions.

With a swagger api definition there can also be added aws features like api validation and custom api resonses to the api gateway, which will replace a lot of small serverless plugins with official aws features. Also, it gives a developer the possibility to use his api definition from other projects directly with the serverless framework and convert the project easier to serverless.

My current approach is to overwrite the serverless api with the aws put-rest-api command, but it’s not the optimal way to use my own api definition.

A similar feature was also requested in this post " Swagger and Serverless in parallel - bad idea?" but there was no solution and the there mentioned plugin looks like, that it will not be developed further.

It would be nice if I can define an api-definition path directly to the serverless resources, which would be used than to create the api-gateway.

1 Like
  1. The Serverless Framework uses CloudFormation to handle deployments.
  2. CloudFormation supports Swagger (see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html)
  3. You can override the default CloudFormation template generated by the Serverless Framework (see https://serverless.com/framework/docs/providers/aws/guide/resources#override-aws-cloudformation-resource)

I would start by trying something like:

resources:
  Resources:
    ApiGatewayRestApi:
      Properties:
        Body:
          # Your swagger definition in YAML format goes here
2 Likes

Your solution seems to be exactly that I was looking for.
I also have successfully tested your approach with this:

resources:
    Resources:
      ApiGatewayRestApi:
        Type: 'AWS::ApiGateway::RestApi'
        Properties:
          Name: ${self:provider.apiName}-${self:provider.stage}
          Body:
            ${file(api-model/api.yml)}
      ApiGatewayDeployment:
        Type: AWS::ApiGateway::Deployment
        Properties:
          RestApiId:
            Ref: ApiGatewayRestApi
          StageName: ${self:provider.stage}

This import my api.yml like expected but it also removes a lot of advantages, the serverless framework gives me.
If I replace the whole API template with my API definition, I also have to define all other API ressources like the API keys, the API deployment and the trigger for the lambda functions by my self.

For me, an better solution would be, if I could use something like the put-rest-api command in cloudformation to overwrite only the Methods and Models from the serverless API template and keep the API Keys and lambda trigger.

aws apigateway put-rest-api --rest-api-id {AWS::ApiGateway::RestApi}
–region {self:provider.stage}
–mode overwrite
–body file://api-model/api.yml

1 Like

Thanks for moving this discussion forward.
My organisation is API therefore Swagger first, rather than Function therefore Serverless first.
If we can achieve the successful marriage of the two (Swagger and Serverless) then I will win them over with Serverless, without it reluctantly I’m likely destined for Swagger/SAM.
The Swagger use case the organisation likes is the ability to forward engineer the consumer/client.

You don’t need to replace the whole template. The Serverless Framework will merge your resources section with the one it generates. My example adds the Body property to the CloudFormation generated by the Serverless Framework.

Hi,

I’m testing your trick by including the following swagger definition in the resource section:

openapi: 3.0.0
info:
  title: Sample API
  description: Description
  version: 0.1.9
paths:
  /users:
    get:
      summary: Returns a list of users.
      description: Optional description
      responses:
        '200':
          description: A JSON array of user names
          content:
            application/json:
              schema:
                type: array
                items:
                  type: string

However, when I serverless deploy my API, I get the following error message:

 An error occurred: ApiGatewayDeployment1554726393541 - No integration defined for method (Service: AmazonApiGateway; Status Code: 400; Error Code: BadRequestException; Request ID: ...).

Should I add x-amazon-apigateway-integration ?
Is there a way that serverless still handles the integration layer for us ?

My second question is:
Using swagger, how does serverless map the routes from the swagger definition, to the JS methods ?
I mean the swagger equivalent to the handler key-value pair:

functions:
  todo:
    handler: handler.todo

Thanks

2 Likes

This works great except for when using the serverless-offline plugin. Does anyone have any strategy to combine the two in your project?

Hey @slaursenfl, did you ever work out a strategy for this?

@thomas-ama, I’m running into the same issue you are now having

I have defined by swagger documentation and included it in the Body attribute of the ApiGatewayRestApi Resource section, but am getting
An error occurred: ApiGatewayDeployment1554726393541 - No integration defined for method (Service: AmazonApiGateway; Status Code: 400; Error Code: BadRequestException; Request ID: ...).

Did you ever find a way to resolve this issue?

Late to the party here but this thread was very helpful. Some documentation on how Serverless merges the resources would be helpful - my google fu hasn’t found anything particularly substantial yet.

@LucasRudd , I ran into this as well when doing SAM. The x-amazon-apigateway-integration property can be added to a path and use intrinsic functions:

"x-amazon-apigateway-integration": {
  "uri": {
    "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:/path/2015-01-31/functions/${MyLambda.Arn}/invocations"
  },
  "passthroughBehavior": "never",
  "httpMethod": "POST",
  "type": "AWS_PROXY"
}

Docs here: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-integration.html

I have been trying to get this working also. If you define all of your API endpoints in your swagger/openAPI specification, and include the x-amazon-apigateway-integration properties to associate endpoints with lambda functions as Ashpabb suggests, then you would not have any http event definitions on your functions (in the “functions” section of serverless.yml), because the relationship between the lambda functions and the API endpoints is defined in the swagger/openAPI spec. Indeed, if you do define http events for functions with the same endpoint names as in the swagger spec then you’ll get a Another resource with the same parent already has this name error from Serverless upon deployment. But if you don’t have any http event definitions, then Serverless does not create an API for you, so you have to do it all yourself in Cloudformation:

  • create the API based on the swagger/openAPI spec
  • grant the API permission to execute the defined functions
  • deploy the API

@buggy you say that Serverless will merge your resources with the one it generates, but it seems to me that it won’t generate any API resources in this scenario. You could add a dummy http event to your functions in order to prompt Serverless to create the API, the permissions and the deployment, but that’d leave a dummy endpoint lying around. So I think @Dragonil was correct when stating that this approach requires the developer to define all the API resources themselves when using a swagger/openAPI specification.

Here is the Cloudformation that I added to serverless.yml to get it working with a swagger specification:

functions:
  fetchData:
    handler: fetchDataLambda.handler

resources:
  Resources:

    # Create the API based on the OpenAPI specification

    ApiGatewayRestApi:
      Type: 'AWS::ApiGateway::RestApi'
      Properties:
        Body:
          ${file(./openApiSpecification.yml)}

    # Deploy the API

    ApiGatewayDeployment:
      Type: AWS::ApiGateway::Deployment
      Properties:
        RestApiId:
          Ref: ApiGatewayRestApi
        StageName: ${self:provider.stage}

    # Grant permission for the API to call the lambda - note that lambda named "fetchData"
    # earlier in this file becomes "FetchDataLambdaFunction" in Cloudformation

    Permission:
      Type: AWS::Lambda::Permission
      Properties:
        FunctionName:
          Fn::GetAtt:
            - FetchDataLambdaFunction
            - Arn
        Action: lambda:InvokeFunction
        Principal: apigateway.amazonaws.com

And the relevant part of my swagger spec looks like this:

/fetch:
  get:
    summary: Returns some data
    responses:
      '200':
        description: OK
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/Item'
    x-amazon-apigateway-integration:
      uri:
        Fn::Join:
          - ""
          - - "arn:aws:apigateway:"
            - Ref: AWS::Region
            - ":lambda:path/2015-03-31/functions/"
            - Fn::GetAtt: ["FetchDataLambdaFunction", "Arn"]
            - '/invocations'
      passthroughBehavior: "when_no_match"
      httpMethod: "POST"
      type: "aws_proxy"

I appreciate this question was asked a long time ago, and perhaps there is a better approach now. If so, I’d love to hear about it. There doesn’t seem to be a lot of documentation around how to get a swagger/openAPI spec working within a Serverless application.

1 Like