"> ?>
Наверх

Почему выбор Node.js-фреймворков снова вызывает раздражение — даже Express уже не выглядит идеальным

Разработчики всё чаще спорят, какой инструмент стоит своих нервов, а какой только усложняет жизнь — от Express и Koa до Fastify и Hono

Опубликовано 10.12.2025 в 18:19
Почему выбор Node.js-фреймворков снова вызывает раздражение — даже Express уже не выглядит идеальным

Основные идеи

Express остаётся самым простым способом собрать базовый API.
Fastify предлагает схемы и высокую скорость для предсказуемых сервисов.
Nest и Adonis дают структуру для крупных долгоживущих проектов.
Next, Nuxt и SvelteKit объединяют фронт и бэкенд в одном стеке.

Мнение автора

В современном Node.js важно трезво оценивать масштаб и требования проекта. Минималистичные решения ускоряют старт, но крупные системы нуждаются в чёткой архитектуре. Стоит выбирать фреймворк не по моде, а по тому, насколько он снижает сложность и облегчает поддержку на длительной дистанции.

Node.js давно превратился в популярную среду для серверной разработки: он позволяет писать привычным JavaScript без браузера и дарит доступ к огромной экосистеме. Именно эта экосистема и формирует главное преимущество платформы, хотя иногда она превращает выбор подходящего инструмента в тот ещё квест.

В этой части мы пройдёмся по самым известным минималистичным решениям — Express, Koa, Fastify, Hono и Nitro. Эти фреймворки часто выглядят похожими, но у каждого свои характерные черты.

Минималистичные фреймворки

Каждый разработчик рано или поздно сталкивается с ситуацией, когда нужен минимум возможностей, но максимум гибкости. Именно на этом и строятся лёгкие фреймворки: они дают базовый функционал, а всё остальное пользователь наращивает сам через плагины.

Видео от DGL.RU

Express.js

Express давно закрепился как самый распространённый пакет из npm, и это не удивляет. Он даёт простой доступ к маршрутам и обработке запросов, а при необходимости можно подключить массу middleware. Такой набор идеально подходит для проектов, где основная задача — просто организовать HTTP-эндпоинты.

Вот пример простого маршрута Express, который возвращает породу собаки по ID. Он демонстрирует традиционную структуру: путь, затем функция обработки запроса и ответа.

import express from 'express';

const app = express();
const port = 3000;

// In-memory array of dog breeds
const dogBreeds = [
  "Shih Tzu",
  "Great Pyrenees",
  "Tibetan Mastiff",
  "Australian Shepherd"
];

app.get('/dogs/:id', (req, res) => {
  const id = parseInt(req.params.id, 10);

  if (id >= 0 && id < dogBreeds.length) {
    res.status(200).json({ breed: dogBreeds[id] });
  } else {
    res.status(404).json({ error: 'Dog breed not found' });
  }
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

Многих удивляет отсутствие файловой маршрутизации, которая есть в более современных фреймворках. Но Express компенсирует это богатой коллекцией middleware.

Koa

Koa создавался как попытка переосмыслить Express. Разработчики хотели избавить архитектуру от старых решений и сделать код чище. В Koa вся логика строится вокруг async/await, а контекст объединён в один объект, поэтому API выглядит аккуратнее.

Пример того же маршрута в Koa:

router.get('/dogs/:id', (ctx) => {
  const id = parseInt(ctx.params.id, 10);

  if (id >= 0 && id < dogBreeds.length) {
    ctx.status = 200;
    ctx.body = { breed: dogBreeds[id] };
  } else {
    ctx.status = 404;
    ctx.body = { error: 'Dog breed not found' };
  }
});

И пример простого middleware-логгера:

const logger = async (ctx, next) => {
  await next();
  console.log(`${ctx.method} ${ctx.url} - ${ctx.status}`);
};

app.use(logger);

По ощущениям, сервер в Koa получается чище и более контролируемым.

Fastify

Fastify выделяется тем, что предлагает явно описывать схемы API. Это помогает документировать приложение и заранее отслеживать ошибки. Если схема не нужна, можно спокойно писать маршруты без неё, и тогда Fastify будет выглядеть почти как Express — только быстрее.

const schema = {
  params: {
    type: 'object',
    properties: {
      id: { type: 'integer' }
    }
  },
  response: {
    200: {
      type: 'object',
      properties: {
        breed: { type: 'string' }
      }
    },
    404: {
      type: 'object',
      properties: {
        error: { type: 'string' }
      }
    }
  }
};

fastify.get('/dogs/:id', { schema }, (request, reply) => {
  const id = request.params.id;

  if (id >= 0 && id < dogBreeds.length) {
    reply.code(200).send({ breed: dogBreeds[id] });
  } else {
    reply.code(404).send({ error: 'Dog breed not found' });
  }
});

Fastify подходит тем, кому важна предсказуемость API и скорость.

Hono

Hono делает ставку на минимализм. В нём легко создать сервер за пару строк, а маршруты читаются максимально компактно.

const app = new Hono();
app.get('/', (c) => c.text('Hello, DGL!'));

Пример маршрута с выбором породы по ID:

app.get('/dogs/:id', (c) => {
  const id = parseInt(c.req.param('id'), 10);

  if (id >= 0 && id < dogBreeds.length) {
    return c.json({ breed: dogBreeds[id] });
  } else {
    c.status(404);
    return c.json({ error: 'Dog breed not found' });
  }
});

По духу Hono ближе к Koa, но синтаксис ещё проще.

Nitro

Nitro — серверный движок, который стоит за многими фулл-стек-фреймворками. Он даёт удобную файловую маршрутизацию и средства для запуска в облачных окружениях. Можно сказать, что Nitro занимает «золотую середину» между минимализмом и полноценной архитектурой.

Пример обработчика маршрута:

export default defineEventHandler((event) => {
  const { id } = getRouterParams(event);
  const parsedId = parseInt(id, 10);

  if (parsedId >= 0 && parsedId < dogBreeds.length) {
    return { breed: dogBreeds[parsedId] };
  } else {
    setResponseStatus(event, 404);
    return { error: 'Dog breed not found' };
  }
});

На практике Nitro выбирают, когда нужно быстрее собирать серверную часть, не жертвуя архитектурной ясностью.

В мире Node.js есть целый пласт инструментов, которые предлагают не минималистичный подход, а полноценную архитектуру «всё в одном». Это удобно на долгой дистанции, хотя иногда такие решения перегружают проект там, где можно было бы обойтись куда проще.

Фреймворки с полной комплектацией

Когда команда хочет строгую структуру и понятное масштабирование, на сцену выходят тяжёлые фреймворки. Они добавляют контроллеры, сервисы, модули, ORM и другие удобства, которые помогают держать проект в порядке.

Nest.js

Nest — один из самых популярных «архитектурных» инструментов. Он построен на TypeScript и во многом напоминает Angular. Здесь есть внедрение зависимостей, аннотированные контроллеры и строгая структура проекта.

Пример сервиса и контроллера:

// The provider:
import { Injectable, NotFoundException } from '@nestjs/common';

@Injectable()
export class DogsService {
  private readonly dogBreeds = [
    "Shih Tzu",
    "Great Pyrenees",
    "Tibetan Mastiff",
    "Australian Shepherd"
  ];

  findOne(id: number) {
    if (id >= 0 && id < this.dogBreeds.length) {
      return { breed: this.dogBreeds[id] };
    }
    throw new NotFoundException('Dog breed not found');
  }
}

// The controller

import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common';
import { DogsService } from './dogs.service';

@Controller('dogs')
export class DogsController {
  constructor(private readonly dogsService: DogsService) {}

  @Get(':id')
  findOneDog(@Param('id', ParseIntPipe) id: number) {
    return this.dogsService.findOne(id);
  }
}

Такой стиль ценят команды, которым важно, чтобы проект выглядел одинаково у всех участников.

Adonis.js

Adonis тоже следует MVC-модели и добавляет ORM, удобные валидаторы и чёткое разделение слоёв. Маршруты выглядят просто, а логика контроллеров напоминает классические серверные фреймворки.

Route.get('/dogs/:id', [DogsController, 'show'])

Пример контроллера:

import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

export default class DogsController {
  public async show({ params, response }: HttpContextContract) {
    const id = Number(params.id);

    if (!isNaN(id) && id >= 0 && id < this.dogBreeds.length) {
      return response.ok({ breed: this.dogBreeds[id] });
    } else {
      return response.notFound({ error: 'Dog breed not found' });
    }
  }
}

В реальном проекте к этому добавляется слой моделей, отвечающий за доступ к данным.

Sails

Sails — один из старейших комплексных фреймворков для Node. В нём есть ORM Waterline, генератор API и встроенная поддержка WebSockets. Он рассчитан на тех, кому нужен максимально готовый сервер «из коробки».

Пример примитивной модели:

/**
 * Dog.js
 *
 * @description :: A model definition represents a database table/collection.
 */
module.exports = {
  attributes: {
    breed: { type: 'string', required: true },
  },
};

После запуска Sails сам создаёт маршруты и подключает хранилище данных. При необходимости разработчик может переопределить любую часть поведения.

Полноценные фулл-стек-фреймворки

Когда разработчику нужен и бэкенд, и фронтенд в одном проекте, на сцене появляются мета-фреймворки. Они позволяют запускать весь стек единым решением.

Next.js

Next, построенный на React, стал фактическим стандартом фулл-стек-разработки. Он объединяет API-эндпоинты и интерфейс в одном проекте и использует файловую маршрутизацию.

Пример API:

export default function handler(req, res) {
  const { id } = req.query;
  const parsedId = parseInt(id, 10);

  if (parsedId >= 0 && parsedId < dogBreeds.length) {
    res.status(200).json({ breed: dogBreeds[parsedId] });
  } else {
    res.status(404).json({ error: 'Dog breed not found' });
  }
}

UI-страница:

function DogPage({ dog }) {
  if (!dog) {
    return <h1>Dog Breed Not Found</h1>;
  }

  return (
    <div>
      <h1>Dog Breed Profile</h1>
      <p>Breed Name: <strong>{dog.breed}</strong></p>
    </div>
  );
}

export async function getServerSideProps(context) {
  const { id } = context.params;
  const res = await fetch(`http://localhost:3000/api/dogs/${id}`);
  const dog = res.ok ? await res.json() : null;

  return { props: { dog } };
}

export default DogPage;

Nuxt.js

Nuxt делает то же самое для Vue — серверные маршруты, готовые методы получения данных и файловую структуру.

Пример API-обработчика:

export default defineEventHandler((event) => {
  const id = getRouterParam(event, 'id');
  const parsedId = parseInt(id, 10);

  if (parsedId >= 0 && parsedId < dogBreeds.length) {
    return { breed: dogBreeds[parsedId] };
  } else {
    setResponseStatus(event, 404);
    return { error: 'Dog breed not found' };
  }
});

UI-страница:

<template>
  <div>
    <div v-if="pending">Loading...</div>
    <div v-else-if="error">
      <h1>{{ error.data.error }}</h1>
    </div>
    <div v-else>
      <h1>Dog Breed Profile</h1>
      <p>Breed Name: <strong>{{ dog.breed }}</strong></p>
    </div>
  </div>
</template>

<script setup>
const route = useRoute();
const { id } = route.params;
const { data: dog, pending, error } = await useFetch(`/api/dogs/${id}`);
</script>

SvelteKit

SvelteKit опирается на ту же идею, но использует Svelte — лёгкую реактивную библиотеку.

Пример API:

import { json, error } from '@sveltejs/kit';

export function GET({ params }) {
  const id = parseInt(params.id, 10);

  if (id >= 0 && id < dogBreeds.length) {
    return json({ breed: dogBreeds[id] });
  }

  throw error(404, 'Dog breed not found');
}

Загрузка данных:

export async function load({ params, fetch }) {
  const response = await fetch(`/api/dogs/${params.id}`);

  if (response.ok) {
    const dog = await response.json();
    return { dog };
  }

  throw error(response.status, 'Dog breed not found');
}

UI-файл:

<script>
  export let data;
</script>

<div>
  <h1>Dog Breed Profile</h1>
  <p>Breed Name: <strong>{data.dog.breed}</strong></p>
</div>

Заключение

Node.js давно перестал быть миром, где любой проект автоматически строится вокруг Express. Сейчас важно подобрать инструмент под конкретную задачу.

Если нужен быстрый микросервис или минимальный API, разработчики обращаются к Fastify или Hono.

Если команда работает над крупной системой, Nest и Adonis дают структуру, которая экономит силы в долгосрочной перспективе.

А для контентных веб-проектов самыми удобными остаются Next, Nuxt и SvelteKit.

Даже альтернативные рантаймы вроде Deno и Bun всё активнее вмешиваются в гонку, предлагая свою архитектуру и собственные фреймворки.

Современная разработка требует обдуманного выбора: каждая задача раскрывается лучше с подходящим инструментом.

Даже самый продвинутый фреймворк может стать обузой, если его воткнуть не туда.

И наоборот — грамотно подобранный инструмент часто превращает сложный проект в аккуратную систему.

Ваш любимый Python для ИИ — большая ошибка. Пора переходить на JavaScript

Мэттью Тайсон

Мэттью Тайсон

Он живёт в мире технологий так уверенно, будто там его дом. Его тянет разбирать сложные системы и находить в них скрытый смысл. Он любит момент, когда громоздкая идея вдруг превращается в понятный и рабочий инструмент. Он общался со многими яркими людьми из техносферы, и каждый разговор давал ему новый толчок. Его опыт огромен: от создания приложений до работы с умными алгоритмами. Он не боится новых задач, потому что именно в них появляется тот самый азарт, который двигает его вперёд.

Ещё статьи автора

Все статьи
Источник: InfoWorld
Подпишитесь на наши новости:
Нажимая кнопку «Подписаться», вы принимаете «Пользовательское соглашение» и даёте согласие с «Политикой обработки персональных данных»