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.
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:
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 h 3 { 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 ; } h 1 , h 2 { 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 h 2 { 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.3 s 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: