Bài 5. Máy chủ HTTP với node:http

Trước khi dùng framework, hãy tự viết máy chủ web bằng mô-đun http có sẵn để hiểu rõ request, response, định tuyến, phục vụ tệp tĩnh và trả JSON. Đây chính là những gì Express làm hộ bạn ở bài sau.

45 phútTrung cấp
Bạn sẽ học được
  • Tạo máy chủ HTTP, lắng nghe cổng
  • Đọc phương thức, URL, header, query, body
  • Trả HTML, JSON, mã trạng thái, header
  • Định tuyến đơn giản và phục vụ tệp tĩnh

Một số khái niệm

Mỗi yêu cầu (request) đến, Node.js gọi hàm xử lý với hai đối tượng: req (thông tin yêu cầu, là stream đọc) và res (phản hồi, là stream ghi). Bạn tự quyết định mã trạng thái, header và nội dung trả về. Xem lại mô hình HTTP ở loạt PHP Bài 1.

Máy chủ đầu tiên

// server.js
import http from 'node:http';

const sinhVien = [{ id: 1, ten: 'Lò Văn An' }, { id: 2, ten: 'Cầm Thị Bình' }];

const server = http.createServer(async (req, res) => {
  const url = new URL(req.url, `http://${req.headers.host}`);
  console.log(req.method, url.pathname);

  if (req.method === 'GET' && url.pathname === '/') {
    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
    return res.end('<h1>Xin chào từ Node.js</h1><a href="/api/sinhvien">Xem API</a>');
  }
  if (req.method === 'GET' && url.pathname === '/api/sinhvien') {
    const q = url.searchParams.get('q') ?? '';
    const kq = sinhVien.filter(s => s.ten.toLowerCase().includes(q.toLowerCase()));
    res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
    return res.end(JSON.stringify(kq));
  }
  if (req.method === 'POST' && url.pathname === '/api/sinhvien') {
    let body = '';
    for await (const chunk of req) body += chunk;          // đọc body (stream)
    const { ten } = JSON.parse(body || '{}');
    if (!ten) { res.writeHead(422, { 'Content-Type': 'application/json' }); return res.end('{"loi":"Thiếu tên"}'); }
    const sv = { id: sinhVien.length + 1, ten }; sinhVien.push(sv);
    res.writeHead(201, { 'Content-Type': 'application/json; charset=utf-8' });
    return res.end(JSON.stringify(sv));
  }
  res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
  res.end('Không tìm thấy');
});

server.listen(3000, () => console.log('Chạy tại http://localhost:3000'));
curl http://localhost:3000/api/sinhvien?q=an
curl -X POST http://localhost:3000/api/sinhvien -H "Content-Type: application/json" -d '{"ten":"Vì Văn Chi"}'

Phục vụ tệp tĩnh (an toàn)

import { readFile } from 'node:fs/promises';
import path from 'node:path';
const PUBLIC = path.join(import.meta.dirname, 'public');
const LOAI = { '.html': 'text/html', '.css': 'text/css', '.js': 'text/javascript', '.png': 'image/png', '.jpg': 'image/jpeg' };

async function tinh(res, pathname) {
  const tep = path.join(PUBLIC, pathname === '/' ? 'index.html' : pathname);
  if (!tep.startsWith(PUBLIC)) { res.writeHead(403); return res.end(); }   // chặn ../
  try {
    const noiDung = await readFile(tep);
    res.writeHead(200, { 'Content-Type': (LOAI[path.extname(tep)] ?? 'application/octet-stream') + '; charset=utf-8' });
    res.end(noiDung);
  } catch { res.writeHead(404); res.end('Không tìm thấy'); }
}
Mẹo hay

Dùng tiện ích REST Client của VS Code hoặc Postman để gửi thử GET/POST, xem header và mã trạng thái.

Lưu ý

Máy chủ tự viết dễ thiếu nhiều thứ quan trọng (giới hạn kích thước body, xử lý lỗi JSON, bảo mật header); dự án thật nên dùng framework như Express, Fastify.

Thực hành
  1. Viết máy chủ trả trang HTML, trang /gioi-thieu và lỗi 404.
  2. Thêm API GET/POST danh sách sinh viên lưu trong mảng.
  3. Phục vụ thư mục public có index.html, style.css.
  4. Lưu dữ liệu sinh viên vào tệp JSON thay vì mảng.
Tóm tắt bài học
  • http.createServer((req, res) => ...).listen(port).
  • URL + searchParams đọc đường dẫn, query; body là stream.
  • writeHead(mã, header) + end(nội dung).
  • Tự định tuyến, phục vụ tĩnh, chặn path traversal.