Раздача статических файлов
Статические файлы — те, что отдаются браузеру как есть, без обработки: HTML-страницы, стили, скрипты, картинки. Фреймворки раздают их одной строкой настройки, а в node:http сервер должен сам найти файл на диске, прочитать его и указать правильный Content-Type.
Папка public
Создайте в корне проекта папку public и в ней два файла:
blog/
├── public/
│ ├── index.html
│ └── style.css
├── src/
│ └── server.js
└── package.json<!-- public/index.html -->
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Блог Анны</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<h1>Блог Анны</h1>
</body>
</html>/* public/style.css */
h1 {
color: darkslateblue;
}Тип по расширению
Браузер понимает, что получил, только по заголовку Content-Type. Соответствие расширений и типов задают объектом:
// src/static.js
export const MIME_TYPES = {
'.html': 'text/html; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.js': 'text/javascript; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.svg': 'image/svg+xml',
};Для неизвестных расширений используют тип application/octet-stream — «просто байты».
Функция раздачи
Добавьте в src/static.js функцию, которая ищет файл в public и отправляет его:
// src/static.js
import path from 'node:path';
import { readFile } from 'node:fs/promises';
const PUBLIC_DIR = path.join(import.meta.dirname, '..', 'public');
export async function serveStatic(pathname, res) {
const name = pathname === '/' ? 'index.html' : pathname;
const file = path.join(PUBLIC_DIR, name);
if (!file.startsWith(PUBLIC_DIR + path.sep)) return false;
try {
const content = await readFile(file);
const type = MIME_TYPES[path.extname(file)] ?? 'application/octet-stream';
res.writeHead(200, { 'Content-Type': type });
res.end(content);
return true;
} catch {
return false;
}
}Функция возвращает true, если файл найден и отправлен, и false, если его нет. Кодировку в readFile() не указывают: картинки — это байты, а не текст.
Подключение к серверу
В src/server.js сначала пробуют отдать файл, а потом — обычные маршруты:
// src/server.js
import http from 'node:http';
import { serveStatic } from './static.js';
const server = http.createServer(async (req, res) => {
const { pathname } = new URL(req.url, 'http://localhost');
if (req.method === 'GET' && (await serveStatic(pathname, res))) return;
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('Страница не найдена');
});
server.listen(3000);http://localhost:3000 покажет фиолетовый заголовок «Блог Анны»: браузер загрузил страницу и отдельным запросом — /style.css.
Частая ошибка
Если склеить путь из адреса без проверки, посетитель сможет запросить /../.env и прочитать ваши пароли — это называется обходом каталога (path traversal). Поэтому в serveStatic() есть строка:
// src/static.js
if (!file.startsWith(PUBLIC_DIR + path.sep)) return false;После path.join() все .. уже раскрыты, и если итоговый путь вышел за пределы public, файл не отдаётся. Никогда не убирайте эту проверку.