Validating the data being sent to an API is important because it filters out bad requests before they attempt to be processed or stored elsewhere. It also provides a way to tell the consumer of the API what they sent incorrectly so they can fix it and try again. We’re going to setup our API to validate our data using a library called express-validator.

Setting up a Validator

Install the express-validator package by opening your terminal and running `npm install express-validator`. Then inside the books.js route file, import `check` and `validationResult` functions  of the library like so;

const { check, validationResult } = require('express-validator');

Now were going to insert the `check` function as a middleware into our POST method handler. Let’s check to make sure the the rating of the book is a number. We also need to check check the results of the validator and send an appropriate response back to the user if the request is bad.

// Define a post method which will be used to accept data into the API
router.post('/', [
  // Check that the rating is a number
  check('rating').isNumeric(),
], async (req, res) => {
  // If there are any validation errors, send back a 400 Bad Request with the errors
  const errors = validationResult(req)
  if (!errors.isEmpty()) {
    res.status(400).json({
      errors: errors.array()
    })
  }
  // ... code removed for brevity
}

Let’s both a good request and a bad request with postman;

As you can see, the one with the bad request returns a 400 bad request status along with what was wrong with the request.

Custom Validators

You can also write custom validators as well. Say you want to check to see if a book exists in the database before saving it. Create a custom validator like so;

router.post('/', [
  // Check that the rating is a number
  check('rating').isNumeric(),
  check('isbn').custom(async value => {
    const params = {
      TableName: 'nodejs-api'
    }
    let books = await docClient.scan(params).promise()
    let existingBook = books.Items.find(b => b.info.isbn === value)
    if (existingBook) {
      return Promise.reject("That book already exists");
    }
  })
], async (req, res) => {
  // ... code removed for brevity
}

Now if we try to save a book with the same ISBN, we’ll receive an error from the API stating that a book already exists with that number;

Organizing Validators

Since we need to validate any number of fields, adding them inline with our POST /books handler can make our code somewhat difficult to work with, so I recommend moving them off to their own file. Lets create a new file called booksValidators.js next to books.js and add the following code to it.

const {
  check
} = require('express-validator');

const AWS = require('aws-sdk');

// Update our AWS Connection Details
AWS.config.update({
  region: process.env.AWS_DEFAULT_REGION,
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
});

// Create the service used to connect to DynamoDB
const docClient = new AWS.DynamoDB.DocumentClient();

exports.postBooksValidators = [
  // Check that the rating is a number
  check('rating').isNumeric(),
  check('isbn').custom(async value => {
    const params = {
      TableName: 'nodejs-api'
    }
    let books = await docClient.scan(params).promise()
    let existingBook = books.Items.find(b => b.info.isbn === value)
    if (existingBook) {
      return Promise.reject("That book already exists");
    }
  })
]

Since were exporting our array of validators, we can require this file in books.js and reference our postBooksValidatorsinstead.

// Add this near the top of the file
const validators = require('./booksValidators')

// Replace your validators with the reference
router.post('/', validators.postBooksValidators, async (req, res) => {
  // ... code removed for brevity
}

Feel free to test your API again to make sure it is setup properly. Next up, I’ll be stepping through how to add authentication to our API using Cognito, an authentication framework from AWS.