NodeJSError Types & the Error Object

Error Types & the Error Object

Before you can handle errors well you need to know what you're dealing with. JavaScript has a base Error class and several built-in subtypes; Node adds system errors with code properties; libraries throw their own shapes. Understanding what each error looks like — and what properties it carries — is what lets you catch the right things, classify them correctly, and respond appropriately.

The base Error object

JS
const err = new Error('Something went wrong')

err.message   // 'Something went wrong'
err.name      // 'Error'
err.stack     // multiline string: message + call frames
              // "Error: Something went wrong\n    at Object.<anonymous> ..."

// You can attach custom properties:
err.statusCode = 400
err.code = 'VALIDATION_ERROR'
`message`, `name`, and `stack` are the standard properties — add your own on top
Every Error in JavaScript carries `message` (the human-readable description), `name` (the class name, defaults to `'Error'`), and `stack` (a snapshot of the call stack at the time of creation). These three are what you log. Custom properties — `statusCode`, `code`, `details` — are how you carry machine-readable context to your error handler without parsing strings. Attaching them to the Error object is better than a parallel `if` chain in the handler.
Built-in Error subtypes

Type

When thrown

TypeError

Wrong type passed — null.property, calling a non-function

RangeError

Value out of allowed range — new Array(-1)

ReferenceError

Undeclared variable accessed

SyntaxError

Invalid JavaScript (usually in eval / JSON.parse)

URIError

Malformed URI in decodeURIComponent etc.

JS
// Catch a specific subtype:
try {
  JSON.parse('{bad json}')
} catch (err) {
  if (err instanceof SyntaxError) {
    console.log('Invalid JSON:', err.message)
  } else {
    throw err          // re-throw what we didn't expect
  }
}
Node system errors — the `code` property

JS
import { readFile } from 'node:fs/promises'

try {
  await readFile('/no/such/file.txt')
} catch (err) {
  err.code     // 'ENOENT'   — no such file or directory
  err.syscall  // 'open'
  err.path     // '/no/such/file.txt'
  err instanceof Error  // true
}

Code

Meaning

ENOENT

No such file or directory

EACCES

Permission denied

EADDRINUSE

Address (port) already in use

ECONNREFUSED

Connection refused (DB/service down)

ETIMEDOUT

Connection timed out

ECONNRESET

Connection forcibly closed by the peer

Branch on `err.code` for system errors, not `err.message`
System error messages can vary across platforms and Node versions; the `code` string (`ENOENT`, `ECONNREFUSED`, etc.) is the stable contract. Always check `err.code` when you need to distinguish "file not found" from "permission denied" or handle a specific network condition. Parsing `err.message` for these is brittle — a Node update or locale change can break it silently.
HTTP errors — statusCode by convention

JS
// A convention many apps adopt — attach statusCode to the Error:
class HttpError extends Error {
  constructor(statusCode, message, code) {
    super(message)
    this.name = 'HttpError'
    this.statusCode = statusCode
    this.code = code
  }
}

throw new HttpError(404, 'User not found', 'USER_NOT_FOUND')
throw new HttpError(400, 'Email is required', 'VALIDATION_ERROR')
throw new HttpError(409, 'Email already in use', 'DUPLICATE_EMAIL')

// In the centralised error handler:
res.status(err.statusCode ?? 500).json({ error: err.message, code: err.code })
Attach `statusCode` to errors so the handler can respond without a lookup table
Rather than mapping error types to status codes in the handler, attach the status code to the error at the throw site where you know the semantics. A 404 isn't a server malfunction — it's expected business logic. The [centralised error handler](/nodejs/centralized-error-handling) then just reads `err.statusCode`: if it's set, it's an operational error with a known HTTP meaning; if not, default to 500. [Custom error classes](/nodejs/custom-errors) formalise this pattern.
Library and ORM errors

JS
// Prisma unique constraint violation:
import { Prisma } from '@prisma/client'
catch (err) {
  if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === 'P2002') {
    throw new HttpError(409, 'Email already in use', 'DUPLICATE_EMAIL')
  }
  throw err   // unexpected — re-throw
}

// pg (Postgres driver) constraint violation:
catch (err) {
  if (err.code === '23505') {   // unique_violation
    throw new HttpError(409, 'Duplicate value', 'DUPLICATE')
  }
  throw err
}
Translate library errors at the boundary — don't let ORM types leak through your app
Each library (Prisma, pg, mongoose, axios) throws its own error types. Translate them to your application's error types (e.g. `HttpError`) at the layer that calls the library — don't let Prisma errors bubble all the way to your HTTP handler. This keeps your domain code independent of the specific library and makes it easy to swap libraries later without updating every error handler.
Next
Handle these errors correctly with async/await: [try/catch with async/await](/nodejs/try-catch-async).