OpenAPI
Open API Specifications
Each module should define its API using OpenAPI specifications, we are using openapi-type NPM package to provide type safety when specifying our APIs
https://www.npmjs.com/package/openapi-types
Each module should contain an openapi folder with specifications for request, response schemas, security and path configuration. The full API schema is composed via a compose function in a file called document in the root of teh openapi folder in each module.
Please review the inventory module for an example of the folder structure and how we compose an Open API document for a module modules/inventory/openapi
OpenAPI Schema Generation
Rather than manually managing typeface interfaces for our request / response schemas and keeping them in sync with our OpenAPI schema definitions we have a process to combine all OpenAPI specs for a given module into a single JSON file and to auto generate the typeface interfaces for schemas defined on the OpenAPI document.
Any time you change the OpenAPI schema files you need to regenerate the associated typescript types, do this with the following command
pnpm run schemas
The schema generation script stores its output in the OpenAPI/auto folder in each module, so when we generate the schemas for inventory we end up with the typescript types for the schemas in the following folder
modules/inventory/openapi/auto
Files in these directories should never be manually edited.
The script outputs a JSON file for each module to the modules client folder scripts/openapi
and is configured to generate typescript interfaces for all schemas set on the OpenAPI document. For example in modules/inventory/openapi/document.ts
you can see we're setting the document.components.schemas
property, these are the only types that will have typescript interfaces generated for them.
document.components.schemas = {
TimelineActivity: schema.responses.records.TimelineActivity,
CreateRecord: schema.requests.records.CreateRecord,
RecordDetail: schema.responses.records.RecordDetail,
CreateStorage: schema.requests.storage.CreateStorage,
PatchStorage: schema.requests.storage.PatchStorage,
StorageDetail: schema.responses.storage.StorageDetail
}
The generated typescript interfaces are output to the following directory where they can be used within the module like any other type.
modules/[module_name]/openapi/auto
We also generate API clients from the OpenAPI schemas, see here for more info
Open API BackEnd
We are using a 3rd party tool called OpenAPIBackend to handle the routing of a given request based on our Open API specs
https://www.npmjs.com/package/openapi-backend
Each module manifest exposes an api field which is an instance of OpenAPIBackend, when creating an insytance of OpenAPI BackEnd we need to compose our OpenAPI document for the module and pass this to the constructor as well as passing in a an object with each of our handlers.
OpenAPIBackend can then handle requests by routing a given path to a handler using the operationId from the OpenAPI document to look up the correct handler.
IMPORTANT: The operationId for a given path in our OpenAPI specs must match the key of the handler in the object passed to the OpenAPIBackEnd constructor, for example...
Given the following path configuration in our OpenAPI document
'/inventory/records/{id}': {
get: {
operationId: 'getInventoryRecord',
summary: 'Get Inventory Record',
description: 'Get Inventory Record',
tags: [ 'records'],
parameters: [schema.parameters.path.recordId],
responses: schema.common.responses.all()
}
}
We must pass a handler to OpenAPIBackEnd constructor with the key 'getInventoryRecord' as follows
const api = new OpenAPIBackend({
definition: compose(),
handlers: {
getInventoryRecord: () => ...
},
strict: true
})
Global Handlers
There are a few handlers that all APIs should expose including 400, 404, 401 & 403 handlers, we register these global handlers here components/context/handlers/global.ts
This is also where we register our security handler (JWT) for the API and handle the request authorisation process.
Mocking Responses
If you need to get endpoints up quickly for the front end you can define the schemas and endpoint configuration as usual (including specifying the opeationId) but not provice a handler implementation for the operation. OpenAPIBackEnd will generate a mocked response based on the specified schema.
More details here
https://github.com/anttiviljami/openapi-backend#mocking-api-responses