API design, database patterns, REST/GraphQL, microservices architecture, and backend best practices
/plugin marketplace add Primadetaautomation/claude-dev-toolkit/plugin install claude-dev-toolkit@primadata-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
This skill provides comprehensive backend development patterns including API design, database architecture, authentication, and scalable backend systems.
URL Structure:
✅ GOOD - Resource-oriented URLs
GET /api/users # List users
GET /api/users/:id # Get specific user
POST /api/users # Create user
PUT /api/users/:id # Update user (full replace)
PATCH /api/users/:id # Update user (partial)
DELETE /api/users/:id # Delete user
# Nested resources
GET /api/users/:id/orders # Get user's orders
POST /api/users/:id/orders # Create order for user
❌ BAD - Verb-based URLs
GET /api/getUser?id=123
POST /api/createUser
POST /api/updateUser
POST /api/deleteUser
HTTP Status Codes:
// Success responses
200 OK // Successful GET, PUT, PATCH, DELETE
201 Created // Successful POST
204 No Content // Successful DELETE (no response body)
// Client errors
400 Bad Request // Invalid input data
401 Unauthorized // Missing or invalid authentication
403 Forbidden // Valid auth but insufficient permissions
404 Not Found // Resource doesn't exist
409 Conflict // Resource conflict (e.g., duplicate email)
422 Unprocessable // Validation errors
// Server errors
500 Internal Error // Unexpected server error
503 Service Unavail // Temporary unavailability
// Example usage
app.post('/api/users', async (req, res) => {
try {
const user = await userService.create(req.body);
res.status(201).json(user); // 201 Created
} catch (error) {
if (error instanceof ValidationError) {
res.status(422).json({ error: error.message }); // 422 Validation
} else if (error instanceof ConflictError) {
res.status(409).json({ error: error.message }); // 409 Conflict
} else {
res.status(500).json({ error: 'Internal server error' }); // 500
}
}
});
Request/Response Format:
// ✅ GOOD - Consistent response structure
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: {
code: string;
message: string;
details?: any;
};
meta?: {
timestamp: string;
requestId: string;
};
}
// Success response
{
"success": true,
"data": {
"id": "123",
"email": "user@example.com",
"name": "John Doe"
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"requestId": "req_abc123"
}
}
// Error response
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid email format",
"details": {
"field": "email",
"value": "invalid-email"
}
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"requestId": "req_abc123"
}
}
Schema Design Best Practices:
-- ✅ GOOD - Proper constraints and indexes
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(100) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Index frequently queried fields
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_created_at ON users(created_at);
-- Foreign key relationships
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
total DECIMAL(10, 2) NOT NULL CHECK (total >= 0),
status VARCHAR(50) NOT NULL DEFAULT 'pending',
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status);
-- ❌ BAD - No constraints, poor naming
CREATE TABLE user_table (
id INT,
mail VARCHAR(1000),
data TEXT
);
Query Optimization:
// ✅ GOOD - Efficient query with specific fields
async function getUserOrders(userId: string) {
return db.query(`
SELECT
o.id,
o.total,
o.status,
o.created_at
FROM orders o
WHERE o.user_id = $1
ORDER BY o.created_at DESC
LIMIT 20
`, [userId]);
}
// ❌ BAD - SELECT * and no limit
async function getUserOrders(userId: string) {
return db.query(`
SELECT * FROM orders WHERE user_id = $1
`, [userId]);
}
// ✅ GOOD - Avoid N+1 queries with JOIN
async function getUsersWithOrders() {
const result = await db.query(`
SELECT
u.id as user_id,
u.name,
u.email,
json_agg(
json_build_object(
'id', o.id,
'total', o.total,
'status', o.status
)
) as orders
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name, u.email
`);
return result.rows;
}
// Generic repository interface
interface Repository<T> {
findById(id: string): Promise<T | null>;
findAll(options?: FindOptions): Promise<T[]>;
create(data: Partial<T>): Promise<T>;
update(id: string, data: Partial<T>): Promise<T>;
delete(id: string): Promise<boolean>;
}
// Implementation for User entity
class UserRepository implements Repository<User> {
constructor(private db: Database) {}
async findById(id: string): Promise<User | null> {
const result = await this.db.query(
'SELECT * FROM users WHERE id = $1',
[id]
);
return result.rows[0] ? this.mapToUser(result.rows[0]) : null;
}
async findByEmail(email: string): Promise<User | null> {
const result = await this.db.query(
'SELECT * FROM users WHERE email = $1',
[email]
);
return result.rows[0] ? this.mapToUser(result.rows[0]) : null;
}
async create(data: CreateUserDto): Promise<User> {
const result = await this.db.query(
`INSERT INTO users (email, name, password_hash)
VALUES ($1, $2, $3)
RETURNING *`,
[data.email, data.name, data.passwordHash]
);
return this.mapToUser(result.rows[0]);
}
async update(id: string, data: Partial<User>): Promise<User> {
const updates: string[] = [];
const values: any[] = [];
let paramCount = 1;
Object.entries(data).forEach(([key, value]) => {
updates.push(`${key} = $${paramCount}`);
values.push(value);
paramCount++;
});
values.push(id);
const result = await this.db.query(
`UPDATE users
SET ${updates.join(', ')}, updated_at = NOW()
WHERE id = $${paramCount}
RETURNING *`,
values
);
return this.mapToUser(result.rows[0]);
}
private mapToUser(row: any): User {
return {
id: row.id,
email: row.email,
name: row.name,
createdAt: row.created_at,
updatedAt: row.updated_at,
};
}
}
// Business logic layer
class UserService {
constructor(
private userRepo: UserRepository,
private emailService: EmailService,
private cacheService: CacheService,
private logger: Logger
) {}
async createUser(dto: CreateUserDto): Promise<User> {
// 1. Validate input
this.validateUserDto(dto);
// 2. Check for duplicates
const existing = await this.userRepo.findByEmail(dto.email);
if (existing) {
throw new ConflictError('Email already registered');
}
// 3. Hash password
const passwordHash = await bcrypt.hash(dto.password, 12);
// 4. Create user
const user = await this.userRepo.create({
email: dto.email,
name: dto.name,
passwordHash,
});
// 5. Send welcome email (async, don't block)
this.emailService.sendWelcomeEmail(user.email)
.catch(err => this.logger.error('Failed to send welcome email', err));
// 6. Invalidate cache
await this.cacheService.delete(`users:all`);
// 7. Log event
this.logger.info('User created', { userId: user.id });
return user;
}
async getUserById(id: string): Promise<User> {
// Try cache first
const cacheKey = `user:${id}`;
const cached = await this.cacheService.get<User>(cacheKey);
if (cached) {
return cached;
}
// Fetch from database
const user = await this.userRepo.findById(id);
if (!user) {
throw new NotFoundError('User not found');
}
// Cache for 5 minutes
await this.cacheService.set(cacheKey, user, 300);
return user;
}
private validateUserDto(dto: CreateUserDto): void {
if (!validator.isEmail(dto.email)) {
throw new ValidationError('Invalid email format');
}
if (dto.password.length < 8) {
throw new ValidationError('Password must be at least 8 characters');
}
if (dto.name.trim().length < 2) {
throw new ValidationError('Name must be at least 2 characters');
}
}
}
import jwt from 'jsonwebtoken';
interface JwtPayload {
userId: string;
email: string;
role: string;
}
class AuthService {
private readonly JWT_SECRET = process.env.JWT_SECRET!;
private readonly JWT_EXPIRES_IN = '1h';
private readonly REFRESH_TOKEN_EXPIRES_IN = '7d';
generateAccessToken(user: User): string {
const payload: JwtPayload = {
userId: user.id,
email: user.email,
role: user.role,
};
return jwt.sign(payload, this.JWT_SECRET, {
expiresIn: this.JWT_EXPIRES_IN,
algorithm: 'HS256',
});
}
generateRefreshToken(user: User): string {
return jwt.sign(
{ userId: user.id },
this.JWT_SECRET,
{ expiresIn: this.REFRESH_TOKEN_EXPIRES_IN }
);
}
verifyToken(token: string): JwtPayload {
try {
return jwt.verify(token, this.JWT_SECRET) as JwtPayload;
} catch (error) {
throw new UnauthorizedError('Invalid or expired token');
}
}
}
// Middleware
function authenticateJWT(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing authentication token' });
}
const token = authHeader.substring(7);
try {
const payload = authService.verifyToken(token);
req.user = payload;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
}
// Authorization middleware
function requireRole(...roles: string[]) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: 'Unauthorized' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
// Usage
app.get('/api/admin/users', authenticateJWT, requireRole('admin'), async (req, res) => {
// Only authenticated admins can access
const users = await userService.getAllUsers();
res.json(users);
});
// URL-based versioning
app.use('/api/v1', v1Routes);
app.use('/api/v2', v2Routes);
// v1 route (old)
v1Router.get('/users/:id', async (req, res) => {
const user = await userService.getUser(req.params.id);
res.json(user); // Old response format
});
// v2 route (new - includes additional fields)
v2Router.get('/users/:id', async (req, res) => {
const user = await userService.getUser(req.params.id);
const enriched = await userService.enrichUserData(user);
res.json(enriched); // New response format with more data
});
// Query parameters for pagination
interface PaginationParams {
page: number;
limit: number;
sortBy?: string;
sortOrder?: 'asc' | 'desc';
filters?: Record<string, any>;
}
async function getUsers(params: PaginationParams) {
const { page = 1, limit = 20, sortBy = 'created_at', sortOrder = 'desc' } = params;
const offset = (page - 1) * limit;
const result = await db.query(`
SELECT * FROM users
WHERE email LIKE $1
ORDER BY ${sortBy} ${sortOrder}
LIMIT $2 OFFSET $3
`, [`%${params.filters?.email || ''}%`, limit, offset]);
const countResult = await db.query('SELECT COUNT(*) FROM users');
const total = parseInt(countResult.rows[0].count);
return {
data: result.rows,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
hasNext: page * limit < total,
hasPrev: page > 1,
},
};
}
// API endpoint
app.get('/api/users', async (req, res) => {
const result = await getUsers({
page: parseInt(req.query.page as string) || 1,
limit: parseInt(req.query.limit as string) || 20,
sortBy: req.query.sortBy as string,
sortOrder: req.query.sortOrder as 'asc' | 'desc',
filters: req.query.filters as any,
});
res.json(result);
});
import rateLimit from 'express-rate-limit';
// Global rate limit
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
message: 'Too many requests, please try again later',
standardHeaders: true,
legacyHeaders: false,
});
app.use('/api/', limiter);
// Strict limit for authentication endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // Only 5 login attempts per 15 minutes
skipSuccessfulRequests: true,
});
app.post('/api/auth/login', authLimiter, loginHandler);
See companion files:
graphql-patterns.md - GraphQL schema design and resolversmicroservices-patterns.md - Service communication and orchestrationcaching-strategies.md - Redis patterns and cache invalidationSee examples directory:
examples/rest-api-complete.ts - Full REST API implementationexamples/graphql-server.ts - Complete GraphQL setupexamples/auth-flow-complete.ts - Authentication implementationsenior-fullstack-developer:
database-migration-specialist:
solutions-architect:
Version 1.0.0 | REST & GraphQL Ready | Scalable Patterns
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 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 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.