Skip to content

Instantly share code, notes, and snippets.

@ValentaTomas
Last active April 26, 2022 13:34
Show Gist options
  • Save ValentaTomas/f7cf5cf2926d63262f61f8a6ae1dae65 to your computer and use it in GitHub Desktop.
Save ValentaTomas/f7cf5cf2926d63262f61f8a6ae1dae65 to your computer and use it in GitHub Desktop.
Add versioning to API routes
import {
RequestHandler,
Request,
Response,
NextFunction,
Router,
} from 'express';
export enum APIVersion {
v0 = 'v0',
v1 = 'v1',
}
function getNewestByKeySort<K extends string, V>(o: { [key in K]?: V }) {
const sortedKeys = (Object.keys(o) as K[]).sort();
const newestKey = sortedKeys[sortedKeys.length - 1];
return o[newestKey];
}
type VersionMap<T> = { [key in APIVersion]?: T };
export function isAPIVersion(version: string): version is APIVersion {
return Object.values(APIVersion).some(v => v === version);
}
export function switchVersions(versions: VersionMap<RequestHandler | RequestHandler[]>) {
const versionsRouters = (Object.keys(versions) as APIVersion[])
.sort()
.reduce<VersionMap<Router>>((prev, version, i, keys) => {
const handlerOrHandlers = versions[version];
if (handlerOrHandlers) {
prev[version] = Router()
.use(handlerOrHandlers);
const previousRouter = i > 0 && prev[keys[i - 1]];
if (previousRouter) {
prev[version]?.use(previousRouter);
}
}
return prev;
}, {});
return (req: Request, res: Response, next: NextFunction) => {
// If the version is not specified in the URL then it defaults to the 'v0'.
const version = req.version || APIVersion.v0;
// If the version handlers were not provided the newest API version handlers will be used.
const versionRouter = versionsRouters[version] || getNewestByKeySort(versionsRouters);
if (!versionRouter) {
return res.status(400).send({
error: {
message: `API version "${version}" for the endpoint "${req.path}" is not supported.`,
},
});
}
return versionRouter(req, res, next);
}
}
// Middleware extracting the API version from the URL.
export function withVersion(handler: RequestHandler) {
return (req: Request, res: Response, next: NextFunction) => {
const { version: versionOrSegment }: { version?: string } = req.params;
if (!versionOrSegment) return res.status(400).send({
error: {
message: 'Invalid URL path.',
},
});
if (isAPIVersion(versionOrSegment)) {
req.version = versionOrSegment;
} else {
req.url = `/${versionOrSegment}${req.url}`;
}
return handler(req, res, next);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment