Вы можете добавить маршруты в Bun.serve() используя свойство routes (для статических путей параметров и подстановочных знаков) или обрабатывая несопоставленные запросы с помощью метода fetch.
Маршрутизатор Bun.serve() построен на основе древовидного подхода от uWebSocket и добавляет SIMD-ускоренное декодирование параметров маршрута и кэширование структур JavaScriptCore для достижения пределов производительности современного оборудования.
Базовая настройка
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-запросов.
// Упрощено для краткости
interface BunRequest<T extends string> extends Request {
params: Record<T, string>;
readonly cookies: CookieMap;
}Асинхронные маршруты
Async/await
Вы можете использовать async/await в обработчиках маршрутов для возврата Promise<Response>.
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> из обработчика маршрута.
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);
});
},
},
});Приоритет маршрутов
Маршруты сопоставляются в порядке специфичности:
- Точные маршруты (
/users/all) - Маршруты с параметрами (
/users/:id) - Подстановочные маршруты (
/users/*) - Глобальный перехватчик (
/*)
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.
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() оптимизирует их для диспетчеризации с нулевым выделением памяти — идеально для проверок работоспособности перенаправлений и фиксированного контента:
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).
Ответы файлами против статических ответов
При обслуживании файлов в маршрутах существуют два различных поведения в зависимости от того буферизируете ли вы содержимое файла или обслуживаете его напрямую:
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 в качестве тела.
Bun.serve({
fetch(req) {
return new Response(Bun.file("./hello.txt"));
},
});Вы можете отправить часть файла используя метод slice(start, end) объекта Bun.file. Это автоматически устанавливает заголовки Content-Range и Content-Length на объекте Response.
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>.
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:
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 также поддерживаются:
Bun.serve({
fetch(req) {
// Пересылка запроса на другой сервер.
return fetch("https://example.com");
},
});Вы также можете получить доступ к объекту Server из обработчика fetch. Это второй аргумент переданный в функцию fetch.
// `server` передаётся как второй аргумент в `fetch`.
const server = Bun.serve({
fetch(req, server) {
const ip = server.requestIP(req);
return new Response(`Ваш IP: ${ip.address}`);
},
});