api-endpoint

from vapvarun/claude-backup

Personal backup of Claude Code skills and plugins

5 stars0 forksUpdated Jan 26, 2026
npx skills add https://github.com/vapvarun/claude-backup --skill api-endpoint

SKILL.md

API Endpoint Development

Best practices for building secure, maintainable REST APIs.

Endpoint Structure

Express/Node.js Template

import { Router, Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import { authenticate, authorize } from '../middleware/auth';
import { validate } from '../middleware/validation';
import { asyncHandler } from '../utils/asyncHandler';
import { ApiError } from '../utils/ApiError';

const router = Router();

// Schema definitions
const createUserSchema = z.object({
  body: z.object({
    name: z.string().min(1).max(100),
    email: z.string().email(),
    role: z.enum(['user', 'admin']).default('user'),
  }),
});

const getUserSchema = z.object({
  params: z.object({
    id: z.string().uuid(),
  }),
});

// Endpoints
router.post(
  '/users',
  authenticate,
  authorize('admin'),
  validate(createUserSchema),
  asyncHandler(async (req: Request, res: Response) => {
    const user = await UserService.create(req.body);
    res.status(201).json({
      success: true,
      data: user,
    });
  })
);

router.get(
  '/users/:id',
  authenticate,
  validate(getUserSchema),
  asyncHandler(async (req: Request, res: Response) => {
    const user = await UserService.findById(req.params.id);
    if (!user) {
      throw new ApiError(404, 'User not found');
    }
    res.json({
      success: true,
      data: user,
    });
  })
);

export default router;

Input Validation

Zod Schema Validation

import { z } from 'zod';

// Basic schemas
const emailSchema = z.string().email().toLowerCase();
const passwordSchema = z.string().min(8).max(100);
const uuidSchema = z.string().uuid();

// Complex object schema
const createPostSchema = z.object({
  body: z.object({
    title: z.string().min(1).max(255).trim(),
    content: z.string().min(10).max(10000),
    tags: z.array(z.string()).max(10).optional(),
    status: z.enum(['draft', 'published']).default('draft'),
    publishAt: z.string().datetime().optional(),
  }),
});

// Query params schema
const listPostsSchema = z.object({
  query: z.object({
    page: z.coerce.number().int().positive().default(1),
    limit: z.coerce.number().int().min(1).max(100).default(20),
    status: z.enum(['draft', 'published', 'all']).default('all'),
    sortBy: z.enum(['createdAt', 'updatedAt', 'title']).default('createdAt'),
    order: z.enum(['asc', 'desc']).default('desc'),
    search: z.string().max(100).optional(),
  }),
});

// Validation middleware
function validate(schema: z.ZodSchema) {
  return async (req: Request, res: Response, next: NextFunction) => {
    try {
      const validated = await schema.parseAsync({
        body: req.body,
        query: req.query,
        params: req.params,
      });
      req.body = validated.body ?? req.body;
      req.query = validated.query ?? req.query;
      req.params = validated.params ?? req.params;
      next();
    } catch (error) {
      if (error instanceof z.ZodError) {
        return res.status(400).json({
          success: false,
          error: {
            code: 'VALIDATION_ERROR',
            message: 'Validation failed',
            details: error.errors.map(e => ({
              field: e.path.join('.'),
              message: e.message,
            })),
          },
        });
      }
      next(error);
    }
  };
}

Input Sanitization

import sanitizeHtml from 'sanitize-html';
import xss from 'xss';

// Sanitize HTML content
function sanitizeContent(content: string): string {
  return sanitizeHtml(content, {
    allowedTags: ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'ul', 'ol', 'li'],
    allowedAttributes: {
      'a': ['href', 'title'],
    },
    allowedSchemes: ['http', 'https', 'mailto'],
  });
}

// Prevent XSS in plain text
function sanitizeText(text: string): string {
  return xss(text);
}

// Sanitize file names
function sanitizeFileName(fileName: string): string {
  return fileName
    .replace(/[^a-zA-Z0-9.-]/g, '_')
    .replace(/\.{2,}/g, '.')
    .substring(0, 255);
}

Authentication

JWT Authentication

import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';

interface JwtPayload {
  userId: string;
  role: string;
  iat: number;
  exp: number;
}

// Generate tokens
function generateTokens(user: User) {
  const accessToken = jwt.sign(
    { userId: user.id, role: user.role },
    process.env.JWT_SECRET!,
    { expiresIn: '15m' }
  );

  const refreshToken = jwt.sign(
    { userId: user.id, tokenVersion: user.tokenVersion },
    process.env.REFRESH_SECRET!,
    { expiresIn: '7d' }
  );

  return { accessToken, refreshToken };
}

// Authentication middleware
async function authenticate(req: Request, res: Response, next: NextFunction) {
  const authHeader = req.headers.authorization;

  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({
      success: false,
      error: { code: 'UNAUTHORIZED', message: 'Miss

...
Read full content

Repository Stats

Stars5
Forks0