Car Vault app using MERN

Organizing your vehicle data and keeping the information updated is very necessary for managing your cars. In this article, we’ll explore the process of building a scalable car vault system using the MERN stack – MongoDB, Express, React, and Node.

Preview of final output: Let us have a look at how the final output will look like.

Final preview

Approach to create vehicle tracker:

  • In the app we have used MongoDB to store the data.
  • There is some initial sample data of the cars are added in the code.
  • You can do CRUD operation on the data with the help of buttons and forms provided.
  • By clicking on the add vehicle button you will be able to add new entry of a vehicle.
  • In the frontend App.js is the main component which is used to fetch data from the backend with the help of Axios.
  • There are various components for each part which all together do the functioning.
  • There is an option for filtering the vehicle on the basis of name, milage and distance covered.
  • There is an option for contact owner, delete and update vehicle information on the card.

Project Structure:

Root Folder Structure

Steps to create the application.

Step 1: Open the root directory in vs code and create a folder `server` and initialize the express application.

cd server
npm init -y

Step 2: Install the required dependencies

npm install express mongoose body-parser cors nodemon

Dependencies(Backend):

"dependencies": {
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.18.2",
"mongoose": "^8.0.3",
"nodemon": "^3.0.2"
}

Example: Create files server.js and seedData.js and add the required codes.

Javascript




//server.js
 
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const seedData = require('./seedData')
const cors = require('cors')
 
const app = express();
app.use(cors())
const PORT = process.env.PORT || 3001;
 
// Connect to MongoDB (make sure MongoDB is running)
mongoose.connect('mongodb://localhost:27017/vehicle-tracking', {
    useNewUrlParser: true,
    useUnifiedTopology: true,
});
 
// Middleware
app.use(bodyParser.json());
 
// MongoDB Schema for Car
const carSchema = new mongoose.Schema({
    companyName: String,
    distanceCovered: Number,
    mileage: Number,
    serviceDates: [Date],
    owner: {
        name: String,
        email: String,
    },
    image: String,
});
 
const Car = mongoose.model('Car', carSchema);
 
// Function to seed data
const seedDatabase = async () => {
    try {
        const existingCars = await Car.find();
 
        if (existingCars.length > 0) {
            // If there are existing cars, delete them
            await Car.deleteMany();
            console.log('Existing cars deleted.');
        }
 
        // Seed the database with new data
        await Car.create(seedData);
        console.log('Seed data added to the database.');
    } catch (error) {
        console.error('Error seeding the database:', error);
    }
};
 
 
// Call the seedDatabase function when the server starts
seedDatabase();
 
// API Endpoints
// Get all cars
app.get('/api/cars', async (req, res) => {
    try {
        const cars = await Car.find();
        res.json(cars);
    } catch (error) {
        res.status(500).json({ error: 'Internal Server Error' });
    }
});
 
// Get a specific car by ID
app.get('/api/cars/:id', async (req, res) => {
    try {
        const car = await Car.findById(req.params.id);
        if (!car) {
            return res.status(404).json({ error: 'Car not found' });
        }
        res.json(car);
    } catch (error) {
        res.status(500).json({ error: 'Internal Server Error' });
    }
});
 
// Add a new car
app.post('/api/cars', async (req, res) => {
    try {
        const newCar = await Car.create(req.body);
        res.status(201).json(newCar);
    } catch (error) {
        res.status(400).json({ error: 'Bad Request' });
    }
});
 
// Update a car by ID
app.put('/api/cars/:id', async (req, res) => {
    try {
        const updatedCar = await Car.findByIdAndUpdate(
            req.params.id,
            req.body,
            { new: true }
        );
        if (!updatedCar) {
            return res.status(404).json({ error: 'Car not found' });
        }
        res.json(updatedCar);
    } catch (error) {
        res.status(400).json({ error: 'Bad Request' });
    }
});
// Delete a car by ID
app.delete('/api/cars/:id', async (req, res) => {
    try {
        const deletedCar = await Car.findByIdAndDelete(req.params.id);
        if (!deletedCar) {
            return res.status(404).json({ error: 'Car not found' });
        }
        console.log('car is deleted successfully');
 
        res.json({ message: 'Car deleted successfully' });
    } catch (error) {
        res.status(500).json({ error: 'Internal Server Error' });
    }
});
 
 
// Start the server
app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});


Javascript




// seedData.js
 
module.exports = [
    {
        companyName: 'Toyota',
        distanceCovered: 10000,
        mileage: 25,
        serviceDates: [new Date('2022-01-15'), new Date('2022-03-20')],
        owner: {
            name: 'John Doe',
            email: 'john.doe@example.com',
        },
        image: 'https://media.w3wiki.net/wp-content/uploads/20240122182422/images1.jpg',
    },
    {
        companyName: 'Honda',
        distanceCovered: 8000,
        mileage: 28,
        serviceDates: [new Date('2022-02-10'), new Date('2022-04-25')],
        owner: {
            name: 'Jane Smith',
            email: 'jane.smith@example.com',
        },
        image: 'https://media.w3wiki.net/wp-content/uploads/20240122184958/images2.jpg',
    },
 
    {
        companyName: 'Volkswagen',
        distanceCovered: 10500,
        mileage: 26,
        serviceDates: [new Date('2022-10-05'), new Date('2022-12-15')],
        owner: {
            name: 'Ava Anderson',
            email: 'ava.anderson@example.com',
        },
        image: 'https://media.w3wiki.net/wp-content/uploads/20240122184958/images2.jpg',
    },
    {
        companyName: 'Toyota',
        distanceCovered: 10000,
        mileage: 25,
        serviceDates: [new Date('2022-01-15'), new Date('2022-03-20')],
        owner: {
            name: 'John Doe',
            email: 'john.doe@example.com',
        },
        image: 'https://media.w3wiki.net/wp-content/uploads/20240122184958/images2.jpg',
    },
    {
        companyName: 'Honda',
        distanceCovered: 8000,
        mileage: 28,
        serviceDates: [new Date('2022-02-10'), new Date('2022-04-25')],
        owner: {
            name: 'Jane Smith',
            email: 'jane.smith@example.com',
        },
        image: 'https://media.w3wiki.net/wp-content/uploads/20240122182422/images1.jpg',
    },
 
    {
        companyName: 'Volkswagen',
        distanceCovered: 10500,
        mileage: 26,
        serviceDates: [new Date('2022-10-05'), new Date('2022-12-15')],
        owner: {
            name: 'Ava Anderson',
            email: 'ava.anderson@example.com',
        },
        image: 'https://media.w3wiki.net/wp-content/uploads/20240122182422/images1.jpg',
    },
    {
        companyName: 'Toyota',
        distanceCovered: 10000,
        mileage: 25,
        serviceDates: [new Date('2022-01-15'), new Date('2022-03-20')],
        owner: {
            name: 'John Doe',
            email: 'john.doe@example.com',
        },
        image: 'hhttps://media.w3wiki.net/wp-content/uploads/20240122185312/images3.jpg',
    },
    {
        companyName: 'Honda',
        distanceCovered: 8000,
        mileage: 28,
        serviceDates: [new Date('2022-02-10'), new Date('2022-04-25')],
        owner: {
            name: 'Jane Smith',
            email: 'jane.smith@example.com',
        },
        image: 'https://media.w3wiki.net/wp-content/uploads/20240122185312/images3.jpg',
    },
 
    {
        companyName: 'Volkswagen',
        distanceCovered: 10500,
        mileage: 26,
        serviceDates: [new Date('2022-10-05'), new Date('2022-12-15')],
        owner: {
            name: 'Ava Anderson',
            email: 'ava.anderson@example.com',
        },
        image: 'https://media.w3wiki.net/wp-content/uploads/20240122185312/images3.jpg',
    },
 
];


Step 3: To start the server run the following command.

nodemon server.js

Step 4: Open a new terminal in project root directory and run the following command to create react app.

 npx create-react-app client
cd client

Step 5: Install Axios

npm install axios

Project dependencies:

  "dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.3",
"moment": "^2.30.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}

Example: Create the required files and add the below code.

Javascript




// src/App.js
 
import React, { useState, useEffect } from "react";
import VehicleList from "./components/VehicleList";
import AddVehicle from "./components/AddVehicle";
import axios from "axios";
import "./App.css";
 
const App = () => {
    const [vehicles, setVehicles] = useState([]);
    const [showForm, setShowForm] = useState(false);
 
    useEffect(() => {
        axios
            .get("http://localhost:3001/api/cars")
            .then((response) => setVehicles(response.data))
            .catch((error) => console.error(error));
    }, []);
 
    const handleAddVehicle = (newVehicle) => {
        console.log("new vehicle", newVehicle);
 
        setVehicles((prevVehicles) => [...prevVehicles, newVehicle]);
    };
 
    const handleContactOwner = (email) => {
        alert(`Contacting the owner of the vehicle at ${email}`);
    };
 
    const handleDeleteVehicle = (vehicleId) => {
        console.log(`Deleting ${vehicleId}`);
        axios
            .delete(`http://localhost:3001/api/cars/${vehicleId}`)
            .then((response) => {
                // Filter out the deleted vehicle from the state
                setVehicles((prevVehicles) =>
                    prevVehicles.filter((vehicle) => vehicle._id !== vehicleId)
                );
            })
            .catch((error) => console.error(error));
    };
 
    const handleUpdateVehicle = async (updatedVehicle) => {
        try {
            const response = await axios.put(
                `
http://localhost:3001/api/cars/${updatedVehicle._id}`,
                updatedVehicle
            );
 
            // Handle the response
            console.log("Vehicle updated successfully:", response.data);
 
            // Update the vehicles array with the updated vehicle
            setVehicles((prevVehicles) =>
                prevVehicles.map((vehicle) =>
                    vehicle._id === updatedVehicle._id ? response.data : vehicle
                )
            );
        } catch (error) {
            // Handle errors, e.g., show an error message to the user
            console.error("Error updating vehicle:", error);
        }
    };
 
    return (
        <div className="main-container">
            <h1 className="gfg">GFG</h1>
            <h1>Vehicle Tracking System</h1>
            <button onClick={() => setShowForm(!showForm)}>
                {showForm ? "Close" : "Add New Vehicle"}
            </button>
            <div className="">
                {showForm && <AddVehicle onAddVehicle={handleAddVehicle} />}
                {vehicles && (
                    <VehicleList
                        onDeleteVehicle={handleDeleteVehicle}
                        onUpdateVehicle={handleUpdateVehicle}
                        vehicles={vehicles}
                        onContactOwner={handleContactOwner}
                    />
                )}
            </div>
        </div>
    );
};
 
export default App;


Javascript




//src/components/AddVehicle.js
 
import axios from "axios";
import React, { useState } from "react";
 
const AddVehicle = ({ onAddVehicle }) => {
    const [newVehicle, setNewVehicle] = useState({
        companyName: "",
        distanceCovered: "",
        mileage: "",
        serviceDates: "",
        owner: {
            name: "",
            email: "",
        },
        image: "",
    });
 
    const handleAddVehicle = () => {
        // Submit a new vehicle
        axios
            .post("http://localhost:3001/api/cars", newVehicle)
            .then((response) => {
                // Notify the parent component about the new vehicle
                onAddVehicle(response.data);
 
                // Clear the newVehicle state for the next entry
                setNewVehicle({
                    companyName: "",
                    distanceCovered: "",
                    mileage: "",
                    serviceDates: [],
                    owner: {
                        name: "",
                        email: "",
                    },
                    image: "", // Reset image URL field
                });
            })
            .catch((error) => console.error(error));
    };
 
    return (
        <div className="form-container">
            <h2 style={{ color: "#007BFF", textAlign: "center" }}>
                Add a New Vehicle
            </h2>
            <form
                onSubmit={(e) => {
                    e.preventDefault();
                    handleAddVehicle();
                }}
            >
                <div className="form-row">
                    <label>
                        Company Name:
                        <input
                            type="text"
                            value={newVehicle.companyName}
                            onChange={(e) =>
                                setNewVehicle({
                                    ...newVehicle,
                                    companyName: e.target.value,
                                })
                            }
                            required
                            className="form-input"
                        />
                    </label>
                    <label>
                        Distance Covered:
                        <input
                            type="number"
                            value={newVehicle.distanceCovered}
                            onChange={(e) =>
                                setNewVehicle({
                                    ...newVehicle,
                                    distanceCovered: e.target.value,
                                })
                            }
                            required
                            className="form-input"
                        />
                    </label>
                </div>
                <div className="form-row">
                    <label>
                        Mileage:
                        <input
                            type="number"
                            value={newVehicle.mileage}
                            onChange={(e) =>
                                setNewVehicle({
                                    ...newVehicle,
                                    mileage: e.target.value,
                                })
                            }
                            required
                            className="form-input"
                        />
                    </label>
                    <label>
                        Service Dates (comma-separated):
                        <input
                            type="text"
                            value={newVehicle.serviceDates}
                            onChange={(e) =>
                                setNewVehicle({
                                    ...newVehicle,
                                    serviceDates: e.target.value,
                                })
                            }
                            required
                            className="form-input"
                        />
                    </label>
                </div>
                <div className="form-row">
                    <label>
                        Owner Name:
                        <input
                            type="text"
                            value={newVehicle.owner.name}
                            onChange={(e) =>
                                setNewVehicle({
                                    ...newVehicle,
                                    owner: {
                                        ...newVehicle.owner,
                                        name: e.target.value,
                                    },
                                })
                            }
                            required
                            className="form-input"
                        />
                    </label>
                    <label>
                        Owner Email:
                        <input
                            type="email"
                            value={newVehicle.owner.email}
                            onChange={(e) =>
                                setNewVehicle({
                                    ...newVehicle,
                                    owner: {
                                        ...newVehicle.owner,
                                        email: e.target.value,
                                    },
                                })
                            }
                            required
                            className="form-input"
                        />
                    </label>
                </div>
                <div className="form-row">
                    <label>
                        Image URL:
                        <input
                            type="text"
                            value={newVehicle.image}
                            onChange={(e) =>
                                setNewVehicle({
                                    ...newVehicle,
                                    image: e.target.value,
                                })
                            }
                            className="form-input"
                        />
                    </label>
                </div>
                <button type="submit" className="form-button">
                    Add Vehicle
                </button>
            </form>
        </div>
    );
};
 
export default AddVehicle;


Javascript




// src/components/VehicleList.js
 
import React, { useState, useMemo } from "react";
import VehicleCard from "./VehicleCard";
 
const VehicleList = ({
    vehicles,
    onContactOwner,
    onDeleteVehicle,
    onUpdateVehicle,
}) => {
    const [companyFilter, setCompanyFilter] = useState("");
    const [sortBy, setSortBy] = useState("distanceCovered"); // Default sorting by distanceCovered
 
    const filteredAndSortedVehicles = useMemo(() => {
        return vehicles
            .filter((vehicle) =>
                vehicle.companyName
                    .toLowerCase()
                    .includes(companyFilter.toLowerCase())
            )
            .sort((a, b) => (a[sortBy] > b[sortBy] ? 1 : -1));
    }, [vehicles, companyFilter, sortBy]);
 
    return (
        <div className="list" style={{ marginTop: "20px" }}>
            <h2 style={{ color: "#007BFF" }}>Vehicle List</h2>
 
            {/* Filter and Sort Controls */}
            <div style={{ marginBottom: "10px" }}>
                <label>
                    Filter by Company Name:
                    <input
                        type="text"
                        value={companyFilter}
                        onChange={(e) => setCompanyFilter(e.target.value)}
                    />
                </label>
                <label style={{ marginLeft: "10px" }}>
                    Sort by:
                    <select
                        value={sortBy}
                        onChange={(e) => setSortBy(e.target.value)}
                    >
                        <option value="distanceCovered">
                            Distance Covered
                        </option>
                        <option value="mileage">Mileage</option>
                    </select>
                </label>
            </div>
 
            <div className="list-container">
                {filteredAndSortedVehicles.map((vehicle) => (
                    <VehicleCard
                        key={vehicle._id}
                        vehicle={vehicle}
                        onContactOwner={onContactOwner}
                        onDeleteVehicle={onDeleteVehicle}
                        onUpdateVehicle={onUpdateVehicle}
                    />
                ))}
            </div>
        </div>
    );
};
 
export default VehicleList;


Javascript




// src/components/VehicleCard.js
 
import React, { useState } from "react";
import moment from "moment";
 
const VehicleCard = ({
    vehicle,
    onContactOwner,
    onDeleteVehicle,
    onUpdateVehicle,
}) => {
    const [isEditing, setIsEditing] = useState(false);
    const [updatedVehicle, setUpdatedVehicle] = useState(vehicle);
 
    const handleUpdateClick = () => {
        setIsEditing(true);
    };
 
    const handleCancelClick = () => {
        setIsEditing(false);
        setUpdatedVehicle(vehicle); // Reset to original values
    };
 
    const handleSaveClick = () => {
        // Implement the logic to save the updated details
 
        onUpdateVehicle(updatedVehicle);
        setIsEditing(false);
    };
 
    const handleInputChange = (fieldName, value) => {
        const [field, subField] = fieldName.split(".");
 
        setUpdatedVehicle((prevVehicle) => ({
            ...prevVehicle,
            [field]: subField
                ? { ...prevVehicle[field], [subField]: value }
                : value,
        }));
    };
 
    return (
        <div className="vehicle-card">
            {isEditing ? (
                // Render editable fields for updating details
                <div>
                    <label>
                        Company Name:
                        <input
                            type="text"
                            value={updatedVehicle.companyName}
                            onChange={(e) =>
                                handleInputChange("companyName", e.target.value)
                            }
                            required
                        />
                    </label>
                    <label>
                        Distance Covered:
                        <input
                            type="number"
                            value={updatedVehicle.distanceCovered}
                            onChange={(e) =>
                                handleInputChange(
                                    "distanceCovered",
                                    e.target.value
                                )
                            }
                            required
                        />
                    </label>
                    <label>
                        Mileage:
                        <input
                            type="number"
                            value={updatedVehicle.mileage}
                            onChange={(e) =>
                                handleInputChange("mileage", e.target.value)
                            }
                            required
                        />
                    </label>
                    <label>
                        Owner Name:
                        <input
                            type="text"
                            value={updatedVehicle.owner.name}
                            onChange={(e) =>
                                handleInputChange("owner.name", e.target.value)
                            }
                            required
                        />
                    </label>
                    <label>
                        Owner Email:
                        <input
                            type="email"
                            value={updatedVehicle.owner.email}
                            onChange={(e) =>
                                handleInputChange("owner.email", e.target.value)
                            }
                            required
                        />
                    </label>
                    {/* Add input fields for other properties like
                        owner name, owner email, and image */}
                    <button onClick={handleSaveClick}>Save</button>
                    <button onClick={handleCancelClick}>Cancel</button>
                </div>
            ) : (
                // Display details
                <div>
                    <h3 style={{ fontWeight: "bold" }}>
                        {vehicle.companyName}
                    </h3>
                    <p>
                        <span style={{ fontWeight: "bold" }}>
                            Distance Covered:
                        </span>{" "}
                        {vehicle.distanceCovered}
                    </p>
                    <p>
                        <span style={{ fontWeight: "bold" }}>Mileage:
                         </span>{" "}
                        {vehicle.mileage}
                    </p>
 
                    {vehicle.owner && (
                        <div>
                            <p style={{ fontWeight: "bold" }}>Owner:</p>
                            <ul style={{ listStyle: "none", padding: 0 }}>
                                <li>{vehicle.owner.name}</li>
                                <li>{vehicle.owner.email}</li>
                            </ul>
                        </div>
                    )}
 
                    {vehicle.image && (
                        <div className="image-container">
                            <img
                                src={vehicle.image}
                                alt={vehicle.companyName}
                                className="vehicle-image"
                            />
                        </div>
                    )}
                    <p>Service Dates:</p>
                    <ul>
                        {vehicle.serviceDates.map((item, i) => (
                            <li key={i}>
                                {moment(item).format("MMMM D, YYYY")}
                            </li>
                        ))}
                    </ul>
 
                    <div className="button-container">
                        <button
                            onClick={() =>
                                onContactOwner(
                                    vehicle.owner ? vehicle.owner.email : ""
                                )
                            }
                        >
                            Contact Owner
                        </button>
                        <button onClick={() => onDeleteVehicle(vehicle._id)}>
                            Delete Vehicle
                        </button>
                        <button onClick={handleUpdateClick}>Update</button>
                    </div>
                </div>
            )}
        </div>
    );
};
 
export default VehicleCard;


CSS




/* src/App.css */
 
.vehicle-list {
    display: flex;
    flex-wrap: wrap;
    gap: 20px;
}
 
.vehicle-card {
    border: 1px solid #ddd;
    border-radius: 18px;
    padding: 15px;
    width: -moz-fit-content;
    width: fit-content;
    box-shadow: 0 14px 18px rgba(0, 0, 0, 0.1);
    background-color: #fff;
}
 
.vehicle-card h3 {
    margin-bottom: 10px;
}
 
.vehicle-card button {
    background-color: #007BFF;
    color: #fff;
    border: none;
    padding: 8px;
    cursor: pointer;
    border-radius: 4px;
}
 
.vehicle-card button:hover {
    background-color: #0056b3;
}
 
img {
    height: 200px;
    width: 300px;
    border-radius: 10px;
    margin-bottom: 10px;
}
 
.list-container {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 15px;
    overflow-x: hidden;
}
 
h1,
h2 {
    text-align: center;
}
 
.vehicle-card {
    border: 1px solid #ddd;
    border-radius: 10px;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
    padding: 20px;
    margin: 20px;
    width: 300px;
}
 
.vehicle-card h2 {
    margin-bottom: 15px;
    color: #333;
}
 
.vehicle-card label {
    display: block;
    margin-bottom: 10px;
    color: #333;
}
 
.vehicle-card input {
    width: 100%;
    padding: 8px;
    margin-bottom: 10px;
    box-sizing: border-box;
}
 
.vehicle-card button {
    background-color: #007BFF;
    color: #fff;
    border: none;
    padding: 10px;
    cursor: pointer;
    border-radius: 4px;
}
 
.vehicle-card button:hover {
    background-color: #0056b3;
}
 
.form-container {
    max-width: 300px;
    margin: 20px auto;
    padding: 20px;
    background-color: #f7f7f7;
    border-radius: 8px;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
 
.form-row {
    display: flex;
    gap: 20px;
    margin-bottom: 15px;
}
 
.form-row label {
    flex: 1;
}
 
.form-row input {
    flex: 2;
    padding: 8px;
    width: 100%;
    box-sizing: border-box;
}
 
button {
    background-color: #007BFF;
    color: #fff;
    border: none;
    padding: 10px;
    cursor: pointer;
    border-radius: 4px;
}
 
button:hover {
    background-color: #0056b3;
}
 
form {
    padding: 10px;
}
 
.gfg {
    background-color: green;
    text-align: center;
    color: white;
    padding: 15px;
    border-radius: 10px;
    margin-bottom: -20px;
}
 
.list {
    margin-top: -50px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}
 
 
.vehicle-image {
    width: 100%;
    border-radius: 10px;
    transition: transform 0.3s ease-in-out;
}
 
.vehicle-image:hover {
    transform: scale(1.1);
}
 
.button-container {
    display: flex;
    justify-content: space-evenly;
    margin-top: 15px;
}
 
.main-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}


Step 6: To start the frontend run the following command.

npm start

Output:

final output