Car Vault app using MERN

Last Updated : 23 Jul, 2025

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)
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.geeksforgeeks.org/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.geeksforgeeks.org/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.geeksforgeeks.org/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.geeksforgeeks.org/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.geeksforgeeks.org/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.geeksforgeeks.org/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: 
'https://media.geeksforgeeks.org/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.geeksforgeeks.org/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.geeksforgeeks.org/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.

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;
}
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/$%7BvehicleId%7D%60)
			.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/$%7BupdatedVehicle._id%7D%60,
				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;


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

npm start

Output:

Comment

Explore