Open In App

Car Vault app using MERN

Last Updated : 01 Feb, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

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.

Screenshot-2567-01-13-at-150320

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:

Screenshot-2567-01-13-at-152635

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)
    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',
        },
    },
    {
        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',
        },
    },
 
    {
        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',
        },
    },
    {
        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',
        },
    },
    {
        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',
        },
    },
 
    {
        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',
        },
    },
    {
        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',
        },
    },
    {
        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',
        },
    },
 
    {
        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',
        },
    },
 
];


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:

Untitled-design-(27)

final output



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads