MERN stack development and deployment using Vercel

Aaron Philip
8 min readNov 18, 2023

--

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

  1. MongoDB
  2. Node, Express and Postman
  3. React
  4. Git and Github
  5. Vercel Deployment

To understand this let us create a simple Attendance Tracking System

  1. MongoDB

Structure of Our Database

MongoDB Structure

Here we have our database called “subjects” and two collections which are -

  1. subjects

Here we will store what subjects are there on what day of the week, we will make only GET requests to this collection

example

2. attendances

Here we will make POST requests to update how many classes have been attended per subject`

example

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-

  1. Node Server in the server folder
  2. 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

  1. 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!

--

--