Claude-skill-registry express-api-generator
Generates Express.js API routes with proper middleware, error handling, validation, and TypeScript support. Use when creating REST APIs or Express 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/express-api-generator" ~/.claude/skills/majiayu000-claude-skill-registry-express-api-generator && rm -rf "$T"
manifest:
skills/data/express-api-generator/SKILL.mdsource content
Express.js API Generator Skill
Expert at creating well-structured Express.js APIs with TypeScript, proper error handling, and best practices.
When to Activate
- "create Express API for [resource]"
- "generate REST endpoints for [feature]"
- "build Express routes for [entity]"
- "scaffold Express API"
Complete API Structure
1. Router File
// routes/users.routes.ts import { Router } from 'express'; import { UserController } from '../controllers/user.controller'; import { validate } from '../middleware/validation.middleware'; import { authenticate } from '../middleware/auth.middleware'; import { createUserSchema, updateUserSchema } from '../schemas/user.schema'; const router = Router(); const userController = new UserController(); // GET /api/users - List all users router.get( '/', authenticate, userController.getAll ); // GET /api/users/:id - Get user by ID router.get( '/:id', authenticate, userController.getById ); // POST /api/users - Create new user router.post( '/', validate(createUserSchema), userController.create ); // PUT /api/users/:id - Update user router.put( '/:id', authenticate, validate(updateUserSchema), userController.update ); // DELETE /api/users/:id - Delete user router.delete( '/:id', authenticate, userController.delete ); export default router;
2. Controller
// controllers/user.controller.ts import { Request, Response, NextFunction } from 'express'; import { UserService } from '../services/user.service'; import { CreateUserDTO, UpdateUserDTO } from '../dto/user.dto'; import { ApiError } from '../utils/ApiError'; import { asyncHandler } from '../utils/asyncHandler'; export class UserController { private userService: UserService; constructor() { this.userService = new UserService(); } getAll = asyncHandler(async (req: Request, res: Response) => { const { page = 1, limit = 10, search } = req.query; const result = await this.userService.getAll({ page: Number(page), limit: Number(limit), search: search as string, }); res.status(200).json({ success: true, data: result.users, meta: { page: result.page, limit: result.limit, total: result.total, totalPages: Math.ceil(result.total / result.limit), }, }); }); getById = asyncHandler(async (req: Request, res: Response) => { const { id } = req.params; const user = await this.userService.getById(id); if (!user) { throw new ApiError(404, 'User not found'); } res.status(200).json({ success: true, data: user, }); }); create = asyncHandler(async (req: Request, res: Response) => { const userData: CreateUserDTO = req.body; const user = await this.userService.create(userData); res.status(201).json({ success: true, data: user, message: 'User created successfully', }); }); update = asyncHandler(async (req: Request, res: Response) => { const { id } = req.params; const userData: UpdateUserDTO = req.body; const user = await this.userService.update(id, userData); if (!user) { throw new ApiError(404, 'User not found'); } res.status(200).json({ success: true, data: user, message: 'User updated successfully', }); }); delete = asyncHandler(async (req: Request, res: Response) => { const { id } = req.params; await this.userService.delete(id); res.status(200).json({ success: true, message: 'User deleted successfully', }); }); }
3. Service Layer
// services/user.service.ts import { User } from '../models/user.model'; import { CreateUserDTO, UpdateUserDTO } from '../dto/user.dto'; import { ApiError } from '../utils/ApiError'; import bcrypt from 'bcrypt'; interface GetAllOptions { page: number; limit: number; search?: string; } export class UserService { async getAll(options: GetAllOptions) { const { page, limit, search } = options; const skip = (page - 1) * limit; const query = search ? { $or: [ { name: { $regex: search, $options: 'i' } }, { email: { $regex: search, $options: 'i' } }, ]} : {}; const [users, total] = await Promise.all([ User.find(query).skip(skip).limit(limit).select('-password'), User.countDocuments(query), ]); return { users, total, page, limit }; } async getById(id: string) { const user = await User.findById(id).select('-password'); return user; } async create(userData: CreateUserDTO) { const existingUser = await User.findOne({ email: userData.email }); if (existingUser) { throw new ApiError(409, 'Email already exists'); } const hashedPassword = await bcrypt.hash(userData.password, 10); const user = await User.create({ ...userData, password: hashedPassword, }); const userObject = user.toObject(); delete userObject.password; return userObject; } async update(id: string, userData: UpdateUserDTO) { if (userData.password) { userData.password = await bcrypt.hash(userData.password, 10); } const user = await User.findByIdAndUpdate( id, { $set: userData }, { new: true, runValidators: true } ).select('-password'); return user; } async delete(id: string) { const user = await User.findByIdAndDelete(id); if (!user) { throw new ApiError(404, 'User not found'); } return true; } }
4. Validation Schema
// schemas/user.schema.ts import Joi from 'joi'; export const createUserSchema = Joi.object({ name: Joi.string().min(2).max(100).required(), email: Joi.string().email().required(), password: Joi.string().min(8).required(), role: Joi.string().valid('user', 'admin').default('user'), }); export const updateUserSchema = Joi.object({ name: Joi.string().min(2).max(100), email: Joi.string().email(), password: Joi.string().min(8), role: Joi.string().valid('user', 'admin'), }).min(1);
5. Middleware
// middleware/validation.middleware.ts import { Request, Response, NextFunction } from 'express'; import { Schema } from 'joi'; import { ApiError } from '../utils/ApiError'; export const validate = (schema: Schema) => { return (req: Request, res: Response, next: NextFunction) => { const { error, value } = schema.validate(req.body, { abortEarly: false, stripUnknown: true, }); if (error) { const message = error.details.map(d => d.message).join(', '); throw new ApiError(400, message); } req.body = value; next(); }; };
// middleware/auth.middleware.ts import { Request, Response, NextFunction } from 'express'; import jwt from 'jsonwebtoken'; import { ApiError } from '../utils/ApiError'; interface JwtPayload { userId: string; email: string; } declare global { namespace Express { interface Request { user?: JwtPayload; } } } export const authenticate = (req: Request, res: Response, next: NextFunction) => { const token = req.headers.authorization?.replace('Bearer ', ''); if (!token) { throw new ApiError(401, 'Authentication required'); } try { const decoded = jwt.verify(token, process.env.JWT_SECRET!) as JwtPayload; req.user = decoded; next(); } catch (error) { throw new ApiError(401, 'Invalid or expired token'); } };
6. Error Handler
// utils/ApiError.ts export class ApiError extends Error { constructor( public statusCode: number, public message: string, public errors: any[] = [] ) { super(message); this.name = 'ApiError'; } } // utils/asyncHandler.ts import { Request, Response, NextFunction } from 'express'; export const asyncHandler = (fn: Function) => { return (req: Request, res: Response, next: NextFunction) => { Promise.resolve(fn(req, res, next)).catch(next); }; }; // middleware/errorHandler.ts import { Request, Response, NextFunction } from 'express'; import { ApiError } from '../utils/ApiError'; export const errorHandler = ( err: Error, req: Request, res: Response, next: NextFunction ) => { if (err instanceof ApiError) { return res.status(err.statusCode).json({ success: false, message: err.message, errors: err.errors, }); } console.error('Unexpected error:', err); res.status(500).json({ success: false, message: 'Internal server error', }); };
File Structure
src/ ├── routes/ │ └── user.routes.ts ├── controllers/ │ └── user.controller.ts ├── services/ │ └── user.service.ts ├── models/ │ └── user.model.ts ├── dto/ │ └── user.dto.ts ├── schemas/ │ └── user.schema.ts ├── middleware/ │ ├── auth.middleware.ts │ ├── validation.middleware.ts │ └── errorHandler.ts └── utils/ ├── ApiError.ts └── asyncHandler.ts
Best Practices
- ✅ Separate routes, controllers, and services
- ✅ Use TypeScript for type safety
- ✅ Implement proper error handling
- ✅ Validate input data
- ✅ Use async/await with error handling
- ✅ Implement authentication/authorization
- ✅ Return consistent response format
- ✅ Add pagination for list endpoints
- ✅ Use HTTP status codes correctly
- ✅ Handle edge cases
- ✅ Add request logging
- ✅ Implement rate limiting
Output Checklist
- ✅ Routes file created
- ✅ Controller implemented
- ✅ Service layer added
- ✅ Validation schemas defined
- ✅ Middleware configured
- ✅ Error handling setup
- ✅ Tests created
- 📝 API documentation provided