Advanced Web Development: React, Node.js, and Modern Deployment Strategies
Advanced web development combines modern frameworks, backend technologies, and deployment automation to build scalable, production-ready applications. This comprehensive guide covers React, Node.js, API design, build optimization, and professional deployment strategies used in enterprise environments.
📑 Table of Contents
- 1. Modern React Development
- React Hooks and State Management
- Context API and Global State
- React Router and Navigation
- 2. Node.js and Express Backend
- RESTful API with Express
- Database Integration (MongoDB)
- Authentication with JWT
- 3. Build Tools and Webpack
- Advanced Webpack Configuration
- Package.json Scripts
- 4. Testing Strategies
- React Component Testing
- API Testing with Supertest
- 5. Deployment and CI/CD
- Docker Containerization
- GitHub Actions CI/CD
- Environment Configuration
- 6. Performance Optimization
- Code Splitting and Lazy Loading
- Caching Strategies
- Conclusion
- Additional Resources
1. Modern React Development
React Hooks and State Management
import React, { useState, useEffect, useCallback, useMemo } from 'react';
// Custom hook for data fetching
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
const fetchData = async () => {
try {
const response = await fetch(url);
if (!response.ok) throw new Error('Network response failed');
const json = await response.json();
if (isMounted) {
setData(json);
setError(null);
}
} catch (err) {
if (isMounted) {
setError(err.message);
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
fetchData();
return () => {
isMounted = false;
};
}, [url]);
return { data, loading, error };
}
// Component using custom hook
const UserDashboard = () => {
const { data: users, loading, error } = useFetch('/api/users');
const [filter, setFilter] = useState('');
// Memoize filtered users
const filteredUsers = useMemo(() => {
if (!users) return [];
return users.filter(user =>
user.name.toLowerCase().includes(filter.toLowerCase())
);
}, [users, filter]);
// Callback optimization
const handleFilterChange = useCallback((e) => {
setFilter(e.target.value);
}, []);
if (loading) return ;
if (error) return ;
return (
);
};
Context API and Global State
// contexts/AuthContext.js
import React, { createContext, useContext, useState, useEffect } from 'react';
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Check for existing session
const checkAuth = async () => {
try {
const response = await fetch('/api/auth/me');
if (response.ok) {
const userData = await response.json();
setUser(userData);
}
} catch (error) {
console.error('Auth check failed:', error);
} finally {
setLoading(false);
}
};
checkAuth();
}, []);
const login = async (credentials) => {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
});
if (response.ok) {
const userData = await response.json();
setUser(userData);
return { success: true };
}
return { success: false, error: 'Login failed' };
};
const logout = async () => {
await fetch('/api/auth/logout', { method: 'POST' });
setUser(null);
};
return (
{children}
);
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
};
// Usage in components
const ProfilePage = () => {
const { user, logout } = useAuth();
return (
Welcome, {user.name}
);
};
React Router and Navigation
import { BrowserRouter, Routes, Route, Navigate, useNavigate } from 'react-router-dom';
// Protected route component
const ProtectedRoute = ({ children }) => {
const { user, loading } = useAuth();
if (loading) return ;
if (!user) return ;
return children;
};
// Main App routing
function App() {
return (
} />
} />
} />
} />
} />
} />
} />
);
}
// Navigation example
const UsersList = () => {
const navigate = useNavigate();
const handleUserClick = (userId) => {
navigate(`/users/${userId}`);
};
return (
{users.map(user => (
handleUserClick(user.id)}
/>
))}
);
};
2. Node.js and Express Backend
RESTful API with Express
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const morgan = require('morgan');
const app = express();
// Security middleware
app.use(helmet());
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
credentials: true
}));
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use('/api/', limiter);
// Body parsing and logging
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(morgan('combined'));
// Error handling middleware
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;
}
}
// Async handler wrapper
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// User routes
const userRouter = express.Router();
userRouter.get('/', asyncHandler(async (req, res) => {
const { page = 1, limit = 10, sort = 'createdAt' } = req.query;
const users = await User.find()
.sort({ [sort]: -1 })
.limit(limit * 1)
.skip((page - 1) * limit);
const total = await User.countDocuments();
res.json({
users,
totalPages: Math.ceil(total / limit),
currentPage: page
});
}));
userRouter.get('/:id', asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) {
throw new AppError('User not found', 404);
}
res.json(user);
}));
userRouter.post('/', asyncHandler(async (req, res) => {
const { email, name, password } = req.body;
// Validation
if (!email || !name || !password) {
throw new AppError('Missing required fields', 400);
}
const existingUser = await User.findOne({ email });
if (existingUser) {
throw new AppError('Email already exists', 409);
}
const user = await User.create({ email, name, password });
res.status(201).json(user);
}));
userRouter.put('/:id', asyncHandler(async (req, res) => {
const user = await User.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
);
if (!user) {
throw new AppError('User not found', 404);
}
res.json(user);
}));
userRouter.delete('/:id', asyncHandler(async (req, res) => {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) {
throw new AppError('User not found', 404);
}
res.status(204).send();
}));
app.use('/api/users', userRouter);
// Global error handler
app.use((err, req, res, next) => {
const { statusCode = 500, message } = err;
res.status(statusCode).json({
status: 'error',
statusCode,
message: process.env.NODE_ENV === 'development' ? message : 'Internal server error'
});
});
// 404 handler
app.use((req, res) => {
res.status(404).json({ error: 'Route not found' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Database Integration (MongoDB)
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
// User Schema
const userSchema = new mongoose.Schema({
email: {
type: String,
required: [true, 'Email is required'],
unique: true,
lowercase: true,
validate: {
validator: (v) => /^[w-.]+@([w-]+.)+[w-]{2,4}$/.test(v),
message: 'Invalid email format'
}
},
name: {
type: String,
required: [true, 'Name is required'],
trim: true,
minlength: 2,
maxlength: 50
},
password: {
type: String,
required: [true, 'Password is required'],
minlength: 6,
select: false // Don't include in queries by default
},
role: {
type: String,
enum: ['user', 'admin'],
default: 'user'
},
isActive: {
type: Boolean,
default: true
},
lastLogin: Date
}, {
timestamps: true
});
// Pre-save hook to hash password
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 12);
next();
});
// Instance method to verify password
userSchema.methods.comparePassword = async function(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
};
// Static method to find active users
userSchema.statics.findActive = function() {
return this.find({ isActive: true });
};
const User = mongoose.model('User', userSchema);
// Database connection
mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => console.log('MongoDB connected'))
.catch(err => console.error('MongoDB connection error:', err));
module.exports = User;
Authentication with JWT
const jwt = require('jsonwebtoken');
// Generate JWT token
const generateToken = (userId) => {
return jwt.sign(
{ userId },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
};
// Authentication middleware
const authMiddleware = asyncHandler(async (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new AppError('No token provided', 401);
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findById(decoded.userId);
if (!user || !user.isActive) {
throw new AppError('Invalid token', 401);
}
req.user = user;
next();
} catch (error) {
throw new AppError('Invalid token', 401);
}
});
// Login route
app.post('/api/auth/login', asyncHandler(async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email }).select('+password');
if (!user || !(await user.comparePassword(password))) {
throw new AppError('Invalid credentials', 401);
}
user.lastLogin = new Date();
await user.save();
const token = generateToken(user._id);
res.json({
user: {
id: user._id,
email: user.email,
name: user.name,
role: user.role
},
token
});
}));
// Protected route example
app.get('/api/profile', authMiddleware, (req, res) => {
res.json(req.user);
});
3. Build Tools and Webpack
Advanced Webpack Configuration
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';
return {
entry: {
main: './src/index.js',
vendor: './src/vendor.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: isProduction ? '[name].[contenthash].js' : '[name].js',
publicPath: '/'
},
module: {
rules: [
{
test: /.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react'
],
plugins: [
'@babel/plugin-transform-runtime'
]
}
}
},
{
test: /.css$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'postcss-loader'
]
},
{
test: /.(png|jpg|gif|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 8kb
}
}
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
minify: isProduction ? {
removeComments: true,
collapseWhitespace: true
} : false
}),
isProduction && new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
})
].filter(Boolean),
optimization: {
minimize: isProduction,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true
}
}
})
],
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\/]node_modules[\/]/,
name: 'vendors',
priority: 10
},
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
}
},
devServer: {
static: {
directory: path.join(__dirname, 'public')
},
historyApiFallback: true,
hot: true,
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true
}
}
},
resolve: {
extensions: ['.js', '.jsx'],
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils')
}
}
};
};
Package.json Scripts
{
"name": "modern-web-app",
"version": "1.0.0",
"scripts": {
"start": "webpack serve --mode development",
"build": "webpack --mode production",
"build:analyze": "webpack --mode production --analyze",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"lint": "eslint src --ext .js,.jsx",
"lint:fix": "eslint src --ext .js,.jsx --fix",
"format": "prettier --write "src/**/*.{js,jsx,css}""
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.10.0"
},
"devDependencies": {
"@babel/core": "^7.21.0",
"@babel/preset-env": "^7.21.0",
"@babel/preset-react": "^7.18.6",
"babel-loader": "^9.1.2",
"webpack": "^5.76.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.13.1",
"html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^2.7.5",
"css-loader": "^6.7.3",
"style-loader": "^3.3.2",
"eslint": "^8.36.0",
"prettier": "^2.8.4",
"jest": "^29.5.0"
}
}
4. Testing Strategies
React Component Testing
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import UserDashboard from './UserDashboard';
// Mock fetch
global.fetch = jest.fn();
describe('UserDashboard', () => {
beforeEach(() => {
fetch.mockClear();
});
test('displays loading state initially', () => {
fetch.mockImplementationOnce(() =>
new Promise(() => {}) // Never resolves
);
render( );
expect(screen.getByText(/loading/i)).toBeInTheDocument();
});
test('fetches and displays users', async () => {
const mockUsers = [
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' }
];
fetch.mockImplementationOnce(() =>
Promise.resolve({
ok: true,
json: async () => mockUsers
})
);
render( );
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('Jane Smith')).toBeInTheDocument();
});
});
test('filters users by search term', async () => {
const mockUsers = [
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' }
];
fetch.mockImplementationOnce(() =>
Promise.resolve({
ok: true,
json: async () => mockUsers
})
);
render( );
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
});
const searchInput = screen.getByPlaceholderText(/search/i);
await userEvent.type(searchInput, 'Jane');
expect(screen.queryByText('John Doe')).not.toBeInTheDocument();
expect(screen.getByText('Jane Smith')).toBeInTheDocument();
});
});
API Testing with Supertest
const request = require('supertest');
const app = require('./app');
const User = require('./models/User');
describe('User API', () => {
beforeEach(async () => {
await User.deleteMany({});
});
describe('POST /api/users', () => {
test('creates a new user', async () => {
const userData = {
name: 'John Doe',
email: 'john@example.com',
password: 'password123'
};
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(201);
expect(response.body).toHaveProperty('id');
expect(response.body.name).toBe(userData.name);
expect(response.body.email).toBe(userData.email);
expect(response.body).not.toHaveProperty('password');
});
test('returns 400 for invalid email', async () => {
const response = await request(app)
.post('/api/users')
.send({
name: 'John Doe',
email: 'invalid-email',
password: 'password123'
})
.expect(400);
expect(response.body).toHaveProperty('error');
});
});
describe('GET /api/users/:id', () => {
test('returns user by ID', async () => {
const user = await User.create({
name: 'John Doe',
email: 'john@example.com',
password: 'password123'
});
const response = await request(app)
.get(`/api/users/${user._id}`)
.expect(200);
expect(response.body.name).toBe(user.name);
});
test('returns 404 for non-existent user', async () => {
const fakeId = '507f1f77bcf86cd799439011';
await request(app)
.get(`/api/users/${fakeId}`)
.expect(404);
});
});
});
5. Deployment and CI/CD
Docker Containerization
# Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production image
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
EXPOSE 3000
USER node
CMD ["node", "server.js"]
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- MONGODB_URI=mongodb://mongo:27017/myapp
- JWT_SECRET=${JWT_SECRET}
depends_on:
- mongo
restart: unless-stopped
mongo:
image: mongo:6
volumes:
- mongo-data:/data/db
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- app
restart: unless-stopped
volumes:
mongo-data:
GitHub Actions CI/CD
# .github/workflows/deploy.yml
name: Build and Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test -- --coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage/coverage-final.json
build:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: dist
path: dist/
deploy:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
steps:
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: dist
path: dist/
- name: Deploy to server
uses: appleboy/scp-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: "dist/*"
target: "/var/www/myapp"
- name: Restart application
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/myapp
pm2 restart myapp
Environment Configuration
# .env.example
NODE_ENV=development
PORT=3000
MONGODB_URI=mongodb://localhost:27017/myapp
JWT_SECRET=your-secret-key-here
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001
# AWS (if using S3, etc.)
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_REGION=us-east-1
# Email (if using SendGrid, etc.)
SENDGRID_API_KEY=
// config/index.js
require('dotenv').config();
module.exports = {
port: process.env.PORT || 3000,
env: process.env.NODE_ENV || 'development',
mongodb: {
uri: process.env.MONGODB_URI,
options: {
useNewUrlParser: true,
useUnifiedTopology: true
}
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: '7d'
},
cors: {
origins: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000']
}
};
6. Performance Optimization
Code Splitting and Lazy Loading
import React, { lazy, Suspense } from 'react';
// Lazy load components
const Dashboard = lazy(() => import('./pages/Dashboard'));
const UserProfile = lazy(() => import('./pages/UserProfile'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
}>
} />
} />
} />
);
}
// Preload on hover
const PreloadLink = ({ to, children }) => {
const handleMouseEnter = () => {
const componentMap = {
'/dashboard': () => import('./pages/Dashboard'),
'/profile': () => import('./pages/UserProfile')
};
if (componentMap[to]) {
componentMap[to]();
}
};
return (
{children}
);
};
Caching Strategies
// Service Worker for PWA
// sw.js
const CACHE_NAME = 'myapp-v1';
const urlsToCache = [
'/',
'/static/css/main.css',
'/static/js/main.js'
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.addAll(urlsToCache))
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => response || fetch(event.request))
);
});
// Redis caching on backend
const redis = require('redis');
const client = redis.createClient();
const cacheMiddleware = (duration) => async (req, res, next) => {
const key = `cache:${req.originalUrl}`;
const cached = await client.get(key);
if (cached) {
return res.json(JSON.parse(cached));
}
res.sendResponse = res.json;
res.json = (body) => {
client.setex(key, duration, JSON.stringify(body));
res.sendResponse(body);
};
next();
};
// Usage
app.get('/api/users', cacheMiddleware(300), async (req, res) => {
const users = await User.find();
res.json(users);
});
Conclusion
Advanced web development requires mastery of modern frameworks, backend technologies, build tools, and deployment automation. Key takeaways:
- React: Use hooks, context, and code splitting for maintainable frontends
- Node.js/Express: Build scalable RESTful APIs with proper error handling and authentication
- Build Tools: Optimize with Webpack, implement code splitting and tree shaking
- Testing: Write comprehensive unit and integration tests
- Deployment: Automate with CI/CD pipelines and containerization
- Performance: Implement caching, lazy loading, and optimization strategies
Additional Resources
Was this article helpful?
R
About Ramesh Sundararamaiah
Red Hat Certified Architect
Expert in Linux system administration, DevOps automation, and cloud infrastructure. Specializing in Red Hat Enterprise Linux, CentOS, Ubuntu, Docker, Ansible, and enterprise IT solutions.