Skip to main content

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