Skip to content

Express: Course Page

Build a course enrollment system with a /courses route that supports both GET and POST requests.

  • The GET /courses route should return a list of available courses, but only if the student is logged in.

  • The POST /courses route should allow the logged-in student to enroll in a specific course.

  • Upon successful enrollment, create a new cookie named lastEnrolledCourse that stores the course name and is valid for 2 minutes.

  • Implement robust error handling for the following scenarios:

    • Attempting to enroll in a course without being logged in.

    • Trying to enroll in a course the student has already taken.

app.js

js
import express from 'express';
import session from 'express-session';
import cookieParser from 'cookie-parser';

// --- App Initialization ---
const app = express();
const PORT = 3001;

// --- Middleware Setup ---
app.use(express.json());

app.use(cookieParser());

app.use(session({
    secret: 'a-secure-secret-key',
    resave: false,
    saveUninitialized: false, // Set to false to avoid empty session objects
    cookie: {
        maxAge: 2 * 60 * 1000, // 2 minutes
        secure: false // In production, set to true for HTTPS
    }
}));

// --- In-Memory Data (for demonstration) ---
const courses = [
    { id: 1, name: 'CS Basics' },
    { id: 2, name: 'JavaScript' },
    { id: 3, name: 'Data Structures' }
];

const users = [
    { id: 1, username: 'student1', 
	    password: 'pass1', enrolled: [] },
    { id: 2, username: 'student2', 
	    password: 'pass2', enrolled: [] }
];

// --- API Routes ---

// Login a user and create a session
app.post('/login', (req, res) => {
    const { username, password } = req.body;
    const user = users.find(u => u.username === username && u.password === password);

    if (!user) {
        return res.status(401).json({ error: 'Invalid credentials' });
    }

    req.session.userId = user.id;
    res.json({ message: 'Login successful' });
});

// Logout a user and destroy the session
app.post('/logout', (req, res) => {
    req.session.destroy(err => {
        if (err) {
            return res.status(500).json({ error: 'Could not log out.' });
        }
        // The session cookie is cleared automatically
        res.json({ message: 'Logged out successfully' });
    });
});

// --- Custom Authentication Middleware ---
const auth = (req, res, next) => {
    // Find user based on the session ID
    const user = users.find(u => u.id === req.session.userId);
    if (!user) {
        return res.status(401).json({ error: 'Access denied. Please log in.' });
    }
    // Attach user object to the request for other routes to use
    req.user = user;
    next();
};
// Get all available and enrolled courses for the logged-in user
app.get('/courses', auth, (req, res) => {
    res.json({
        availableCourses: courses,
        enrolledCourses: req.user.enrolled
    });
});

// Enroll the logged-in user in a new course
app.post('/courses', auth, (req, res) => {
    const courseId = parseInt(req.body.courseId, 10);
    const course = courses.find(c => c.id === courseId);

    if (!course) {
        return res.status(404).json({ error: 'Course not found' });
    }

    if (req.user.enrolled.includes(courseId)) {
        return res.status(409).json({ error: 'Already enrolled in this course' });
    }

    req.user.enrolled.push(courseId);
    
    res.cookie('lastEnrolledCourse', course.name, {
        maxAge: 2 * 60 * 1000,
        httpOnly: true
    });
    res.status(200).json({ message: `Successfully enrolled in ${course.name}` });
});


// --- Server Start ---
app.listen(PORT, () => {
    console.log(`Server running on http://localhost:${PORT}`);
});

FULL Registration and Session with ejs

Updated Express.js application with the protected course enrollment system.

This implementation adds the GET and POST routes for /courses, includes the required error handling, and sets the lastEnrolledCourse cookie upon successful enrollment.

app.js

js
import express from 'express';
import session from 'express-session';
import cookieParser from 'cookie-parser';
import path from 'path';
import { fileURLToPath } from 'url';

const app = express();
const PORT = 3000;

// Needed for __dirname in ES Modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// --- Middleware Setup ---
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(session({
    secret: 'a_secret_key_to_sign_the_cookie',
    resave: false,
    saveUninitialized: true,
    cookie: { secure: false }
}));

// Set EJS as the view engine
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));

// --- Data Stores ---
const students = []; // In-memory student database
const availableCourses = ['Calculus 101', 'History of Art', 'Introduction to Python', 'Quantum Physics'];

// Custom middleware to check for authenticated session
const authMiddleware = (req, res, next) => {
    if (req.session && req.session.student) {
        return next();
    }
    res.status(403).send('Access denied: Please login first.');
};

// --- Routes ---

// Registration and Login Routes
app.get('/register', (req, res) => res.render('register'));
app.post('/register', (req, res) => {
    const { rollNo, name, password } = req.body;
    // Add enrolledCourses array for each new student
    students.push({ rollNo, name, password, enrolledCourses: [] });
    console.log('Students:', students);
    res.redirect('/login');
});

app.get('/login', (req, res) => res.render('login'));
app.post('/login', (req, res) => {
    const { rollNo, password } = req.body;
    const student = students.find(s => s.rollNo === rollNo && s.password === password);
    if (student) {
        req.session.student = { rollNo: student.rollNo, name: student.name };
        res.cookie('studentPortalAccess', student.rollNo, { maxAge: 3 * 60 * 1000 });
        res.redirect('/dashboard');
    } else {
        res.status(401).send('Invalid roll number or password.');
    }
});

// Protected Dashboard and Result Routes
app.get('/dashboard', authMiddleware, (req, res) => {
    res.render('dashboard', { student: req.session.student });
});

app.get('/result', authMiddleware, (req, res) => {
    res.status(200).send(`Hi ${req.session.student.name}, your results are available!`);
});

// NEW: Course Enrollment Routes
app.get('/courses', authMiddleware, (req, res) => {
    const studentRecord = students.find(s => s.rollNo === req.session.student.rollNo);
    res.render('courses', {
        courses: availableCourses,
        enrolled: studentRecord.enrolledCourses
    });
});

app.post('/courses', authMiddleware, (req, res) => {
    const { course } = req.body;
    const studentRecord = students.find(s => s.rollNo === req.session.student.rollNo);

    // Error handling: Check if course exists or if student is already enrolled
    if (!availableCourses.includes(course)) {
        return res.status(400).send('Error: This course does not exist.');
    }
    if (studentRecord.enrolledCourses.includes(course)) {
        return res.status(400).send('Error: You are already enrolled in this course.');
    }

    // Enroll the student
    studentRecord.enrolledCourses.push(course);
    
    // Set the new cookie for 2 minutes
    res.cookie('lastEnrolledCourse', course, { maxAge: 2 * 60 * 1000 });

    console.log(`${studentRecord.name} enrolled in ${course}.`);
    res.redirect('/courses');
});


// Logout Route
app.get('/logout', (req, res) => {
    req.session.destroy(err => {
        if (err) return res.redirect('/dashboard');
        res.clearCookie('studentPortalAccess');
        res.clearCookie('lastEnrolledCourse'); // Also clear the enrollment cookie
        res.redirect('/login');
    });
});

// Start the server
app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}/register`);
});

EJS Files

You will need to add a new courses.ejs file and update dashboard.ejs with a link to it.

views/courses.ejs (New File)

html
<!DOCTYPE html>
<html lang="en">
<head><title>Course Enrollment</title></head>
<body>
    <h1>Course Enrollment</h1>
    
    <h2>Your Enrolled Courses:</h2>
    <% if (enrolled.length > 0) { %>
        <ul>
            <% enrolled.forEach(course => { %>
                <li><%= course %></li>
            <% }) %>
        </ul>
    <% } else { %>
        <p>You are not enrolled in any courses yet.</p>
    <% } %>

    <hr>

    <h2>Enroll in a New Course:</h2>
    <form action="/courses" method="POST">
        <label for="course">Select a course:</label>
        <select name="course" id="course">
            <% courses.forEach(course => { %>
                <option value="<%= course %>"><%= course %></option>
            <% }) %>
        </select>
        <button type="submit">Enroll</button>
    </form>
    
    <p><a href="/dashboard">Back to Dashboard</a></p>
</body>
</html>

views/dashboard.ejs (Updated)

html
<!DOCTYPE html>
<html lang="en">
<head><title>Dashboard</title></head>
<body>
    <h1>Welcome to the Student Portal, <%= student.name %>!</h1>
    <p>Your Roll Number is: <%= student.rollNo %></p>
    
    <p><a href="/result">View Your Results</a></p>
    <p><a href="/courses">Course Enrollment</a></p> <a href="/logout">Logout</a>
</body>
</html>

Run and Test

bash
npm init -y
npm install express express-session cookie-parser ejs

Then, ensure your package.json includes "type": "module".

node app.js
  1. Log In: Go to http://localhost:3000/register to create an account, then log in.

  2. Access Courses Page: From the dashboard, click the "Course Enrollment" link.

  3. Enroll: Select a course from the dropdown menu (e.g., "Introduction to Python") and click "Enroll".

  4. Verify Enrollment: The page will reload, and "Introduction to Python" will now appear under "Your Enrolled Courses."

  5. Check Cookie: Open your browser's developer tools and look at the cookies for the site. A new cookie named lastEnrolledCourse will be present with the value "Introduction to Python" and an expiry time of 2 minutes.

  6. Test Error Handling: Try to enroll in "Introduction to Python" again. The server will respond with a 400 Bad Request status and the message: "Error: You are already enrolled in this course."

  7. Test Unauthorized Access: Log out, then try to access http://localhost:3000/courses directly. You will see the "Access denied" message.

Made with ❤️ for students, by a fellow learner.