Awesome-omni-skill Global Error Handling
Your approach to handling global error handling. Use this skill when working on files where global error handling comes into play.
git clone https://github.com/diegosouzapw/awesome-omni-skill
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/development/global-error-handling" ~/.claude/skills/diegosouzapw-awesome-omni-skill-global-error-handling && rm -rf "$T"
skills/development/global-error-handling/SKILL.mdGlobal 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):
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:
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:
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
throw ValidationError.missingField("email"); throw ValidationError.invalidFormat("phone", "+1-XXX-XXX-XXXX"); throw ValidationError.invalidId("contact"); throw ValidationError.fromZod(zodError);
UnauthorizedError
throw UnauthorizedError.noToken(); throw UnauthorizedError.invalidToken(); throw UnauthorizedError.tokenExpired(); throw UnauthorizedError.invalidCredentials();
ForbiddenError
throw ForbiddenError.emailNotVerified(); throw ForbiddenError.workspaceAccess(); throw ForbiddenError.insufficientPermissions("delete contacts");
NotFoundError
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
throw ConflictError.emailExists(); throw ConflictError.duplicate("Contact", "email", "user@example.com");
ExternalServiceError
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:
// BAD - Don't do this res.status(404).json({ success: false, error: "Contact not found", });
❌ Never catch and swallow errors silently:
// BAD - Don't do this try { await someOperation(); } catch (error) { console.error(error); // Lost error! }
❌ Never use generic error messages:
// BAD - Not helpful throw new Error("Something went wrong");
DO THIS INSTEAD
✅ Throw specific error classes:
// GOOD throw NotFoundError.contact(contactId);
✅ Use asyncHandler to auto-catch:
// 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:
// GOOD - Passes to global error handler } catch (error) { next(error); }
Server Setup Required
The global error handler must be registered in
server.ts:
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:
{ "success": false, "error": { "message": "Contact not found", "code": "RES_001", "details": { "resource": "Contact", "identifier": "123abc" } } }
Best Practices Summary
- Always use asyncHandler/asyncAuthHandler for async route handlers
- Always throw specific error classes instead of generic Error
- Always use factory methods when available (e.g.,
)NotFoundError.contact() - Never manually send error responses - let the global handler do it
- Pass errors to next() if you catch them locally
- Use logger for debugging, not console.log
- 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