Разработка серверной части на JavaScript
Серверы на JavaScript: основные подходы и библиотеки
JavaScript давно перестал быть только языком для браузеров. Благодаря среде выполнения Node.js появилась возможность создавать серверные приложения. Современные альтернативы, такие как Deno и Bun, предлагают улучшенную производительность и встроенную поддержку TypeScript. В статье рассматриваются разные способы построения серверов на JavaScript, от встроенного модуля http до популярных фреймворков и альтернативных рантаймов.
Наиболее эффективным решением для большинства задач является использование Node.js в сочетании с фреймворком Express. Express предоставляет удобную маршрутизацию, поддержку промежуточных обработчиков (middleware) и обширную экосистему. Ниже приведён пример создания простого API-сервера с одним эндпоинтом.
const express = require('express');
const app = express();
const port = 3000;
app.use(express.json());
app.get('/api/hello', (req, res) => {
res.json({ message: 'Hello, world!' });
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});Nest js (nestjs фреймворк)
При запуске сервер отвечает на GET-запрос к /api/hello JSON-объектом. express.json() позволяет обрабатывать тела запросов в формате JSON. Это базовая конструкция, от которой отталкиваются при построении более сложных приложений.
Как создать минимальный HTTP-сервер на чистом Node.js без фреймворков?
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello from Node.js');
});
server.listen(3000, () => {
console.log('Server listening on port 3000');
});серверы js (серверы на javascript)
Такой сервер обрабатывает все входящие запросы одинаково. Для маршрутизации нужно вручную анализировать req.url и req.method. Это подходит для очень простых сценариев, но быстро становится громоздким.
Типичная ошибка: не обработать запрос на корневой URL / или не вернуть ответ (res.end() не вызван), что приведёт к зависанию соединения. Всегда необходимо завершать ответ.
Как организовать маршрутизацию с помощью Express?
const express = require('express');
const app = express();
app.get('/', (req, res) => res.send('Home'));
app.get('/about', (req, res) => res.send('About'));
app.post('/data', (req, res) => {
console.log(req.body);
res.send('Data received');
});
app.listen(3000);Express автоматически сопоставляет HTTP-методы и пути. Для POST-запросов требуется express.json() или body-parser.
Проблема: если забыть подключить парсер для JSON, req.body останется undefined. Решение - всегда добавлять app.use(express.json()) до определения маршрутов.
Как использовать Koa для асинхронных middleware?
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello from Koa';
});
app.listen(3000);Koa построен на async/await, что делает цепочки middleware более предсказуемыми. В отличие от Express, у Koa нет встроенной маршрутизации - используется отдельный пакет koa-router.
Частая ошибка: забыть вызвать await next() в middleware, если нужна передача управления дальше. Без этого цепочка прерывается.
Как работает Fastify со схемами валидации?
const fastify = require('fastify')({ logger: true });
fastify.get('/user/:id', {
schema: {
params: {
type: 'object',
properties: { id: { type: 'integer' } }
}
}
}, async (request, reply) => {
return { userId: request.params.id };
});
fastify.listen(3000);Fastify автоматически валидирует входные данные по JSON Schema и сериализует ответы. Это повышает производительность и безопасность.
Типичная ошибка: указать неправильный тип в схеме (например, строка вместо числа) - запрос будет отклонён с кодом 400. Нужно следить за согласованностью типов.
Как создать сервер на Deno с использованием стандартного модуля http?
import { serve } from "https://deno.land/std/http/server.ts";
const handler = (request) => {
return new Response("Hello from Deno", { status: 200 });
};
serve(handler, { port: 3000 });Deno поставляется со встроенной поддержкой TypeScript и современными API. Сервер использует Request и Response, аналогичные Web API.
Проблема: отсутствие доступа к некоторым файловым путям без явных разрешений. Для чтения файлов используется флаг --allow-read. Если забыть его указать, сервер упадёт с ошибкой.
Как запустить сервер на Bun?
Bun.serve({
port: 3000,
fetch(request) {
return new Response("Hello from Bun!");
},
});Bun - быстрый рантайм, ориентированный на скорость. Сервер работает аналогично Deno, но использует собственный API Bun.serve.
Ошибка: вызов Bun.serve() без асинхронной обработки - функция fetch должна возвращать Response или Promise<Response>. Иначе сервер не сработает.
Выбор конкретного решения зависит от требований к производительности, экосистеме и знакомству команды. Node.js с Express остаётся стандартом для REST API и веб-приложений, Koa предпочитают за асинхронность, Fastify - за скорость и валидацию, Deno и Bun - для современных проектов с TypeScript.
Расширенные примеры серверных приложений на JavaScript
В этом разделе приводятся более сложные примеры, демонстрирующие обработку разных типов запросов, работу с базами данных, WebSocket и кластеризацию.
Пример 1: Node.js с кластеризацией для многопоточности
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker) => {
console.log(`Worker ${worker.process.pid} died`);
cluster.fork();
});
} else {
http.createServer((req, res) => {
res.writeHead(200);
res.end('Hello from worker ' + process.pid);
}).listen(8000);
console.log(`Worker ${process.pid} started`);
}При запуске видно: Master 12345 is running Worker 12346 started Worker 12347 started ... (количество равно числу ядер)
Кластеризация позволяет использовать все ядра процессора, обходя ограничение однопоточности Node.js. Каждый воркер обрабатывает запросы независимо. При падении воркера мастер создаёт новый.
Пример 2: Express с middleware для аутентификации и логирования
const express = require('express');
const app = express();
// Middleware логирования
app.use((req, res, next) => {
console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
next();
});
// Middleware аутентификации (проверка токена в заголовке)
const authMiddleware = (req, res, next) => {
const token = req.headers['authorization'];
if (token === 'secret123') {
req.user = { name: 'Alice' };
next();
} else {
res.status(401).json({ error: 'Unauthorized' });
}
};
app.get('/profile', authMiddleware, (req, res) => {
res.json({ user: req.user });
});
app.listen(3000);Запрос GET /profile с заголовком Authorization: secret123 даёт ответ: { "user": { "name": "Alice" } }
Без токена - статус 401.Показана цепочка middleware: логирование выполняется для всех маршрутов, аутентификация - только для защищённого /profile. Порядок объявления middleware важен: первыми идут глобальные.
Пример 3: Koa с маршрутизатором и обработкой ошибок
const Koa = require('koa');
const Router = require('@koa/router');
const app = new Koa();
const router = new Router();
router.get('/items/:id', async (ctx) => {
const id = ctx.params.id;
// Имитация поиска в БД
if (id === '1') {
ctx.body = { id: 1, name: 'Item 1' };
} else {
ctx.throw(404, 'Item not found');
}
});
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = { error: err.message };
}
});
app.use(router.routes());
app.listen(3000);GET /items/1 -> { "id": 1, "name": "Item 1" }
GET /items/999 -> { "error": "Item not found" } (статус 404)Централизованная обработка ошибок через try-catch в middleware гарантирует, что любое исключение будет преобразовано в корректный HTTP-ответ. ctx.throw() генерирует ошибку с заданным статусом.
Пример 4: Fastify с PostgreSQL через плагин
const fastify = require('fastify')({ logger: true });
// Регистрация плагина для работы с БД
fastify.register(require('@fastify/postgres'), {
connectionString: 'postgres://user:pass@localhost/db'
});
fastify.get('/users', async (request, reply) => {
const client = await fastify.pg.connect();
try {
const result = await client.query('SELECT id, name FROM users');
return result.rows;
} finally {
client.release();
}
});
fastify.listen(3000);Ответ при наличии данных: [{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]Fastify поддерживает плагины для интеграции с БД. Подключение к PostgreSQL выполняется через @fastify/postgres, который управляет пулом соединений. Важно освобождать клиента после запроса.
Пример 5: Deno с oak (Express-подобный фреймворк)
import { Application, Router } from "https://deno.land/x/oak/mod.ts";
const app = new Application();
const router = new Router();
router.get("/api/time", (ctx) => {
ctx.response.body = { time: new Date().toISOString() };
});
app.use(router.routes());
app.use(router.allowedMethods());
await app.listen({ port: 8080 });Запрос GET http://localhost:8080/api/time -> { "time": "2025-03-20T12:34:56.789Z" }Oak - популярный middleware-фреймворк для Deno, напоминающий Express. Синтаксис близок к Koa. App.listen() возвращает Promise, поэтому используется await.
Пример 6: Bun с обработкой статических файлов и WebSocket
Bun.serve({
port: 3000,
fetch(req) {
const url = new URL(req.url);
// Отдача статического файла
if (url.pathname === "/" || url.pathname.startsWith("/static/")) {
const filePath = `.${url.pathname}`;
const file = Bun.file(filePath);
return new Response(file);
}
// WebSocket upgrade
if (url.pathname === "/ws") {
const upgraded = Bun.upgrade(req);
if (upgraded) {
return upgraded;
}
return new Response("Upgrade failed", { status: 426 });
}
return new Response("Not Found", { status: 404 });
},
websocket: {
message(ws, message) {
ws.send(`Echo: ${message}`);
},
open(ws) {
console.log("WebSocket opened");
},
close(ws) {
console.log("WebSocket closed");
}
}
});При подключении к ws://localhost:3000/ws можно отправлять сообщения и получать эхо. Статические файлы из папки static доступны по HTTP.
Bun предоставляет встроенную поддержку WebSocket и работы с файлами. Для статики используется Bun.file(), который возвращает ReadableStream. Обработка WebSocket настраивается через опции websocket.