Created
April 7, 2020 09:54
-
-
Save ilabacheuski/243a8cdc6985e00800f219b7181613bb to your computer and use it in GitHub Desktop.
Proper error extension in NodeJs based on https://rclayton.silvrback.com/custom-errors-in-node-js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Boom is a Hapi framework for creating HTTP error responses. | |
const Boom = require('boom'); | |
const { ResourceNotFoundError, InternalError } = require('../lib/errors'); | |
const findPerson = require('../lib/people/find'); | |
// This would be a common utility function used across handlers | |
function mapDomainErrorToHttpResponse(error) { | |
if (error instanceof ResourceNotFoundError) { | |
return Boom.notFound(error.message, error.data.query); | |
} | |
return Boom.badImplementation('Internal Error'); | |
} | |
server.route({ | |
method: 'GET', | |
path: '/people/{name}', | |
async handler(request, reply) { | |
try { | |
reply(await findPerson(request.params.name)); | |
} catch (error) { | |
reply(mapDomainErrorToHttpResponse(error)); | |
} | |
}, | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class DomainError extends Error { | |
constructor(message) { | |
super(message); | |
// Ensure the name of this error is the same as the class name | |
this.name = this.constructor.name; | |
// This clips the constructor invocation from the stack trace. | |
// It's not absolutely essential, but it does make the stack trace a little nicer. | |
// @see Node.js reference (bottom) | |
Error.captureStackTrace(this, this.constructor); | |
} | |
} | |
class ResourceNotFoundError extends DomainError { | |
constructor(resource, query) { | |
super(`Resource ${resource} was not found.`); | |
this.data = { resource, query }; | |
} | |
} | |
// I do something like this to wrap errors from other frameworks. | |
// Correction thanks to @vamsee on Twitter: | |
// https://twitter.com/lakamsani/status/1035042907890376707 | |
class InternalError extends DomainError { | |
constructor(error) { | |
super(error.message); | |
this.data = { error }; | |
} | |
} | |
module.exports = { | |
ResourceNotFoundError, | |
InternalError, | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { describe, it } = require('mocha'); | |
const { expect } = require('chai'); | |
const { ResourceNotFoundError, InternalError } = require('../lib/errors'); | |
describe('when looking up People', () => { | |
it('should throw an error if the person cannot be found', async () => { | |
expect(() => await findPerson('John Doe')) | |
.to.throw(ResourceNotFoundError); | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const Person = require('../models/person'); | |
const { ResourceNotFoundError, InternalError } = require('../errors'); | |
async function findPerson(name) { | |
const query = { name }; | |
let person; | |
try { | |
person = await Person.find(query); | |
} catch (error) { | |
throw new InternalError(error); | |
} | |
if (!person) { | |
throw new ResourceNotFoundError('person', query); | |
} | |
return person; | |
} | |
module.exports = findPerson; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment