Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 65 additions & 4 deletions docs/error-handling.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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",
});
}
});
```
Comment on lines +77 to +98
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fastify error handler uses reply, not res; fix API usage.

The example is Express-like. Use Fastify’s reply and preferred naming.

-app.setErrorHandler((err, req, res) => {
+fastify.setErrorHandler((err, req, reply) => {
   if (err instanceof ApiError) {
-    logger.error({ err, path: req.url, user: req.user }, "API error");
-    res.status(err.status).send({
+    logger.error({ err, path: req.url, user: req.user }, "API error");
+    reply.status(err.status).send({
       success: false,
       error: err.message,
       code: err.code,
       details: err.details || undefined,
     });
   } else {
-    logger.error({ err, path: req.url }, "Unhandled error");
-    res.status(500).send({
+    logger.error({ err, path: req.url }, "Unhandled error");
+    reply.status(500).send({
       success: false,
       error: "Internal server error",
       code: "INTERNAL_ERROR",
     });
   }
 });

(fastify.dev)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In docs/error-handling.md around lines 96 to 117, the example uses an
Express-style error handler signature (err, req, res) and res methods; change it
to Fastify's signature (err, request, reply), use request instead of req for
naming, and replace res.status(...).send(...) with reply.code(...).send(...).
For ApiError use reply.code(err.status).send({...}) and for unexpected errors
use reply.code(500).send({...}); keep logging but reference request.url and
request.user consistently.


## 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

Expand Down
76 changes: 71 additions & 5 deletions docs/rate-limiting.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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

Expand Down Expand Up @@ -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.