I am developing a website for the first time and I have chosen to work with mongodb, node, express, and angular i.e. the MEAN Stack. One of the first few features I am working on is a simple form and current task at hand is form validation. I have done quiet a lot of research on how to validate a form submission and understand that it is necessary to implement both client-side and server-side validation. Client-side validation for a better user experience and server-side validation for various reasons but primarily security.
However, the problem I am facing is regarding how should I respond to different errors that can arise when a form data is submitted to my REST API. I want to know each situation that can arise and how to handle each of those situations, as in which status codes to respond with and should I provide a specific error message?
Edit: I will add some more specific questions but please do not limit your answer to these.
1) Can I assume that a request without the fields required by my client is actually not coming from my client and is likely an adversary so I should just send a 'Bad Request(400)' without providing any error message?
2) Should I be even using any other status code than 200 for a validation error like a missing required field or a field in an incorrect format? I am asking this because when I send a 4XX response an error is generated in the console of my browser and that does not seem right when the website is behaving as it should.
I hope I am making myself clear. If not, please let me know so I can try to be more specific.
You are correct when you state that both client-side and server-side validation is needed. How you want to present client-side validation errors is completely up to you. Disabling the submit button, making the borders of invalid input boxes red, etc., is typical. HTML5 already has built-in validation methods for some things (numbers, dates, length). If that's not enough you can look up angular form validation on Google.
As for the HTTP status codes during server-side validation, there's no set standard on what status codes you should respond with in the different scenarios. If you are the only one using the API then you're fairly free to do whatever you want.
However, there is a de facto standard if you want to follow REST principles for the API. If we look at the link provided by Lequoa we find the following categories:
The codes starting with 1 and 3 are not interesting to us at this time. In fact, the only codes we care about are the starred ones:
Here are some rules of thumb for how these usually are used:
The status codes in the 200 range are for successful requests.
When the server successfully returns a resource after a GET you use 200 OK. The message is the requested content.
When the server successfully creates a resource after a POST you use 201 CREATED. The message is usually the created content (new blog post for instance).
Whenever you DELETE, PATCH or PUT something you can use 204 No Content. You never provide a message with this status code.
The 400 range is used when the client sends an erroneous request.
400 Bad Request is used for requests that don't pass validation, requests that are missing fields and so on. The message sent is up to you, but it's often wrapped inside a message field like so:
response.status = HttpStatus.BAD_REQUEST;
response.message = { 'message': 'Illegal schoolID' };
res.status(response.status).json(response.message);
This is the message I would use for a GET request that supplied an illegal schoolID. You could of course supply the actual validation result object if you want to prettify the error message on the client side.
401 Unauthorized and 403 Forbidden is used for authentication purposes (login, etc.).
404 Not Found is used when the client sends a GET for a resource that doesn't exists.
422 Unprocessable Entity can also be used for validation errors when the input is syntactically correct, but semantically erroneous.
If an error is thrown or you get an error in a callback you can use 500 Internal Server Error. For example
Model
.findById(modelId)
.exec(function (err, models) {
if (err) {
res.status(500).json(err);
return;
}
...
This is not used for validation errors, but for errors that should not happen, ever. Validation errors are not uncommon after all. Internal server errors are errors in your server-side code, or if the server is on fire or stuff like that.
When the browser gets a 4** or 5** status code returned it has to deal with that. They will get printed to the console, that's fine, and you shouldn't use a 2** code to make it disappear. The browser has to read the error message and show it to the user in a user-friendly way. If the message supplied with the server response is user friendly then it can be shown directly, otherwise you have to write something like this (AngularJS):
schoolDataFactory.patchUpdateSchool(vm.school._id, newSchoolData).then(function (res) {
if (res.status === 204) {
vm.errorMessage = ''
vm.message = 'School info was updated.'
} else {
console.log('The server should send 204 No Content on successful PATCH, so this shouldn't happen.')
}
}).catch(function (error) {
vm.message = ''
if (error.status === 400 && error.data.message === 'ValidationError: schoolName') {
vm.errorMessage = 'Fix the school name'
} else if (error.status === 400 && error.data.message === 'ValidationError: schoolAddress') {
vm.errorMessage = 'Fix the school address'
} else if (error.status === 500) {
vm.errorMessage = 'Something is wrong with the server.'
}
})
This hypothetical example sends a PATCH request to the server with new school data. If the PATCH is successful the browser receives status code 204 and runs the callback in then(). If it receives 4** or 5** it runs the callback in catch(), which deals with the error. vm.errorMessage and vm.message are bound in the view and shown to the user.
The point is that you might have to transform the response messages in the client-side code if the response from the server is ugly.
I think a good way of designing it is to pretend you are only writing the server-side code and API. Pretend that someone else is doing the front-end and then imagine what they would like to see. Or imagine that your API is completely public and that everyone can use it. If you do it that way and make no assumptions on what or who the client is then the back-end will get loosly coupled from the front-end, which is what you want.
The only thing you can assume is that someone will try to ruin your day with an application called Postman.