Project Structure of Address Book
The updated dependencies in package.json will look like:
Frontend :
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.5.1",
"http": "^0.0.1-security",
"react": "^18.2.0",
"react-bootstrap-icons": "^1.10.3",
"react-dom": "^18.2.0",
"react-router-dom": "^6.16.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}
Backend :
"dependencies": {
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.18.2",
"mongoose": "^7.6.0",
"nodemon": "^3.0.1"
}
Example: Write the following code in respective file
Frontend Code :
- App.js: This file implements routing and imports all components
- AddAddress.js: This component creates a forma to save new addresses
- AddressList.j: This component displays Address in a list format
- Navigation.js: This component implements navigation and handle update and delete function.
Javascript
//App.js import React from 'react' ; import { BrowserRouter as Router, Route, Routes } from 'react-router-dom' ; import AddressList from './components/AddressList' ; import AddAddress from './components/AddAddress' ; function App() { return ( <Router> <div className= 'container container-fluid min-vh-100 d-flex flex-column' > <Routes> <Route exact path= "/" element={<AddressList/>} component={AddressList} /> <Route exact path= "/add" element={<AddAddress/>} component={AddAddress} /> </Routes> </div> </Router> ); } export default App; |
Javascript
//AddAddress.js import React, { useState } from 'react' ; import axios from 'axios' ; import { useNavigate } from 'react-router-dom' ; import Navigation from './Navigation' ; const AddAddress = () => { const nav = useNavigate(); //state for saving form data const [formData, setFormData] = useState({ name: '' , email: '' , contact: '' , address: '' }); //handle for updating form const handleChange = (e) => { setFormData({ ...formData, [e.target.name]: e.target.value }); }; //handle for creating new address const handleSubmit = (e) => { e.preventDefault(); axios.post( 'http://localhost:5000/api/addresses' , formData).then(() => { nav( '/' ); }); }; return ( <div className= 'container container-fluid min-vh-100 justify-content-center' > <h2 className= 'display-2 text-center' >Add Address</h2> < Navigation /> <form onSubmit={handleSubmit}> <div className= 'form-group' > <label>Name:</label> <input type= "text" name= "name" onChange={handleChange} required className= 'form-control' /> </div> <div className= 'form-group' > <label>Email:</label> <input type= "email" name= "email" onChange={handleChange} required className= 'form-control' /> </div> <div className= 'form-group' > <label>Contact:</label> <input type= "text" name= "contact" onChange={handleChange} required className= 'form-control' /> </div> <div className= 'form-group' > <label>Address:</label> <input type= "text" name= "address" onChange={handleChange} required className= 'form-control' /> </div> <div> <button type= "submit" className= 'btn btn-primary p-2 m-2' >Add Address</button> </div> </form> </div> ); }; export default AddAddress; |
Javascript
//AddressList.js import React, { useState, useEffect } from 'react' ; import axios from 'axios' ; import Navigation from './Navigation' ; import { PencilSquare, Trash } from 'react-bootstrap-icons' ; const AddressList = () => { const [addresses, setAddresses] = useState([]); const [selectedAddress, setSelectedAddress] = useState( null ); //handle for fetching addresses on page load useEffect(() => { axios.get( 'http://localhost:5000/api/addresses' ).then((response) => { setAddresses(response.data); }); }, []); //handle for deleting address const handleDelete = (addressId) => { axios . delete (`http: //localhost:5000/api/addresses/${addressId}`) .then(() => { axios.get( 'http://localhost:5000/api/addresses' ).then((response) => { setAddresses(response.data); }); }) . catch ((error) => { console.error( 'Error deleting address: ' , error); }); }; //handle for setting address to be deleted const handleEdit = (address) => { setSelectedAddress(address); }; //handle for updating address const handleUpdate = (updatedAddress) => { axios .put(`http: //localhost:5000/api/addresses/${updatedAddress._id}`, updatedAddress) .then(() => { axios.get( 'http://localhost:5000/api/addresses' ).then((response) => { setAddresses(response.data); setSelectedAddress( null ); }); }) . catch ((error) => { console.error( 'Error updating address: ' , error); }); }; return ( <div className= 'container container-fluid min-vh-100 justify-content-center' > <h2 className= 'display-2 text-center' >Address Book</h2> < Navigation /> {selectedAddress && ( <div> <h2>Edit Address</h2> <form onSubmit={() => handleUpdate(selectedAddress)}> <div className= "form-group" > <label>Name:</label> <input type= "text" className= "form-control" name= "name" value={selectedAddress.name} onChange={(e) => setSelectedAddress({ ...selectedAddress, name: e.target.value, }) } required /> </div> <div className= "form-group" > <label>Email:</label> <input type= "email" className= "form-control" name= "email" value={selectedAddress.email} onChange={(e) => setSelectedAddress({ ...selectedAddress, email: e.target.value, }) } required /> </div> <div className= "form-group" > <label>Phone:</label> <input type= "text" className= "form-control" name= "phone" value={selectedAddress.contact} onChange={(e) => setSelectedAddress({ ...selectedAddress, contact: e.target.value, }) } required /> </div> <div className= "form-group" > <label>Address:</label> <input type= "text" className= "form-control" name= "name" value={selectedAddress.address} onChange={(e) => setSelectedAddress({ ...selectedAddress, address: e.target.value, }) } required /> </div> <div> <button type= "submit" className= "btn btn-primary m-2" > Update Address </button> </div> </form> </div> )} <ul> {addresses.map((address) => ( <div className= 'container border border-dark rounded m-2 p-2 text-right' key={address._id}> <h5>Name : {address.name}</h5> <h5>Email : {address.email}</h5> <h5>Contact : {address.contact}</h5> <h5>Address : {address.address}</h5> <button className= "btn btn-sm" onClick={() => handleDelete(address._id)} > <h5>< Trash /></h5> </button> <button type= "button" className= "btn" onClick={() => handleEdit(address)} > <h5>< PencilSquare /></h5> </button> </div> ))} </ul> </div> ); }; export default AddressList; |
Javascript
//Navigation.js import React from 'react' ; import { useNavigate } from 'react-router-dom' ; import { PlusCircleFill , JournalText} from 'react-bootstrap-icons' ; const Navigation = () => { const nav = useNavigate(); //route for home page const gotoHome = ()=>{ nav( '/' ); } //route for add address page const gotoAdd = ()=>{ nav( '/add' ); } return ( <nav className= 'container d-flex flex-row m-3' > <h2 className= 'p-2 m-1' onClick={gotoHome}><JournalText /></h2> <h2 className= 'p-2 m-1' onClick={gotoAdd}><PlusCircleFill /></h2> </nav> ); }; export default Navigation; |
Backend Code :
- app.js: This file creates a connection between backend and frontend.
- address.js: This file creates a schema for saving data in database.
Javascript
//app.js const express = require( 'express' ); const mongoose = require( 'mongoose' ); const bodyParser = require( 'body-parser' ); const cors = require( 'cors' ); const app = express(); //bodyparser used for parsing request body app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); app.use(cors()); mongoose.connect( 'mongodb://127.0.0.1:27017/addressbook' ); const Address = require( './models/address' ); //Get route for fetching addresses from database app.get( '/api/addresses' , async (req, res) => { try { const addresses = await Address.find(); res.json(addresses); } catch (err) { res.status(500).json({ error: err.message }); } }); //Post route for storing new address app.post( '/api/addresses' , async (req, res) => { try { const newAddress = new Address(req.body); const savedAddress = await newAddress.save(); res.status(201).json(savedAddress); } catch (err) { res.status(400).json({ error: err.message }); } }); //Put route for updating address with new data app.put( '/api/addresses/:id' , async (req, res) => { try { const updatedAddress = await Address.findByIdAndUpdate( req.params.id, req.body, { new : true } ); res.json(updatedAddress); } catch (err) { res.status(400).json({ error: err.message }); } }); //Delete route for deleting address with specified id app. delete ( '/api/addresses/:id' , async (req, res) => { try { await Address.findByIdAndRemove(req.params.id); res.json({ message: 'Address deleted' }); } catch (err) { res.status(500).json({ error: err.message }); } }); const PORT = 5000; app.listen(PORT, () => { console.log(`Server is started on port ${PORT}`); }); |
Javascript
//address.js const mongoose = require( 'mongoose' ); //address schema for storing address const addressSchema = new mongoose.Schema({ name: String, email: String, contact: String, address : String }); module.exports = mongoose.model( 'Address' , addressSchema , 'addresses' ); |
Steps to run the application :
Steps for running frontend code :
Step 1: Open terminal inside frontend folder and run below command .
npm start
Step 2: The browser will automatically open tab with home page.
Steps for running backend code :
Step 1: For running application first lets add start script inside package.json of backend. add line for nodemon start script.
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon app.js"
}
Step 2: Open another terminal inside backend folder and run below command.
npm start
Output :
The saved data in database will look like:
Address Book using MERN
In this article, we will build a Address Book using the MERN stack. The application will enable users to add, remove, edit and view addresses. The project will use the mongoDB database for storage and React for UI part. This classic address book helps you to save your contacts easily and data can be retrieved easily.
Preview of Final Output: Let us have a look at how our final project will look like: