Server-Side Routing

On the server-side, a RESTful (Representational State Transfer) API associates a HTTP verb (getpostputdelete, patch) to an endpoint on the client-side and to a function that is called to handle the server-side endpoint.

An endpoint is a string that identifies a physical or logical resource. An endpoint looks similar to a URL. Endpoints can also be called URIs (Uniform Resource Identifier).

HTTP verb CRUD Action Endpoint (URI) Examples
get read returns requested data

/cars/

returns the details of all cars
     

/cars/123

returns the details of product 123
post create creates a new record /cars/corrolla/red/2020/25000 creates a product with model=corolla, colour=red, year=2020 and price=25000
put update updates an entire existing record /cars/123
updates product 123 with the contents of the body (also called the payload) that is passed to the server with the api call
      /cars/123/corrolla/red/2020/20000 updates product 123 so that model=corolla, colour=red, year=2020 and price=20000
patch update updates part of an existing record /cars/123 updates product 123 with the contents of the body (also called the payload) that is passed to the server with the api call
      /cars/price/123/20000 updates the price of product 123 with the value 20000
delete delete deletes an existing record /cars/123 deletes product 123

The RESTful API data communication is independent of the development language being used in either the requester or the provider of the RESTful service.

In our projects, where do you think the client-side endpoints are handled?

Solution:They are handled in the React Component code.

Within a Node.js web application, Express routing implements the RESTful API to allow access to data sources that are stored on the server-side.

In Express, all the server-side RESTful APIs are written using the following template:

router.HTTP_verb(endpoint, middleware_function(req, res))

router
This is the server-side router. The router will be declared at the top of the file that contains the server-side routes.
const router = require(`express`).Router()
HTTP_verb
This is the RESTful API verb:
endpoint
This is the endpoint that the route handles:
middleware_function(req, res, next)
Middleware functions have access to:

 

We create one router file for each resource that we wish to access. We place router files into the server/routes folder. In our Cars Worked Example, we currently have one resource (ie the cars JSON array object). We shall call its router file server/routes/cars.js

In order to use routers in a node Express application, we must:

1) include express Router in any file that uses routing

server/routes/cars.js

const router = require(`express`).Router()


... 


module.exports = router
2) include all route-handling files in the server/server.js file

server/server.js

...



app.use(require(`./routes/cars`))


...

Client-side Endpoints

We use axios on the client-side to communicate with server-side Express routes.

We shall store the server-side URL in a variable called SERVER_HOST. We shall make a file called "client/src/config/global_constants.js" to hold this (and any other client-side global variables that we use in our project), as shown below:

client/src/config/global_constants.js

...


export const SERVER_HOST = `http://localhost:4000`


...

In order to use SERVER_HOST in a client-side React Component file, we must import it.

import {SERVER_HOST} from "../config/global_constants"


...

Other than the inclusion of SERVER_HOST at the beginning of the client-side endpoint, the client-side and server-side endpoints must match, as shown below:

// client-side
axios.get(`${SERVER_HOST}/cars`)
.then(res => 
{
    ...
})






// server-side
router.get(`/cars`, (req, res) => 
{
    ...
})

Each RESTful API HTTP_verb has a client-side axios endpoint and a server-side endpoint associated with it.

get
On the client-side, the JSON object that is returned from an axios() method is stored in res.data
If res.data is empty, it means that no JSON data was returned.
// client-side endpoint 
axios.get(`${SERVER_HOST}/cars`)
.then(res => 
{
    if(res.data)
    {          
        console.log("Record read") 

        // we can now use res.data on the client-side   

    }
    else // res.data is empty
    {
        console.log("Record not found")
    }
})







// server-side endpoint
router.get(`/cars`, (req, res) => 
{
    console.log(req)
})

If we are getting one record, we can include the record's id in the client-side and server-side endpoints.

// client-side endpoint 
axios.get(`${SERVER_HOST}/cars/${id}`)
.then(res => 
{
    if(res.data)
    {          
        console.log("Record read") 

        // we can now use res.data on the client-side   

    }
    else // res.data is empty
    {
        console.log("Record not found")
    }
})







// server-side endpoint
router.get(`/cars/:id`, (req, res) => 
{
    console.log(req.params.id)

    // we can use req.params.id on the server side
})
post
// client-side endpoint
axios.post(`${SERVER_HOST}/cars/${model}/${colour}/${year}/${price}`)
.then(res => 
{
    if(!res.data)
    {          
        console.log("Record not added")
    }
})






// server-side endpoint
router.post(`/cars/:model/:colour/:year/:price`, (req, res) => 
{
    console.log(req.params.model)    // req.params will hold the model, colour, year and price
})
Axios allows us to pass the post data as a single object. In the example below, newProductJSON would hold the model, colour , year and price.
// client-side endpoint
axios.post(`${SERVER_HOST}/cars`, newProductJSON)
.then(res => 
{
    if(!res.data)
    {          
        console.log("Record not added")
    }
})






// server-side endpoint
// On the server-side, the newProductJSON will be held in req.body
router.post(`/cars`, (req, res) => 
{
    console.log(req.body)    // req.body holds the properties from newProductJSON
})
put
We us put when we want to modify all of the properties of a server-side resource. Axios allows us to pass the put data as a single object. In the example below, updatedProductJSON would hold the model, colour , year and price.
// client-side endpoint
axios.put(`${SERVER_HOST}/cars/${id}`, updatedProductJSON)
.then(res => 
{
    if(!res.data)
    {          
        console.log("Record not modified")
    }
})






// server-side endpoint
router.put(`/cars/:id`, (req, res) => 
{
    console.log(req.params.id)
    console.log(req.body.model)   // req.body holds the properties from updatedProductJSON
})
patch
We us patch when we only want to modify some (but not all) of the properties of a server-side resource. In the example below, we are only modifying the price.
// client-side endpoint
axios.patch(`${SERVER_HOST}/cars/${id}`, {price: newPrice})
.then(res => 
{
    if(!res.data)
    {          
        console.log("Record not modified")
    }
})






// server-side endpoint
router.patch(`/cars/:id`, (req, res) => 
{
    console.log(req.params.id)
    console.log(req.body.price)  // req.body holds the value of the JSON object {price: newPrice}
})
delete
// client-side endpoint
axios.delete(`${SERVER_HOST}/cars/${id}`)
.then(res => 
{
    if(!res.data)
    {          
        console.log("Record not deleted")
    }
})






// server-side endpoint
router.delete(`/cars/:id`, (req, res) => 
{
    console.log(req.params.id)
})

Open the axios project from the previous section in these notes. Follow the instructions in the notes above to create a file server/routes/cars.js and then copy the code below into the server/routes/cars.js file.

let cars = [{_id:0, model:"Avensis", colour:"Red", year:2020, price:30000},
            {_id:1, model:"Yaris", colour:"Green", year:2010, price:2000},
            {_id:2, model:"Corolla", colour:"Red", year:2019, price:20000},
            {_id:3, model:"Avensis", colour:"Silver", year:2018, price:20000},
            {_id:4, model:"Camry", colour:"White", year:2020, price:50000}]

let uniqueId = cars.length // use this to ensure that we give a unique id to each new object that is added to cars.
The above code will allow us to store the JSON data in a variable (called cars) on the server-side.

Change the "axios" example code to implement server-side routing, as shown below:

Add to the code above to implement server-side routing that allows the user to add, modify and delete items from the cars JSON data. Your client-side web application user interface should look similar to the one shown below:

"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.json data on the client side. We shall now store the cars data on the server-side and access it using routing and the RESTful API. We shall store the car data in a JSON object.

We shall use Axios methods on the client-side to access the server-side endpoints. The server-side endpoints will access the cars JSON object and return the relavent data to the client-side.

Client-Side

client/src/config/global_constants.js

// This file holds global constants that are visible on the Client-side


// Server
export const SERVER_HOST = `http://localhost:4000`

We need access to the server from the client-side Axios methods. We shall store the server name in SERVER_HOST.

client/src/components/Button.js

//Author: Derek O Reilly
//
// Helper Component class that allows us to have a button that renders the same way as a <Link> component
// Use this class to link to functions within the same class
// Use <Link> to link to Components that are in other endpoints
import React, {Component} from "react"


export default class Button extends Component
{
    render()
    {
        return (
            <span tabIndex="0" className={this.props.className} onClick={(event) => {this.props.onClick(event)}}>     
                {this.props.value}
            </span>
        )
    }
}

Button will allow us to have links that point to a method within a class, but that have the same look-and-feel as a <Link>. In this example, we use it to make all of the buttons look the same.

client/src/components/CarTable.js

import React, {Component} from "react"
import CarTableRow from "./CarTableRow"


export default class CarTable extends Component 
{
    render() 
    {
        return (
            <table>
                <thead>
                    <tr>
                        <th>Model</th>
                        <th>Colour</th>
                        <th>Year</th>
                        <th>Price</th>
                        <th> </th>
                    </tr>
                </thead>
                  
                <tbody>
                    {this.props.cars.map((car) => <CarTableRow key={car._id} car={car}/>)}
                </tbody>
            </table>      
        )
    }
}

We shall add an additional column, which will have an EDIT and DELETE button for each car in the table. The empty table header cell in the code below accounts for this in the table header.

                        <th> </th>

client/src/components/CarTableRow.js

import React, {Component} from "react"
import {Link} from "react-router-dom"


export default class CarTableRow extends Component 
{    
    render() 
    {
        return (
            <tr>
                <td>{this.props.car.model}</td>
                <td>{this.props.car.colour}</td>
                <td>{this.props.car.year}</td>
                <td>{this.props.car.price}</td>
                <td>
                    <Link className="green-button" to={"/EditCar/" + this.props.car._id}>Edit</Link>                    
                    <Link className="red-button" to={"/DeleteCar/" + this.props.car._id}>Delete</Link>   
                </td>
            </tr>
        )
    }
}

The code below includes an EDIT and DELETE button for each car in the table.

                <td>
                    <Link className="green-button" to={"/EditCar/" + this.props.car._id}>Edit</Link>                    
                    <Link className="red-button" to={"/DeleteCar/" + this.props.car._id}>Delete</Link>   
                </td>

client/src/components/AddCar.js

import React, {Component} from "react"
import {Redirect, Link} from "react-router-dom"
import Form from "react-bootstrap/Form"

import axios from "axios"

import Button from "../components/Button"

import {SERVER_HOST} from "../config/global_constants"


export default class AddCar extends Component
{
    constructor(props)
    {
        super(props)

        this.state = {
            model:"",
            colour:"",
            year:"",
            price:"",
            redirectToDisplayAllCars:false
        }
    }


    componentDidMount() 
    {     
        this.inputToFocus.focus()        
    }
 
 
    handleChange = (e) => 
    {
        this.setState({[e.target.name]: e.target.value})
    }


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

        const carObject = {
            model: this.state.model,
            colour: this.state.colour,
            year: this.state.year,
            price: this.state.price
        }        

        axios.post(`${SERVER_HOST}/cars`, carObject)
        .then(res => 
        {   
            if(res.data)
            {
                if (res.data.errorMessage)
                {
                    console.log(res.data.errorMessage)    
                }
                else
                {   
                    console.log("Record added")
                    this.setState({redirectToDisplayAllCars:true})
                } 
            }
            else
            {
                console.log("Record not added")
            }
        })
    }


    render()
    { 
        return (
            <div className="form-container"> 
                {this.state.redirectToDisplayAllCars ? <Redirect to="/DisplayAllCars"/> : null}                                            
                    
                <Form>               
                    <Form.Group controlId="model">
                        <Form.Label>Model</Form.Label>
                        <Form.Control ref = {(input) => { this.inputToFocus = input }} type="text" name="model" value={this.state.model} onChange={this.handleChange} />
                    </Form.Group>
    
                    <Form.Group controlId="colour">
                        <Form.Label>Colour</Form.Label>
                        <Form.Control type="text" name="colour" value={this.state.colour} onChange={this.handleChange} />
                    </Form.Group>
    
                    <Form.Group controlId="year">
                        <Form.Label>Year</Form.Label>
                        <Form.Control type="text" name="year" value={this.state.year} onChange={this.handleChange} />
                    </Form.Group>
    
                    <Form.Group controlId="price">
                        <Form.Label>Price</Form.Label>
                        <Form.Control type="text" name="price" value={this.state.price} onChange={this.handleChange} />
                    </Form.Group> 
            
                    <Button value="Add" className="green-button" onClick={this.handleSubmit}/>            
            
                    <Link className="red-button" to={"/DisplayAllCars"}>Cancel</Link>
                </Form>
            </div>
        )
    }
}

In order to use Axios in a file, we need to import the axios library.

import axios from "axios"

In all of our "Cars" examples, there will always be three possible responses that a server-side endpoint can give to any of the Axios methods:

res.data can contain an errorMessage
In this example, on the server-side we can set an errorMessage as part of the data that will be returned to the Axios method. This allows us to have custom error messages.
res.data can contain data
This is the data that is returned by the server-side endpoint.
res.data can be empty
If res.data is empty, then it is an error.
        axios.post(`${SERVER_HOST}/cars`, carObject)
        .then(res => 
        {   
            if(res.data)
            {
                if (res.data.errorMessage)
                {
                    console.log(res.data.errorMessage)    
                }
                else
                {   
                    console.log("Record added")
                    this.setState({redirectToDisplayAllCars:true})
                } 
            }
            else
            {
                console.log("Record not added")
            }
        })

The redirectToDisplayAllCars flag is used to control the exit from this Component and the return to the main DisplayAllCars Component upon successful addition of a new car. The flag is set to false in the constructor. The same redirectToDisplayAllCars redirect flag logic will be used in several different components throughout this code.

    constructor(props)
    {
        super(props)

        this.state = {
            model:"",
            colour:"",
            year:"",
            price:"",
            redirectToDisplayAllCars:false
        }

At the beginning of the render() method the flag is checked. If it is true, then the code redirects to the DisplayAllCars Component.

    render()
    { 
        return (
            <div className="form-container"> 
                {this.state.redirectToDisplayAllCars ? <Redirect to="/DisplayAllCars"/> : null}    

                ...

The flag will be set to true after the Axios method receives a success indicator from the server-side endpoint (i.e. if the res.data from the server-side endpoint is not empty and does not contain an error message).

        axios.post(`${SERVER_HOST}/cars`, carObject)
        .then(res => 
        {   
            if(res.data)
            {
                if (res.data.errorMessage)
                {
                    console.log(res.data.errorMessage)    
                }
                else
                {   
                    console.log("Record added")
                    this.setState({redirectToDisplayAllCars:true})
                } 
            }
            else
            {
                console.log("Record not added")
            }
        })

client/src/components/EditCar.js

import React, {Component} from "react"
import Form from "react-bootstrap/Form"
import {Redirect, Link} from "react-router-dom"
import axios from "axios"

import Button from "../components/Button"

import {SERVER_HOST} from "../config/global_constants"

export default class EditCar extends Component 
{
    constructor(props) 
    {
        super(props)

        this.state = {
            model: ``,
            colour: ``,
            year: ``,
            price: ``,
            redirectToDisplayAllCars:false
        }
    }

    componentDidMount() 
    {      
        this.inputToFocus.focus()
  
        axios.get(`${SERVER_HOST}/cars/${this.props.match.params.id}`)
        .then(res => 
        {     
            if(res.data)
            {
                if (res.data.errorMessage)
                {
                    console.log(res.data.errorMessage)    
                }
                else
                { 
                    this.setState({
                        model: res.data.model,
                        colour: res.data.colour,
                        year: res.data.year,
                        price: res.data.price
                    })
                }
            }
            else
            {
                console.log(`Record not found`)
            }
        })
    }


    handleChange = (e) => 
    {
        this.setState({[e.target.name]: e.target.value})
    }


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

        const carObject = {
            model: this.state.model,
            colour: this.state.colour,
            year: this.state.year,
            price: this.state.price
        }

        axios.put(`${SERVER_HOST}/cars/${this.props.match.params.id}`, carObject)
        .then(res => 
        {             
            if(res.data)
            {
                if (res.data.errorMessage)
                {
                    console.log(res.data.errorMessage)    
                }
                else
                {      
                    console.log(`Record updated`)
                    this.setState({redirectToDisplayAllCars:true})
                }
            }
            else
            {
                console.log(`Record not updated`)
            }
        })
    }

    
    render() 
    {  
        return (
            <div className="form-container">
    
                {this.state.redirectToDisplayAllCars ? <Redirect to="/DisplayAllCars"/> : null}  
                        
                <Form>
                    <Form.Group controlId="model">
                        <Form.Label>Model</Form.Label>
                        <Form.Control ref = {(input) => { this.inputToFocus = input }} type="text" name="model" value={this.state.model} onChange={this.handleChange} />
                    </Form.Group>

                    <Form.Group controlId="colour">
                        <Form.Label>Colour</Form.Label>
                        <Form.Control type="text" name="colour" value={this.state.colour} onChange={this.handleChange} />
                    </Form.Group>

                    <Form.Group controlId="year">
                        <Form.Label>Year</Form.Label>
                        <Form.Control type="text" name="year" value={this.state.year} onChange={this.handleChange} />
                    </Form.Group>
        
                    <Form.Group controlId="price">
                        <Form.Label>Price</Form.Label>
                        <Form.Control type="text" name="price" value={this.state.price} onChange={this.handleChange} />
                    </Form.Group>
  
                    <Button value="Update" className="green-button" onClick={this.handleSubmit}/>  
    
                    <Link className="red-button" to={"/DisplayAllCars"}>Cancel</Link>
                </Form>
            </div>
        )
    }

}

When the component mounts, we get the car details from the server via an axios.get() method. This will place the details into the component's state.

    componentDidMount() 
    {      
        this.inputToFocus.focus()
  
        axios.get(`${SERVER_HOST}/cars/${this.props.match.params.id}`)
        .then(res => 
        {     
            if(res.data)
            {
                if (res.data.errorMessage)
                {
                    console.log(res.data.errorMessage)    
                }
                else
                { 
                    this.setState({
                        model: res.data.model,
                        colour: res.data.colour,
                        year: res.data.year,
                        price: res.data.price
                    })
                }
            }
            else
            {
                console.log(`Record not found`)
            }
        })
    }

When the user submits the form, the modified data is sent to the server via an axios.push() method.

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

        const carObject = {
            model: this.state.model,
            colour: this.state.colour,
            year: this.state.year,
            price: this.state.price
        }

        axios.put(`${SERVER_HOST}/cars/${this.props.match.params.id}`, carObject)
        .then(res => 
        {             
            if(res.data)
            {
                if (res.data.errorMessage)
                {
                    console.log(res.data.errorMessage)    
                }
                else
                {      
                    console.log(`Record updated`)
                    this.setState({redirectToDisplayAllCars:true})
                }
            }
            else
            {
                console.log(`Record not updated`)
            }
        })
    }

The redirectToDisplayAllCars redirect flag logic is described in the AddCar.js code previous on this webpage.

client/src/components/DeleteCar.js

import React, {Component} from "react"
import {Redirect} from "react-router-dom"
import axios from "axios"

import {SERVER_HOST} from "../config/global_constants"


export default class DeleteCar extends Component 
{
    constructor(props) 
    {
        super(props)
        
        this.state = {
            redirectToDisplayAllCars:false
        }
    }
    
    
    componentDidMount() 
    {   
        axios.delete(`${SERVER_HOST}/cars/${this.props.match.params.id}`)
        .then(res => 
        {
            if(res.data)
            {
                if (res.data.errorMessage)
                {
                    console.log(res.data.errorMessage)    
                }
                else // success
                { 
                    console.log("Record deleted")
                }
                this.setState({redirectToDisplayAllCars:true})
            }
            else 
            {
                console.log("Record not deleted")
            }
        })
    }
  
  
    render() 
    {
        return (
            <div>   
                {this.state.redirectToDisplayAllCars ? <Redirect to="/DisplayAllCars"/> : null}                      
            </div>
        )
    }
}

When the component mounts, the axios.delete() method is used to send theid to the server, so that this data can be deleted. The redirectToDisplayAllCars is then set, which will force the component to exit and the DisplayAllCars component to load.

    componentDidMount() 
    {   
        axios.delete(`${SERVER_HOST}/cars/${this.props.match.params.id}`)
        .then(res => 
        {
            if(res.data)
            {
                if (res.data.errorMessage)
                {
                    console.log(res.data.errorMessage)    
                }
                else // success
                { 
                    console.log("Record deleted")
                }
                this.setState({redirectToDisplayAllCars:true})
            }
            else 
            {
                console.log("Record not deleted")
            }
        })
    }

Server-Side

server/server.js

// Server-side global variables
require(`dotenv`).config({path:`./config/.env`})


// Express
const express = require(`express`)
const app = express()

app.use(require(`body-parser`).json())
app.use(require(`cors`)({credentials: true, origin: process.env.LOCAL_HOST}))


// Routers
app.use(require(`./routes/cars`))


// Port
app.listen(process.env.SERVER_PORT, () => 
{
    console.log(`Connected to port ` + process.env.SERVER_PORT)
})


// Error 404
app.use((req, res, next) => {next(createError(404))})

// Other errors
app.use(function (err, req, res, next)
{
    console.error(err.message)
    if (!err.statusCode) 
    {
        err.statusCode = 500
    }
    res.status(err.statusCode).send(err.message)
})

Express allows us to have more than one router file. This allows us to write more structured code, as we can put the endpoint handlers that related to a particular server-side resource into a single file. In this example, we only have one server-side resource (i.e. the cars JSON object). Therefore, we place all of our server-side endpoints into one file, which is called server/routes/cars.js

// Routers
app.use(require(`./routes/cars`))

 

server/routes/cars.js

const router = require(`express`).Router()



let cars = [{_id:0, model:"Avensis", colour:"Red", year:2020, price:30000},
            {_id:1, model:"Yaris", colour:"Green", year:2010, price:2000},
            {_id:2, model:"Corolla", colour:"Red", year:2019, price:20000},
            {_id:3, model:"Avensis", colour:"Silver", year:2018, price:20000},
            {_id:4, model:"Camry", colour:"White", year:2020, price:50000}]

let uniqueId = cars.length



// read all items from cars JSON
router.get(`/cars`, (req, res) => {   
    res.json(cars)
})


// Read one item from cars JSON
router.get(`/cars/:id`, (req, res) => 
{
    const selectedCars = cars.filter(car => car._id === parseInt(req.params.id))
    
    res.json(selectedCars[0])
})


// Add new item to cars JSON
router.post(`/cars`, (req, res) => {
    let newCar = req.body
    newCar._id = uniqueId
    cars.push(newCar)
    
    uniqueId++
    
    res.json(cars)
})


// Update one item in cars JSON
router.put(`/cars/:id`, (req, res) => 
{
    const updatedCar = req.body
    cars.map(car => 
    {
        if(car._id === parseInt(req.params.id))
        {
            car.model = updatedCar.model
            car.colour = updatedCar.colour
            car.year = updatedCar.year
            car.price = updatedCar.price
        }
    })
    
    res.json(cars)   
})


// Delete one item from cars JSON
router.delete(`/cars/:id`, (req, res) => 
{
    let selectedIndex
    cars.map((car, index) => 
    {
        if(car._id === parseInt(req.params.id))
        {
            selectedIndex = index
        }
    })
    cars.splice(selectedIndex, 1)
    
    res.json(cars)       
})

module.exports = router

Create an Express router.

const router = require(`express`).Router()

The server-side JSON object will be used to hold the applications's car data.

let cars = [{_id:0, model:"Avensis", colour:"Red", year:2020, price:30000},
            {_id:1, model:"Yaris", colour:"Green", year:2010, price:2000},
            {_id:2, model:"Corolla", colour:"Red", year:2019, price:20000},
            {_id:3, model:"Avensis", colour:"Silver", year:2018, price:20000},
            {_id:4, model:"Camry", colour:"White", year:2020, price:50000}]

We shall allow the user to add new cars to the cars JSON. Each time we add a new car, we shall assign uniqueId to be its id property. We shall then increment uniqueId. We initialise uniqueId to be the first available unique number, which happens to be cars.length

let uniqueId = cars.length

router was created at the top of the file. It is used for all the endpoints.

// read all items from cars JSON
router.get(`/cars`, (req, res) => 
{   
    res.json(cars)
})

To read one item from the cars JSON object, we filter all of the items in the JSON against the search car's id.

// Read one item from cars JSON
router.get(`/cars/:id`, (req, res) => 
{
    const selectedCars = cars.filter(car => car._id === parseInt(req.params.id))
    
    res.json(selectedCars[0])
})

To add a new car to the cars JSON object, we push it onto the cars JSON object. We use uniqueId to ensure that each item in the cars JSON object has a unique id.

// Add new item to cars JSON
router.post(`/cars`, (req, res) => {
    let newCar = req.body
    newCar._id = uniqueId
    cars.push(newCar)
    
    uniqueId++
    
    res.json(cars)
})

To modify a car, we search through the cars JSON object until we match the search id. We then modify this item.

// Update one item in cars JSON
router.put(`/cars/:id`, (req, res) => 
{
    const updatedCar = req.body
    cars.map(car => 
    {
        if(car._id === parseInt(req.params.id))
        {
            car.model = updatedCar.model
            car.colour = updatedCar.colour
            car.year = updatedCar.year
            car.price = updatedCar.price
        }
    })
    
    res.json(cars)   
})

To delete a car, we search through the cars JSON object until we match the search id. We then use this item's index to remove it from the cars JSON object.

// Delete one item from cars JSON
router.delete(`/cars/:id`, (req, res) => 
{
    let selectedIndex
    cars.map((car, index) => 
    {
        if(car._id === parseInt(req.params.id))
        {
            selectedIndex = index
        }
    })
    console.log(selectedIndex)
    cars.splice(selectedIndex, 1)
    
    res.json(cars)       
})

We need to make the router available to the server/server.js file.

module.exports = router
 
<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>