Claude-skill-registry api-endpoint-generator
Generates CRUD REST API endpoints with request validation, TypeScript types, consistent response formats, error handling, and documentation. Includes route handlers, validation schemas (Zod/Joi), typed responses, and usage examples. Use when building "REST API", "CRUD endpoints", "API routes", or "backend endpoints".
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/api-contract-normalizer" ~/.claude/skills/majiayu000-claude-skill-registry-api-endpoint-generator && rm -rf "$T"
manifest:
skills/data/api-contract-normalizer/SKILL.mdsource content
API Endpoint Generator
Generate production-ready CRUD API endpoints with validation and type safety.
Core Workflow
- Define resource: Entity name and schema
- Generate routes: POST, GET, PUT/PATCH, DELETE endpoints
- Add validation: Request body/query validation with Zod/Joi
- Type responses: TypeScript interfaces for all responses
- Error handling: Consistent error responses
- Documentation: OpenAPI/Swagger specs
- Examples: Request/response samples
Express + TypeScript Pattern
// types/user.types.ts export interface User { id: string; email: string; name: string; role: "user" | "admin"; createdAt: Date; updatedAt: Date; } export interface CreateUserDto { email: string; name: string; password: string; } export interface UpdateUserDto { name?: string; email?: string; } export interface ApiResponse<T> { success: boolean; data?: T; error?: ApiError; meta?: PaginationMeta; } export interface ApiError { code: string; message: string; details?: Record<string, string[]>; }
Validation Schemas (Zod)
// schemas/user.schema.ts import { z } from "zod"; export const createUserSchema = z.object({ email: z.string().email("Invalid email address"), name: z.string().min(2, "Name must be at least 2 characters"), password: z .string() .min(8, "Password must be at least 8 characters") .regex(/[A-Z]/, "Password must contain uppercase letter") .regex(/[0-9]/, "Password must contain number"), }); export const updateUserSchema = z .object({ name: z.string().min(2).optional(), email: z.string().email().optional(), }) .refine((data) => Object.keys(data).length > 0, { message: "At least one field must be provided", }); export const getUsersQuerySchema = z.object({ page: z.coerce.number().int().positive().default(1), limit: z.coerce.number().int().min(1).max(100).default(10), sortBy: z.enum(["name", "email", "createdAt"]).optional(), sortOrder: z.enum(["asc", "desc"]).default("desc"), search: z.string().optional(), }); export type CreateUserDto = z.infer<typeof createUserSchema>; export type UpdateUserDto = z.infer<typeof updateUserSchema>; export type GetUsersQuery = z.infer<typeof getUsersQuerySchema>;
CRUD Route Handlers
// routes/users.routes.ts import { Router } from "express"; import { UserController } from "../controllers/user.controller"; import { validateRequest } from "../middleware/validate"; import { authenticate } from "../middleware/auth"; import { createUserSchema, updateUserSchema, getUsersQuerySchema, } from "../schemas/user.schema"; const router = Router(); const controller = new UserController(); // Create router.post( "/", authenticate, validateRequest({ body: createUserSchema }), controller.create ); // Read (list) router.get( "/", authenticate, validateRequest({ query: getUsersQuerySchema }), controller.list ); // Read (single) router.get("/:id", authenticate, controller.getById); // Update router.patch( "/:id", authenticate, validateRequest({ body: updateUserSchema }), controller.update ); // Delete router.delete("/:id", authenticate, controller.delete); export default router;
Controller Implementation
// controllers/user.controller.ts import { Request, Response, NextFunction } from "express"; import { UserService } from "../services/user.service"; import { CreateUserDto, UpdateUserDto, GetUsersQuery, } from "../types/user.types"; import { ApiResponse } from "../types/api.types"; export class UserController { private service = new UserService(); create = async ( req: Request<{}, {}, CreateUserDto>, res: Response<ApiResponse<User>>, next: NextFunction ) => { try { const user = await this.service.create(req.body); res.status(201).json({ success: true, data: user, }); } catch (error) { next(error); } }; list = async ( req: Request<{}, {}, {}, GetUsersQuery>, res: Response<ApiResponse<User[]>>, next: NextFunction ) => { try { const { page, limit, sortBy, sortOrder, search } = req.query; const result = await this.service.findAll({ page, limit, sortBy, sortOrder, search, }); res.json({ success: true, data: result.users, meta: { page: result.page, limit: result.limit, total: result.total, totalPages: result.totalPages, }, }); } catch (error) { next(error); } }; getById = async ( req: Request<{ id: string }>, res: Response<ApiResponse<User>>, next: NextFunction ) => { try { const user = await this.service.findById(req.params.id); if (!user) { return res.status(404).json({ success: false, error: { code: "USER_NOT_FOUND", message: "User not found", }, }); } res.json({ success: true, data: user, }); } catch (error) { next(error); } }; update = async ( req: Request<{ id: string }, {}, UpdateUserDto>, res: Response<ApiResponse<User>>, next: NextFunction ) => { try { const user = await this.service.update(req.params.id, req.body); if (!user) { return res.status(404).json({ success: false, error: { code: "USER_NOT_FOUND", message: "User not found", }, }); } res.json({ success: true, data: user, }); } catch (error) { next(error); } }; delete = async ( req: Request<{ id: string }>, res: Response<ApiResponse<void>>, next: NextFunction ) => { try { const deleted = await this.service.delete(req.params.id); if (!deleted) { return res.status(404).json({ success: false, error: { code: "USER_NOT_FOUND", message: "User not found", }, }); } res.status(204).send(); } catch (error) { next(error); } }; }
Validation Middleware
// middleware/validate.ts import { Request, Response, NextFunction } from "express"; import { ZodSchema } from "zod"; interface ValidationSchemas { body?: ZodSchema; query?: ZodSchema; params?: ZodSchema; } export const validateRequest = (schemas: ValidationSchemas) => { return (req: Request, res: Response, next: NextFunction) => { try { if (schemas.body) { req.body = schemas.body.parse(req.body); } if (schemas.query) { req.query = schemas.query.parse(req.query); } if (schemas.params) { req.params = schemas.params.parse(req.params); } next(); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ success: false, error: { code: "VALIDATION_ERROR", message: "Invalid request data", details: error.flatten().fieldErrors, }, }); } next(error); } }; };
NestJS Pattern
// users/users.controller.ts import { Controller, Get, Post, Put, Delete, Body, Param, Query, } from "@nestjs/common"; import { ApiTags, ApiOperation, ApiResponse } from "@nestjs/swagger"; import { UsersService } from "./users.service"; import { CreateUserDto, UpdateUserDto, GetUsersQueryDto } from "./dto"; @ApiTags("users") @Controller("users") export class UsersController { constructor(private readonly usersService: UsersService) {} @Post() @ApiOperation({ summary: "Create user" }) @ApiResponse({ status: 201, description: "User created" }) @ApiResponse({ status: 400, description: "Validation error" }) async create(@Body() dto: CreateUserDto) { return this.usersService.create(dto); } @Get() @ApiOperation({ summary: "List users" }) async findAll(@Query() query: GetUsersQueryDto) { return this.usersService.findAll(query); } @Get(":id") @ApiOperation({ summary: "Get user by ID" }) async findOne(@Param("id") id: string) { return this.usersService.findOne(id); } @Put(":id") @ApiOperation({ summary: "Update user" }) async update(@Param("id") id: string, @Body() dto: UpdateUserDto) { return this.usersService.update(id, dto); } @Delete(":id") @ApiOperation({ summary: "Delete user" }) async remove(@Param("id") id: string) { return this.usersService.remove(id); } }
FastAPI Pattern (Python)
# routers/users.py from fastapi import APIRouter, Depends, HTTPException, Query from typing import List, Optional from pydantic import BaseModel, EmailStr, Field router = APIRouter(prefix="/users", tags=["users"]) class CreateUserDto(BaseModel): email: EmailStr name: str = Field(..., min_length=2) password: str = Field(..., min_length=8) class UserResponse(BaseModel): id: str email: str name: str role: str created_at: datetime class PaginatedResponse(BaseModel): data: List[UserResponse] total: int page: int limit: int @router.post("/", status_code=201, response_model=UserResponse) async def create_user(dto: CreateUserDto, service: UserService = Depends()): return await service.create(dto) @router.get("/", response_model=PaginatedResponse) async def list_users( page: int = Query(1, ge=1), limit: int = Query(10, ge=1, le=100), search: Optional[str] = None, service: UserService = Depends() ): return await service.find_all(page, limit, search)
Response Format Standards
// Success Response { "success": true, "data": { ... }, "meta": { "page": 1, "limit": 10, "total": 50, "totalPages": 5 } } // Error Response { "success": false, "error": { "code": "VALIDATION_ERROR", "message": "Invalid request data", "details": { "email": ["Invalid email address"], "password": ["Password must contain uppercase letter"] } } }
Status Codes
- 200: Success (GET, PUT)
- 201: Created (POST)
- 204: No Content (DELETE)
- 400: Validation Error
- 401: Unauthorized
- 403: Forbidden
- 404: Not Found
- 409: Conflict
- 500: Server Error
Best Practices
- Type everything: Request, response, DTOs, errors
- Validate early: Before hitting service layer
- Consistent responses: Same structure everywhere
- HTTP semantics: Use correct status codes
- Error details: Include validation errors
- Pagination: Always paginate lists
- Filtering/sorting: Support common queries
- Documentation: OpenAPI/Swagger specs
Output Checklist
- Route definitions with HTTP methods
- Request validation schemas
- TypeScript types for all DTOs
- Controller handlers with error handling
- Consistent response format
- Pagination for list endpoints
- HTTP status codes correctly used
- Error response format
- OpenAPI/Swagger documentation
- Usage examples with curl/requests