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.
//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}`);
}); |
// 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.
// 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
.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;
|
//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
.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;
|
// 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;
|
// 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;
|
/* 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: