0.11.2 • Published 10 months ago

@stackspot/cdk-component-openapi-typescript v0.11.2

Weekly downloads
-
License
MIT
Repository
github
Last release
10 months ago

StackSpot OpenAPI contract first API Gateway + Typescript Lambda construct library

A CDK construct for Typescript that can be used to create the AWS infrastructure to support a API Gateway to Lambda integration based on an OpenAPI specification.

When CDK commands are executed all boilerplate code for API endpoints implementation is also created abstracting this complexity from the developer and letting him focus only on service funcionality implementation.

Prerequisites

Optional (Recommended) development tools

How to build library package

  • To build the library package clone the repository and run npm scripts
git clone https://github.com/stack-spot/domainservices-cdk-openapi-lambda.git
cd omainservices-cdk-openapi-lambda
npm install
npm run build
npm run package
  • The library will be built and package cdk-component-openapi-typescript@<version>.jsii.tgz will be generated in dist/js folder

Quick start

The below example will generate a simple hello world service to ilustrate the library usage.

  1. Init a CDK app using the cli
mkdir hello-world-service
cd hello-world-service
cdk init --language=typescript
  1. Add cdk-component-openapi-typescript@@<version>.jsii.tgz and @types/aws-lambda as project dependencies
npm install @types/aws-lambda <@stackspot/cdk-component-openapi-typescript location>/dist/js/cdk-component-openapi-typescript@<version>.jsii.tgz
  1. Create file hello-world-service/spec/hello-world.yaml with the OpenAPI specification of hello world service
openapi: 3.0.3
info:
  title: hello-world
  version: '1.0'
  description: A simple hello-world REST service
  contact:
    email: user@stackspot.com.br
servers:
  - url: 'http://localhost:3000'
tags:
  - name: hello
    description: Hello world services
paths:
  /hello:
    post:
      tags:
        - hello
      description: Receive a name in request body and respond with a greeting message for the name informed
      operationId: post-hello
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HelloResponse'
              examples:
                example-1:
                  value:
                    greeting: Hello John!
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HelloRequest'
            examples:
              example-1:
                value:
                  name: John
components:
  schemas:
    HelloRequest:
      title: HelloRequest
      type: object
      properties:
        name:
          type: string
      required:
        - name
    HelloResponse:
      title: HelloResponse
      type: object
      properties:
        greeting:
          type: string
      required:
        - greeting
  1. Edit the stack definition hello-world-service/lib/hello-world-service-stack.ts, import StackSpotOpenApiServices and create the construct pointing to service spec yaml
import * as cdk from '@aws-cdk/core';
// import StackSpotOpenApiServices
import { StackSpotOpenApiServices } from '@stackspot/cdk-component-openapi-typescript';

export class HelloWorldServiceStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Create StackSpotOpenApiServices pointing to spec file
    new StackSpotOpenApiServices(this, 'HelloWorldServiceApi', {
      specPath: 'spec/hello-world.yaml',
    });
  }
}
  1. Execute CDK bootstrap to prepare stack and generate service stubs
cdk bootstrap --profile <your-aws-profile>

If you have permissions problems related to S3 public access block configuration permissions on bootstrap you could add the option --public-access-block-configuration false to the bootstrap command as shown below:

cdk bootstrap --profile <your-aws-profile> --public-access-block-configuration false

After bootstrap service controller and usecase stubs will be generated at path src/post-hello.

  1. Edit generated usecase stub (src/post-hello/usecase.ts) and implement the code to generate the expected response imported from src/api-models.ts as shown below:
import { HelloRequest, HelloResponse } from '../api-schemas';

export type PostHelloParams = {
  requestBody: HelloRequest,
};

export const postHello = async ({ requestBody }: PostHelloParams): Promise<HelloResponse> => {
  return {
    greeting: `Hello ${requestBody.name}!`,
  };
};
  1. Run npm run build, cdk deploy and call api at the endpoint created with an appropriate payload.
npm run build
cdk deploy
curl -X POST -H 'Content-Type: application/json' -d '{"name": "StackSpot"}' https://mhi8zrb3c7.execute-api.us-east-1.amazonaws.com/prod/hello

Congratulations! You created your API based on an OpenAPI specification and deployed it at AWS with API Gateway and Lambda!

Useful commands

  • npm run build compile typescript to jsii
  • npm run watch watch for changes and compile
  • npm run test perform the jest unit tests
  • npm run package package library using jsii
  • npm run coverage run tests with coverage reports

Developer workflow overview

Overview

  1. Developer creates an OpenAPI spec using his favorite tools
  2. Developer runs StackSpot cli or cdk init to create CDK project (when cdk init is used is necessary to import and initialize the StackSpotOpenAPIServices construct in project stack)
  3. Developer runs cdk synth to generate boilerplate code for endpoints controllers and usecases implementation.
  4. Developer edits/creates typescript source code generated to implement usecases
  5. Develop run cdk deploy or commit the code to run CI/CD pipeline and all infrastructure needed to provide the defined services is created as a Cloudformation Stack in AWS Account.

Generated source code

Overview

The generated source code is organized in a layered archicture with some basic components: Core components, API Schemas and Errors, Parameters Configurations, Operation Controllers and Operation Use Cases.

Core components (src/core/**/*.ts)

  • Core components are responsible to provide utilities and base classes for other components.

  • You can create you own files in core components but the files created by the CDK Construction are overwrited when a cdk command is runned by the user. DON'T change the core generated files or your changes WILL BE LOST.

  • You can customize the base components like Controller extending them and adding/changing behaviours as you need.

  • The base classes provides some template methods to customize their behaviour in subclasses.

API schemas (src/api-schemas.ts and src/error.ts)

  • All schemas defined in OpenAPI components section are parsed and represented as typescript interfaces in `src/api-schemas.ts.

  • All changes made to the schemas are reflected to this file when use execute cdk synth and the file is overwrited, so DON'T change this file or your changes WILL BE LOST.

  • The construct also create an error structure that is used as base class for errors in api.

  • All responses of api operations that are not 2xx response codes generates an Error subclass representing this return code. When you need to return this response to user you can throw the corresponding error and the controller will convert the response accordingly.

Parameters configuration (src/\<operationId>/parameters-configuration.json)

  • All operations defined in OpenAPI operations objects generates a JSON file with the parameters defined by the operation.

  • The operation controller uses this configuration to know how to parse the parameters from API Gateway event and convert them to API schema objects and parameters to operation use case responsible to process the request.

  • Parameters configurations are overwrited every time the user execute a cdk command so DON'T change this file or your changes WILL BE LOST.

  • There are some known limitations in parameters definitions in your OpenAPI spec:

    • cookie parameters are not supported.
    • arrays are not supported in path or header parameters.
    • parameters can be only primitive types object parameters are not supported.
    • style an explode parameters modifiers are not supported.
    • parameters cannot be arrays of arrays.

Operation controllers (src/\<operationId>/controller.ts)

  • Controllers are responsible to convert API Gateway event to parameters and API schemas representations a to execute use cases with the parameters already converted.

  • They also can be responsible to validate resource access using an use case and throwing AccessDeniedError when necessary.

  • Controllers have some template methods that can be use to customize their behaviour:

    • buildUseCaseArgs can be ovewrited to customize use case arguments when needed.
    • transformUseCaseResponse can be overwrited to transform use case response before converting it to success response.
    • convertResponseToApiGatewayResponse can be overwrited to customize the response creation. A common usecase is to return binary data instead of JSON.
  • Controllers are generated when user executes a cdk command but don't ovewrite already generated controllers, so you can safelly modify controllers source code as you need.

Operation use cases (src/\<operationId>/usecase.ts)

  • Use cases are responsible to implement the business rules of API.

  • We recomend to isolate database interactions in repositories to abstract database acesss.

  • Error codes represented by operation responses are generated as error classes in usecase file and this errors can be throw to generate the non success response of API.

  • The requestBody when exists is converted by controllers and received as use case parameter.

  • When JWT authentication is enabled jwtTokenPayload is passed as parameter for use case so they can do security assertions.

  • Use cases are generated when user executes a cdk command but don't ovewrite already generated use cases, so you can safelly modify use case source code as you need.

Generated Infrastucture

infrastructure

  • The CDK construct generates the following components in the stack:
    • API gateway with all operations defined in spec
    • One lambda per operation defined
    • Cloudwatch logs of lambdas
    • Enables X-Ray for api gateway and generated lambdas
    • Adjust lambda permissions to be invoked by api gateway

Enabling JWT Security

  • The construct supports JWT tokens for security and the security can be enabled defining the following OpenAPI security scheme:
components:
  securitySchemes:
    jwtAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  • You can enable JWT token validation for all operations defining a securty constraint at api root level or at operation level as shown below:
# root level
security:
  - jwtAuth: []

paths:
  /auctions/{id}:
    parameters:
      - $ref: '#/components/parameters/AuctionId'
      - $ref: '#/components/parameters/Authorization'
    get:
      operationId: get-auction
      description: Get auction data by id

      # operation level
      security:
        - jwtAuth: []

      tags:
        - Auction services
      responses:
        '200':
          description: Auction returned successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Auction'
        '404':
          description: Auction not found
  • Most IDM providers expose a JWKS_URI with their public keys to verify JWT token signatures. You need to configure the construct as shown below to inform JWKS_URI to be used to get the public keys:
const api = new StackSpotOpenApiServices(this, 'StackSampleAPI', {
  specPath: 'spec/auction-api.yaml',
  jwksUri: 'https://some.idm.provider/auth/realms/some-realm/protocol/openid-connect/certs',
});
  • If you are using an IDM that supports OpenID connect you can get JWKS_URI endpoint in well-known endpoint of OpenID connect provider.

  • When you enable JWT authorization your controllers will extend JWTAuthorizationController class and you can override the authorizeResourceAccess method to do some custom authorization logic. The JWT token payload can be accessed using this.jwtTokenPayload protected property. Controlles already generated before JWT authorization will not be overwrited an must be changed by the user.

  • With JWT Authorization enabled an API Gateway Lambda authorizer will be configured to validate the token.

  • Authorization logic is not made by this lambda only the authenticity and validity of token is verified. You need to implement your authorization logic using token claims in operations controllers or create a new base constroller class based on JWTAuthorizationControler to use as base class of your controllers and implement authorization logic on it.

Properties Definition

It's possible to configure properties on the construct as shown below:

export interface StackSpotOpenAPIServicesProps {
  readonly specPath: string;
  readonly sourceDir?: string;
  readonly enableValidation?: boolean;
  readonly enableTracing?: boolean;
  readonly jwksUri?: string;
  readonly endpointTypes?: apigateway.EndpointType;
}

export declare enum EndpointType {
  /**
   * For an edge-optimized API and its custom domain name.
   *
   * @stability stable
   */
  EDGE = 'EDGE',
  /**
   * For a regional API and its custom domain name.
   *
   * @stability stable
   */
  REGIONAL = 'REGIONAL',
  /**
   * For a private API and its custom domain name.
   *
   * @stability stable
   */
  PRIVATE = 'PRIVATE',
}

const serviceProps: StackSpotOpenAPIServicesProps = {
  specPath: 'spec/auction-api.yaml',
  sourceDir: 'app/src',
  enableValidation: true,
  enableTracing: true,
  jwksUri: 'https://some.idm.provider/auth/realms/some-realm/protocol/openid-connect/certs',
  endpointTypes: EndpointType.EDGE,
};

const api = new StackSpotOpenApiServices(this, 'StackSpotOpenApiServicesID', serviceProps);

specPath: Defines the path to OpenAPI specification file.
sourceDir: Defines the path to the source code generated based on OpenAPI specification file. Default: 'src'
enableValidation: If true, enable validators config in OpenAPI specification
enableTracing: If true, enable Amazon X-Ray tracing for ApiGateway and Lambda Function
jwksUri: JWKS URI to verify JWT Token signatures
endpointTypes: Defines the ApiGateway endpoint type. Default: EndpointType.EDGE

References

CDK

API Gateway

OpenAPI

0.11.2

10 months ago

0.11.1

1 year ago

0.10.10

2 years ago

0.10.9

2 years ago

0.10.5

2 years ago

0.10.6

2 years ago

0.10.7

2 years ago

0.10.8

2 years ago

0.10.3

2 years ago

0.10.2

2 years ago

0.9.2

2 years ago

0.8.1

2 years ago

0.9.1

2 years ago

0.7.4

2 years ago