MongoDB Multiple Collections

We can have more than one collection in a database.

Each collection needs to have its own model and schema. All of the models and schema are stored in the server/models folder.

Each collection should have its own router file associated with it. The router file will contain the server-side endpoints that interact with the collection. Each router will need to require the model that it accesses. In our previous example, we had a cars model (server/models/cars.js) and cars router (server/routers/cars.js). We shall need an additional model file and router file for any other collections that we want to use. For example, the cars router file (server/routers/cars.js) contains the line of code below:

server/routers/cars.js

...


const carsModel = require(`../models/cars`)


...

 

Each additional router needs to be included in the server/server.js file, as shown below:

server/server.js

...


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

Open the mongoDB project from the previous section in these notes. Change the code so that a new user can register by adding their name, email and password into a "users" collection, as shown below:


Add a "Reset Database" button to empty the "users" collection, as shown below. The "Reset Button" can be useful for testing.:

Use MongoDB Compass to view the data in the "users" collection.

"Cars" Worked Example

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

We shall create a second collection, called "users". This collection will hold a user's name, email, password and access level. The file server/routes/users.js shows how registration and login works on the server-side. Here, we add a property called "user" to the server-side session variable. The file client/src/components/Login showns that whenever a user logs in, the user's name and access level are sent back to the client. This is then stored in sessionStorage, so that it is available on the client-side.

Client-Side

client/src/components/Register.js

import React, {Component} 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 default class Register extends Component
{
    constructor(props)
    {
        super(props)
        
        this.state = {
            name:"",
            email:"",
            password:"",
            confirmPassword:"",    
            isRegistered:false
        } 
    }
    
    
    handleChange = (e) => 
    {
        this.setState({[e.target.name]: e.target.value})
    }
    
    
    handleSubmit = (e) => 
    {
        e.preventDefault()

        axios.post(`${SERVER_HOST}/users/register/${this.state.name}/${this.state.email}/${this.state.password}`)
        .then(res => 
        {     
            if(res.data)
            {
                if (res.data.errorMessage)
                {
                    console.log(res.data.errorMessage)    
                }
                else // user successfully registered
                { 
                    console.log("User registered")                    
                    
                    this.setState({isRegistered:true})
                }        
            }
            else
            {
                console.log("Registration failed")
            }
        })   
    }


    render() 
    {     
        return (
            <form className="form-container" noValidate = {true} id = "loginOrRegistrationForm">
           
                {this.state.isRegistered ? <Redirect to="/DisplayAllCars"/> : null} 
            
                <h2>New User Registration</h2>
           
                <input  
                    name = "name"              
                    type = "text"
                    placeholder = "Name"
                    autoComplete="name"
                    value = {this.state.name}
                    onChange = {this.handleChange}
                    ref = {(input) => { this.inputToFocus = input }} 
                /><br/>           

	        <input  
                    name = "email"              
                    type = "email"
                    placeholder = "Email"
                    autoComplete="email"
                    value = {this.state.email}
                    onChange = {this.handleChange}
                /><br/>              

	        <input  
                    name = "password"           
                    type = "password"
                    placeholder = "Password"
                    autoComplete="password"
                    title = "Password must be at least ten-digits long and contains at least one lowercase letter, one uppercase letter, one digit and one of the following characters (£!#€$%^&*)"
                    value = {this.state.password}
                    onChange = {this.handleChange}
                /><br/>           

                <input          
                    name = "confirmPassword"    
                    type = "password"
                    placeholder = "Confirm password"
                    autoComplete="confirmPassword"
                    value = {this.state.confirmPassword}
                    onChange = {this.handleChange}
                /><br/><br/>
                
                <Button value="Register New User" className="green-button" onClick={this.handleSubmit} />
                <Link className="red-button" to={"/DisplayAllCars"}>Cancel</Link>   
            </form>
        )
    }
}

When the flag this.state.isRegistered is set to true, we redirect to the DisplayAllCars component.

When the user submits the form, the name, email and password are passed to the server-side router, where they will be added to the database.

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

        axios.post(`${SERVER_HOST}/users/register/${this.state.name}/${this.state.email}/${this.state.password}`)
        .then(res => 
        {     
            if(res.data)
            {
                if (res.data.errorMessage)
                {
                    console.log(res.data.errorMessage)    
                }
                else // user successfully registered
                { 
                    console.log("User registered")                    
                    
                    this.setState({isRegistered:true})
                }        
            }
            else
            {
                console.log("Registration failed")
            }
        })   
    }

client/src/components/ResetDatabase.js

import React, {Component} 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 default class ResetDatabase extends Component
{
    constructor(props)
    {
        super(props)
        
        this.state = {   
            isReset:false
        } 
    }
    
    
    handleChange = (e) => 
    {
        this.setState({[e.target.name]: e.target.value})
    }
    

    resetUsersModel = () =>
    {
        axios.post(`${SERVER_HOST}/users/reset_user_collection`)
        .then(res => 
        {     
            if(res.data)
            {
                if (res.data.errorMessage)
                {
                    console.log(res.data.errorMessage)    
                }
                else // user successfully reset the User collection
                { 
                    console.log("User collection reset")
                }        
            }
            else
            {
                console.log("Failed to reset User collection")
            }
            
            this.setState({isReset:true})
        })   
    }



    render() 
    { 
        return (
            <form className="form-container" noValidate = {true} id = "loginOrRegistrationForm">

               {this.state.isReset ? <Redirect to="/DisplayAllCars"/> : null} 

                <p>"Reset User Database" is only for testing purposes.<br/>All code on the client-side and server-side relating to resetting the database should be removed from any development release</p>
                <Button value="Reset User Database" className="red-button" onClick={this.resetUsersModel}/> <br/><br/>

                <Link className="red-button" to={"/DisplayAllCars"}>Cancel</Link>
            </form>
        )
    }
}

When the this.state.isReset flag is set to true, we rediect to the DisplayAllCars component.

When the user submits the form, the axios() method calls the server-side reset_user_collection router.

    resetUsersModel = () =>
    {
        axios.post(`${SERVER_HOST}/users/reset_user_collection`)
        .then(res => 
        {     
            if(res.data)
            {
                if (res.data.errorMessage)
                {
                    console.log(res.data.errorMessage)    
                }
                else // user successfully reset the User collection
                { 
                    console.log("User collection reset")
                }        
            }
            else
            {
                console.log("Failed to reset User collection")
            }
            
            this.setState({isReset:true})
        })   
    }  

client/src/components/DisplayAllCars.js

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

import axios from "axios"

import CarTable from "./CarTable"

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


export default class DisplayAllCars extends Component 
{
    constructor(props) 
    {
        super(props)
        
        this.state = {
            cars:[]
        }
    }
    
    
    componentDidMount() 
    {
        axios.get(`${SERVER_HOST}/cars`)
        .then(res => 
        {
            if(res.data)
            {
                if (res.data.errorMessage)
                {
                    console.log(res.data.errorMessage)    
                }
                else
                {           
                    console.log("Records read")   
                    this.setState({cars: res.data}) 
                }   
            }
            else
            {
                console.log("Record not found")
            }
        })
    }

  
    render() 
    {   
        return (           
            <div className="form-container">
                <div>
                    <Link className="blue-button" to={"/Register"}>Register</Link>  
                    <Link className="red-button" to={"/ResetDatabase"}>Reset Database</Link>  <br/><br/><br/>
                </div>
                
                <div className="table-container">
                    <CarTable cars={this.state.cars} /> 

                    <div className="add-new-car">
                        <Link className="blue-button" to={"/AddCar"}>Add New Car</Link>
                    </div>
                </div>
            </div> 
        )
    }
}

The render() method now includes the Register and Reset Database buttons.

    render() 
    {   
        return (           
            <div className="form-container">
                <div>
                    <Link className="blue-button" to={"/Register"}>Register</Link>  
                    <Link className="red-button" to={"/ResetDatabase"}>Reset Database</Link>  <br/><br/><br/>
                </div>
                
                <div className="table-container">
                    <CarTable cars={this.state.cars} /> 

                    <div className="add-new-car">
                        <Link className="blue-button" to={"/AddCar"}>Add New Car</Link>
                    </div>
                </div>
            </div> 
        )
    }

Server-Side

server/models/users.js

const mongoose = require(`mongoose`)

let usersSchema = new mongoose.Schema(
   {
        name: {type: String, required:true},
        email: {type: String, required:true},
        password: {type: String,required:true}
   },
   {
       collection: `users`
   })

module.exports = mongoose.model(`users`, usersSchema)

The usersSchema is created in the same way that the carsSchema was previously created in the notes.

server/server.js

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


// Database
require(`./config/db`)


// 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`))
app.use(require(`./routes/users`))


// 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)
})

The users route needs to be included along with the previously developed cars route in the server.js file.

server/routes/users.js

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

const usersModel = require(`../models/users`)


// IMPORTANT
// Obviously, in a production release, you should never have the code below, as it allows a user to delete a database collection
// The code below is for development testing purposes only 
router.post(`/users/reset_user_collection`, (req, res) =>
{
    usersModel.deleteMany({}, (error, data) =>
    {
        if (data)
        {
            res.json(data)
        } else
        {
            res.json({errorMessage: `Failed to delete "user" collection for testing purposes`})
        }
    })
})


router.post(`/users/register/:name/:email/:password`, (req, res) =>
{
    // If a user with this email does not already exist, then create new user
    usersModel.findOne({email: req.params.email}, (uniqueError, uniqueData) =>
    {
        if (uniqueData)
        {
            res.json({errorMessage: `User already exists`})
        } else
        {
            usersModel.create({name: req.params.name, email: req.params.email, password: req.params.password}, (error, data) =>
            {
                if (data)
                {
                    res.json({name: data.name})
                } else
                {
                    res.json({errorMessage: `User was not registered`})
                }
            })
        }
    })
})


module.exports = router

When we register a new user, we use the findOne() method to ensure that no user with the same email already exits in the collection.
If the email does not already exist, we add the new user to the collection.

 
<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>