Operational vs Programmer Errors
Not all errors are the same kind of problem. A user submitting a bad email address is a normal, expected event; your code calling undefined.property is a bug. Treating them identically — either by crashing the process on every user mistake, or by silently continuing after a programming bug — are both wrong. The classic Node.js distinction, articulated by Joyent's error-handling guide and still the standard mental model, is operational errors vs programmer errors.
The distinction
Operational error | Programmer error | |
|---|---|---|
Definition | Expected failure of a correct program | A bug — the code is wrong |
Examples | File not found, DB down, invalid input, 404 |
|
Cause | Environment, user input, external service | Developer mistake |
Response | Handle gracefully, return HTTP error | Log, then crash or alert for a fix |
Should crash? | No | Yes (or restart cleanly) |
Operational errors — handle and respond
// These are operational — expected, handled, user gets a meaningful response:
// 1. User input invalid:
if (!email.includes('@')) throw new HttpError(400, 'Invalid email')
// 2. Resource not found:
const user = await db.users.findById(id)
if (!user) throw new HttpError(404, 'User not found')
// 3. External service down:
try {
await paymentService.charge(amount)
} catch (err) {
if (err.code === 'ECONNREFUSED') throw new HttpError(503, 'Payment service unavailable')
throw err
}
// 4. Database unique constraint:
try {
await db.users.create({ email })
} catch (err) {
if (err.code === '23505') throw new HttpError(409, 'Email already registered')
throw err
}Programmer errors — log and crash
// These are bugs — they should NEVER happen in correct code:
// TypeError: can't read property of undefined
const name = user.profile.name // programmer forgot to check user exists
// Wrong argument type — should be a number, got undefined
function divide(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError(`divide expects numbers, got ${typeof a} and ${typeof b}`)
}
}
// Failed assertion
import assert from 'node:assert'
assert(config.dbUrl, 'DATABASE_URL must be set') // programmer error if missing at startupHow to tell them apart
Ask: "could a correct, deployed program encounter this error at runtime?" If yes → operational.
TypeError,ReferenceError, failedassert→ almost always programmer errors.Network errors, file-not-found, DB constraint violations → operational.
If you have to read the stack trace to understand why it happened → probably a programmer error.
If a user action directly triggered it → probably operational.
In the centralised error handler
app.use((err, req, res, next) => {
const statusCode = err.statusCode ?? 500
const isOperational = statusCode < 500
// Log everything, but with different severity:
if (isOperational) {
logger.warn({ err, path: req.path }, 'Operational error')
} else {
logger.error({ err, path: req.path }, 'Programmer/unknown error')
// Optionally: notify alerting (Sentry, PagerDuty, etc.)
}
// Never expose internals to the client:
res.status(statusCode).json({
error: isOperational ? err.message : 'Internal server error',
code: isOperational ? err.code : 'INTERNAL_ERROR',
})
})