Error Handling

All Mongoose queries will either return data that matches the query or throw an error exception (using a catch() method) if an error orrurs when executing the query. We catch errors by adding the code below to every Mongoose query:

.catch(err => next(err))

For example, to execute the find() query, the code is:

carsModel.find()
.then(data => 
{
    res.json(data)
})
.catch(err => next(err))

Server-Side Router endpoint

To use the next() method when dealing with the Mongoose errors we need to add an additional parameter (called next) to the route endpoints on the server-side, as shown below:

router.get(`/cars`, (req, res, next) => 
{   
    carsModel.find()
    .then(data => 
    {
        res.json(data)
    })
    .catch(err => next(err))
})

Client-Side Axios Error Handling

All client-side axios function calls can include error exception handling to catch any errors that are returned from the server-side. We catch axios errors using the code below:

.catch(err => console.log(`${err.response.data}\n${err}`))

For example, when using an axios.get() method, we could use the code below:

axios.get(`${SERVER_HOST}/cars`)
.then(res =>
{
    setCars(res.data)
})
.catch(err => console.log(`${err.response.data}\n${err}`))

In the real-world, we should not just write the error to the console log. Instead, we should write additional code on the client-side to provide useful feedback to the user and to prevent the user from continuing in the case of an axios error occuring.

In addition to the err.response.data property, the err.response property that is returned in the axios catch() method includes other useful information. Add additional code to the catch() method above to display the error message's:

Answer
axios.get(`${SERVER_HOST}/cars`)
.then(res =>
{
    setCars(res.data)
})
.catch(err => {
    console.log(`${err.response.data}`)
    console.log(`${err.response.status}`)})
    console.log(`${err.response.text}`)})
    console.log(`${err}`)
})

Custom Error Messages

We can use the createError() function from the http-errors package to create our own custom error messages.

Install the http-errors package.

We need to include the http-errors package in any any server-side router files that use the createError() method, as shown below:

const createError = require('http-errors')

 

The createError() function has two optional parameters: a HTTP status code (i.e. an error code) and/or an error message.

Best practice is to always give an error code.

If we do not provide an error message, then a default error message that matches the error code is automatically created. If we include an error message, then our error message overwrites the default error message. In the example below, the default error message is overwritten with the text `The requested data cannot be found` to provide additional information.

axios.get(`${SERVER_HOST}/cars`)
.then(res =>
{
    setCars(res.data)
})
.catch(err => next(createError(404, `The requested data cannot be found`)))

Below is a list of some HTTP status codes:

A complete list of HTTP status codes can be found at https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml.

 

 

Take the code from the MongoDB section of the notes and change it so that it include error handling on both the server-side and client-side.

Change the code on the client-side so that the user receives user friendly prompting when an error occurs.

"Cars" Worked Example

The full project code for the "Cars" Worked Example that is described below can be downloaded from this link.

In the previous example, we stored the cars data in a JSON object on the server-side. In the real-world, it makes much more sense to store the data in a database. In this example, we shall use a database to store the car data.

Client-Side

The client-side needs to be updated to deal with any errors that are generated on axios calls, as we are changing the resource that we access on the server-side to be a MongoDB. It was a JSON object in the previous example, which did not produce server-side errors.

client/src/components/DisplayAllCars.js

import React, {useState, useEffect} from "react"
import {Link} from "react-router-dom"
import axios from "axios"
import {CarTable} from "./CarTable"
import {SERVER_HOST} from "../config/global_constants"


export const DisplayAllCars = props =>
{
    const [cars, setCars] = useState([])


    useEffect(() =>
    {
        axios.get(`${SERVER_HOST}/cars`)
        .then(res =>
        {
            setCars(res.data)
        })
        .catch(err => console.log(`${err.response.data}\n${err}`))
    }, [])


    return (
    <div className="form-container">
        <div className="table-container">
            <CarTable cars={cars} /> 
    
            <div className="add-new-car">
                <Link className="blue-button" to={"/AddCar"}>Add New Car</Link>
            </div>
        </div>
    </div>
    )
}

We need to deal with errors that might be returned when we make an axios call.

        axios.get(`${SERVER_HOST}/cars`)
        .then(res =>
        {
            setCars(res.data)
        })         
        .catch(err => console.log(`${err.response.data}\n${err}`))

We should replace the error logging with our own user friendly code.

client/src/components/addCar.js

import React, {useState} from "react"
import {Redirect, Link} from "react-router-dom"
import axios from "axios"
import {Button} from "../components/Button"
import {SERVER_HOST} from "../config/global_constants"


export const AddCar = props =>
{
    const [model, setModel] = useState("")
    const [colour, setColour] = useState("")
    const [year, setYear] = useState("")
    const [price, setPrice] = useState("")
    const [redirectToDisplayAllCars, setRedirectToDisplayAllCars] = useState(false)


    const handleModelChange = e =>
    {
        setModel(e.target.value)
    }


    const handleColourChange = e =>
    {
        setColour(e.target.value)
    }


    const handleYearChange = e =>
    {
        setYear(e.target.value)
    }


    const handlePriceChange = e =>
    {
        setPrice(e.target.value)
    }


    const handleSubmit = e =>
    {
        e.preventDefault()

        const carObject = {
            model: model,
            colour: colour,
            year: year,
            price: price
        }

        axios.post(`${SERVER_HOST}/cars`, carObject)
        .then(res =>
        {
            setRedirectToDisplayAllCars(true)
        })
        .catch(err => console.log(`${err.response.data}\n${err}`))
    }


    return (
    <div className="form-container"> 
        {redirectToDisplayAllCars ? <Redirect to="/DisplayAllCars"/> : null}                                            
    
        <form>               
            <label>Model</label>
            <input autoFocus type="text" name="model" value={model} onChange={handleModelChange} />
    
            <label>Colour</label>
            <input type="text" name="colour" value={colour} onChange={handleColourChange} />
    
            <label>Year</label>
            <input type="text" name="year" value={year} onChange={handleYearChange} />
    
            <label>Price</label>
            <input type="text" name="price" value={price} onChange={handlePriceChange} />
    
            <Button value="Add" className="green-button" onClick={handleSubmit}/>            
    
            <Link className="red-button" to={"/DisplayAllCars"}>Cancel</Link>
        </form>
    </div>
    )
}

client/src/components/EditCar.js

import React, {useState, useEffect} from "react"
import {Redirect, Link} from "react-router-dom"
import axios from "axios"
import {Button} from "../components/Button"
import {SERVER_HOST} from "../config/global_constants"


export const EditCar = props =>
{
    const [model, setModel] = useState("")
    const [colour, setColour] = useState("")
    const [year, setYear] = useState("")
    const [price, setPrice] = useState("")
    const [redirectToDisplayAllCars, setRedirectToDisplayAllCars] = useState(false)


    useEffect(() =>
    {
        axios.get(`${SERVER_HOST}/cars/${props.match.params.id}`)
        .then(res =>
        {
                setModel(res.data.model)
                setColour(res.data.colour)
                setYear(res.data.year)
                setPrice(res.data.price)
         
        })
        .catch(err => console.log(`${err.response.data}\n${err}`))   
    }, [props.match.params.id])
    

    const handleModelChange = e =>
    {
        setModel(e.target.value)
    }


    const handleColourChange = e =>
    {
        setColour(e.target.value)
    }


    const handleYearChange = e =>
    {
        setYear(e.target.value)
    }


    const handlePriceChange = e =>
    {
        setPrice(e.target.value)
    }
    

    const handleSubmit = e =>
    {
        e.preventDefault()

        const carObject = {
            model: model,
            colour: colour,
            year: year,
            price: price
        }

        axios.put(`${SERVER_HOST}/cars/${props.match.params.id}`, carObject)
        .then(res =>
        {
            setRedirectToDisplayAllCars(true)
        })
        .catch(err => console.log(`${err.response.data}\n${err}`))
    }


    return (
    <div className="form-container">
        
        {redirectToDisplayAllCars ? <Redirect to="/DisplayAllCars"/> : null}  
        
        <form>
            <label>Model</label>
            <input autoFocus type="text" name="model" value={model} onChange={handleModelChange} />
    
            <label>Colour</label>
            <input type="text" name="colour" value={colour} onChange={handleColourChange} />
    
            <label>Year</label>
            <input type="text" name="year" value={year} onChange={handleYearChange} />
    
            <label>Price</label>
            <input type="text" name="price" value={price} onChange={handlePriceChange} /><Button value="Update" className="green-button" onClick={handleSubmit}/>  
        
            <Link className="red-button" to={"/DisplayAllCars"}>Cancel</Link>
        </form>
    </div>
    )
}

client/src/components/DeleteCar.js

import React, {useState, useEffect} from "react"
import {Redirect} from "react-router-dom"
import axios from "axios"
import {SERVER_HOST} from "../config/global_constants"


export const DeleteCar = props =>
{
    const [redirectToDisplayAllCars, setRedirectToDisplayAllCars] = useState(false)


    useEffect(() =>
    {
        axios.delete(`${SERVER_HOST}/cars/${props.match.params.id}`)
        .then(res =>
        {
            setRedirectToDisplayAllCars(true)
        })
        .catch(err => console.log(`${err.response.data}\n${err}`))
    }, [props.match.params.id])


    return (
    <div>   
        {redirectToDisplayAllCars ? <Redirect to="/DisplayAllCars"/> : null}                      
    </div>
    )
}

Server-Side

server/routes/cars.js

const router = require(`express`).Router()
const createError = require('http-errors')
const carsModel = require(`../models/cars`)

// read all records
router.get(`/cars`, (req, res, next) => 
{   
    carsModel.find()
    .then(data => 
    {
        res.json(data)
    })
    .catch(err => next(err))
})


// Read one record
router.get(`/cars/:id`, (req, res, next) => 
{
    carsModel.findById(req.params.id)
    .then(data => 
    {              
        res.json(data)
    })
    .catch(err => next(err))    
})


// Add new record
router.post(`/cars`, (req, res, next) => 
{
    carsModel.create(req.body) 
    .then(data => 
    {
        res.json(data)
    })
    .catch(err => next(err))    
})


// Update one record
router.put(`/cars/:id`, (req, res, next) => 
{
    carsModel.findByIdAndUpdate(req.params.id, {$set: req.body})
    .then(data => 
    {
        res.json(data)
    })    
    .catch(err => next(err))    
})


// Delete one record
router.delete(`/cars/:id`, (req, res, next) => 
{
    carsModel.findByIdAndDelete(req.params.id)
    .then(data => 
    {
        res.json(data)
    })     
    .catch(err => next(err))    
})

module.exports = router

Import the createError package

const createError = require('http-errors')

Catch server-side errors and return them to the the client-side axios() call.

 
<div align="center"><a href="../versionC/index.html" title="DKIT Lecture notes homepage for Derek O&#39; Reilly, Dundalk Institute of Technology (DKIT), Dundalk, County Louth, Ireland. Copyright Derek O&#39; Reilly, DKIT." target="_parent" style='font-size:0;color:white;background-color:white'>&nbsp;</a></div>