Skip to content

Вы можете добавить маршруты в Bun.serve() используя свойство routes (для статических путей параметров и подстановочных знаков) или обрабатывая несопоставленные запросы с помощью метода fetch.

Маршрутизатор Bun.serve() построен на основе древовидного подхода от uWebSocket и добавляет SIMD-ускоренное декодирование параметров маршрута и кэширование структур JavaScriptCore для достижения пределов производительности современного оборудования.

Базовая настройка

ts
Bun.serve({
  routes: {
    "/": () => new Response("Главная"),
    "/api": () => Response.json({ success: true }),
    "/users": async () => Response.json({ users: [] }),
  },
  fetch() {
    return new Response("Несопоставленный маршрут");
  },
});

Маршруты в Bun.serve() получают BunRequest (который расширяет Request) и возвращают Response или Promise<Response>. Это упрощает использование одного и того же кода для отправки и получения HTTP-запросов.

ts
// Упрощено для краткости
interface BunRequest<T extends string> extends Request {
  params: Record<T, string>;
  readonly cookies: CookieMap;
}

Асинхронные маршруты

Async/await

Вы можете использовать async/await в обработчиках маршрутов для возврата Promise<Response>.

ts
import { sql, serve } from "bun";

serve({
  port: 3001,
  routes: {
    "/api/version": async () => {
      const [version] = await sql`SELECT version()`;
      return Response.json(version);
    },
  },
});

Promise

Вы также можете вернуть Promise<Response> из обработчика маршрута.

ts
import { sql, serve } from "bun";

serve({
  routes: {
    "/api/version": () => {
      return new Promise(resolve => {
        setTimeout(async () => {
          const [version] = await sql`SELECT version()`;
          resolve(Response.json(version));
        }, 100);
      });
    },
  },
});

Приоритет маршрутов

Маршруты сопоставляются в порядке специфичности:

  1. Точные маршруты (/users/all)
  2. Маршруты с параметрами (/users/:id)
  3. Подстановочные маршруты (/users/*)
  4. Глобальный перехватчик (/*)
ts
Bun.serve({
  routes: {
    // Наиболее специфичный первый
    "/api/users/me": () => new Response("Текущий пользователь"),
    "/api/users/:id": req => new Response(`Пользователь ${req.params.id}`),
    "/api/*": () => new Response("Перехватчик API"),
    "/*": () => new Response("Глобальный перехватчик"),
  },
});

Типобезопасные параметры маршрута

TypeScript анализирует параметры маршрута при передаче их как строковый литерал поэтому ваш редактор будет показывать автозаполнение при доступе к request.params.

ts
import type { BunRequest } from "bun";

Bun.serve({
  routes: {
    // TypeScript знает форму params при передаче как строковый литерал
    "/orgs/:orgId/repos/:repoId": req => {
      const { orgId, repoId } = req.params;
      return Response.json({ orgId, repoId });
    },

    "/orgs/:orgId/repos/:repoId/settings": (
      // опционально: вы можете явно передать тип в BunRequest:
      req: BunRequest<"/orgs/:orgId/repos/:repoId/settings">,
    ) => {
      const { orgId, repoId } = req.params;
      return Response.json({ orgId, repoId });
    },
  },
});

Значения параметров маршрута с процентным кодированием автоматически декодируются. Символы Unicode поддерживаются. Недопустимый unicode заменяется символом замены unicode &0xFFFD;.

Статические ответы

Маршруты также могут быть объектами Response (без функции обработчика). Bun.serve() оптимизирует их для диспетчеризации с нулевым выделением памяти — идеально для проверок работоспособности перенаправлений и фиксированного контента:

ts
Bun.serve({
  routes: {
    // Проверки работоспособности
    "/health": new Response("OK"),
    "/ready": new Response("Ready", {
      headers: {
        // Передача пользовательских заголовков
        "X-Ready": "1",
      },
    }),

    // Перенаправления
    "/blog": Response.redirect("https://bun.com/blog"),

    // Ответы API
    "/api/config": Response.json({
      version: "1.0.0",
      env: "production",
    }),
  },
});

Статические ответы не выделяют дополнительную память после инициализации. Обычно можно ожидать как минимум 15% улучшение производительности по сравнению с ручным возвратом объекта Response.

Статические маршруты кэшируются на время жизни объекта сервера. Для перезагрузки статических маршрутов вызовите server.reload(options).

Ответы файлами против статических ответов

При обслуживании файлов в маршрутах существуют два различных поведения в зависимости от того буферизируете ли вы содержимое файла или обслуживаете его напрямую:

ts
Bun.serve({
  routes: {
    // Статический маршрут - содержимое буферизируется в памяти при запуске
    "/logo.png": new Response(await Bun.file("./logo.png").bytes()),

    // Маршрут файла - содержимое читается из файловой системы при каждом запросе
    "/download.zip": new Response(Bun.file("./download.zip")),
  },
});

Статические маршруты (new Response(await file.bytes())) буферизируют содержимое в памяти при запуске:

  • Нулевой ввод-вывод файловой системы во время запросов — содержимое обслуживается полностью из памяти
  • Поддержка ETag — автоматически генерирует и валидирует ETags для кэширования
  • If-None-Match — возвращает 304 Not Modified когда ETag клиента совпадает
  • Нет обработки 404 — отсутствующие файлы вызывают ошибки запуска а не runtime 404
  • Использование памяти — полное содержимое файла хранится в RAM
  • Лучше для: небольших статических ресурсов ответов API часто используемых файлов

Маршруты файлов (new Response(Bun.file(path))) читают из файловой системы при каждом запросе:

  • Чтение файловой системы при каждом запросе — проверяет существование файла и читает содержимое
  • Встроенная обработка 404 — возвращает 404 Not Found если файл не существует или становится недоступным
  • Поддержка Last-Modified — использует время модификации файла для заголовков If-Modified-Since
  • If-Modified-Since — возвращает 304 Not Modified когда файл не изменился с момента кэшированной версии клиента
  • Поддержка диапазонных запросов — автоматически обрабатывает запросы частичного содержимого с заголовками Content-Range
  • Потоковые передачи — использует буферизированный читатель с обработкой обратного давления для эффективного использования памяти
  • Эффективно по памяти — буферизирует только небольшие части во время передачи а не весь файл
  • Лучше для: больших файлов динамического контента пользовательских загрузок файлов которые часто изменяются

Потоковая передача файлов

Для потоковой передачи файла верните объект Response с объектом BunFile в качестве тела.

ts
Bun.serve({
  fetch(req) {
    return new Response(Bun.file("./hello.txt"));
  },
});

Вы можете отправить часть файла используя метод slice(start, end) объекта Bun.file. Это автоматически устанавливает заголовки Content-Range и Content-Length на объекте Response.

ts
Bun.serve({
  fetch(req) {
    // разбор заголовка `Range`
    const [start = 0, end = Infinity] = req.headers
      .get("Range") // Range: bytes=0-100
      .split("=") // ["Range: bytes", "0-100"]
      .at(-1) // "0-100"
      .split("-") // ["0", "100"]
      .map(Number); // [0, 100]

    // возврат части файла
    const bigFile = Bun.file("./big-video.mp4");
    return new Response(bigFile.slice(start, end));
  },
});

Обработчик запросов fetch

Обработчик fetch обрабатывает входящие запросы которые не были сопоставлены ни с одним маршрутом. Он получает объект Request и возвращает Response или Promise<Response>.

ts
Bun.serve({
  fetch(req) {
    const url = new URL(req.url);
    if (url.pathname === "/") return new Response("Страница!");
    if (url.pathname === "/blog") return new Response("Блог!");
    return new Response("404!");
  },
});

Обработчик fetch поддерживает async/await:

ts
import { sleep, serve } from "bun";

serve({
  async fetch(req) {
    const start = performance.now();
    await sleep(10);
    const end = performance.now();
    return new Response(`Сон в течение ${end - start}мс`);
  },
});

Ответы на основе Promise также поддерживаются:

ts
Bun.serve({
  fetch(req) {
    // Пересылка запроса на другой сервер.
    return fetch("https://example.com");
  },
});

Вы также можете получить доступ к объекту Server из обработчика fetch. Это второй аргумент переданный в функцию fetch.

ts
// `server` передаётся как второй аргумент в `fetch`.
const server = Bun.serve({
  fetch(req, server) {
    const ip = server.requestIP(req);
    return new Response(`Ваш IP: ${ip.address}`);
  },
});

Bun от www.bunjs.com.cn