Skip to content
Back to skills

Global Error Handling

ASecurity

Your approach to handling global error handling. Use this skill when working on files where global error handling comes into play.

  • 57 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 17, 2026
developmenttypescriptgodebuggingbackend

Works with

  • claude code

Security analysis

A100/100

Scanned October 17, 2026

npx -y skills add diegosouzapw/awesome-omni-skill --skill global-error-handling --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Global Error Handling?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Global Error Handling
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/diegosouzapw-global-error-handling/badge)](https://www.skillsdirectory.com/skills/diegosouzapw-global-error-handling)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: Global Error Handling
description: Your approach to handling global error handling. Use this skill when working on files where global error handling comes into play.
globs:
  - backend/src/routes/**/*.ts
  - backend/src/middleware/**/*.ts
  - backend/src/services/**/*.ts
  - backend/src/controllers/**/*.ts
alwaysApply: true
---

# Global Error Handling

This Skill provides Claude Code with specific guidance on how to handle errors consistently across the MorrisB application.

## CRITICAL: Always Follow This Pattern

When writing or modifying any backend route, service, or middleware, you MUST use the custom error handling system.

## Error Classes Available

Import errors from `../errors` (adjust path as needed):

```typescript
import {
    // Error classes
    AppError,
    ValidationError,
    UnauthorizedError,
    ForbiddenError,
    NotFoundError,
    ConflictError,
    RateLimitError,
    InternalError,
    ExternalServiceError,

    // Async handler wrapper
    asyncHandler,
    asyncAuthHandler,
} from "../errors";
```

## Route Handler Pattern (REQUIRED)

### Option 1: asyncHandler Wrapper (Preferred)

Use `asyncHandler` or `asyncAuthHandler` to automatically catch errors:

```typescript
import { asyncAuthHandler, NotFoundError, ForbiddenError } from "../errors";

router.get("/:workspaceId/contacts/:id", authenticate, asyncAuthHandler(async (req, res) => {
    const { workspaceId, id } = req.params;
    const userId = req.user?._id?.toString();

    // Validate workspace access - throws ForbiddenError
    const workspace = await Project.findById(workspaceId);
    if (!workspace) {
        throw NotFoundError.workspace(workspaceId);
    }
    if (workspace.userId.toString() !== userId) {
        throw ForbiddenError.workspaceAccess();
    }

    // Find contact - throws NotFoundError
    const contact = await Contact.findOne({ _id: id, workspaceId });
    if (!contact) {
        throw NotFoundError.contact(id);
    }

    res.json({ success: true, data: { contact } });
}));
```

### Option 2: Traditional try/catch (Use sparingly)

Only use try/catch when you need specific error handling:

```typescript
router.post("/register", async (req, res, next) => {
    try {
        const validatedData = registerSchema.parse(req.body);
        // ... logic
    } catch (error: any) {
        if (error.name === "ZodError") {
            return next(ValidationError.fromZod(error));
        }
        next(error); // Pass to global error handler
    }
});
```

## Error Factory Methods

Use static factory methods for common error scenarios:

### ValidationError
```typescript
throw ValidationError.missingField("email");
throw ValidationError.invalidFormat("phone", "+1-XXX-XXX-XXXX");
throw ValidationError.invalidId("contact");
throw ValidationError.fromZod(zodError);
```

### UnauthorizedError
```typescript
throw UnauthorizedError.noToken();
throw UnauthorizedError.invalidToken();
throw UnauthorizedError.tokenExpired();
throw UnauthorizedError.invalidCredentials();
```

### ForbiddenError
```typescript
throw ForbiddenError.emailNotVerified();
throw ForbiddenError.workspaceAccess();
throw ForbiddenError.insufficientPermissions("delete contacts");
```

### NotFoundError
```typescript
throw NotFoundError.workspace(workspaceId);
throw NotFoundError.contact(contactId);
throw NotFoundError.user(userId);
throw NotFoundError.campaign(campaignId);
throw NotFoundError.sequence(sequenceId);
throw new NotFoundError("Custom Resource", identifier);
```

### ConflictError
```typescript
throw ConflictError.emailExists();
throw ConflictError.duplicate("Contact", "email", "user@example.com");
```

### ExternalServiceError
```typescript
throw ExternalServiceError.email("Failed to send verification email");
throw ExternalServiceError.ai("OpenAI rate limit exceeded");
```

## DO NOT DO THIS

❌ **Never manually send error responses in routes:**
```typescript
// BAD - Don't do this
res.status(404).json({
    success: false,
    error: "Contact not found",
});
```

❌ **Never catch and swallow errors silently:**
```typescript
// BAD - Don't do this
try {
    await someOperation();
} catch (error) {
    console.error(error); // Lost error!
}
```

❌ **Never use generic error messages:**
```typescript
// BAD - Not helpful
throw new Error("Something went wrong");
```

## DO THIS INSTEAD

✅ **Throw specific error classes:**
```typescript
// GOOD
throw NotFoundError.contact(contactId);
```

✅ **Use asyncHandler to auto-catch:**
```typescript
// GOOD - Errors automatically passed to global handler
router.get("/", asyncHandler(async (req, res) => {
    // If this throws, global handler catches it
    const data = await getData();
    res.json({ success: true, data });
}));
```

✅ **Pass errors to next() when in try/catch:**
```typescript
// GOOD - Passes to global error handler
} catch (error) {
    next(error);
}
```

## Server Setup Required

The global error handler must be registered in `server.ts`:

```typescript
import { globalErrorHandler, notFoundHandler } from "./middleware/errorHandler";

// After all routes
app.use(notFoundHandler);
app.use(globalErrorHandler);
```

## Error Response Format

All errors are returned in this consistent format:

```json
{
    "success": false,
    "error": {
        "message": "Contact not found",
        "code": "RES_001",
        "details": {
            "resource": "Contact",
            "identifier": "123abc"
        }
    }
}
```

## Best Practices Summary

1. **Always use asyncHandler/asyncAuthHandler** for async route handlers
2. **Always throw specific error classes** instead of generic Error
3. **Always use factory methods** when available (e.g., `NotFoundError.contact()`)
4. **Never manually send error responses** - let the global handler do it
5. **Pass errors to next()** if you catch them locally
6. **Use logger for debugging**, not console.log
7. **Include helpful details** in error constructors for debugging

## File References

- Error classes: `backend/src/errors/AppError.ts`
- Error handler middleware: `backend/src/middleware/errorHandler.ts`
- Index exports: `backend/src/errors/index.ts`

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.