diff --git a/docs/error-handling.md b/docs/error-handling.md index b3b4db5..a84e912 100644 --- a/docs/error-handling.md +++ b/docs/error-handling.md @@ -1,12 +1,19 @@ + # Error Handling in devoter-api -This document explains the error handling strategy implemented in the devoter-api service. +Robust error handling is critical for building reliable APIs. The `devoter-api` service implements a comprehensive error handling strategy to ensure predictable, actionable, and secure responses for both developers and API consumers. This document details the approach, patterns, and best practices used. ## Architecture The error handling system is built around these key components: -1. **ApiError Class**: A custom error class that extends JavaScript's native Error +1. **ApiError Class**: A custom error class that extends JavaScript's native Error, providing structured error information +## ApiError Class Structure + +The `ApiError` class standardizes error creation and propagation. It typically includes: + + +Extend or customize this class as needed for your use case. 2. **HTTP Status Codes**: Well-defined status codes used consistently across all endpoints 3. **Error Response Format**: A consistent format for all API error responses 4. **Global Error Handler**: Centralized handling of all errors @@ -61,8 +68,62 @@ We use standardized error codes to help identify specific error conditions: 1. Route handlers use `asyncHandler` to automatically catch exceptions 2. Specific error conditions throw appropriate `ApiError` instances 3. Global error handler processes all uncaught errors -4. Prisma database errors are converted to appropriate API errors -5. All errors are logged with request context for debugging +4. Prisma/database errors are mapped to API errors (see below) +5. All errors are logged with request context for debugging and traceability +## Global Error Handler Example + +The global error handler ensures all errors are returned in the standard format and logs relevant context: + +```typescript +app.setErrorHandler((err, req, res) => { + if (err instanceof ApiError) { + // Log error with request context + logger.error({ err, path: req.url, user: req.user }, "API error"); + res.status(err.status).send({ + success: false, + error: err.message, + code: err.code, + details: err.details || undefined, + }); + } else { + // Handle unexpected errors + logger.error({ err, path: req.url }, "Unhandled error"); + res.status(500).send({ + success: false, + error: "Internal server error", + code: "INTERNAL_ERROR", + }); + } +}); +``` + +## Prisma/Database Error Mapping + +Prisma errors (e.g., unique constraint violations) are caught and mapped to API errors: + +```typescript +try { + await prisma.user.create({ ... }); +} catch (e) { + if (e.code === 'P2002') { + throw ApiError.conflict("Duplicate resource", "UNIQUE_CONSTRAINT_VIOLATION"); + } + throw ApiError.internal("Database error", "DATABASE_ERROR"); +} +``` +## Logging + +All errors are logged with relevant request context (user, endpoint, params) to aid debugging and monitoring. Use a structured logger (e.g., pino, winston) for best results. +## Troubleshooting Common Errors + +| Scenario | Likely Cause | Resolution | +|----------|-------------|------------| +| 401 Unauthorized | Missing/invalid auth headers | Check `Authorization` header and signature | +| 409 Conflict | Duplicate resource | Ensure unique fields (e.g., wallet) are not reused | +| 429 Too Many Requests | Rate limit exceeded | Wait and retry after `retryAfter` seconds | +| 500 Internal Error | Unexpected server error | Check logs for stack trace and context | + +If you encounter an error not listed here, consult the logs or contact the API maintainer. ## Example Usage diff --git a/docs/rate-limiting.md b/docs/rate-limiting.md index 6aeeb1d..1a10666 100644 --- a/docs/rate-limiting.md +++ b/docs/rate-limiting.md @@ -1,5 +1,8 @@ + # Rate Limiting Documentation +Rate limiting is essential for protecting APIs from abuse, ensuring fair access, and maintaining service reliability. The `devoter-api` uses a flexible, secure rate limiting strategy tailored to endpoint sensitivity and user context. + ## Overview The devoter-api implements comprehensive rate limiting to prevent abuse and ensure fair usage across all endpoints. Different endpoints have different rate limits based on their security requirements and expected usage patterns. @@ -8,10 +11,55 @@ The devoter-api implements comprehensive rate limiting to prevent abuse and ensu ### Key Generation Rate limits are applied based on a combination of: -- IP address (always included) -- Wallet address (when available for authenticated endpoints) +- **IP address** (always included) +- **Wallet address** (when available for authenticated endpoints) -This provides more granular control while preventing both IP-based and wallet-based abuse. +This dual-key approach prevents both IP-based and wallet-based abuse, and allows for fine-grained control. + +**Example key generation logic:** +```typescript +function getRateLimitKey(req) { + const ip = req.ip; + const wallet = req.user?.walletAddress; + return wallet ? `${ip}:${wallet}` : ip; +} +``` +## Customizing Per-Route Rate Limits + +You can set different rate limits for each route using the Fastify plugin: + +```typescript +fastify.register(require('@fastify/rate-limit'), { + max: 100, + timeWindow: '1 minute', + keyGenerator: getRateLimitKey, + // ...other options +}); + +fastify.route({ + method: 'POST', + url: '/register', + config: { + rateLimit: { max: 5, timeWindow: '1 minute' } + }, + handler: registerHandler +}); +``` +## Example Middleware Configuration + +```typescript +import rateLimit from '@fastify/rate-limit'; +fastify.register(rateLimit, { + global: false, // allow per-route overrides + keyGenerator: getRateLimitKey, + errorResponseBuilder: (req, context) => ({ + success: false, + error: 'Rate limit exceeded', + message: `Too many requests. Try again in ${context.after} seconds.`, + retryAfter: context.after + }) +}); +``` ### Rate Limit Configurations @@ -96,7 +144,25 @@ Rate limit violations are logged for monitoring purposes. In production, conside ## Production Considerations For production deployment: -1. Consider using Redis for rate limit storage in a distributed environment +1. **Use Redis** for rate limit storage in distributed/multi-instance environments: + - Install and configure Redis + - Use `@fastify/rate-limit` with `redis` store: + ```typescript + fastify.register(require('@fastify/rate-limit'), { + redis: new Redis(process.env.REDIS_URL), + // ...other options + }); + ``` 2. Monitor rate limit patterns and adjust as needed -3. Implement additional security measures for persistent abusers +3. Implement additional security measures for persistent abusers (e.g., IP bans) 4. Set up monitoring and alerting for rate limit violations +## Troubleshooting & FAQ + +| Problem | Possible Cause | Solution | +|---------|---------------|----------| +| All requests blocked | Misconfigured rate limit or key generator | Check per-route config and key logic | +| Rate limit not enforced | Plugin not registered or wrong route config | Ensure plugin is loaded and route has correct settings | +| High false positives | Shared IPs (e.g., proxies) | Consider using wallet or user ID in key | +| Redis errors | Redis not running or misconfigured | Check Redis connection and logs | + +If you encounter persistent issues, review logs and verify your environment variables and plugin configuration.