backend-developer

SKILL.md

Backend Developer

Expert guidance for building secure, scalable, and maintainable backend systems.

When to Use This Skill

  • Designing REST or GraphQL APIs
  • Creating database schemas
  • Implementing authentication/authorization
  • Writing business logic
  • Setting up data validation
  • Handling errors and logging
  • Optimizing database queries
  • Ensuring security best practices

API Design

REST API Principles

Resource Naming

Good:
GET    /users              # List users
GET    /users/:id          # Get user
POST   /users              # Create user
PUT    /users/:id          # Update user (full)
PATCH  /users/:id          # Update user (partial)
DELETE /users/:id          # Delete user

GET    /users/:id/orders   # User's orders (nested resource)
GET    /orders?userId=123  # Filter orders (query param)

Bad:
GET    /getUsers
POST   /createUser
GET    /user/delete/:id

HTTP Status Codes

Success:
200 OK                  # Request succeeded
201 Created             # Resource created
204 No Content          # Success, no body (DELETE)

Client Errors:
400 Bad Request         # Invalid request data
401 Unauthorized        # Authentication required
403 Forbidden           # Insufficient permissions
404 Not Found           # Resource doesn't exist
409 Conflict            # Resource conflict
422 Unprocessable Entity # Validation failed

Server Errors:
500 Internal Server Error  # Unexpected error
502 Bad Gateway           # Upstream service error
503 Service Unavailable   # Temporarily down

Response Format

// Success response
{
  "data": {
    "id": "123",
    "name": "John Doe",
    "email": "john@example.com"
  }
}

// List response with pagination
{
  "data": [
    { "id": "1", "name": "Item 1" },
    { "id": "2", "name": "Item 2" }
  ],
  "pagination": {
    "page": 1,
    "pageSize": 20,
    "totalPages": 5,
    "totalItems": 100
  }
}

// Error response
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input data",
    "details": [
      { "field": "email", "message": "Invalid email format" },
      { "field": "password", "message": "Must be at least 8 characters" }
    ]
  }
}

API Versioning

URL versioning (recommended for simplicity):
/api/v1/users
/api/v2/users

Header versioning:
Accept: application/vnd.api+json; version=1

Query parameter:
/api/users?version=1

Rate Limiting

Headers to include:
X-RateLimit-Limit: 100        # Max requests per window
X-RateLimit-Remaining: 95     # Remaining requests
X-RateLimit-Reset: 1609459200 # Window reset timestamp

Response when exceeded:
HTTP 429 Too Many Requests
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Retry after 60 seconds.",
    "retryAfter": 60
  }
}

Database Design

Schema Design Principles

  1. Normalization: Reduce redundancy (3NF for OLTP)
  2. Denormalization: Strategic for read performance
  3. Indexing: Based on query patterns
  4. Constraints: Enforce data integrity at DB level

Common Patterns

User Authentication Schema

CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email VARCHAR(255) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL  -- Soft delete
);

CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_deleted_at ON users(deleted_at) WHERE deleted_at IS NULL;

Soft Delete Pattern

-- Add deleted_at column
ALTER TABLE users ADD COLUMN deleted_at TIMESTAMP NULL;

-- Query active records
SELECT * FROM users WHERE deleted_at IS NULL;

-- Soft delete
UPDATE users SET deleted_at = CURRENT_TIMESTAMP WHERE id = ?;

-- Hard delete (if needed)
DELETE FROM users WHERE id = ? AND deleted_at IS NOT NULL;

Audit Trail

CREATE TABLE audit_logs (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    table_name VARCHAR(100) NOT NULL,
    record_id UUID NOT NULL,
    action VARCHAR(20) NOT NULL, -- INSERT, UPDATE, DELETE
    old_values JSONB,
    new_values JSONB,
    user_id UUID REFERENCES users(id),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_audit_record ON audit_logs(table_name, record_id);
CREATE INDEX idx_audit_user ON audit_logs(user_id);

Query Optimization

-- Use EXPLAIN ANALYZE to understand query plans
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = ?;

-- Cover queries with composite indexes
CREATE INDEX idx_orders_user_status ON orders(user_id, status);

-- Partial indexes for frequent filters
CREATE INDEX idx_orders_pending ON orders(created_at) 
WHERE status = 'pending';

-- Avoid SELECT *
SELECT id, name, email FROM users WHERE id = ?;  -- Good
SELECT * FROM users WHERE id = ?;                 -- Avoid

Authentication & Authorization

JWT Authentication Flow

1. Login Request:
   POST /auth/login { email, password }
   
2. Validate credentials, generate tokens:
   {
     "accessToken": "eyJ...",   // Short-lived (15-60 min)
     "refreshToken": "abc...",  // Long-lived (7-30 days)
     "expiresIn": 3600
   }
   
3. Access protected resources:
   GET /api/users
   Authorization: Bearer eyJ...
   
4. Refresh when expired:
   POST /auth/refresh { refreshToken: "abc..." }

JWT Best Practices

// Token payload (keep minimal)
interface JwtPayload {
  sub: string;      // User ID
  email: string;    // For display
  roles: string[];  // Authorization
  iat: number;      // Issued at
  exp: number;      // Expiration
}

// Security considerations:
// - Use RS256 (asymmetric) for distributed systems
// - Use HS256 (symmetric) for single service
// - Never store sensitive data in payload
// - Implement token blacklisting for logout
// - Use short expiration for access tokens

Role-Based Access Control (RBAC)

// Define permissions
const PERMISSIONS = {
  users: {
    create: 'users:create',
    read: 'users:read',
    update: 'users:update',
    delete: 'users:delete',
  },
  orders: {
    create: 'orders:create',
    read: 'orders:read',
    update: 'orders:update',
    delete: 'orders:delete',
  },
};

// Define roles
const ROLES = {
  admin: [
    'users:*',     // All user permissions
    'orders:*',    // All order permissions
  ],
  manager: [
    'users:read',
    'orders:*',
  ],
  user: [
    'orders:create',
    'orders:read',  // Own orders only
  ],
};

// Middleware example
function requirePermission(permission: string) {
  return (req, res, next) => {
    if (!hasPermission(req.user.roles, permission)) {
      return res.status(403).json({ error: 'Forbidden' });
    }
    next();
  };
}

Input Validation

Validation Schema Example

// Using Zod
import { z } from 'zod';

const createUserSchema = z.object({
  email: z.string().email('Invalid email'),
  password: z.string()
    .min(8, 'Password must be at least 8 characters')
    .regex(/[A-Z]/, 'Must contain uppercase')
    .regex(/[0-9]/, 'Must contain number'),
  name: z.string().min(1).max(100),
  age: z.number().int().min(18).optional(),
});

// Validate in controller
async function createUser(req, res) {
  const result = createUserSchema.safeParse(req.body);
  
  if (!result.success) {
    return res.status(422).json({
      error: {
        code: 'VALIDATION_ERROR',
        details: result.error.issues,
      },
    });
  }
  
  // Proceed with validated data
  const user = await userService.create(result.data);
  res.status(201).json({ data: user });
}

Sanitization

// Sanitize string inputs
function sanitizeString(input: string): string {
  return input
    .trim()
    .replace(/[<>]/g, ''); // Basic XSS prevention
}

// Sanitize for SQL (use parameterized queries instead)
// NEVER do string concatenation for SQL

// Good: Parameterized query
const user = await db.query(
  'SELECT * FROM users WHERE email = $1',
  [email]
);

// Bad: String concatenation (SQL injection risk)
const user = await db.query(
  `SELECT * FROM users WHERE email = '${email}'`
);

Error Handling

Error Classes

// Base application error
class AppError extends Error {
  constructor(
    public statusCode: number,
    public code: string,
    message: string,
    public details?: unknown
  ) {
    super(message);
    this.name = 'AppError';
  }
}

// Specific error types
class ValidationError extends AppError {
  constructor(details: unknown) {
    super(422, 'VALIDATION_ERROR', 'Validation failed', details);
  }
}

class NotFoundError extends AppError {
  constructor(resource: string) {
    super(404, 'NOT_FOUND', `${resource} not found`);
  }
}

class UnauthorizedError extends AppError {
  constructor(message = 'Authentication required') {
    super(401, 'UNAUTHORIZED', message);
  }
}

class ForbiddenError extends AppError {
  constructor(message = 'Insufficient permissions') {
    super(403, 'FORBIDDEN', message);
  }
}

Global Error Handler

function errorHandler(err: Error, req: Request, res: Response, next: NextFunction) {
  // Log error
  logger.error({
    error: err.message,
    stack: err.stack,
    path: req.path,
    method: req.method,
    userId: req.user?.id,
  });
  
  // Handle known errors
  if (err instanceof AppError) {
    return res.status(err.statusCode).json({
      error: {
        code: err.code,
        message: err.message,
        details: err.details,
      },
    });
  }
  
  // Handle unknown errors
  res.status(500).json({
    error: {
      code: 'INTERNAL_ERROR',
      message: 'An unexpected error occurred',
    },
  });
}

Logging

Structured Logging

// Logger setup
const logger = {
  info: (message: string, meta?: object) => {
    console.log(JSON.stringify({
      level: 'info',
      message,
      timestamp: new Date().toISOString(),
      ...meta,
    }));
  },
  
  error: (message: string, meta?: object) => {
    console.error(JSON.stringify({
      level: 'error',
      message,
      timestamp: new Date().toISOString(),
      ...meta,
    }));
  },
};

// Request logging middleware
function requestLogger(req, res, next) {
  const start = Date.now();
  
  res.on('finish', () => {
    logger.info('HTTP Request', {
      method: req.method,
      path: req.path,
      statusCode: res.statusCode,
      duration: Date.now() - start,
      userId: req.user?.id,
    });
  });
  
  next();
}

Security Best Practices

Security Checklist

  • HTTPS only in production
  • Parameterized queries for all database operations
  • Input validation on all endpoints
  • Rate limiting to prevent abuse
  • CORS configuration restricted to allowed origins
  • Secure headers (Helmet.js or equivalent)
  • Password hashing with bcrypt/argon2
  • Secrets management (environment variables, vault)
  • Dependency scanning for vulnerabilities
  • SQL injection prevention via ORM or parameterized queries
  • XSS prevention via output encoding

Security Headers

// Essential security headers
app.use((req, res, next) => {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('X-XSS-Protection', '1; mode=block');
  res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
  res.setHeader('Content-Security-Policy', "default-src 'self'");
  next();
});

Project Structure

src/
├── controllers/        # Request handlers
├── services/          # Business logic
├── repositories/      # Data access layer
├── models/            # Database models/entities
├── middleware/        # Express middleware
├── routes/            # Route definitions
├── validators/        # Request validation schemas
├── utils/             # Utility functions
├── types/             # TypeScript types
├── config/            # Configuration
└── app.ts             # Application entry point

Output Artifacts

When this skill is activated, I can help create:

  1. API Endpoint Implementation: Controllers and routes
  2. Database Schema: Migrations and models
  3. Authentication System: JWT, sessions, OAuth
  4. Validation Schemas: Request validation
  5. Error Handling: Error classes and middleware
  6. Security Configuration: Headers, CORS, rate limiting
  7. API Documentation: OpenAPI/Swagger specs
Weekly Installs
2
First Seen
10 days ago
Installed on
opencode2
gemini-cli2
claude-code2
github-copilot2
windsurf2
codex2