Stage 3: Backend Essentials

Node.js · Lesson 04

Express Basics

Servers, routing, middleware and serving static files with Express.

Node.js2 min readFree lesson

A raw Node HTTP server works, but routing and body parsing by hand get tedious fast. Express is a thin layer over http that gives you routes, middleware and static file serving.

Learning objectives

  • Create an Express app and start a server.
  • Define routes for GET and POST.
  • Understand middleware and the order it runs in.
  • Serve static files and return JSON.

1. Setup

npm init -y
npm install express
const express = require("express");

const app = express();
const PORT = process.env.PORT || 3000;

app.get("/", (req, res) => {
  res.send("Hello from Express");
});

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

app.listen keeps the process alive. Stop it with Ctrl+C.

2. Routes

app.get("/about", (req, res) => res.send("About us"));

app.get("/users/:id", (req, res) => {
  res.json({ id: req.params.id });
});

app.get("/search", (req, res) => {
  res.json({ q: req.query.q ?? "" }); // /search?q=node
});

app.post("/users", (req, res) => {
  res.status(201).json({ created: true, body: req.body });
});
  • req.params — values from the path (:id).
  • req.query — values after ?.
  • req.body — parsed request body, only after express.json().

Response helpers: res.send (text/HTML), res.json, res.status(code).json(...), res.sendFile(absolutePath), res.redirect(url).

3. Middleware

Middleware is a function (req, res, next) that runs before the route handler. Call next() to continue; send a response to stop.

app.use(express.json()); // parse JSON bodies

app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
});

Order matters: middleware registered before a route runs before it. express.json() must be registered before any handler that reads req.body.

4. Static files

const path = require("node:path");
app.use(express.static(path.join(__dirname, "public")));

Every file in public/ is now served at the matching URL: public/about.html → /about.html, public/logo.png → /logo.png.

5. 404 and error handling

A catch-all middleware at the end handles unmatched routes:

app.use((req, res) => {
  res.status(404).sendFile(path.join(__dirname, "public", "404.html"));
});

Error middleware takes four arguments and must be registered last:

app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({ error: "Something went wrong" });
});

Common mistakes

  • Registering the 404 handler before the routes — it then catches everything.
  • Reading req.body without express.json().
  • Using a relative path with sendFile; Express resolves it against the process cwd. Always use path.join(__dirname, ...).

Practice

Build the small portfolio server in the assignment: static pages, two JSON endpoints, request logging and a real 404 page.

Example files

Complete, runnable files used in this lesson. Copy them into your project and experiment.

app.jsJavaScript
// ===================================================
// 강의 04 예제 — Express 기초: HTML 파일 서빙
// 실행: node app.js
// 접속: http://localhost:3000
//
// 사전 준비:
//   npm init -y
//   npm install express
// ===================================================

const express = require('express');
const path = require('path');

// Express 앱 생성
const app = express();
const PORT = 3000;

// ① 정적 파일 제공 미들웨어 — public 폴더의 파일을 자동으로 제공
app.use(express.static(path.join(__dirname, 'public')));

// ② 로그 미들웨어 — 모든 요청을 콘솔에 출력
app.use((req, res, next) => {
  const timestamp = new Date().toLocaleTimeString('ko-KR');
  console.log(`[${timestamp}] ${req.method} ${req.url}`);
  next(); // 다음 미들웨어/라우트로 넘김
});

// ③ JSON/폼 데이터 파싱 미들웨어
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// ④ 라우트 정의
// 홈 페이지
app.get('/', (req, res) => {
  // sendFile로 HTML 파일 직접 전송
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

// 소개 페이지
app.get('/about', (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'about.html'));
});

// 연락처 페이지
app.get('/contact', (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'contact.html'));
});

// ⑤ 연락처 폼 제출 처리 (POST)
app.post('/contact', (req, res) => {
  const { name, email, message } = req.body; // 폼 데이터 추출
  console.log('\n--- 연락처 폼 수신 ---');
  console.log('이름:', name);
  console.log('이메일:', email);
  console.log('메시지:', message);

  // 실제로는 이메일 발송 또는 DB 저장
  res.send(`
    <h2>감사합니다, ${name}님!</h2>
    <p>메시지가 성공적으로 전달되었습니다.</p>
    <a href="/">홈으로 돌아가기</a>
  `);
});

// ⑥ API 엔드포인트 — 사이트 정보 반환 (JSON)
app.get('/api/info', (req, res) => {
  res.json({
    siteName: 'My Express Site',
    version: '1.0.0',
    pages: ['/', '/about', '/contact'],
    serverTime: new Date().toISOString(),
  });
});

// ⑦ 404 처리 — 존재하지 않는 경로 접근 시
app.use((req, res) => {
  res.status(404).send(`
    <h1>404 - 페이지를 찾을 수 없습니다</h1>
    <p>요청하신 페이지 <strong>${req.url}</strong>가 존재하지 않습니다.</p>
    <a href="/">홈으로 돌아가기</a>
  `);
});

// ⑧ 서버 시작
app.listen(PORT, () => {
  console.log(`서버가 시작되었습니다: http://localhost:${PORT}`);
  console.log('종료하려면 Ctrl+C를 누르세요.');
});
public/about.htmlHTML
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>My Express Site - 소개</title>
  <style>
    body { font-family: sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
    nav a { margin-right: 15px; text-decoration: none; color: #0066cc; }
  </style>
</head>
<body>
  <nav>
    <a href="/">홈</a>
    <a href="/about">소개</a>
    <a href="/contact">연락처</a>
  </nav>
  <hr>
  <h1>소개 페이지</h1>
  <p>이 사이트는 Node.js + Express 강의 예제입니다.</p>
</body>
</html>
public/contact.htmlHTML
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>My Express Site - 연락처</title>
  <style>
    body { font-family: sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
    nav a { margin-right: 15px; text-decoration: none; color: #0066cc; }
    input, textarea { display: block; width: 100%; margin: 5px 0 15px; padding: 8px; box-sizing: border-box; }
    button { padding: 10px 20px; background: #0066cc; color: white; border: none; cursor: pointer; }
  </style>
</head>
<body>
  <nav>
    <a href="/">홈</a>
    <a href="/about">소개</a>
    <a href="/contact">연락처</a>
  </nav>
  <hr>
  <h1>연락처</h1>
  <form method="POST" action="/contact">
    <label>이름: <input type="text" name="name" required></label>
    <label>이메일: <input type="email" name="email" required></label>
    <label>메시지: <textarea name="message" rows="5" required></textarea></label>
    <button type="submit">보내기</button>
  </form>
</body>
</html>
public/index.htmlHTML
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My Express Site - 홈</title>
  <style>
    body { font-family: sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
    nav a { margin-right: 15px; text-decoration: none; color: #0066cc; }
    nav a:hover { text-decoration: underline; }
  </style>
</head>
<body>
  <nav>
    <a href="/">홈</a>
    <a href="/about">소개</a>
    <a href="/contact">연락처</a>
  </nav>
  <hr>
  <h1>환영합니다!</h1>
  <p>Express로 만든 나의 첫 웹 서버입니다.</p>
</body>
</html>

Assignment

Assignment 04 — Portfolio server with Express

Build an Express app that serves a small static site and two JSON endpoints.

Structure

app.js
public/
  index.html
  about.html
  portfolio.html
  contact.html
  404.html

Requirements

  1. Serve the public folder with express.static.
  2. Add routes for /, /about, /portfolio and /contact that send the matching HTML file with res.sendFile.
  3. Add a request logger middleware that prints method, URL and time for every request.
  4. Count page visits in a variable, ignoring requests for assets (URLs containing a dot).
  5. GET /api/pages returns the list of pages with path and title.
  6. GET /api/time returns serverTime (ISO), localTime and visitorCount as JSON.
  7. Any unknown route returns 404.html with status 404.

Hints

app.use(express.static(path.join(__dirname, "public")));

app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
});

app.get("/api/time", (req, res) => {
  res.json({ serverTime: new Date().toISOString(), visitorCount });
});

Remember: the 404 middleware goes after all routes and static middleware.

Done when

  • /api/pages returns valid JSON.
  • Refreshing / increments the counter; refreshing style.css does not.
  • An unknown URL shows the custom 404 page, not Express's default text.
Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

app.jsJavaScript
// ===================================================
// 과제 04 정답 — 개인 홈페이지 서버
// 실행: node app.js
// 접속: http://localhost:3000
// ===================================================

const express = require('express');
const path = require('path');

const app = express();
const PORT = process.env.PORT || 3000;

// ① 방문자 카운터 (메모리에 저장 — 서버 재시작 시 초기화)
let visitorCount = 0;

// ② 정적 파일 미들웨어 — public 폴더 전체를 정적 파일로 제공
app.use(express.static(path.join(__dirname, 'public')));

// ③ 요청 로그 미들웨어
app.use((req, res, next) => {
  console.log(`[${new Date().toLocaleTimeString('ko-KR')}] ${req.method} ${req.url}`);
  next();
});

// ④ 방문자 카운터 증가 미들웨어 (HTML 페이지 요청 시만)
app.use((req, res, next) => {
  // 정적 리소스(CSS, JS, 이미지)는 카운트 제외
  if (!req.url.includes('.')) {
    visitorCount++;
  }
  next();
});

// ⑤ 페이지 라우트 — HTML 파일 서빙
app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

app.get('/about', (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'about.html'));
});

app.get('/portfolio', (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'portfolio.html'));
});

app.get('/contact', (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'contact.html'));
});

// ⑥ API 엔드포인트 — 페이지 목록 반환
app.get('/api/pages', (req, res) => {
  res.json({
    pages: [
      { path: '/', title: '홈' },
      { path: '/about', title: '소개' },
      { path: '/portfolio', title: '포트폴리오' },
      { path: '/contact', title: '연락처' },
    ],
    total: 4,
  });
});

// ⑦ API 엔드포인트 — 현재 시간 반환
app.get('/api/time', (req, res) => {
  res.json({
    serverTime: new Date().toISOString(),
    localTime: new Date().toLocaleString('ko-KR'),
    visitorCount, // 누적 방문자 수
  });
});

// ⑧ 404 처리 — 앞의 모든 라우트에서 처리되지 않은 요청
app.use((req, res) => {
  // 상태 코드 404와 함께 404.html 파일 응답
  res.status(404).sendFile(path.join(__dirname, 'public', '404.html'));
});

// ⑨ 서버 시작
app.listen(PORT, () => {
  console.log(`개인 홈페이지 서버가 시작되었습니다!`);
  console.log(`접속 주소: http://localhost:${PORT}`);
  console.log('종료: Ctrl+C');
});

Learn this lesson with a mentor

Work through the Build Your Homepage series on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now