Validating Input to an Express API

Jamund Ferguson
InstructorJamund Ferguson
Share this video with your friends

Social Share Links

Send Tweet
Published 5 years ago
Updated 4 years ago

Whenever you're designing an API that receives input, there's a chance people are going to use it maliciously or incorrectly.

This lesson will look at some common problems that can occur when we assume input exists that doesn't and how to pro-actively handle it by sending status code 400 Bad Request with custom response messages. A popular library for input validation is Joi.

A few notes:

  • Always assume query params and post bodies are undefined
  • Set defaults or check that they exist
  • When required values aren't found send a 400 status code and appropriate message
  • When you use urlencoded({ extended: true }) with express it tries to parse numbers and booleans into those primitives instead of strings, so be ready for that.
  • Always validate each value that is sent as input into your API, don't assume it's safe to use it without checking (see below for an example).

One simple strategy for validating input is to put the correct values in an Array or Set:

const { sort = "desc" } = req.query;
const validSorts = new Set(["desc", "asc"]);
if (!validSorts.has(sort)) {
    return res.status(400).send("Invalid Sort Param");
}

One of the worst things you can do in your express app is forget to return when you call res.send(). It leads to this dreaded Error: Can't set headers after they are sent to the client error which can be very hard to trace.


Another important concept is to never ever ever blindly pass in the results of req.query or req.body to some other object. It's potentially a massive security vulnerability. Imagine a scenario like this:

** DO THIS **

const { squareFootage, numRooms} = req.body
const house = new House({ squareFootage, numRooms})

DO NOT DO THIS

const body = req.body // someone could pass in price: 0
const house = new House(body) // free house for me!!

Jamund Ferguson: [0:00] Our API returns a list of notes, and it can be sorted either in ascending or descending order. What happens if someone sends in an unsupported sort format, such as sort=time or sort=123? Let's make sure our API knows how to handle invalid input.

[0:15] Open up your route file. To validate these parameters, let's add an if statement. if (!(sort === "ASC" || sort === "DESC"), so if we're not either ascending or descending. Inside that if block type return res.status(400).send("Invalid sort Params"). Save that and [inaudible] back on.

[0:41] Let's make sure that ascending worked. Descending works. We're still getting our 200 status code over here. What about sort=time? We're getting Invalid sort Params and the status code of 400. There are a lot of cases that can come up. What about sort=asc lowercase? I guess that's something we should support. Let's do it.

[1:00] Go back into your routes. Let's change this to let { sort }. Then, we can say sort = sort.toLowerCase(), and now sort will always come in lowercase. If they pass in an uppercase or lowercase, we can always compare it against the lowercase letters, and sort= lowercase asc is good, uppercase ASC is good.

[1:23] What happens if we don't pass in sort at all? We get Cannot read property 'toLowerCase' of undefined. That's because without a sort variable, the query parameter in our route sort is undefined. They don't even assign it to a string by default.

[1:37] We can add a ternary here. If there's a sort, let's get toLowerCase, otherwise, let's set the default to desc. Now, even if you don't pass in a sort order, it's sorted in descending order, and it's not crashing or throwing errors. You can think about these edge cases for pretty much any of our routes.

[1:56] Let's look at our post body now, so curl -X POST. Let's post some new note with absolutely nothing. It's saying undefined and undefined received. That's not really OK, so let's go back into our code here. Inside of create, we can type if (title === undefined || body === undefined) return res.status(400).send("Missing title or body").

[2:23] Let's do the same thing for our update call.

[2:27] We can also say title's undefined. In this case, instead of ||, here we'll use &&, because we can accept for an update either a new title or a new body. It's only a problem of both of them are undefined. For deleteNote there's nothing we need to add for a validation at this point.

[2:42] Start the server, we'll hit this without a title or body. Let's go and add it now, title="hello". Again, this one is supposed to require both a title and a body, and it does. Let's add the body, -d body="world", and now it's OK.

[3:01] Let's try to update. We had the id there. That's fine. Let's get rid of body. It should allow us to have only one parameter. That worked OK. It still says updating 123 with hello and undefined, and we decided that's OK.

[3:12] Once again, make sure that our initial list call is working. We can say sort as ascending, uppercase, lowercase, anything in between. If I say time, I get Invalid sort Params and we're returning a 400.