Here's a complete production-ready Node.js Express server for a gym website: ```javascript // server.js const express = require('express'); const cors = require('cors'); const helmet = require('helmet'); const rateLimit = require('express-rate-limit'); const morgan = require('morgan'); const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const { Pool } = require('pg'); const path = require('path'); require('dotenv').config(); const app = express(); const PORT = process.env.PORT || 3000; // Database connection const pool = new Pool({ user: process.env.DB_USER || 'postgres', host: process.env.DB_HOST || 'localhost', database: process.env.DB_NAME || 'gym_db', password: process.env.DB_PASSWORD || 'password', port: process.env.DB_PORT || 5432, max: 20, idleTimeoutMillis: 30000, connectionTimeoutMillis: 2000, }); // Test database connection pool.on('connect', () => { console.log('✅ Connected to PostgreSQL database'); }); pool.on('error', (err) => { console.error('❌ Database connection error:', err); }); // Security middleware app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"], scriptSrc: ["'self'"], imgSrc: ["'self'", "data:", "https:"], connectSrc: ["'self'"], fontSrc: ["'self'", "https://fonts.gstatic.com"], }, }, })); // CORS configuration const corsOptions = { origin: process.env.NODE_ENV === 'production' ? [process.env.FRONTEND_URL || 'https://yourgym.com'] : ['http://localhost:3000', 'http://localhost:3001'], methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], allowedHeaders: ['Content-Type', 'Authorization'], credentials: true, optionsSuccessStatus: 200 }; app.use(cors(corsOptions)); // Rate limiting const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per windowMs message: { error: 'Too many requests from this IP, please try again later.', }, standardHeaders: true, legacyHeaders: false, }); const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5, // Limit auth attempts message: { error: 'Too many authentication attempts, please try again later.', }, }); app.use('/api/', limiter); app.use('/api/auth/', authLimiter); // Logging app.use(morgan(process.env.NODE_ENV === 'production' ? 'combined' : 'dev')); // Body parser app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true, limit: '10mb' })); // Static files app.use(express.static(path.join(__dirname, 'public'))); // JWT verification middleware const verifyToken = async (req, res, next) => { try { const token = req.headers.authorization?.split(' ')[1]; if (!token) { return res.status(401).json({ error: 'Access token required' }); } const decoded = jwt.verify(token, process.env.JWT_SECRET || 'gym_secret_key'); // Get user from database const result = await pool.query( 'SELECT id, email, first_name, last_name, role FROM users WHERE id = $1', [decoded.userId] ); if (result.rows.length === 0) { return res.status(401).json({ error: 'Invalid token' }); } req.user = result.rows[0]; next(); } catch (error) { console.error('Token verification error:', error); res.status(401).json({ error: 'Invalid token' }); } }; // Health check endpoint app.get('/health', async (req, res) => { try { await pool.query('SELECT 1'); res.status(200).json({ status: 'OK', timestamp: new Date().toISOString(), uptime: process.uptime(), database: 'Connected', environment: process.env.NODE_ENV || 'development' }); } catch (error) { res.status(503).json({ status: 'ERROR', timestamp: new Date().toISOString(), database: 'Disconnected', error: error.message }); } }); // Authentication routes app.post('/api/auth/register', async (req, res) => { try { const { email, password, firstName, lastName, phone } = req.body; // Validation if (!email || !password || !firstName || !lastName) { return res.status(400).json({ error: 'All fields are required' }); } // Check if user exists const existingUser = await pool.query( 'SELECT id FROM users WHERE email = $1', [email] ); if (existingUser.rows.length > 0) { return res.status(409).json({ error: 'User already exists' }); } // Hash password const saltRounds = 12; const hashedPassword = await bcrypt.hash(password, saltRounds); // Create user const result = await pool.query( `INSERT INTO users (email, password, first_name, last_name, phone, role, created_at) VALUES ($1, $2, $3, $4, $5, $6, NOW()) RETURNING id, email, first_name, last_name, role`, [email, hashedPassword, firstName, lastName, phone, 'member'] ); const user = result.rows[0]; // Generate JWT const token = jwt.sign( { userId: user.id, email: user.email }, process.env.JWT_SECRET || 'gym_secret_key', { expiresIn: '7d' } ); res.status(201).json({ message: 'User registered successfully', user: { id: user.id, email: user.email, firstName: user.first_name, lastName: user.last_name, role: user.role }, token }); } catch (error) { console.error('Registration error:', error); res.status(500).json({ error: 'Internal server error' }); } }); app.post('/api/auth/login', async (req, res) => { try { const { email, password } = req.body; if (!email || !password) { return res.status(400).json({ error: 'Email and password are required' }); } // Get user const result = await pool.query( 'SELECT id, email, password, first_name, last_name, role FROM users WHERE email = $1', [email] ); if (result.rows.length === 0) { return res.status(401).json({ error: 'Invalid credentials' }); } const user = result.rows[0]; // Verify password const isValidPassword = await bcrypt.compare(password, user.password); if (!isValidPassword) { return res.status(401).json({ error: 'Invalid credentials' }); } // Generate JWT const token = jwt.sign( { userId: user.id, email: user.email }, process.env.JWT_SECRET || 'gym_secret_key', { expiresIn: '7d' } ); res.json({ message: 'Login successful', user: { id: user.id, email: user.email, firstName: user.first_name, lastName: user.last_name, role: user.role }, token }); } catch (error) { console.error('Login error:', error); res.status(500).json({ error: 'Internal server error' }); } }); // Booking routes app.get('/api/booking/classes', async (req, res) => { try { const result = await pool.query(` SELECT c.id, c.name, c.description, c.duration, c.capacity, c.price, cs.id as schedule_id, cs.class_date, cs.start_time, cs.end_time, cs.available_spots, t.first_name as trainer_first_name, t.last_name as trainer_last_name, t.specialization, t.image_url as trainer_image FROM classes c JOIN class_schedules cs ON c.id = cs.class_id JOIN trainers t ON cs.trainer_id = t.id WHERE cs.class_date >= CURRENT_DATE ORDER BY cs.class_date, cs.start_time `); const classes = result.rows.map(row => ({ id: row.id, name: row.name, description: row.description, duration: row.duration, capacity: row.capacity, price: parseFloat(row.price), scheduleId: row.schedule_id, date: row.class_date, startTime: row.start_time, endTime: row.end_time, availableSpots: row.available_spots, trainer: { name: `${row.trainer_first_name} ${row.trainer_last_name}`, specialization: row.specialization, image: row.trainer_image } })); res.json({ classes }); } catch (error) { console.error('Get classes error:', error); res.status(500).json({ error: 'Internal server error' }); } }); app.post('/api/booking/book', verifyToken, async (req, res) => { const client = await pool.connect(); try { await client.query('BEGIN'); const { scheduleId } = req.body; const userId = req.user.id; // Check if user already booked this class const existingBooking = await client.query( 'SELECT id FROM bookings WHERE user_id = $1 AND schedule_id = $2', [userId, scheduleId] ); if (existingBooking.rows.length > 0) { await client.query('ROLLBACK'); return res.status(409).json({ error: 'You have already booked this class' }); } // Check available spots const scheduleResult = await client.query( 'SELECT available_spots FROM class_schedules WHERE id = $1', [scheduleId] ); if (scheduleResult.rows.length === 0) { await client.query('ROLLBACK'); return res.status(404).json({ error: 'Class not found' }); } if (scheduleResult.rows[0].available_spots <= 0) { await client.query('ROLLBACK'); return res.status(400).json({ error: 'No available spots' }); } // Create booking