API versioning strategies for Node.js backends. Use when implementing versioned APIs.
Provides API versioning strategies for Node.js backends using URL paths, headers, or query parameters. Use when building public APIs that need backward compatibility and planned evolution.
/plugin marketplace add IvanTorresEdge/molcajete.ai/plugin install node@Molcajete.aiThis skill inherits all available tools. When active, it can use any tool Claude has access to.
This skill covers API versioning strategies for maintaining backward compatibility.
Use this skill when:
BACKWARD COMPATIBILITY - Existing clients should continue working. New features require new versions only when breaking changes are necessary.
// src/routes/index.ts
import { FastifyPluginAsync } from 'fastify';
const routes: FastifyPluginAsync = async (fastify) => {
// Version 1
await fastify.register(import('./v1'), { prefix: '/api/v1' });
// Version 2
await fastify.register(import('./v2'), { prefix: '/api/v2' });
};
export default routes;
// src/routes/v1/users.ts
import { FastifyPluginAsync } from 'fastify';
const usersV1: FastifyPluginAsync = async (fastify) => {
fastify.get('/', async () => {
// V1 response format
return fastify.db.user.findMany({
select: { id: true, name: true, email: true },
});
});
};
export default usersV1;
// src/routes/v2/users.ts
import { FastifyPluginAsync } from 'fastify';
const usersV2: FastifyPluginAsync = async (fastify) => {
fastify.get('/', async () => {
// V2 response format with pagination
return {
data: await fastify.db.user.findMany(),
meta: {
total: await fastify.db.user.count(),
page: 1,
perPage: 20,
},
};
});
};
export default usersV2;
// src/plugins/api-version.ts
import { FastifyPluginAsync, FastifyRequest } from 'fastify';
import fp from 'fastify-plugin';
declare module 'fastify' {
interface FastifyRequest {
apiVersion: string;
}
}
const apiVersionPlugin: FastifyPluginAsync = async (fastify) => {
fastify.decorateRequest('apiVersion', '');
fastify.addHook('onRequest', async (request) => {
const version = request.headers['api-version'] as string | undefined;
request.apiVersion = version ?? '1';
});
};
export default fp(apiVersionPlugin);
// src/routes/users.ts
import { FastifyPluginAsync } from 'fastify';
const users: FastifyPluginAsync = async (fastify) => {
fastify.get('/', async (request) => {
const users = await fastify.db.user.findMany();
// Response based on version
if (request.apiVersion === '2') {
return {
data: users,
meta: { total: users.length },
};
}
// V1 default response
return users;
});
};
export default users;
// src/routes/users.ts
import { FastifyPluginAsync } from 'fastify';
import { z } from 'zod';
const QuerySchema = z.object({
version: z.enum(['1', '2']).default('1'),
});
const users: FastifyPluginAsync = async (fastify) => {
fastify.get<{ Querystring: z.infer<typeof QuerySchema> }>('/', async (request) => {
const { version } = request.query;
const users = await fastify.db.user.findMany();
if (version === '2') {
return { data: users, meta: { total: users.length } };
}
return users;
});
};
export default users;
// src/lib/version-router.ts
import { FastifyPluginAsync, FastifyRequest, FastifyReply } from 'fastify';
type VersionHandler<T> = (
request: FastifyRequest,
reply: FastifyReply
) => Promise<T>;
interface VersionedHandlers<T> {
v1: VersionHandler<T>;
v2?: VersionHandler<T>;
v3?: VersionHandler<T>;
}
export function createVersionedHandler<T>(
handlers: VersionedHandlers<T>
): VersionHandler<T> {
return async (request, reply) => {
const version = request.apiVersion as keyof VersionedHandlers<T>;
const handler = handlers[version] ?? handlers.v1;
return handler(request, reply);
};
}
// Usage
import { createVersionedHandler } from '../lib/version-router';
fastify.get('/users', createVersionedHandler({
v1: async (request) => {
return fastify.db.user.findMany();
},
v2: async (request) => {
return {
data: await fastify.db.user.findMany(),
meta: { version: 2 },
};
},
}));
// src/plugins/deprecation.ts
import { FastifyPluginAsync } from 'fastify';
import fp from 'fastify-plugin';
interface DeprecatedRoute {
path: string;
method: string;
sunsetDate: string;
alternative?: string;
}
const deprecatedRoutes: DeprecatedRoute[] = [
{
path: '/api/v1/users',
method: 'GET',
sunsetDate: '2025-06-01',
alternative: '/api/v2/users',
},
];
const deprecationPlugin: FastifyPluginAsync = async (fastify) => {
fastify.addHook('onSend', async (request, reply) => {
const deprecated = deprecatedRoutes.find(
(r) => r.path === request.url && r.method === request.method
);
if (deprecated) {
reply.header('Deprecation', `date="${deprecated.sunsetDate}"`);
reply.header('Sunset', deprecated.sunsetDate);
if (deprecated.alternative) {
reply.header('Link', `<${deprecated.alternative}>; rel="successor-version"`);
}
}
});
};
export default fp(deprecationPlugin);
// src/transformers/user.ts
import { User } from '@prisma/client';
interface UserV1Response {
id: string;
name: string;
email: string;
}
interface UserV2Response {
id: string;
fullName: string;
emailAddress: string;
createdAt: string;
updatedAt: string;
}
export function toUserV1(user: User): UserV1Response {
return {
id: user.id,
name: user.name,
email: user.email,
};
}
export function toUserV2(user: User): UserV2Response {
return {
id: user.id,
fullName: user.name,
emailAddress: user.email,
createdAt: user.createdAt.toISOString(),
updatedAt: user.updatedAt.toISOString(),
};
}
// src/schemas/user.ts
import { z } from 'zod';
// V1 Schema
export const UserV1Schema = z.object({
id: z.string(),
name: z.string(),
email: z.string(),
});
// V2 Schema - Added fields
export const UserV2Schema = z.object({
id: z.string(),
fullName: z.string(),
emailAddress: z.string(),
createdAt: z.string().datetime(),
updatedAt: z.string().datetime(),
profile: z.object({
avatar: z.string().nullable(),
bio: z.string().nullable(),
}).optional(),
});
export type UserV1 = z.infer<typeof UserV1Schema>;
export type UserV2 = z.infer<typeof UserV2Schema>;
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.