openapi-backend
openapi-backend copied to clipboard
API Versioning / Multiple OpenAPI Backends: potential issue?
Hi there,
I need to support multiple versions of my API and checked the official documentation and also the referenced example at etherpad-lite. I am using version 5.3.0 of openapi-backend.
However, when wiring the api.handleRequest calls into express
app.use(apiRoot, async (req, res) => {
return api.handleRequest(req as OpenApiRequest, req, res);
});
neither of the versioned APIs gets called and I'll get a Error: 404-notFound: no route matches request. I dug around a bit and found an a existing (closed) issue which very much described the issue that I am having.
I can't use your solution #1 since multiple apiRoots is basically what I need. So I tried your solution #2
app.use(apiRoot, async (req, res) => {
return api.handleRequest({ ...req, path: req.baseUrl + req.path } as OpenApiRequest, req, res);
});
and this works, the requests are now correctly routed and arrive at the endpoints.
Atm, I don't know if this is a bug, or 'works as designed'. If its the latter, maybe it would make sense to add this to the documentation or create an example for supporting multiple versions in the repo (I could create a PR for this).
For reference, here is the code that I used for testing:
// index.ts
import express, { Request, Response } from 'express';
import { Context, OpenAPIBackend, Request as OpenApiRequest } from 'openapi-backend';
import path from 'path';
async function run() {
const app = express();
app.use(express.json());
const port = 3000;
let versions: Record<string, string> = {
'v1': 'I am old version 1',
'v2': 'I am new version 2'
};
for (const version of Object.keys(versions)) {
let apiRoot = `/${version}`;
let api = new OpenAPIBackend({ definition: path.join(__dirname, `./${version}.yaml`), apiRoot: apiRoot });
api.register({
getVersion: (c: Context, req: Request, res: Response) => {
const content = versions[version];
console.log(`Serving content for ${apiRoot}: ${content}`);
console.log(`Available query params: ${JSON.stringify(c.request.query)}`);
return res.send(content);
},
createVersion: (c: Context, req: Request, res: Response) => {
console.log(`Received CreateVersionRequest for version ${c.request.body.newVersion}`);
return res.status(200).send();
}
});
api.register('validationFail', (c: Context, req: Request, res: Response) => {
return res.status(400).json({ message: 'Invalid client request', details: c.validation.errors?.map(e => e.message) });
});
api.register('notImplemented', (c: Context, req: Request, res: Response) => {
res.status(501).json({ message: 'Not Implemented' });
});
api.register('notFound', (c: Context, req: Request, res: Response) => {
return res.status(404).json({ message: 'Not found' });
});
await api.init();
app.use(apiRoot, async (req, res) => {
// bug still at large? https://github.com/anttiviljami/openapi-backend/issues/81
// THIS WORKS:
return api.handleRequest({ ...req, path: req.baseUrl + req.path } as OpenApiRequest, req, res);
// THIS DOESN'T:
// return api.handleRequest(req as OpenApiRequest, req, res);
});
}
app.listen(port, () => {
console.log(`Server is running at https://localhost:${port}`);
});
}
run();
# v1.yaml
openapi: 3.0.1
info:
title: A Component (Version 1.0.0)
version: 1.0.0
servers:
- url: http://localhost:3000/v1
paths:
/version:
get:
operationId: getVersion
parameters:
- name: testParamOnlyInV1
required: true
in: query
schema:
type: boolean
responses:
'200':
description: 'ok'
content:
application/text:
schema:
type: string
# v2.yaml
openapi: 3.0.1
info:
title: A Component (Version 2.0.0)
version: 2.0.0
servers:
- url: http://localhost:3000/v2
paths:
/version:
get:
operationId: getVersion
parameters:
- name: testParamOnlyInV2
required: true
in: query
schema:
type: boolean
responses:
'200':
description: 'ok'
content:
application/text:
schema:
type: string
post:
operationId: createVersion
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateVersionRequest'
responses:
'200':
description: OK
components:
schemas:
CreateVersionRequest:
required:
- newVersion
properties:
newVersion:
type: string