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