In this lab, you will set up a complete MERN (MongoDB, Express, React, Node.js) development environment and demonstrate your understanding of web architecture fundamentals. By the end of this lab, you should have multiple tools running and understand the core concepts that underpin modern web development.
MERN is a client–server architecture that uses layered architecture on the backend, middleware pipelines for cross-cutting concerns, MVC-style separation for request handling, and component-based architecture on the frontend.
- Set up and verify a complete MERN stack development environment
- Understand client-server architecture and HTTP fundamentals
- Explore containerization with Docker
- Practice version control with Git
- Demonstrate understanding of web scaling concepts
- A computer running macOS, Windows, or Linux
- Administrator/sudo access on your machine
- A GitHub account
- Approximately 10GB of free disk space
Install Node.js (LTS version recommended) from nodejs.org or using a version manager like nvm.
Verification:
node --version
npm --versionTask: Record your installed versions in answers.yml.
Install Git from git-scm.com or via your package manager.
Verification:
git --version
git config --global user.name
git config --global user.emailTask: Ensure your Git is configured with your name and email.
Install Docker Desktop from docker.com.
Verification:
docker --version
docker compose versionTask: Run the Docker hello-world container:
docker run hello-worldWe'll use MongoDB via Docker for consistency across environments.
Task: Pull and run MongoDB:
docker pull mongo:latest
docker run -d -p 27017:27017 --name mongodb mongo:latestVerification:
docker psInstall VS Code from code.visualstudio.com or use your preferred editor.
Recommended Extensions:
- ESLint
- Prettier
- MongoDB for VS Code
- Docker
- GitLens
Create a new directory called simple-server and initialize a Node.js project:
mkdir simple-server
cd simple-server
npm init -y
npm install expressCreate server.js:
const express = require('express');
const app = express();
const PORT = 3000;
app.get('/', (req, res) => {
res.json({
message: 'Hello from Express!',
timestamp: new Date().toISOString(),
studentName: 'YOUR_NAME_HERE' // Replace with your name
});
});
app.get('/health', (req, res) => {
res.json({ status: 'healthy' });
});
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});Task:
- Replace
YOUR_NAME_HEREwith your actual name - Run the server:
node server.js - Visit
http://localhost:3000in your browser - Record observations in
answers.yml
In a new terminal, create a React app:
npx create-react-app simple-client
cd simple-clientConfigure React to use port 3001: Create a .env file in the simple-client directory to set the port (since the Express server uses port 3000):
# Create .env file
echo "PORT=3001" > .envOr manually create a file named .env with this content:
PORT=3001
Now start the React app:
npm startTask:
- Observe the default React application at
http://localhost:3001 - Note which ports are being used (React on 3001, Express on 3000)
Modify your React app to fetch data from the Express server. Update src/App.js:
import React, { useState, useEffect } from 'react';
import './App.css';
function App() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('http://localhost:3000')
.then(response => response.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(error => {
console.error('Error:', error);
setLoading(false);
});
}, []);
return (
<div className="App">
<header className="App-header">
<h1>MERN Stack Lab 1</h1>
{loading ? (
<p>Loading...</p>
) : data ? (
<div>
<p>Message: {data.message}</p>
<p>From: {data.studentName}</p>
<p>Timestamp: {data.timestamp}</p>
</div>
) : (
<p>Error connecting to server</p>
)}
</header>
</div>
);
}
export default App;Note: You'll encounter a CORS error. This is intentional!
Task:
- Observe the CORS error in your browser console
- Research CORS and explain it in
answers.yml - Fix the error by adding CORS support to your Express server:
cd simple-server
npm install corsUpdate server.js to include:
const cors = require('cors');
app.use(cors());Now let's add the "M" in MERN by connecting our Express server to MongoDB.
Step 1: First, explore MongoDB directly using the MongoDB shell:
# Connect to your running MongoDB container
docker exec -it mongodb mongosh
# Once connected, try these commands:
show dbs
use lab1
db.createCollection("visitors")
db.visitors.insertOne({ name: "Your Name", visitedAt: new Date() })
db.visitors.find()
exitStep 2: Install Mongoose (MongoDB ODM for Node.js) and dotenv (for environment variables):
cd simple-server
npm install mongoose dotenvStep 3: Create a .env file in the simple-server directory for local development:
# Create .env file with environment variables
cat > .env << 'EOF'
PORT=3000
MONGO_URI=mongodb://localhost:27017/lab1
EOFOr manually create a file named .env with this content:
PORT=3000
MONGO_URI=mongodb://localhost:27017/lab1
Note: The
.envfile should never be committed to version control as it may contain sensitive information. It's already included in the.gitignorefile.
Step 4: Update your server.js to connect to MongoDB and add visitor tracking:
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(cors());
app.use(express.json());
// MongoDB Connection
const MONGO_URI = process.env.MONGO_URI || 'mongodb://localhost:27017/lab1';
mongoose.connect(MONGO_URI)
.then(() => console.log('Connected to MongoDB'))
.catch(err => console.error('MongoDB connection error:', err));
// Define a simple schema and model
const visitorSchema = new mongoose.Schema({
name: { type: String, required: true },
message: String,
visitedAt: { type: Date, default: Date.now }
});
const Visitor = mongoose.model('Visitor', visitorSchema);
// Routes
app.get('/', (req, res) => {
res.json({
message: 'Hello from Express!',
timestamp: new Date().toISOString(),
studentName: 'YOUR_NAME_HERE'
});
});
app.get('/health', (req, res) => {
res.json({ status: 'healthy' });
});
// GET all visitors
app.get('/api/visitors', async (req, res) => {
try {
const visitors = await Visitor.find().sort({ visitedAt: -1 });
res.json(visitors);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// POST a new visitor
app.post('/api/visitors', async (req, res) => {
try {
const visitor = new Visitor({
name: req.body.name,
message: req.body.message
});
await visitor.save();
res.status(201).json(visitor);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// GET visitor count
app.get('/api/visitors/count', async (req, res) => {
try {
const count = await Visitor.countDocuments();
res.json({ count });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});Step 5: Test your MongoDB-connected API:
# Restart your server
node server.js
# In another terminal, test the API:
# Add a visitor
curl -X POST http://localhost:3000/api/visitors \
-H "Content-Type: application/json" \
-d '{"name": "Your Name", "message": "Hello from Lab 1!"}'
# Get all visitors
curl http://localhost:3000/api/visitors
# Get visitor count
curl http://localhost:3000/api/visitors/countTask:
- Successfully connect your Express server to MongoDB
- Add at least 2 visitors using the POST endpoint
- Verify you can retrieve them with the GET endpoint
- Record the visitor count in
answers.yml
Create a Dockerfile in your simple-server directory:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]Create a .dockerignore file:
node_modules
npm-debug.log
Task: Build and run your container:
docker build -t simple-server .
docker run -p 3000:3000 simple-serverCreate a Dockerfile in your simple-client directory. This uses a multi-stage build to first build the React app, then serve the static files:
# Build stage
FROM node:18-alpine as build
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy source code
COPY . .
# Build the React app
RUN npm run build
# Production stage - serve with Node.js
FROM node:18-alpine
WORKDIR /app
# Install serve (lightweight static file server)
RUN npm install -g serve
# Copy built assets from build stage
COPY --from=build /app/build ./build
# Expose port
EXPOSE 3001
# Serve the static files
CMD ["serve", "-s", "build", "-l", "3001"]Create a .dockerignore file in simple-client:
node_modules
npm-debug.log
build
Task: Build and test your React container:
cd simple-client
docker build -t simple-client .
docker run -p 3001:3001 simple-clientCreate a docker-compose.yml in the lab root directory that orchestrates all services:
services:
# Express.js Backend Server
server:
build:
context: ./simple-server
dockerfile: Dockerfile
container_name: lab1-server
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- PORT=3000
- MONGO_URI=mongodb://mongo:27017/lab1
depends_on:
mongo:
condition: service_healthy
networks:
- lab1-network
restart: unless-stopped
# React Frontend
client:
build:
context: ./simple-client
dockerfile: Dockerfile
container_name: lab1-client
ports:
- "3001:3001"
depends_on:
- server
networks:
- lab1-network
restart: unless-stopped
# MongoDB Database
mongo:
image: mongo:7-jammy
container_name: lab1-mongo
ports:
- "27018:27017" # Using 27018 externally to avoid conflicts
volumes:
- mongo-data:/data/db
- mongo-config:/data/configdb
networks:
- lab1-network
restart: unless-stopped
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
# Mongo Express - Web-based MongoDB admin interface
mongo-express:
image: mongo-express:latest
container_name: lab1-mongo-express
ports:
- "8081:8081"
environment:
- ME_CONFIG_MONGODB_SERVER=mongo
- ME_CONFIG_BASICAUTH_USERNAME=admin
- ME_CONFIG_BASICAUTH_PASSWORD=admin123
depends_on:
mongo:
condition: service_healthy
networks:
- lab1-network
restart: unless-stopped
networks:
lab1-network:
driver: bridge
volumes:
mongo-data:
name: lab1-mongo-data
mongo-config:
name: lab1-mongo-configTask:
- Stop any existing MongoDB containers:
docker stop mongodb && docker rm mongodb - Run
docker compose up --build - Verify all four services are running:
docker ps - Access your services:
- React frontend: http://localhost:3001
- Express API: http://localhost:3000
- Mongo Express (DB admin): http://localhost:8081 (login: admin/admin123)
- Document the benefits of Docker Compose in
answers.yml
Mongo Express provides a web-based interface to browse and manage your MongoDB database.
Task:
- Open http://localhost:8081 in your browser
- Log in with username
adminand passwordadmin123 - Navigate to the
lab1database and explore thevisitorscollection - Try adding a new visitor document directly through the interface
Answer the following questions in your answers.yml file. These questions test your understanding of web architecture and scaling concepts.
-
HTTP Fundamentals: What is the difference between GET and POST requests? When would you use each?
-
Client-Server Model: Explain the client-server architecture. What role does each component play in a web application?
-
Ports: Why do we need different ports for the frontend (React) and backend (Express)? What would happen if they tried to use the same port?
-
CORS: Explain what CORS is and why it exists. Why did you encounter a CORS error in Part 2.3?
-
Containers vs VMs: What is the difference between Docker containers and traditional virtual machines? Why might containers be preferred for web development?
-
Scaling Concepts:
- What is the difference between horizontal and vertical scaling?
- How might Docker help with horizontal scaling?
-
State Management: Why is managing state (like database connections) more complex in a distributed system compared to a single-server application?
-
RESTful APIs: What makes an API "RESTful"? List at least 3 characteristics.
-
Complete
answers.yml: Fill in all version numbers and answer all conceptual questions -
Screenshot Evidence: Take screenshots showing:
- All running Docker containers (
docker ps) - Your Express server response in the browser
- Your React app successfully displaying data from the backend
- All running Docker containers (
-
Code: Ensure your directories contain:
simple-server/:server.js(with your name)package.jsonDockerfile.env(for local development - not committed to git)
simple-client/:src/App.js(modified to fetch from backend)package.jsonDockerfile.env(with PORT=3001 - not committed to git)
Root directory:
docker-compose.yml
-
Push to GitHub: Commit and push all changes to your repository
| Component | Points |
|---|---|
| Part 1: Tool Installation & Versions | 30 |
| Part 2: MERN Application | 30 |
| Part 3: Docker Containerization | 20 |
| Part 4: Conceptual Questions | 20 |
| Total | 100 |
Port already in use:
# Find what's using the port
lsof -i :3000
# Kill the process
kill -9 <PID>Docker not running: Ensure Docker Desktop is started and running.
MongoDB connection issues:
# Check if MongoDB container is running
docker ps
# View MongoDB logs
docker logs mongodbCORS errors:
If you see an error like Access to fetch at 'http://localhost:3000' from origin 'http://localhost:3001' has been blocked by CORS policy:
- Ensure you've installed the
corspackage:npm install cors - Add it to your
server.js:const cors = require('cors'); app.use(cors());
- Make sure
app.use(cors())is placed before your route definitions - Restart your Express server after making changes
The error occurs because browsers block requests from one origin (localhost:3001) to another (localhost:3000) for security reasons. The cors middleware adds headers that tell the browser these cross-origin requests are allowed.
React not starting on port 3001:
Make sure you created the .env file in the simple-client directory with PORT=3001. Restart the React development server after creating the file.
Environment variables not loading:
Ensure you've installed the dotenv package and added require('dotenv').config(); at the very top of your server.js file (before any other requires that use environment variables).
- Node.js Documentation
- Express.js Guide
- React Documentation
- MongoDB Manual
- Docker Documentation
- MDN Web Docs - HTTP
In Lab 2, we will:
- Expand our API with more complex CRUD operations
- Explore database design, schemas, and indexing
- Add authentication and authorization
- Deploy our application to a cloud platform