A tale of JavaScript errors
Handling errors is probably one the first things we (Software Engineers) should think of when working on new features.
"Happy path" is sure to be working but there's always room for those sneaky errors: missing or invalid arguments, string length, schema validation, response codes from API calls...
In this blog post, I'd like to focus on the last item from that list: response codes from API calls.
Let's use the following example: we have a fetchUserProfile
method which takes a profileId
argument that needs to be sent via an API call to a fake profileService
endpoint.
If the response is successful we simply return it. Otherwise we need to handle the error based on the status code returned by the API.
To make things interesting, for each type of status code we need to return a "human-readable" error message back to the client that has initially made the call.
class CustomError extends Error {
statusCode: number
constructor(message: string, statusCode: number) {
super(message)
this.statusCode = statusCode || 500
}
}
function fetchUserProfile(profileId: string) {
try {
return profileService.fetchProfile(profileId)
} catch (error) {
switch (error.statusCode) {
case 400:
throw new CustomError('Malformed profile id', error.statusCode)
case 401:
throw new CustomError(
'Invalid or expired access token',
error.statusCode
)
case 403:
throw new CustomError(
'You do not have permission to access this profile',
error.statusCode
)
case 404:
throw new CustomError('Profile not found', error.statusCode)
default:
throw new CustomError('Internal server error', error.statusCode)
}
}
}
CustomError
object gets the job done: it encapsulates the custom message and status code into an Error object which is then thrown back to the client.
After doing this so many times, i.e., copy + paste of the same CustomError
object throughout all of my repositories and projects over and over, I realised I could extract it into a simple and reusable lib.
Moreover, I could finaly give proper names to all of those errors such as BadRequestError
, UnauthorizedError
, ForbiddenError
, NotFoundError
and many more...
With that in mind, I created @mersocarlin/api-error for dealing with different types of status codes by using named error types.
Nothing drastically would change, but the final result would be:
import {
BadRequestError,
ForbiddenError,
InternalServerError,
NotFoundError,
UnauthorizedError,
} from '@mersocarlin/api-error'
function fetchUserProfile(profileId: string) {
try {
return profileService.fetchProfile(profileId)
} catch (error) {
switch (error.statusCode) {
case 400:
throw new BadRequestError('Malformed profile id', error)
case 401:
throw new UnauthorizedError('Invalid or expired access token', error)
case 403:
throw new ForbiddenError(
'You do not have permission to access this profile',
error
)
case 404:
throw new NotFoundError('Profile not found', error)
default:
throw new InternalServerError('Internal server error', error)
}
}
}
The original error as well as stack trace are all still available should you need them as all of the error types derive from the same api-error object.
Did you like the idea? Would like to contribute?
You can find everything you need to know in api-error's repository.
Thanks for reading,
Happy coding!
Did you know you can help me with this page?
If you see something wrong, think this page needs clarification, you found a typo or any other suggestion you might have feel free to open a PR and I will take care of the rest.
My entire site is available to edit on GitHub and all contributions are very welcome 🤙🏼.
Hemerson Carlin, also known as mersocarlin, is passionate and resourceful full-stack Software Engineer with 10+ years of experience focused on agile development, architecture and team building.
This is the space to share the things he likes, a couple of ideas and some of his work.