MERN stack development and deployment using Vercel
MERN — Mongo, Express, React and Node is one of the most popular tech stacks for full stack development these days
Most of us get MERN apps working pretty effortlessly on our local machines, but when it comes to deployment, it often becomes a challenging endeavor due to the intricacies of configuring server environments, handling database connections, and ensuring seamless integration between the various components of the stack.
To overcome these deployment challenges, I’ll guide you through a systematic approach to streamline the entire process.
I’ll divide the article into 5 major parts
- MongoDB
- Node, Express and Postman
- React
- Git and Github
- Vercel Deployment
To understand this let us create a simple Attendance Tracking System
- MongoDB
Structure of Our Database
Here we have our database called “subjects” and two collections which are -
- subjects
Here we will store what subjects are there on what day of the week, we will make only GET requests to this collection
2. attendances
Here we will make POST requests to update how many classes have been attended per subject`
For the subjects collection, I simply uploaded a json document
[
{
"day_name": "Monday",
"subjects": ["Math", "History", "Physics"]
},
{
"day_name": "Tuesday",
"subjects": ["English", "Chemistry", "Biology"]
},
{
"day_name": "Wednesday",
"subjects": ["Computer Science", "Geography", "Art"]
},
{
"day_name": "Thursday",
"subjects": ["Music", "Physical Education", "Literature"]
},
{
"day_name": "Friday",
"subjects": ["Spanish", "Economics", "Psychology"]
},
{
"day_name": "Saturday",
"subjects": ["Social Studies", "French", "Health"]
}
]
Now that we have setup our MongoDB, let us move forward to setting up our development environment
All MERN apps have a client server architecture so we will set it up this way-
- Node Server in the server folder
- React App in the client folder
Run the command below in the server folder to set up the server
npm init -y
Run the command below in the client folder to set up the React App
npx create-react-app .
2. Node, Express and Postman
First we will make a basic Express App and connect it to MongoDB
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const Attendance = require('./attendanceSchema'); // Adjust the path as per your project structure
const app = express();
const PORT = process.env.PORT || 5000;
const atlasConnectionUri = 'MONGO-URI';
const dbName="subjects";
mongoose.connect(atlasConnectionUri, {
dbName: 'subjects'
});
mongoose.connection.on('connected', () => {
console.log('Connected to MongoDB Atlas');
});
app.use(cors(
{
origin: ["deployed-vercel-frontend-app","localhost:3000"],
methods: ["POST", "GET"],
credentials: true
}
));
app.get("/", (req, res) => {
res.json("Hello");
})
you can add the “deployed-vercel-frontend-app” link after we deploy the React App on Vercel, you can just connect localhost:3000 for now
We wrote our first “GET” request, so the “/” will return a “Hello”
Now we’ll write all the POST and GET requests below
// Example route to fetch all "subjects" from the "subjects" collection
mongoose.connection.on('connected', () => {
app.get('/subjects', async (req, res) => {
try {
const db = mongoose.connection.db;
const subjectsCollection = db.collection('subjects');
const subjects = await subjectsCollection.find().toArray();
res.json(subjects);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal Server Error' });
}
});
});
app.get('/getattendance', async (req, res) => {
try {
const db = mongoose.connection.db;
const subjectsCollection = db.collection('attendances');
const attendanceStud = await subjectsCollection.find().toArray();
console.log(attendanceStud)
res.json(attendanceStud);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal Server Error' });
}
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
// Example route to update attendance for a subject
app.post('/update-attendance-yes/:subject_name', async (req, res) => {
try {
const subjectName = req.params.subject_name;
const attended = req.params.attendance;
// Find the attendance record for the subject
let attendanceRecord = await Attendance.findOne({ subject_name: subjectName });
// If the record doesn't exist, create a new one
if (!attendanceRecord) {
attendanceRecord = new Attendance({
subject_name: subjectName,
attendance: { attended: 0, total: 0 },
});
}
// Ensure "attendance" field is defined
if (!attendanceRecord.attendance) {
attendanceRecord.attendance = { attended: 0, total: 0 };
}
// Update attendance based on the "attended" parameter
if (attended === 'yes') {
attendanceRecord.attendance.attended += 1;
attendanceRecord.attendance.total += 1;
}
// Always increment the total
attendanceRecord.attendance.total += 1;
attendanceRecord.attendance.attended += 1;
// Save the updated attendance record
await attendanceRecord.save();
res.json({ success: true, message: 'Attendance updated successfully' });
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal Server Error' });
}
});
app.post('/update-attendance-no/:subject_name', async (req, res) => {
try {
const subjectName = req.params.subject_name;
const attended = req.params.attendance;
// Find the attendance record for the subject
let attendanceRecord = await Attendance.findOne({ subject_name: subjectName });
// If the record doesn't exist, create a new one
if (!attendanceRecord) {
attendanceRecord = new Attendance({
subject_name: subjectName,
attendance: { attended: 0, total: 0 },
});
}
// Ensure "attendance" field is defined
if (!attendanceRecord.attendance) {
attendanceRecord.attendance = { attended: 0, total: 0 };
}
// Update attendance based on the "attended" parameter
if (attended === 'yes') {
attendanceRecord.attendance.attended += 1;
attendanceRecord.attendance.total += 1;
}
// Always increment the total
attendanceRecord.attendance.total += 1;
// Save the updated attendance record
await attendanceRecord.save();
res.json({ success: true, message: 'Attendance updated successfully' });
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal Server Error' });
}
});
Above we have written the GET request to fetch all subjects and also the POST requests to update attendance
Schema for the POST request
// attendanceSchema.js
const mongoose = require('mongoose');
const attendanceSchema = new mongoose.Schema({
subject_name: {
type: String,
required: true,
},
attendance: {
attended: {
type: Number,
default: 0,
},
total: {
type: Number,
default: 0,
},
},
});
const AttendanceModel = mongoose.model('Attendance', attendanceSchema);
module.exports = AttendanceModel;
After this npm start
the server side and use postman to test the post and get requests
Now we have finished writing our server side logic, we’ll move on to the client side React App
3. React
App.js
import './App.css';
import ScheduleDropdown from './components/ScheduleDropdown';
import AttendanceList from './components/AttendanceList';
import React, { useEffect, useState } from 'react';
import { format } from 'date-fns';
function App() {
const [attendanceData, setAttendanceData] = useState([]);
const [currentDay, setCurrentDay] = useState('');
useEffect(() => {
// Fetch data from the endpoint
fetch('deployed-vercel-app/getattendance')// just keep /getattendance if not deployed it will route to localhost:5000
.then((response) => response.json())
.then((data) => setAttendanceData(data))
.catch((error) => console.error('Error fetching attendance data:', error));
// Set the current day
setCurrentDay(format(new Date(), 'EEEE, do MMMM'));
}, []);
return (
<div className="App">
<h2 className='font-bold text-4xl'>Attendance for {currentDay}</h2>
<ScheduleDropdown/>
<AttendanceList attendanceData={attendanceData} />
</div>
);
}
export default App;
AttendanceList.js
import React from 'react';
const AttendanceList = ({ attendanceData }) => {
return (
<div className="container mx-auto px-4 sm:px-0">
<h2 className="font-bold text-2xl mb-4">Attendance List</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-1 sm:gap-4 font-bold">
<div>Subject</div>
<div>Percentage</div>
</div>
{attendanceData.map((subject) => {
const { _id, subject_name, attendance } = subject;
const { attended, total } = attendance;
const attendancePercentage = ((attended / total) * 100).toFixed(2);
return (
<div key={_id} className="grid grid-cols-1 sm:grid-cols-2 gap-1 sm:gap-4">
<div>{subject_name}</div>
<div>{attendancePercentage}%</div>
</div>
);
})}
</div>
);
};
export default AttendanceList;
ScheduleDropdown.js
// Import the Tailwind CSS classes
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const ScheduleDropdown = () => {
const [scheduleData, setScheduleData] = useState([]);
const [selectedDay, setSelectedDay] = useState('');
const [selectedSubjects, setSelectedSubjects] = useState([]);
const [attendanceStatus, setAttendanceStatus] = useState({});
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get('vercel-deployed-server/subjects');
setScheduleData(response.data);
} catch (error) {
console.error('Error fetching schedule data:', error);
}
};
fetchData();
}, []);
const handleDayChange = (event) => {
const selectedDayName = event.target.value;
const selectedDayObject = scheduleData.find((day) => day.day_name === selectedDayName);
setSelectedDay(selectedDayName);
setSelectedSubjects(selectedDayObject ? selectedDayObject.subjects : []);
setAttendanceStatus({});
};
const handleAttendance = async (subject, status) => {
try {
let endpoint;
if (status === 'yes') {
endpoint = `vercel-deployed-server/update-attendance-yes/${subject}`;
} else if (status === 'no') {
endpoint = `vercel-deployed-server/update-attendance-no/${subject}`;
}
// Make API call to update attendance
const response = await axios.post(endpoint, {
subject_name: subject,
});
console.log(response.data); // Log the response if needed
// Toggle the attendance status for the subject
setAttendanceStatus((prevStatus) => ({
...prevStatus,
[subject]: prevStatus[subject] === status ? '' : status,
}));
} catch (error) {
console.error('Error updating attendance:', error);
}
};
const handleSubmitAttendance = () => {
console.log('Attendance Status:', attendanceStatus);
setAttendanceStatus({});
};
return (
<div className="mx-auto bg-purple-400 mt-10 p-6 rounded-lg shadow-md">
<label htmlFor="dayDropdown" className="block text-sm font-medium text-gray-600">
Select a day:
</label>
<select
id="dayDropdown"
onChange={handleDayChange}
value={selectedDay}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
>
<option value="" disabled>Select a day</option>
{scheduleData.map((day) => (
<option key={day._id} value={day.day_name}>
{day.day_name}
</option>
))}
</select>
{selectedDay && (
<div className="mt-4">
<h2 className="text-lg font-medium text-gray-900">Subjects for {selectedDay}:</h2>
<ul className="mt-2">
{selectedSubjects.map((subject) => (
<li key={subject}>
{subject}
<button
onClick={() => handleAttendance(subject, 'yes')}
style={{ backgroundColor: attendanceStatus[subject] === 'yes' ? 'green' : 'white' }}
className="ml-2 p-2 rounded"
>
Yes
</button>
<button
onClick={() => handleAttendance(subject, 'no')}
style={{ backgroundColor: attendanceStatus[subject] === 'no' ? 'red' : 'white' }}
className="ml-2 p-2 rounded "
>
No
</button>
</li>
))}
</ul>
</div>
)}
<button
onClick={handleSubmitAttendance}
className="mt-4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
>
Submit Attendance
</button>
</div>
);
};
export default ScheduleDropdown;
4. Git and Github
Upload the server side and the client side apps to different repositories so that deployment becomes seamless
5. Vercel Deployment
Now we’ll cover the most important part about this article, deployment of this full stack website on Vercel
We need to add a first do a few necessary configurations
- Whitelist all IPs on Mongo
Click on Add IP ADDRESS and add 0.0.0.0/0 so that your cluster can be accessed from anywhere
2. Add vercel.json to your server folder
{
"version":2,
"builds": [
{ "src": "*.js", "use": "@vercel/node" }
],
"routes": [
{
"src": "/(.*)",
"dest": "/"
}
]
}
3. Go to Vercel.com deploy them by connecting them to the repositories
click on Add New…
and deploy the server side
then do the same process for the client side
Note : While deploying the server side, remember that vercel searches for index.js so name the node file only as index.js
Thank you for taking the time to read this article. I hope the step-by-step guide on deploying a MERN stack application using Vercel has been helpful to you. If you have any questions or need further clarification, please feel free to leave a comment. Happy coding!