A full-stack MERN application for managing contacts, built as part of the Erino SDE Internship Assignment.
- Create, read, update, and delete contacts
- Sortable and paginated contact list
- Responsive Material-UI design
- Real-time form validation
- Search functionality
- Error handling and notifications
- React.js
- Material-UI (MUI)
- Axios for API calls
- React Hooks for state management
- Node.js
- Express.js
- MongoDB with Mongoose
- Environment variables for configuration
- MongoDB Atlas
MongoDB was chosen for this project for several key reasons:
- Flexible Schema: The document-based model allows for easy schema modifications as requirements evolve.
- JSON Compatibility: Natural integration with JavaScript/Node.js stack.
- Scalability: Horizontal scaling capabilities for growing contact lists.
- Query Performance: Built-in indexing for optimized search operations.
- Rich Querying: Powerful query capabilities for filtering and sorting contacts.
// MongoDB Schema (using Mongoose)
const mongoose = require('mongoose');
const contactSchema = new mongoose.Schema({
firstName: {
type: String,
required: [true, 'First name is required']
},
lastName: {
type: String,
required: [true, 'Last name is required']
},
email: {
type: String,
required: [true, 'Email is required'],
unique: true,
validate: {
validator: function(v) {
return /\S+@\S+\.\S+/.test(v);
},
message: props => `${props.value} is not a valid email!`
}
},
phoneNumber: {
type: String,
required: [true, 'Phone number is required'],
validate: {
validator: function(v) {
return /\d{10,}/.test(v);
},
message: props => `${props.value} is not a valid phone number!`
}
},
company: {
type: String,
required: false
},
jobTitle: {
type: String,
required: false
}
}, {
timestamps: true // Adds createdAt and updatedAt fields automatically
});
// Compound index for better search performance
contactSchema.index({ firstName: 1, lastName: 1 });
// Index for email searches and ensuring uniqueness
contactSchema.index({ email: 1 }, { unique: true });
// Pre-save middleware for data cleaning
contactSchema.pre('save', function(next) {
// Trim whitespace from string fields
this.firstName = this.firstName.trim();
this.lastName = this.lastName.trim();
this.email = this.email.trim().toLowerCase();
if (this.company) this.company = this.company.trim();
if (this.jobTitle) this.jobTitle = this.jobTitle.trim();
next();
});
module.exports = mongoose.model('Contact', contactSchema);
├── client/
│ ├── src/
│ │ ├── components/
│ │ │ ├── common/
│ │ │ ├── layout/
│ │ │ ├── ContactForm.jsx
│ │ │ └── ContactsTable.jsx
│ │ ├── api.js
│ │ ├── App.js
│ │ └── theme.js
├── server/
│ ├── models/
│ │ └── Contact.js
│ ├── index.js
│ └── .env
- Clone the repository
git clone [repository-url]
- Backend Setup
cd server npm install
- Create a .env file:
MONGODB_URI=your_mongodb_connection_string PORT=3001
- Frontend Setup
cd client npm install
- Run the Application
Start backend (from server directory)npm start
Start frontend (from client directory)
npm start
Run the test suite: npm test
🛠️ Technical Decisions & Implementation
Backend Implementation
Validation: Mongoose schemas handle data validation
Error Handling: Comprehensive error handling for duplicate entries and validation failures
API Structure: RESTful API design with clear endpoints for each operation
Frontend Implementation
Component Structure: Modular components for maintainability
State Management: React hooks for efficient state management
UI/UX: Material-UI components for consistent design
Form Handling: Real-time validation and error feedback
-
Challenge: Handling real-time form validation
Solution: Implemented custom validation hooks with Material-UI's error handling
-
Challenge: Managing complex state for table sorting and pagination
Solution: Created dedicated state management for table operations
-
Challenge: Ensuring consistent error handling across the stack
Solution: Implemented a standardized error handling system with appropriate HTTP status codes