fastify-print-routes
Version:
A simple plugin for Fastify prints all available routes.
128 lines (127 loc) • 4.73 kB
JavaScript
import { clean, colorize } from 'acquerello';
import fastifyPlugin from 'fastify-plugin';
import { table } from 'table';
import { toJSONSchema, ZodObject } from 'zod';
const methodsOrder = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'PATCH', 'OPTIONS'];
function getRouteConfig(r) {
return r.config ?? {};
}
function sortRoutes(a, b) {
return a.url.localeCompare(b.url);
}
function unifyRoutes(routes) {
const routesMap = new Map();
for (const route of routes) {
const unifiedRoute = routesMap.get(route.url);
if (unifiedRoute) {
if (typeof unifiedRoute.method === 'string') {
unifiedRoute.method = [unifiedRoute.method];
}
if (typeof route.method === 'string') {
unifiedRoute.method.push(route.method);
}
else {
unifiedRoute.method.push(...route.method);
}
const config = unifiedRoute?.config;
if (config && config?.description !== route.config?.description) {
config.description = undefined;
}
}
else {
routesMap.set(route.url, route);
}
}
return [...routesMap.values()].sort(sortRoutes);
}
function printRoutes(routes, useColors, compact, filter, showQueryString) {
if (routes.length === 0) {
return;
}
const styler = useColors ? colorize : clean;
routes = routes.filter(r => getRouteConfig(r).hide !== true && filter(r)).sort(sortRoutes);
if (compact) {
routes = unifyRoutes(routes);
}
const hasDescription = routes.some(r => 'description' in getRouteConfig(r));
const headers = [styler('{{bold white}}Method(s){{-}}'), styler('{{bold white}}Path{{-}}')];
if (hasDescription) {
headers.push(styler('{{bold white}}Description{{-}}'));
}
const rows = [headers];
for (const route of routes) {
const methods = Array.isArray(route.method) ? route.method : [route.method];
const url = route.url.replaceAll(/:\w+|\[:\w+]/g, '{{yellow}}$&{{-}}');
const querystringComponents = [];
let queryString = '';
if (showQueryString) {
if (route.schema?.querystring) {
let schema = route.schema.querystring;
if (schema instanceof ZodObject) {
schema = toJSONSchema(schema);
}
const requiredProperties = schema.required ?? [];
for (const property of Object.keys(schema.properties)) {
const param = `${property}={{yellow}}value{{-}}`;
const separator = querystringComponents.length === 0 ? '?' : '&';
if (requiredProperties.includes(property)) {
querystringComponents.push(separator + param);
}
else {
querystringComponents.push(`${separator}(${param})`);
}
}
}
queryString = querystringComponents
.join('')
.replaceAll('&(', '(&')
.replaceAll(')&', '&)')
.replaceAll(')(&', '&)(');
}
const row = [
styler(methods
.sort((a, b) => methodsOrder.indexOf(a) - methodsOrder.indexOf(b))
.map(m => `{{cyan}}${m}{{-}}`)
.join(' | ')),
styler(`{{bold green}}${url}${queryString}{{-}}`)
];
if (hasDescription) {
row.push(styler(`{{italic}}${getRouteConfig(route).description ?? ''}{{-}}`));
}
rows.push(row);
}
const output = table(rows, {
columns: {
0: {
alignment: 'right'
},
1: {
alignment: 'left'
},
2: {
alignment: 'left'
}
},
drawHorizontalLine(index, size) {
return index < 2 || index > size - 1;
}
});
console.log(`Available routes:\n\n${output}`);
}
export const plugin = fastifyPlugin(function (instance, options, done) {
const useColors = options.useColors ?? true;
const compact = options.compact ?? false;
const filter = options.filter ?? (() => true);
const querystring = options.querystring ?? true;
const routes = [];
instance.addHook('onRoute', route => {
routes.push(route);
});
instance.addHook('onReady', done => {
printRoutes(routes, useColors, compact, filter, querystring);
done();
});
instance.decorate('routes', routes);
done();
}, { name: 'fastify-print-routes', fastify: '5.x' });
export default plugin;