|
| 1 | +# cdk-rest-api-with-spec |
| 2 | + |
| 3 | +Describe an [Amazon API Gateway (API Gateway)](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html) REST API and the [OpenAPI](https://spec.openapis.org/oas/latest.html) definition at once with `cdk-rest-api-with-spec`. |
| 4 | + |
| 5 | +## For whom is this library? |
| 6 | + |
| 7 | +This library could help you if you would like to write a REST API and the OpenAPI definition at once using the CDK building blocks. |
| 8 | +See [_Background_](#background) for more details. |
| 9 | + |
| 10 | +## Prerequisites |
| 11 | + |
| 12 | +You have to install [Node.js](https://nodejs.org/en/) v12 or later. |
| 13 | +I have developed this library with Node.js v16.x. |
| 14 | + |
| 15 | +This library is implemented for the [AWS Cloud Development Kit (CDK)](https://docs.aws.amazon.com/cdk/v2/guide/home.html) **version 2** and does not work with the CDK version 1. |
| 16 | + |
| 17 | +## How to install |
| 18 | + |
| 19 | +Please add this repository to your dependencies. |
| 20 | + |
| 21 | +```sh |
| 22 | +npm install https://github.com/codemonger-io/cdk-cors-utils.git#v0.1.0 |
| 23 | +``` |
| 24 | + |
| 25 | +This library is supposed to be used in a CDK v2 project, so it does not include the following modules in the `dependencies` but does in the `peerDependencies`. |
| 26 | +- [`aws-cdk-lib`](https://www.npmjs.com/package/aws-cdk-lib) |
| 27 | +- [`constructs`](https://www.npmjs.com/package/constructs) |
| 28 | + |
| 29 | +As long as you are working on a CDK v2 project, you should not have to separately install them. |
| 30 | + |
| 31 | +## Getting started |
| 32 | + |
| 33 | +Please use [`RestApiWithSpec.createRestApi`](./api-docs/markdown/cdk-rest-api-with-spec.restapiwithspec.createrestapi.md) instead of the constructor of [`aws_apigateway.RestApi`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.RestApi.html). |
| 34 | +It will return an object which augments [`aws_apigateway.RestApi`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.RestApi.html) with the features to describe the OpenAPI definition. |
| 35 | + |
| 36 | +```ts |
| 37 | +const api = RestApiWithSpec.createRestApi(this, 'example-api', { |
| 38 | + description: 'Example of RestApiWithSpec', |
| 39 | + openApiInfo: { |
| 40 | + version: '0.0.1', |
| 41 | + }, |
| 42 | + openApiOutputPath: 'openapi.json', |
| 43 | + // ... other options |
| 44 | +}); |
| 45 | +``` |
| 46 | + |
| 47 | +Please refer to the [use cases](#use-cases) and the [API documentation](#api-documentation) for more details. |
| 48 | +You can also find a working example in the [`example`](./example) folder. |
| 49 | + |
| 50 | +## Background |
| 51 | + |
| 52 | +Recently, I have been urged to write the OpenAPI definition of my REST API on API Gateway. |
| 53 | +As far as I know, there are two options to have the OpenAPI definition of a REST API on API Gateway. |
| 54 | +1. [Export the OpenAPI definition from an existing REST API](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-export-api.html) |
| 55 | +2. [Create a REST API by importing the OpenAPI definition](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-import-api.html) |
| 56 | + |
| 57 | +### 1. Exporting the OpenAPI definition |
| 58 | + |
| 59 | +Without any additional documentation, the OpenAPI definition exported from API Gateway is poorly constrained and useless. |
| 60 | +I have to [add a separate documentation resource](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-documenting-api.html) to every component of a REST API. |
| 61 | +It would be nice if I could construct REST API components and document them at once. |
| 62 | + |
| 63 | +### 2. Importing the OpenAPI definition |
| 64 | + |
| 65 | +I am familiar with the CDK building blocks to describe a REST API on API Gateway. |
| 66 | +I used to write a REST API with a plain [CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html) template and got tired of a lot of repetition. |
| 67 | +CDK has relieved me of that pain. |
| 68 | +I think writing a plain OpenAPI definition could bring the pain back to me, though I have not tried. |
| 69 | + |
| 70 | +### Third option |
| 71 | + |
| 72 | +Thus, I want a third option that enables me to **write a REST API and the OpenAPI definition at once** using the CDK building blocks. |
| 73 | +I hope this library would be the solution. |
| 74 | + |
| 75 | +## Difference between SpecRestApi |
| 76 | + |
| 77 | +CDK provides a construct with a similar name [`aws_apigateway.SpecRestApi`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.SpecRestApi.html). |
| 78 | +The goal of [`aws_apigateway.SpecRestApi`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.SpecRestApi.html) is to create a REST API by importing an existing OpenAPI definition, whereas the goal of this library is, the opposite, to create an OpenAPI definition by constructing a REST API. |
| 79 | + |
| 80 | +## Use cases |
| 81 | + |
| 82 | +### Describing a method |
| 83 | + |
| 84 | +You can specify `summary` and `description` properties to the third argument of the [`IResourceWithSpec.addMethod`](./api-docs/markdown/cdk-rest-api-with-spec.iresourcewithspec.addmethod.md) method. |
| 85 | + |
| 86 | +```ts |
| 87 | +api.root.addMethod( |
| 88 | + 'GET', |
| 89 | + new apigateway.MockIntegration({ |
| 90 | + // ... integration settings |
| 91 | + }), |
| 92 | + { |
| 93 | + operationName: 'getRoot', |
| 94 | + summary: 'Get root', // NEW! |
| 95 | + description: 'Returns the root object', // NEW! |
| 96 | + methodResponses: [ |
| 97 | + { |
| 98 | + statusCode: '200', |
| 99 | + description: 'successful operation', |
| 100 | + }, |
| 101 | + ], |
| 102 | + } |
| 103 | +); |
| 104 | +``` |
| 105 | + |
| 106 | +The `operationName`, `summary`, and `description` properties correspond to the `operationId`, `summary`, and `description` properties of the [Operation Object](https://spec.openapis.org/oas/latest.html#operation-object) in the OpenAPI definition respectively. |
| 107 | + |
| 108 | +Elements in the `methodResponses` property can have the `description` property which corresponds to the `description` property of the [Response Object](https://spec.openapis.org/oas/latest.html#response-object) in the OpenAPI definition. |
| 109 | + |
| 110 | +### Describing request parameters |
| 111 | + |
| 112 | +You can describe request parameters in the [`requestParameterSchemas`](./api-docs/markdown/cdk-rest-api-with-spec.methodoptionswithspec.requestparameterschemas.md) property of the third argument of the [`IResourceWithSpec.addMethod`](./api-docs/markdown/cdk-rest-api-with-spec.iresourcewithspec.addmethod.md) method. |
| 113 | + |
| 114 | +```ts |
| 115 | +findByStatus.addMethod( |
| 116 | + 'GET', |
| 117 | + new apigateway.MockIntegration({ |
| 118 | + // ... integration settings |
| 119 | + }), |
| 120 | + { |
| 121 | + operationName: 'findPetsByStatus', |
| 122 | + requestParameterSchemas: { |
| 123 | + 'method.request.querystring.status': { |
| 124 | + description: 'Status values that need to be considered for filter', |
| 125 | + required: false, |
| 126 | + explode: true, |
| 127 | + schema: { |
| 128 | + type: 'string', |
| 129 | + enum: ['available', 'pending', 'sold'], |
| 130 | + default: 'available', |
| 131 | + }, |
| 132 | + }, |
| 133 | + }, |
| 134 | + }, |
| 135 | +); |
| 136 | +``` |
| 137 | + |
| 138 | +The [`requestParameterSchemas`](./api-docs/markdown/cdk-rest-api-with-spec.methodoptionswithspec.requestparameterschemas.md) is a collection of key-value pairs and takes the same key as the [`requestParameters`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.MethodOptions.html#requestparameters) property, but it maps a key to an object which represents a [Parameter Object](https://spec.openapis.org/oas/latest.html#parameter-object), except for the `name` and `in` properties, in the OpenAPI definition rather than a `boolean` value. |
| 139 | +The `name` and `in` properties of the [Parameter Object](https://spec.openapis.org/oas/latest.html#parameter-object) are inferred from the key. |
| 140 | +So the above [`requestParameterSchemas`](./api-docs/markdown/cdk-rest-api-with-spec.methodoptionswithspec.requestparameterschemas.md) will become the following [Parameter Object](https://spec.openapis.org/oas/latest.html#parameter-object), |
| 141 | + |
| 142 | +```ts |
| 143 | +[ |
| 144 | + { |
| 145 | + name: 'status', |
| 146 | + in: 'query', |
| 147 | + description: 'Status values that need to be considered for filter', |
| 148 | + required: false, |
| 149 | + explode: true, |
| 150 | + schema: { |
| 151 | + type: 'string', |
| 152 | + enum: ['available', 'pending', 'sold'], |
| 153 | + default: 'available', |
| 154 | + }, |
| 155 | + }, |
| 156 | +] |
| 157 | +``` |
| 158 | + |
| 159 | +If you specify the [`requestParameterSchemas`](./api-docs/markdown/cdk-rest-api-with-spec.methodoptionswithspec.requestparameterschemas.md) property, you do not have to specify the [`requestParameters`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.MethodOptions.html#requestparameters) property. |
| 160 | +The [`requestParameters`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.MethodOptions.html#requestparameters) property given to the underlying [`aws_apigateway.IResource.addMethod`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.IResource.html#addwbrmethodhttpmethod-target-options) will be generated from the [`requestParameterSchemas`](./api-docs/markdown/cdk-rest-api-with-spec.methodoptionswithspec.requestparameterschemas.md) property such that `requestParameters[key] = requestParameterSchemas[key].required`. |
| 161 | + |
| 162 | +If you omit the [`requestParameterSchemas`](./api-docs/markdown/cdk-rest-api-with-spec.methodoptionswithspec.requestparameterschemas.md) property but specify the [`requestParameters`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.MethodOptions.html#requestparameters) property, minimal [Parameter Object](https://spec.openapis.org/oas/latest.html#parameter-object)s will be created from the [`requestParameters`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.MethodOptions.html#requestparameters) property. |
| 163 | +Suppose you specify the following object to the [`requestParameters`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.MethodOptions.html#requestparameters) property, |
| 164 | + |
| 165 | +```ts |
| 166 | +{ |
| 167 | + 'method.request.querystring.status': false, |
| 168 | +} |
| 169 | +``` |
| 170 | + |
| 171 | +then you will get |
| 172 | + |
| 173 | +```ts |
| 174 | +[ |
| 175 | + { |
| 176 | + name: 'status', |
| 177 | + in: 'query', |
| 178 | + required: false, |
| 179 | + schema: { |
| 180 | + type: 'string', |
| 181 | + }, |
| 182 | + }, |
| 183 | +] |
| 184 | +``` |
| 185 | + |
| 186 | +If the [`requestParameters`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.MethodOptions.html#requestparameters) and [`requestParameterSchemas`](./api-docs/markdown/cdk-rest-api-with-spec.methodoptionswithspec.requestparameterschemas.md) properties are both specified, the [`requestParameterSchemas`](./api-docs/markdown/cdk-rest-api-with-spec.methodoptionswithspec.requestparameterschemas.md) property precedes. |
| 187 | + |
| 188 | +### Defining a model (schema) |
| 189 | + |
| 190 | +The [`IRestApiWithSpec.addModel`](./api-docs/markdown/cdk-rest-api-with-spec.irestapiwithspec.addmodel.md) method will add a [Schema Object](https://spec.openapis.org/oas/latest.html#schema-object) to the `schemas` property of the [Components Object](https://spec.openapis.org/oas/latest.html#components-object) in the OpenAPI definition. |
| 191 | +Here is an example, |
| 192 | + |
| 193 | +```ts |
| 194 | +const petModel = api.addModel('PetModel', { |
| 195 | + description: 'A pet', |
| 196 | + contentType: 'application/json', |
| 197 | + schema: { |
| 198 | + schema: apigateway.JsonSchemaVersion.DRAFT4, |
| 199 | + title: 'pet', |
| 200 | + description: 'A pet', |
| 201 | + type: apigateway.JsonSchemaType.OBJECT, |
| 202 | + properties: { |
| 203 | + id: { |
| 204 | + description: 'ID of the pet', |
| 205 | + type: apigateway.JsonSchemaType.INTEGER, |
| 206 | + format: 'int64', |
| 207 | + example: 123, |
| 208 | + }, |
| 209 | + name: { |
| 210 | + description: 'Name of the pet', |
| 211 | + type: apigateway.JsonSchemaType.STRING, |
| 212 | + example: 'Monaka', |
| 213 | + }, |
| 214 | + }, |
| 215 | + }, |
| 216 | +}); |
| 217 | +``` |
| 218 | + |
| 219 | +The `schema` property, [`JsonSchemaEx`](./api-docs/markdown/cdk-rest-api-with-spec.jsonschemaex.md), of the second argument of the [`IRestApiWithSpec.addModel`](./api-docs/markdown/cdk-rest-api-with-spec.irestapiwithspec.addmodel.md) method will be translated into an equivalent [Schema Object](https://spec.openapis.org/oas/latest.html#schema-object) in the OpenAPI definition. |
| 220 | + |
| 221 | +The [`aws_apigateway.IModel`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.IModel.html) referenced in the `responseModels` property in the `methodResponses` property of the third argument of the [`IResourceWithSpec.addMethod`](./api-docs/markdown/cdk-rest-api-with-spec.iresourcewithspec.addmethod.md) method will be replaced with a reference to the schema corresponding to the [`aws_apigateway.IModel`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.IModel.html) in the OpenAPI definition. |
| 222 | + |
| 223 | +The `methodResponses` property in the following example, |
| 224 | + |
| 225 | +```ts |
| 226 | +petId.addMethod( |
| 227 | + 'GET', |
| 228 | + new apigateway.MockIntegration({ |
| 229 | + // ... integration settings |
| 230 | + }), |
| 231 | + { |
| 232 | + operationName: 'getPetById', |
| 233 | + methodResponses: [ |
| 234 | + { |
| 235 | + statusCode: '200', |
| 236 | + description: 'successful operation', |
| 237 | + responseModels: { |
| 238 | + 'application/json': petModel, |
| 239 | + }, |
| 240 | + }, |
| 241 | + ], |
| 242 | + }, |
| 243 | +); |
| 244 | +``` |
| 245 | + |
| 246 | +will become a [Responses Object](https://spec.openapis.org/oas/latest.html#responses-object) similar to the following in the OpenAPI definition. |
| 247 | + |
| 248 | +```ts |
| 249 | +{ |
| 250 | + '200': { |
| 251 | + description: 'successful operation', |
| 252 | + content: { |
| 253 | + 'application/json': { |
| 254 | + schema: { |
| 255 | + '$ref': '#/components/schemas/exampleapiPetModel43E308F7' |
| 256 | + }, |
| 257 | + }, |
| 258 | + }, |
| 259 | + }, |
| 260 | +} |
| 261 | +``` |
| 262 | + |
| 263 | +The CloudFormation resource ID given to an [`aws_apigateway.IModel`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.IModel.html) is used to represent the reference path to the [`aws_apigateway.IModel`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.IModel.html). |
| 264 | + |
| 265 | +[`JsonSchemaEx`](./api-docs/markdown/cdk-rest-api-with-spec.jsonschemaex.md) which extends [`aws_apigateway.JsonSchema`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.JsonSchema.html) has an additional property [`modelRef`](./api-docs/markdown/cdk-rest-api-with-spec.jsonschemaex.modelref.md). |
| 266 | +You can reference another [`aws_apigateway.IModel`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.IModel.html) by using the [`modelRef`](./api-docs/markdown/cdk-rest-api-with-spec.jsonschemaex.modelref.md) property. |
| 267 | + |
| 268 | +```ts |
| 269 | +const petArrayModel = api.addModel('PetArrayModel', { |
| 270 | + description: 'An array of pets', |
| 271 | + contentType: 'application/json', |
| 272 | + schema: { |
| 273 | + schema: apigateway.JsonSchemaVersion.DRAFT4, |
| 274 | + title: 'petArray', |
| 275 | + description: 'An array of pets', |
| 276 | + type: apigateway.JsonSchemaType.ARRAY, |
| 277 | + items: { |
| 278 | + modelRef: petModel, |
| 279 | + }, |
| 280 | + }, |
| 281 | +}); |
| 282 | +``` |
| 283 | + |
| 284 | +### Describing an Authorizer |
| 285 | + |
| 286 | +You can augment an existing [aws_apigateway.IAuthorizer](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.IAuthorizer.html) with the properties for the OpenAPI definition with the [`augmentAuthorizer`](./api-docs/markdown/cdk-rest-api-with-spec.augmentauthorizer.md) function. |
| 287 | + |
| 288 | +```ts |
| 289 | +const authorizer = augmentAuthorizer( |
| 290 | + new apigateway.TokenAuthorizer( |
| 291 | + this, |
| 292 | + 'ExampleAuthorizer', |
| 293 | + { |
| 294 | + handler: new nodejs.NodejsFunction(this, 'authorizer', { |
| 295 | + description: 'Example authorizer', |
| 296 | + runtime: lambda.Runtime.NODEJS_16_X, |
| 297 | + }), |
| 298 | + }, |
| 299 | + ), |
| 300 | + { |
| 301 | + type: 'apiKey', |
| 302 | + in: 'header', |
| 303 | + name: 'Authorization', |
| 304 | + }, |
| 305 | +); |
| 306 | +``` |
| 307 | + |
| 308 | +The second argument of the [`augmentAuthorizer`](./api-docs/markdown/cdk-rest-api-with-spec.augmentauthorizer.md) function is a [Security Scheme Object](https://spec.openapis.org/oas/latest.html#security-scheme-object) to describe the authorizer in the OpenAPI definition. |
| 309 | + |
| 310 | +## API documentation |
| 311 | + |
| 312 | +The latest API documentation is available on [`api-docs/markdown/index.md`](./api-docs/markdown/index.md). |
| 313 | + |
| 314 | +## Development |
| 315 | + |
| 316 | +### Resolving dependencies |
| 317 | + |
| 318 | +```sh |
| 319 | +npm install |
| 320 | +``` |
| 321 | + |
| 322 | +### Building the library |
| 323 | + |
| 324 | +```sh |
| 325 | +npm run build |
| 326 | +``` |
| 327 | + |
| 328 | +You will find the following files created or updated in the `dist` folder, |
| 329 | +- `index.js` |
| 330 | +- `index.js.map` |
| 331 | +- `index.d.ts` |
| 332 | + |
| 333 | +The `dist` folder will be created if it does not exist. |
| 334 | + |
| 335 | +You will also find the file [`api-docs/cdk-rest-api-with-spec.api.md`](./api-docs/cdk-rest-api-with-spec.api.md) updated if there are any changes in the API of this library. |
| 336 | + |
| 337 | +### Generating the documentation |
| 338 | + |
| 339 | +```sh |
| 340 | +npm run doc |
| 341 | +``` |
| 342 | + |
| 343 | +This will replace the contents of the [`api-docs/markdown`](./api-docs/markdown) folder. |
0 commit comments