From 212639b3c3dcc1cda2dcf689edffc98cfb7ff608 Mon Sep 17 00:00:00 2001 From: seantokuzo Date: Wed, 26 Jun 2024 09:52:38 -0700 Subject: [PATCH 1/7] begin readme --- docs/ErrorHandling.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 docs/ErrorHandling.md diff --git a/docs/ErrorHandling.md b/docs/ErrorHandling.md new file mode 100644 index 0000000..d7acc4e --- /dev/null +++ b/docs/ErrorHandling.md @@ -0,0 +1,20 @@ +# Error Handling + +## Quick Links + +- [Back End Error Handling](#backend) +- [Front End Error Handling](#frontend) + +
+ + + +## Back End Error Handling + +Codehammers makes use of the + +
+ + + +## Front End Error Handling From d8fcd65aa49c7979f1b8933c36bc95cd8c2e7ae3 Mon Sep 17 00:00:00 2001 From: seantokuzo Date: Wed, 26 Jun 2024 19:11:06 -0700 Subject: [PATCH 2/7] custom errors docs started --- docs/ErrorHandling.md | 99 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/docs/ErrorHandling.md b/docs/ErrorHandling.md index d7acc4e..14049e5 100644 --- a/docs/ErrorHandling.md +++ b/docs/ErrorHandling.md @@ -3,6 +3,8 @@ ## Quick Links - [Back End Error Handling](#backend) + - [Express Async Errors](#async-errors) + - [Custom Error Classes](#custom-errors) - [Front End Error Handling](#frontend)
@@ -11,10 +13,105 @@ ## Back End Error Handling -Codehammers makes use of the + + +#### express-async-errors + +Codehammers makes use of the [express-async-errors](https://www.npmjs.com/package/express-async-errors) package to simplify error handling in asynchronous route handlers and middlewares. This package enables us to simply throw errors instead of manually passing error objects to Express's `next` function. An example would be: + +``` +/* with express-async-errors */ + +const middleware = async (req: Request, res: Response) => { + throw new Error('This error will be automatically caught and passed to next') +} +``` + +whereas typically we would have to do this: + +``` +/* without express-async-errors */ + +const middleware = async (req: Request, res: Response, next: NextFunction) => { + next({ + message: 'This error was manually passed to next' + }) +} +``` + +This implementation eliminates the need for try/catch blocks and allows us to simply throw errors where needed without manually invoking `next` + +**How it works** +The Express framework automatically catches errors thrown in synchronous route handlers and middlewares. However, in the case of asynchronous route handlers and middlewares, Express requires us to manually call `next`, passing in an error object (or anything besides the string `'route'`), in order to invoke the global error handling middleware. + +[This package wraps](https://github.com/davidbanham/express-async-errors/blob/master/index.js) Express's Router's [Layer object's](https://github.com/expressjs/express/blob/master/lib/router/layer.js) `handle` property 😵‍💫, enabling errors thrown in asynchronous route handlers and middlewares to automatically be caught and passed to `next`. We simply require/import this package once (in `server/app.ts`) and it's functionality is enabled. + +Similar approaches require defining our own wrapper such as: + +``` +const catchAsync = (fn) => { + return (req, res, next) => { + fn(req, res, next).catch(next) + } +} +``` + +we would then have to wrap every route handler and middleware in this catchAsync function like: + +``` +const middleware = catchAsync(async (req, res) => { + throw new Error('Oops, something went wrong') +}) +``` + +Much nicer to not have to manually wrap every route handler and middleware! + + + +#### Custom Error Classes + +We have created reuseable error classes for the most common types of errors that occur on the server. These classes can be found in the `server/errors/` directory. These errors and their corresponding status codes are: + +- BadRequestError - 400 - (requires a message argument when instantiated) +- DatabaseConnectionError - 500 +- InternalError - 500 +- NotAuthorizedError - 401 +- NotFoundError - 404 +- RequestValidationError - 400 - (requires an array of `ValidationError`s as an argument when instantiated - see [below](#request-validation-error)) +- ValidationError - no status code - (requires `message` and `field` arguments when instantiated) + +Each of these error classes extend our `CustomError` abstract class. This abstract class cannot be instantiated, but is used to enforce a structure for our custom error classes: requiring a `statusCode` property, a `message` property, as well as a `serializeErrors` method. The `serializeErrors` method ensures that errors are consistently formatted as an array of objects, each object containing a `message` property and an optional `field` property. + +**Notes:** + + +##### RequestValidationError + +When validating multiple user inputs, we create an array for errors and push in a `ValidationError` for each failed validation. We then throw a `RequestValidationError` if this array contains any `ValidationError`s. An example of this could be: + +``` +const createUser = async (req: Request, res: Response) => { + const { email, password } = req.body + + const validationErrors: ValidationError[] = [] + if (!email) { + // create the ValidationError with a message argument and field argument + validationErrors.push(new ValidationError('Invalid email', 'email')) + } + if (!password) { + validationErrors.push(new ValidationError('Invalid password', 'password')) + } + if (validationErrors.length) { + throw new RequestValidationError(validationErrors) + } + // continue if input validation checks pass... +} +```
## Front End Error Handling + +TBD From da5dc925399dd053dbd29369e2d247566377844f Mon Sep 17 00:00:00 2001 From: seantokuzo Date: Thu, 27 Jun 2024 20:55:48 -0700 Subject: [PATCH 3/7] draft 1 --- docs/ErrorHandling.md | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/docs/ErrorHandling.md b/docs/ErrorHandling.md index 14049e5..977e74b 100644 --- a/docs/ErrorHandling.md +++ b/docs/ErrorHandling.md @@ -5,6 +5,7 @@ - [Back End Error Handling](#backend) - [Express Async Errors](#async-errors) - [Custom Error Classes](#custom-errors) + - [Global Error Handler](#global-error-handler) - [Front End Error Handling](#frontend)
@@ -23,7 +24,7 @@ Codehammers makes use of the [express-async-errors](https://www.npmjs.com/packag /* with express-async-errors */ const middleware = async (req: Request, res: Response) => { - throw new Error('This error will be automatically caught and passed to next') + throw new Error('This error will automatically be caught and passed to next') } ``` @@ -39,14 +40,14 @@ const middleware = async (req: Request, res: Response, next: NextFunction) => { } ``` -This implementation eliminates the need for try/catch blocks and allows us to simply throw errors where needed without manually invoking `next` +Using `express-async-errors` eliminates the need for try/catch blocks and allows us to simply throw errors where needed without manually invoking `next` **How it works** The Express framework automatically catches errors thrown in synchronous route handlers and middlewares. However, in the case of asynchronous route handlers and middlewares, Express requires us to manually call `next`, passing in an error object (or anything besides the string `'route'`), in order to invoke the global error handling middleware. -[This package wraps](https://github.com/davidbanham/express-async-errors/blob/master/index.js) Express's Router's [Layer object's](https://github.com/expressjs/express/blob/master/lib/router/layer.js) `handle` property 😵‍💫, enabling errors thrown in asynchronous route handlers and middlewares to automatically be caught and passed to `next`. We simply require/import this package once (in `server/app.ts`) and it's functionality is enabled. +The [express-async-errors package wraps](https://github.com/davidbanham/express-async-errors/blob/master/index.js) Express's Router's [Layer object's](https://github.com/expressjs/express/blob/master/lib/router/layer.js) `handle` property, enabling errors thrown in asynchronous route handlers and middlewares to automatically be caught and passed to `next`. We simply require/import this package once (in `server/app.ts`) and it's functionality is enabled. -Similar approaches require defining our own wrapper such as: +A similar approach would require defining our own wrapper such as: ``` const catchAsync = (fn) => { @@ -56,7 +57,7 @@ const catchAsync = (fn) => { } ``` -we would then have to wrap every route handler and middleware in this catchAsync function like: +we would then have to wrap every route handler and middleware in this catchAsync function to automatically catch errors and pass them to next: ``` const middleware = catchAsync(async (req, res) => { @@ -70,24 +71,22 @@ Much nicer to not have to manually wrap every route handler and middleware! #### Custom Error Classes -We have created reuseable error classes for the most common types of errors that occur on the server. These classes can be found in the `server/errors/` directory. These errors and their corresponding status codes are: +Codehammers uses reuseable error classes for handling the most common types of errors that occur on the server. These classes can be found in the `server/errors/` directory. These errors and their corresponding status codes are: -- BadRequestError - 400 - (requires a message argument when instantiated) -- DatabaseConnectionError - 500 -- InternalError - 500 -- NotAuthorizedError - 401 -- NotFoundError - 404 -- RequestValidationError - 400 - (requires an array of `ValidationError`s as an argument when instantiated - see [below](#request-validation-error)) -- ValidationError - no status code - (requires `message` and `field` arguments when instantiated) +- `BadRequestError` : 400 (requires a `message` argument when instantiated) +- `DatabaseConnectionError` : 500 +- `InternalError` : 500 +- `NotAuthorizedError` : 401 +- `NotFoundError` : 404 +- `RequestValidationError` : 400 (requires an array of `ValidationError`s as an argument when instantiated - see [below](#request-validation-error)) -Each of these error classes extend our `CustomError` abstract class. This abstract class cannot be instantiated, but is used to enforce a structure for our custom error classes: requiring a `statusCode` property, a `message` property, as well as a `serializeErrors` method. The `serializeErrors` method ensures that errors are consistently formatted as an array of objects, each object containing a `message` property and an optional `field` property. +Each of these error classes extend our `CustomError` abstract class. This abstract class cannot be instantiated, but is used to enforce a structure for our custom error classes: requiring a `statusCode` property, a `message` property, as well as a `serializeErrors` method. The `serializeErrors` method ensures that errors sent back to the client are consistently formatted as an array of objects, each object containing a `message` property and an optional `field` property. -**Notes:** ##### RequestValidationError -When validating multiple user inputs, we create an array for errors and push in a `ValidationError` for each failed validation. We then throw a `RequestValidationError` if this array contains any `ValidationError`s. An example of this could be: +When validating multiple user inputs, we create an array for validation errors and push in a `ValidationError` for each failed validation. We then throw a `RequestValidationError` if this array contains any `ValidationError`s. An example of this could be: ``` const createUser = async (req: Request, res: Response) => { @@ -95,7 +94,6 @@ const createUser = async (req: Request, res: Response) => { const validationErrors: ValidationError[] = [] if (!email) { - // create the ValidationError with a message argument and field argument validationErrors.push(new ValidationError('Invalid email', 'email')) } if (!password) { @@ -108,6 +106,14 @@ const createUser = async (req: Request, res: Response) => { } ``` +`ValidationError`s include a relevant `message` for the user, and the `field` representing which input failed validation, passed as arguments when instantiating a `ValidationError`. + + + +#### Global Error Handler + +We are currently migrating our error handling to make use of `express-async-errors` and our custom error classes. While this migration is ongoing, we still need to handle error objects that have been manually passed to Express's `next` function. Our global error handler (`server/middleware/errorHandler.ts`) is setup to handle both of these cases, while still sending back consistently formatted errors. If an error is neither a custom error class or manually caught error object, we send back a generic `InternalError` with `500` status code +
From 563f1af321c0ff501267a6c610b8a8b0e32b50f8 Mon Sep 17 00:00:00 2001 From: seantokuzo Date: Thu, 27 Jun 2024 21:00:23 -0700 Subject: [PATCH 4/7] wording change --- docs/ErrorHandling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ErrorHandling.md b/docs/ErrorHandling.md index 977e74b..ac82791 100644 --- a/docs/ErrorHandling.md +++ b/docs/ErrorHandling.md @@ -65,7 +65,7 @@ const middleware = catchAsync(async (req, res) => { }) ``` -Much nicer to not have to manually wrap every route handler and middleware! +Much nicer not having to manually wrap every route handler and middleware! From d5bc1adddbdadcfd3b10d06ff007e26d75ca364a Mon Sep 17 00:00:00 2001 From: seantokuzo Date: Thu, 27 Jun 2024 21:01:43 -0700 Subject: [PATCH 5/7] wording change --- docs/ErrorHandling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ErrorHandling.md b/docs/ErrorHandling.md index ac82791..06e2c41 100644 --- a/docs/ErrorHandling.md +++ b/docs/ErrorHandling.md @@ -71,7 +71,7 @@ Much nicer not having to manually wrap every route handler and middleware! #### Custom Error Classes -Codehammers uses reuseable error classes for handling the most common types of errors that occur on the server. These classes can be found in the `server/errors/` directory. These errors and their corresponding status codes are: +Codehammers uses custom error classes for handling the most common types of errors that occur on the server. These classes can be found in the `server/errors/` directory. These errors and their corresponding status codes are: - `BadRequestError` : 400 (requires a `message` argument when instantiated) - `DatabaseConnectionError` : 500 From 956331f1b42ff998984007f1cec6dc092416a7cd Mon Sep 17 00:00:00 2001 From: seantokuzo Date: Thu, 27 Jun 2024 21:02:35 -0700 Subject: [PATCH 6/7] wording change --- docs/ErrorHandling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ErrorHandling.md b/docs/ErrorHandling.md index 06e2c41..2bb90ff 100644 --- a/docs/ErrorHandling.md +++ b/docs/ErrorHandling.md @@ -71,7 +71,7 @@ Much nicer not having to manually wrap every route handler and middleware! #### Custom Error Classes -Codehammers uses custom error classes for handling the most common types of errors that occur on the server. These classes can be found in the `server/errors/` directory. These errors and their corresponding status codes are: +Codehammers uses custom error classes for handling the most common types of errors that occur on the server. These classes can be found in the `server/errors/` directory. These error classes and their corresponding status codes are: - `BadRequestError` : 400 (requires a `message` argument when instantiated) - `DatabaseConnectionError` : 500 From 9b9b5baaf8a1a1f3e52f431e04f348bc5ea7acad Mon Sep 17 00:00:00 2001 From: seantokuzo Date: Thu, 27 Jun 2024 21:21:01 -0700 Subject: [PATCH 7/7] updates to ValidationError --- docs/ErrorHandling.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/ErrorHandling.md b/docs/ErrorHandling.md index 2bb90ff..fda15d5 100644 --- a/docs/ErrorHandling.md +++ b/docs/ErrorHandling.md @@ -80,12 +80,14 @@ Codehammers uses custom error classes for handling the most common types of erro - `NotFoundError` : 404 - `RequestValidationError` : 400 (requires an array of `ValidationError`s as an argument when instantiated - see [below](#request-validation-error)) -Each of these error classes extend our `CustomError` abstract class. This abstract class cannot be instantiated, but is used to enforce a structure for our custom error classes: requiring a `statusCode` property, a `message` property, as well as a `serializeErrors` method. The `serializeErrors` method ensures that errors sent back to the client are consistently formatted as an array of objects, each object containing a `message` property and an optional `field` property. +Each of these error classes extend our `CustomError` abstract class (which itself extends JavaScript's built-in `Error` object). This abstract class cannot be instantiated, but is used to enforce a structure for our custom error classes: requiring a `statusCode` property, a `message` property, as well as a `serializeErrors` method. The `serializeErrors` method ensures that errors sent back to the client are consistently formatted as an array of objects, each object containing a `message` property and an optional `field` property - used for validation errors. ##### RequestValidationError +Another custom utility error class is `ValidationError`. This error is for user inputs that fail validation, such as an invalid email, or a password that is too short. + When validating multiple user inputs, we create an array for validation errors and push in a `ValidationError` for each failed validation. We then throw a `RequestValidationError` if this array contains any `ValidationError`s. An example of this could be: ``` @@ -94,25 +96,28 @@ const createUser = async (req: Request, res: Response) => { const validationErrors: ValidationError[] = [] if (!email) { - validationErrors.push(new ValidationError('Invalid email', 'email')) + validationErrors.push(new ValidationError('Please provide a valid email', 'email')) } if (!password) { - validationErrors.push(new ValidationError('Invalid password', 'password')) + validationErrors.push(new ValidationError('Please provide a valid password', 'password')) } if (validationErrors.length) { throw new RequestValidationError(validationErrors) } + // continue if input validation checks pass... } ``` `ValidationError`s include a relevant `message` for the user, and the `field` representing which input failed validation, passed as arguments when instantiating a `ValidationError`. +Having the ability to collect and send back multiple errors for each validation failure helps our front end developers relay useful information to the user about which inputs were invalid and need fixing. + #### Global Error Handler -We are currently migrating our error handling to make use of `express-async-errors` and our custom error classes. While this migration is ongoing, we still need to handle error objects that have been manually passed to Express's `next` function. Our global error handler (`server/middleware/errorHandler.ts`) is setup to handle both of these cases, while still sending back consistently formatted errors. If an error is neither a custom error class or manually caught error object, we send back a generic `InternalError` with `500` status code +We are currently migrating our error handling to make use of `express-async-errors` and our custom error classes. While this migration is ongoing, we still need to handle error objects that have been manually passed to Express's `next` function. Our global error handler (`server/middleware/errorHandler.ts`) is setup to handle both of these cases, while still sending back consistently formatted errors. If an error is neither a custom error class or manually caught error object, we send back a generic `InternalError` with `500` status code.