1. Can you think of a situation where using the returned issue to append to the list is not appropriate?
answers are available at the end of the chapter.
Error Handling
I discussed error handling in the Create API section as part of the exercises, but we did not implement it then. Let’s do so now. We will not handle malformed JSON kind of errors, because they don’t cause great harm. Also, handling malformed JSON is a bit complicated, because we need to write a own middleware for this. As for application-level validations, we’ll handle missing required fields and incorrect list values.
One decision that we need to make is: how do we return errors back to the client? The success or failure of any REST API call is typically reflected in the HTTP status code. Some prefer returning a 400 Bad Request status code, but in my opinion, a 400 status indicates a malformed request, or something that does not conform to HTTP standards, or for syntactically incorrect requests. Application errors are not problems with the syntax of the input, so we’ll instead use 422 Unprocessable Entity as the error status code for them. If and when you implement errors for malformed JSON, you could use the 400 error code.
The error message (a description of what went wrong) itself can be returned in the response body, again as a JSON string. We will return an object with a single property called message that holds a readable as the description. For programmatic interpretation of the errors, it may also be useful to include an error code, but we won’t do that in the application because the API is only for internal consumption.
At the server, sending an error is simple; all you need to do is set the status using res.status() and send the error message as the response. Apart from this, the rest of the changes (as seen in Listing 5-7) are straightforward logic, which can be implemented in many different ways. Listing 5-7 shows a few new global variables, a validation function, and the new modified POST handler for creating an issue.
Chapter 5 ■ express rest apIs
86
Listing 5-7. server.js: Adding Server-Side Validation const validIssueStatus = {
New: true, Open: true, Assigned: true, Fixed: true, Verified: true, Closed: true, };
const issueFieldType = { id: 'required', status: 'required', owner: 'required', effort: 'optional', created: 'required', completionDate: 'optional', title: 'required',
};
function validateIssue(issue) {
for (const field in issueFieldType) { const type = issueFieldType[field];
if (!type) {
delete issue[field];
} else if (type === 'required' && !issue[field]) { return `${field} is required.`;
} }
if (!validIssueStatus[issue.status])
return `${issue.status} is not a valid status.`;
return null;
}
app.post('/api/issues', (req, res) => { const newIssue = req.body;
newIssue.id = issues.length + 1;
newIssue.created = new Date();
if (!newIssue.status) newIssue.status = 'New';
const err = validateIssue(newIssue) if (err) {
res.status(422).json({ message: `Invalid requrest: ${err}` });
return;
Chapter 5 ■ express rest apIs }
issues.push(newIssue);
res.json(newIssue);
});
The first two global objects are a kind of schema definition to indicate what is a valid Issue object. The function validateIssue checks against this specification and returns an error if the validation fails. Note that we also deleted any fields that do not belong, effectively ignoring such errors rather than reporting them back to the client. It’s a matter of preference whether you want to report or ignore them; you could handle them either way.
In the handler, we called the validation function and in case of an error, we set the status to 422 and sent back an object with an appropriate message. The single line containing res.status() and json() is a shortcut for res.status() followed by a res.json() as two different statements.
At the client, we need to modify the code to detect a non-success HTTP status code. Note that the Fetch API does not throw an error for failure HTTP status codes, so relying on the catch section is not going to work. We must check the response’s property response.ok, and if it is not OK, we need to show an error. Listing 5-8 shows the client- side code, the complete createIssue() method.
Listing 5-8. App.jsx, IssueList’s createIssue method: Client-Side Error Handling createIssue(newIssue) {
fetch('/api/issues', { method: 'POST',
headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newIssue),
}).then(response => { if (response.ok) {
response.json().then(updatedIssue => {
updatedIssue.created = new Date(updatedIssue.created);
if (updatedIssue.completionDate)
updatedIssue.completionDate = new Date(updatedIssue.completionDate);
const newIssues = this.state.issues.concat(updatedIssue);
this.setState({ issues: newIssues });
});
} else {
response.json().then(error => {
alert("Failed to add issue: " + error.message) });
}
}).catch(err => {
alert("Error in sending data to server: " + err.message);
});
}
Chapter 5 ■ express rest apIs
88
We moved the entire success path processing to within an if (response.ok) check. The else part just shows an error message as an alert. In both cases, we had to parse the response body. In the success case, the response body represents the updated Issue object, and in the error case, the response body contains the error object with the message.
Another way to handle this could be to throw an error if response.ok indicates a failure, so that you can handle all this in a single catch block. But this complicates things, especially since you want to show the error message that’s within the response body, as parsing the response is another asynchronous call. If you did not care about the response message, you could have just thrown an error in the first step, where you only pass on the status text message instead of the application error message. Like this:
...
}).then(response => { if (!response.ok) {
throw new Error(response.statusText);
} else {
return response.json();
} ...
To simulate the errors and test the new code, create an issue via the UI, skipping the mandatory field. An error message should show. For an invalid status, you need to test via a direct API call, or hardcode an invalid status to send to the server, because there’s no UI for setting the status yet.