Skip to content

Instantly share code, notes, and snippets.

@slavafomin
Last active November 14, 2024 11:23
Show Gist options
  • Save slavafomin/b164e3e710a6fc9352c934b9073e7216 to your computer and use it in GitHub Desktop.
Save slavafomin/b164e3e710a6fc9352c934b9073e7216 to your computer and use it in GitHub Desktop.
Custom ES6 errors in Node.js

Here's how you could create custom error classes in Node.js using latest ES6 / ES2015 syntax.

I've tried to make it as lean and unobtrusive as possible.

Defining our own base class for errors

errors/AppError.js

module.exports = class AppError extends Error {
  constructor (message, status) {
  
    // Calling parent constructor of base Error class.
    super(message);
    
    // Saving class name in the property of our custom error as a shortcut.
    this.name = this.constructor.name;

    // Capturing stack trace, excluding constructor call from it
    // (this is probably no longer required in node >=8, see the comments)
    Error.captureStackTrace(this, this.constructor);
    
    // You can use any additional properties you want.
    // I'm going to use preferred HTTP status for this error types.
    // `500` is the default value if not specified.
    this.status = status || 500;
    
  }
};

Defining specific error types

errors/EmailTakenError.js

module.exports = class EmailTakenError extends require('./errors/AppError') {
  constructor (message) {
    // Providing default message and overriding status code.
    super(message || 'Specified E-Mail is already taken', 400);
  }
};

errors/RequestValidationError.js

module.exports = class extends require('./AppError') {
  constructor (fields) {
    // Overriding both message and status code.
    super('Request validation failed', 400);
    // Saving custom property.
    this.fields = fields || {};
  }
};

Throwing and catching

const AppError = require('./../api/errors/AppError');
const EmailTakenError = require('./../api/errors/EmailTakenError');
const RequestValidationError = require('./../api/errors/RequestValidationError');


try {
  // Throwing EmailTakenError exception.
  throw new EmailTakenError();
} catch (error) {
  // Catching exception by class.
  if (error instanceof EmailTakenError) {
    console.log('E-Mail validation failed!', error);
  } else {
    // If failed to catch, throwing it higher.
    console.log('Unknown error', error);
    throw error;
  }
}


try {
  // Throwing RequestValidationError exception.
  throw new RequestValidationError();
} catch (error) {
  // Catching error by base (parent) class.
  if (error instanceof AppError) {
    console.log('Some application error occurred!', error);
  } else {
    console.log('Unknown error', error);
    throw error;
  }
}

Feedback

It works great for my application, however criticism is welcomed.

@olegsu
Copy link

olegsu commented Feb 18, 2017

Just a small order fix

    // Saving class name in the property of our custom error as a shortcut.
    this.name = this.constructor.name;
    // Capturing stack trace, excluding constructor call from it.
    Error.captureStackTrace(this, this.constructor);

This way the stacktrace will hold the name as well

@fgarcia
Copy link

fgarcia commented Apr 21, 2017

Extending Errors with ES6 classes has one minor downside: you are forced to use the new operator, while the native Error() object is a factory by itself.

Both of this statements work

throw Error()
throw new Error()

While this will fail

throw EmailTakenError();

But the approach is clearly the most modern solution

@masiamj
Copy link

masiamj commented May 16, 2017

This looks awesome! I'm just having trouble actually capturing the stacktrace. Is this working for you?

@gezza-b
Copy link

gezza-b commented Jun 24, 2017

I really like this Error class approach and I added a print() method to the class to help me with error tracing.
However, it does not work when I move the error class to a separate module. Can I do that in the class or do I need a prototype?

@herlon214
Copy link

Thanks! This is really what I wanted.

@momocow
Copy link

momocow commented Nov 1, 2017

@gezza-b After moving the error class to a separate module, you should export the error class and use require to import it. Maybe you can provide an example here.

@fgarcia A wrapper function is needed if you want to handle the use with and without new.

errors/EmailTakenError.js

module.exports = function(){
    return new EmailTakenError()
}

Hoewever, using the new operator make it more clearly that it returns an instance of a class. (And it is only 3 letters longer)
It is all up to your preference, though.

@PaulSebi
Copy link

Hey, if you'd like an easy way to manage error Codes, check out snerr
npm
github

@wickd
Copy link

wickd commented Dec 21, 2017

thank you, very useful :)

@Maxtermax
Copy link

nice

@DaneTheory
Copy link

fantastic tutorial. Mind is blown on how slim the codebase is to pull this off. thanks!

@izorsa
Copy link

izorsa commented May 15, 2018

nice, thanks!

@turakvlad
Copy link

turakvlad commented Aug 22, 2018

I've run your code a few times and noticed that in the Node.js 8.11.2 I'm getting the same output with and without Error.captureStackTrace(this, this.constructor); line in the AppError.js constructor.

It looks like in the Node.js 8.11.2 and above it's the default behavior. If we're extending Error class, we don't need to use captureStackTrace to omit all frames above this.constructor.

Please, take a look at the documentation. The most important part:

The error.stack property will represent the point in the code at which new Error() was called. Stack traces are dependent on V8's stack trace API. Stack traces extend only to either (a) the beginning of synchronous code execution, or (b) the number of frames given by the property Error.stackTraceLimit, whichever is smaller.

.stack will be added automatically.

Please let me know if I am not correct.

@bautigaraventa
Copy link

I've run your code a few times and noticed that in the Node.js 8.11.2 I'm getting the same output with and without Error.captureStackTrace(this, this.constructor); line in the AppError.js constructor.

It looks like in the Node.js 8.11.2 and above it's the default behavior. If we're extending Error class, we don't need to use captureStackTrace to omit all frames above this.constructor.

Please, take a look at the documentation. The most important part:

The error.stack property will represent the point in the code at which new Error() was called. Stack traces are dependent on V8's stack trace API. Stack traces extend only to either (a) the beginning of synchronous code execution, or (b) the number of frames given by the property Error.stackTraceLimit, whichever is smaller.

.stack will be added automatically.

Please let me know if I am not correct.

I'm going to implement this solution. Is it confirmed what you say about the captureStackTrace?

Thanks in advance!

@raghavendra7992
Copy link

class ErrorHandler extends Error{
constructor(message,statusCode){
super(message);
this.statusCode = statusCode||500;
Error.captureStackTrace(this,this.constructor);

}

};
I used this as an error handler but it does not give output for wrong input means login authentication

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment