Async and Errors
This skill describes pragmatic patterns for async flows and error handling in backend services.
1. Core rules
- •Do not swallow errors: either handle them or propagate them.
- •Catch once per layer: avoid repeated catch-and-rethrow without adding value.
- •Make failures observable: capture exceptions to monitoring (with safe context).
- •Prefer typed domain errors: map to HTTP responses in controllers.
2. Async route handlers (Express-style)
Avoid unhandled promise rejections by using an async wrapper:
ts
export function asyncHandler(fn) {
return (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
}
router.post("/users", asyncHandler(async (req, res) => {
// ...
}));
Register an error boundary after routes:
ts
app.use("/api", routes);
app.use(errorBoundary);
3. Domain errors (recommended)
Define typed errors for common cases:
ts
export class NotFoundError extends Error {}
export class ConflictError extends Error {}
export class ForbiddenError extends Error {}
Controllers translate domain errors into status codes:
- •
NotFoundError→ 404 - •
ConflictError→ 409 - •
ForbiddenError→ 403
Unexpected errors → 500 (and captured to monitoring).
4. Error boundary pattern
An error boundary should:
- •capture the error (with safe tags/context)
- •return a consistent response shape
ts
export function errorBoundary(err, req, res, _next) {
monitor.captureException(err, {
tags: { layer: "http", route: req.path, method: req.method },
});
res.status(500).json({ error: "Internal server error" });
}
Related Skills
- •
routing-and-controllers - •
sentry-and-monitoring(in repo/)