Api scaffold
17 professional Claude Code skills - code review, API scaffold, n8n workflow generator, and more
npx -y skills add xmqywx/claude-code-skills --skill api-scaffoldAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Scaffold a production-ready REST API endpoint with CRUD operations, input validation, error handling, rate limiting, and test skeletons. Supports Express and Fastify.
SKILL.md
10.8 KB, as published. Nobody here has run it
API Scaffold
Scaffold a complete REST API resource with CRUD endpoints, validation, error handling, and tests.
Inputs
When invoked, determine these from the user's request:
- Resource name (required): e.g., "user", "product", "order". Singularize if plural.
- Framework (optional, default: auto-detect from
package.json): "express" or "fastify". - Language (optional, default: auto-detect from project): "typescript" or "javascript".
- Fields (optional): If the user specifies fields, use them. Otherwise, create a sensible default schema with
id,createdAt,updatedAt, and 2-3 domain-relevant fields.
Steps
1. Detect Project Setup
Run these commands to understand the project:
cat package.json
ls src/ || ls app/ || ls .
Determine:
- Framework: Check
dependenciesforexpress,fastify,@fastify/,koa, etc. - Language: Check for
tsconfig.json, file extensions insrc/. - Project structure: Identify where routes/controllers live (e.g.,
src/routes/,src/api/,routes/). - Existing patterns: Read one existing route file to match the project's conventions.
If no package.json exists, ask the user which framework and language to use.
2. Create the Validation Schema
Create <resource>.schema.<ext> in the appropriate directory.
TypeScript + Zod example (src/schemas/product.schema.ts):
import { z } from 'zod';
export const createProductSchema = z.object({
name: z.string().min(1).max(255),
description: z.string().max(2000).optional(),
price: z.number().positive().finite(),
category: z.string().min(1).max(100),
inStock: z.boolean().default(true),
});
export const updateProductSchema = createProductSchema.partial();
export const productIdSchema = z.object({
id: z.string().uuid(),
});
export const listProductsQuerySchema = z.object({
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
sort: z.enum(['createdAt', 'name', 'price']).default('createdAt'),
order: z.enum(['asc', 'desc']).default('desc'),
search: z.string().max(255).optional(),
});
export type CreateProductInput = z.infer<typeof createProductSchema>;
export type UpdateProductInput = z.infer<typeof updateProductSchema>;
export type ListProductsQuery = z.infer<typeof listProductsQuerySchema>;
If the project uses Joi instead of Zod, use Joi syntax. If neither is installed, use Zod and note that the user should npm install zod.
3. Create the Route/Controller File
Create the route file following the detected project structure.
Express pattern (src/routes/product.routes.<ext>):
import { Router, Request, Response, NextFunction } from 'express';
const router = Router();
// GET /api/products - List with pagination, sorting, search
router.get('/', async (req: Request, res: Response, next: NextFunction) => {
try {
const query = listProductsQuerySchema.parse(req.query);
// TODO: Replace with actual data source
const { page, limit, sort, order, search } = query;
const offset = (page - 1) * limit;
const items: any[] = []; // TODO: query database
const total = 0; // TODO: count query
res.json({
data: items,
meta: { page, limit, total, totalPages: Math.ceil(total / limit) },
});
} catch (err) { next(err); }
});
// GET /api/products/:id - Get single resource
router.get('/:id', async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = productIdSchema.parse(req.params);
const item = null; // TODO: find by id
if (!item) { res.status(404).json({ error: 'Product not found' }); return; }
res.json({ data: item });
} catch (err) { next(err); }
});
// POST /api/products - Create resource
router.post('/', async (req: Request, res: Response, next: NextFunction) => {
try {
const data = createProductSchema.parse(req.body);
const item = null; // TODO: insert into database
res.status(201).json({ data: item });
} catch (err) { next(err); }
});
// PUT /api/products/:id - Update resource
router.put('/:id', async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = productIdSchema.parse(req.params);
const data = updateProductSchema.parse(req.body);
const item = null; // TODO: update in database
if (!item) { res.status(404).json({ error: 'Product not found' }); return; }
res.json({ data: item });
} catch (err) { next(err); }
});
// DELETE /api/products/:id - Delete resource
router.delete('/:id', async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = productIdSchema.parse(req.params);
const deleted = false; // TODO: delete from database
if (!deleted) { res.status(404).json({ error: 'Product not found' }); return; }
res.status(204).send();
} catch (err) { next(err); }
});
export default router;
Fastify pattern (src/routes/product.routes.<ext>):
import { FastifyInstance } from 'fastify';
export default async function productRoutes(app: FastifyInstance) {
app.get('/', { schema: { querystring: listSchema } }, async (req, reply) => {
// ... pagination + list logic
});
app.get('/:id', async (req, reply) => { /* ... */ });
app.post('/', async (req, reply) => { /* ... */ });
app.put('/:id', async (req, reply) => { /* ... */ });
app.delete('/:id', async (req, reply) => { /* ... */ });
}
4. Create Error Handling Middleware
If the project does not already have centralized error handling, create src/middleware/errorHandler.<ext>:
import { Request, Response, NextFunction } from 'express';
import { ZodError } from 'zod';
export function errorHandler(err: Error, req: Request, res: Response, _next: NextFunction) {
// Validation errors
if (err instanceof ZodError) {
res.status(400).json({
error: 'Validation failed',
details: err.errors.map(e => ({
field: e.path.join('.'),
message: e.message,
})),
});
return;
}
// Known application errors
if ('statusCode' in err && typeof (err as any).statusCode === 'number') {
res.status((err as any).statusCode).json({ error: err.message });
return;
}
// Unknown errors - do not leak internals
console.error('[ERROR]', err);
res.status(500).json({ error: 'Internal server error' });
}
5. Create Rate Limiting Middleware
If not already present, create src/middleware/rateLimit.<ext>:
const requestCounts = new Map<string, { count: number; resetAt: number }>();
export function rateLimit({ windowMs = 60_000, max = 100 } = {}) {
return (req: any, res: any, next: any) => {
const key = req.ip || req.connection?.remoteAddress || 'unknown';
const now = Date.now();
const record = requestCounts.get(key);
if (!record || now > record.resetAt) {
requestCounts.set(key, { count: 1, resetAt: now + windowMs });
return next();
}
record.count++;
res.setHeader('X-RateLimit-Limit', String(max));
res.setHeader('X-RateLimit-Remaining', String(Math.max(0, max - record.count)));
if (record.count > max) {
res.status(429).json({ error: 'Too many requests. Please try again later.' });
return;
}
next();
};
}
Note: For production, recommend express-rate-limit or @fastify/rate-limit instead of this in-memory implementation.
6. Create Test Skeleton
Create <resource>.test.<ext> in the test directory (or alongside the route if no test dir exists):
import { describe, it, expect, beforeAll, afterAll } from 'vitest'; // or jest
// import supertest if available
const BASE_URL = '/api/products';
describe('Product API', () => {
describe('POST /api/products', () => {
it('should create a product with valid data', async () => {
// TODO: send POST request with valid body
// expect status 201
// expect response.data to match input
});
it('should return 400 for invalid data', async () => {
// TODO: send POST with missing required fields
// expect status 400
// expect response.error to be 'Validation failed'
});
});
describe('GET /api/products', () => {
it('should return paginated list', async () => {
// TODO: send GET request
// expect status 200
// expect response.data to be an array
// expect response.meta to have page, limit, total, totalPages
});
it('should respect pagination params', async () => {
// TODO: send GET with ?page=2&limit=5
// verify correct offset behavior
});
});
describe('GET /api/products/:id', () => {
it('should return a product by ID', async () => {
// TODO: create a product first, then fetch by ID
// expect status 200
});
it('should return 404 for non-existent ID', async () => {
// TODO: fetch with random UUID
// expect status 404
});
});
describe('PUT /api/products/:id', () => {
it('should update an existing product', async () => {
// TODO: create, then update
// expect status 200
// expect updated fields
});
it('should allow partial updates', async () => {
// TODO: send PUT with only one field
// expect other fields unchanged
});
});
describe('DELETE /api/products/:id', () => {
it('should delete an existing product', async () => {
// TODO: create, then delete
// expect status 204
});
it('should return 404 for non-existent ID', async () => {
// TODO: delete with random UUID
// expect status 404
});
});
});
7. Show Registration Instructions
After creating all files, show the user how to register the new route in their app entry point:
// In your main app file (e.g., src/app.ts or src/index.ts):
import productRoutes from './routes/product.routes';
app.use('/api/products', productRoutes);
8. Summary
Print a summary of all created files and any packages that need to be installed:
Created:
- src/schemas/product.schema.ts
- src/routes/product.routes.ts
- src/middleware/errorHandler.ts (if new)
- src/middleware/rateLimit.ts (if new)
- tests/product.test.ts
Install (if needed):
npm install zod
npm install -D vitest supertest @types/supertest
Next steps:
1. Register the route in your app entry point
2. Replace TODO comments with actual database logic
3. Run tests: npx vitest product
Notes
- Always match existing project conventions (naming, file structure, import style, semicolons, quotes).
- If the project uses ESM (
"type": "module"), useimport/export. If CJS, userequire/module.exports. - Do not overwrite existing files. If a route file already exists, warn the user and ask before proceeding.