As a good developer, you should not only think about happy path. There are many things that can do wrong. If you are looking for a user in database, the database server might be down, or there could be network issues.
How can you handle those error scenarios?
The Express Framework offers one solution on this.
Errors that occur in synchronous code inside route handlers and middleware require no extra work. If synchronous code throws an error, then Express will catch and process it. For example:
app.get("/", function(req, res) {
throw new Error("BROKEN"); // Express will catch this on its own.
});
If you call an asynchronous API in a route handler and you would like to handle errors returned/thrown by those asynchronous operations, you just need to call next(error)
when some error happens. That is, you call the next
callback (which is an argument of every middleware and route handler) with an instance of Error.
e.g.
app.get("/", function(req, res, next) {
fs.readFile("/file-does-not-exist", function(err, data) {
if (err) {
next(err); // Pass errors to Express.
} else {
res.send(data);
}
});
});
Once Express Framework detects the argument to next
call is an Error
, it would skip all the other non-error-handling middlewares and route handlers on the happy path, and call Error Handlers
for that request. The Error Handler
can examine the error situation and return proper responses to the client side.
The signature of an Error Handler
is like the one below:
function (err, req, res, next) {
}
Note that Error Handlers always take four arguments. You must provide four arguments to identify it as an error-handling middleware function. Even if you don’t need to use the next object, you must specify it to maintain the signature. Otherwise, the next object will be interpreted as regular middleware and will fail to handle errors.
Within an error handler, you typically need to do one of two things:
- call
res.send()
to generate a response, or - call
next(err)
to pass the execution to the next error handler
These error handlers are actually just middlewares. They can be registered to an app
instance using the same app.use(path, middleware_function)
call.
There can be multiple error handling middlewares for each request path and when error happens, they will be executed in the same order as their registration order using app.use()
API.
As a best practice, we should always declare a default error handler for all requests, to ensure that handle all unforeseeable error scenarios. You define error-handling middleware last, after other app.use() and routes calls.
For example:
var bodyParser = require('body-parser')
var methodOverride = require('method-override')
app.use(bodyParser.urlencoded({
extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use(function (err, req, res, next) {
// logic
})
Now let's take a look at one example error_handler_example.js
.
node error_handler_example.js
If you visit http://localhost:3000, you should see the response generated by the error handler.
For more information on error handling, you can refer to the materials below:
- Official Express Documentation on Error Handling
- Robust Node Applications Error Handling
- Best Practices of NodJS Error Handling
There are also open-sourced error handler implementations like this one
So far for our Songs API we have only handled the situation where user sends correct song ids to our routes. In a real life scenario for a robust API we would need to also handle error scenarios.
- GET /songs/:id
- PUT /songs/:id
- DELETE /songs/:id
Based on our understanding of error handlers, in this lab you will need to create a default error handler for your Songs API routes.
- If user input an invalid id for GET /songs/:id, PUT /songs/:id , DELETE /songs/:id
- Return HTTP status code 404
- Expected response: { message: “Unable to find song with id : xx” }
You are also required to write tests to prove that the 3 routes can now handle the error condition of an invalid song id.
- Navigate to the express-songs-api project folder
- Ensure that the work from your previous lab is already commited.
- checkout to branch
9-add-error-handling
to get started
git checkout 9-add-error-handling
-
After you have completed Lab 5, please commit all your work
-
You may optionally install the plugin Git History Diff for viewing the difference between git branches. This will allow you to easily compare your work with the sample solution
-
https://marketplace.visualstudio.com/items?itemName=huizhou.githd
-
Next checkout to the solution branch and we will now walk through the sample solution
git checkout 10-add-error-handling-solution
-
To view the difference between your work and the sample solution go to the VS Code file explorer, right click your folder and select
GitHD: View Folder Diff
and select compare with9-add-error-handling
branch -
The diff sidebar will popup and you can click on the files to view the difference between your work and the sample solution.
-
When viewing the diff you can temporarily toggle the sidebar with
cmd + B
to hide it.