When to Use It
Add a try/catch only when it adds semantic value — not just to silence errors.
Use it when you need to:
- Downgrade or alter behavior for an expected, recoverable condition (e.g., missing session → benign redirect instead of 500)
- Map specific library errors to meaningful HTTP statuses (Prisma
P2002unique constraint → 409, connectivity failure → 503) - Perform partial multi-step logic requiring cleanup or compensating action (custom rollback beyond what the DB handles)
- Produce structured, consistent logging with correlation IDs or redact sensitive info before the framework logs
Skip it when you would only:
- Rethrow the same error
- Wrap everything in a generic 500 — that adds noise and can hide stack traces
Decision Flow
Is this an expected domain/validation condition?
→ Don't throw raw; detect proactively (validation + createError)
Is the library likely to throw opaque errors you want to translate?
→ Wrap just that call (Prisma P2002, connection errors)
Do you need a graceful fallback instead of failing the request?
→ Wrap
Otherwise:
→ Let it bubble — Nuxt will emit a 500 and your handler stays clean
Examples
// ✅ Map Prisma unique constraint to 409
try {
await db.user.create({ data })
} catch (e) {
if (e.code === 'P2002') throw createError({ statusCode: 409, message: 'Email already exists' })
throw e // re-throw anything unexpected
}
// ✅ Graceful fallback for optional data
let preferences = defaultPrefs
try {
preferences = await db.userPrefs.findUniqueOrThrow({ where: { userId } })
} catch {
// non-critical — use defaults
}
// ❌ Adds nothing — just re-throws as generic 500
try {
return await db.posts.findMany()
} catch (e) {
throw createError({ statusCode: 500, message: 'Something went wrong' })
}