@stackspot/cdk-component-openapi-typescript
v0.11.2
Published
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.
Downloads
4
Readme
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
- NodeJS 14.x - https://nodejs.org
- AWS account properly configured - https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html
- AWS CDK CLI - https://docs.aws.amazon.com/cdk/latest/guide/cli.html
Optional (Recommended) development tools
- Visual Studio Code (IDE) - https://code.visualstudio.com/
- Prettier VSCode extension (Code formatter) - https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode
- Jest Runner VSCode extension (Runs jest tests from IDE) - https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner
- OpenAPI (Swagger) Editor VSCode extension (OpenAPI intellisense and documentation preview) - https://marketplace.visualstudio.com/items?itemName=42Crunch.vscode-openapi
- StopLight Spectral VSCode extension (OpenAPI linter) - https://marketplace.visualstudio.com/items?itemName=stoplight.spectral
- StopLight Studio (OpenAPI visual editor) - https://stoplight.io/studio/
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 indist/js
folder
Quick start
The below example will generate a simple hello world service to ilustrate the library usage.
- Init a CDK app using the cli
mkdir hello-world-service
cd hello-world-service
cdk init --language=typescript
- 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
- 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: [email protected]
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
- Edit the stack definition
hello-world-service/lib/hello-world-service-stack.ts
, importStackSpotOpenApiServices
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',
});
}
}
- 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
.
- Edit generated usecase stub (
src/post-hello/usecase.ts
) and implement the code to generate the expected response imported fromsrc/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}!`,
};
};
- 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 jsiinpm run watch
watch for changes and compilenpm run test
perform the jest unit testsnpm run package
package library using jsiinpm run coverage
run tests with coverage reports
Developer workflow overview
- Developer creates an OpenAPI spec using his favorite tools
- 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)
- Developer runs cdk synth to generate boilerplate code for endpoints controllers and usecases implementation.
- Developer edits/creates typescript source code generated to implement usecases
- 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
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
anexplode
parameters modifiers are not supported.- parameters cannot be
arrays
ofarrays
.
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
- 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 theauthorizeResourceAccess
method to do some custom authorization logic. The JWT token payload can be accessed usingthis.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
- https://docs.aws.amazon.com/cdk/latest/guide/home.html - CDK Developer guide
- https://docs.aws.amazon.com/cdk/api/latest/docs/aws-apigateway-readme.html - CDK Amazon API Gateway Construct Library
- https://aws.github.io/jsii/user-guides/lib-author/ - jsii library author guide
- https://cdk-advanced.workshop.aws/ - CDK advanced workshop
- https://docs.aws.amazon.com/cdk/latest/guide/videos.html - CDK Videos
API Gateway
- https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html - API Gateway developer guide
- https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-validation-set-up.html#api-gateway-request-validation-setup-importing-swagger - How to setup API Gateway validation with OpenAPI extensions
- https://stackoverflow.com/questions/47953570/get-detailed-error-messages-from-aws-api-gateway-request-validator - How to get detailed body request validation messages in API Gateway
OpenAPI
- https://swagger.io/specification/ - OpenAPI 3.0 specification
- https://stoplight.io/studio/ - StopLight Studio for visual specification editor and other cool features