server.js in detail

Mini Messageboard: A Complete Walkthrough of server.js

(Node.js + Express + MariaDB)

This page explains the entire JavaScript program that powers the Mini Messageboard web app. The goal is to understand how a request from a browser becomes database queries and HTML responses.

Reference material used


The Setup

import express from 'express';
import 'dotenv/config';
import { query } from './db.js';

const app = express();
app.use(express.urlencoded({ extended: false }));

const port = process.env.PORT;
if (!port) {
  throw new Error('PORT not set. Create .env (cp .env.example .env) and set PORT=41xx');
}

What this section does

  • Imports Express for routing
  • Loads environment variables using dotenv
  • Imports a helper function query() that runs SQL using MariaDB
  • Creates the Express app
  • Enables parsing of HTML form submissions
  • Reads the port from the environment and fails early if it is missing

Why fail early?
On a shared server, every student must use a different port. If the port is wrong or missing, the program should stop immediately.


Route 1: GET / (Home Page)

app.get('/', async (req, res) => {
  const users = await query('SELECT id, name FROM users ORDER BY name');
  const selected = Number(req.query.user_id || 0);

  res.send(`
    <h1>Mini Messageboard</h1>
    <p><b>No login:</b> select who you are, then view/post messages.</p>

    <form method="GET" action="/messages">
      <label for="user_id"><b>Who are you?</b></label>
      <select name="user_id" id="user_id" required>
        <option value="" ${selected ? '' : 'selected'} disabled>Select a user...</option>
        ${users.map(u => `<option value="${u.id}" ${u.id === selected ? 'selected' : ''}>${u.name}</option>`).join('')}
      </select>
      <button type="submit">Continue</button>
    </form>

    <hr/>
    <p>Tip: If you refresh and lose your selection, just pick yourself again.</p>
  `);
});

What’s happening here

  1. Express routing
    This handler runs when the browser requests /.
  2. Database query SELECT id, name FROM users ORDER BY name This pulls user data from MariaDB so the UI reflects the database, not hard-coded values.
  3. Query parameters
    req.query.user_id comes from URLs like: /?user_id=2
  4. Dynamic HTML generation
    The <select> dropdown is built by mapping database rows into <option> elements.

HTML Generated by the Home Page

<pre data-enlighter-language=”html”> <form method=”GET” action=”/messages”> <label for=”user_id”><b>Who are you?</b></label> <select name=”user_id” id=”user_id” required> <option value=”” disabled>Select a user…</option> <option value=”1″>Alice</option> <option value=”2″>Bob</option> </select> <button type=”submit”>Continue</button> </form> </pre>

Submitting this form creates a URL like:

/messages?user_id=1

This is a GET request carrying data in the query string.


Route 2: GET /messages (View + Post Messages)

app.get('/', async (req, res) => {
  const users = await query('SELECT id, name FROM users ORDER BY name');
  const selected = Number(req.query.user_id || 0);

  res.send(`
    <h1>Mini Messageboard</h1>
    <p><b>No login:</b> select who you are, then view/post messages.</p>

    <form method="GET" action="/messages">
      <label for="user_id"><b>Who are you?</b></label>
      <select name="user_id" id="user_id" required>
        <option value="" ${selected ? '' : 'selected'} disabled>Select a user...</option>
        ${users.map(u => `<option value="${u.id}" ${u.id === selected ? 'selected' : ''}>${u.name}</option>`).join('')}
      </select>
      <button type="submit">Continue</button>
    </form>

    <hr/>
    <p>Tip: If you refresh and lose your selection, just pick yourself again.</p>
  `);
});

Key ideas here

  • Validation: redirect if user_id is missing
  • Prepared statements (WHERE id=?) keep SQL separate from data
  • SQL JOIN combines users and posts correctly
  • The server generates all HTML — no client JavaScript required

Route 3: POST /messages (Insert a Message)

app.post('/messages', async (req, res) => {
  const userId = Number(req.body.user_id || 0);
  const content = String(req.body.content || '').trim();
  if (!userId || !content) return res.redirect('/');

  if (content.length > 500) {
    return res.status(400).send('Message too long (max 500 chars).');
  }

  await query('INSERT INTO posts (user_id, content) VALUES (?, ?)', [userId, content]);
  res.redirect(`/messages?user_id=${userId}`);
});

Why this pattern matters

  • POST modifies data
  • Redirect after POST prevents duplicate submissions on refresh
  • Parameterized SQL prevents injection bugs

Helper: Escaping Output (XSS Prevention)

function escapeHtml(s) {
  return String(s)
    .replaceAll('&', '&')
    .replaceAll('<', '<')
    .replaceAll('>', '>')
    .replaceAll('"', '"')
    .replaceAll("'", ''');
}

Any time user input is displayed in HTML, it must be escaped. This prevents malicious users from injecting scripts into the page.


Helper: Formatting Timestamps

function formatTime(ts) {
  try {
    const d = (ts instanceof Date) ? ts : new Date(ts);
    if (Number.isNaN(d.getTime())) return '';
    return d.toLocaleString();
  } catch {
    return '';
  }
}


Starting the Server

app.listen(Number(port), () => {
  console.log(`Server running on port ${port}`);
});

>

If everything is configured correctly, you’ll see:

Server running on port 4178

What This Program Teaches

  • Express routing and request handling
  • GET vs POST semantics
  • HTML form mechanics
  • SQL queries and JOINs
  • Safe database access from Node
  • Why escaping output matters
  • Clean separation of configuration, logic, and data
Scroll to Top