Базовая настройка
const server = Bun.serve({
// `routes` требует Bun v1.2.3+
routes: {
// Статические маршруты
"/api/status": new Response("OK"),
// Динамические маршруты
"/users/:id": req => {
return new Response(`Привет пользователь ${req.params.id}!`);
},
// Обработчики для каждого HTTP-метода
"/api/posts": {
GET: () => new Response("Список постов"),
POST: async req => {
const body = await req.json();
return Response.json({ created: true, ...body });
},
},
// Подстановочный маршрут для всех маршрутов начинающихся с "/api/" которые не сопоставлены иначе
"/api/*": Response.json({ message: "Не найдено" }, { status: 404 }),
// Перенаправление с /blog/hello на /blog/hello/world
"/blog/hello": Response.redirect("/blog/hello/world"),
// Ленивая загрузка файла в память
"/favicon.ico": Bun.file("./favicon.ico"),
},
// (опционально) обработчик для несопоставленных маршрутов:
// Требуется если версия Bun < 1.2.3
fetch(req) {
return new Response("Не найдено", { status: 404 });
},
});
console.log(`Сервер запущен на ${server.url}`);Импорт HTML
Bun поддерживает прямой импорт HTML-файлов в код сервера что позволяет создавать полнофункциональные приложения с серверным и клиентским кодом. Импорт HTML работает в двух режимах:
Разработка (bun --hot): Ресурсы компилируются по требованию во время выполнения обеспечивая горячую замену модулей (HMR) для быстрой итеративной разработки. Когда вы изменяете код фронтенда браузер автоматически обновляется без полной перезагрузки страницы.
Продакшен (bun build): При сборке с bun build --target=bun оператор import index from "./index.html" разрешается в предварительно собранный объект манифеста содержащий все клиентские ресурсы. Bun.serve использует этот манифест для обслуживания оптимизированных ресурсов с нулевыми накладными расходами на компиляцию во время выполнения. Это идеально для развёртывания в продакшен.
import myReactSinglePageApp from "./index.html";
Bun.serve({
routes: {
"/": myReactSinglePageApp,
},
});Импорт HTML не просто обслуживает HTML — это полнофункциональный сборщик фронтенда транспайлер и набор инструментов построенный с использованием сборщика транспайлера JavaScript и парсера CSS от Bun. Вы можете использовать это для создания полнофункциональных фронтендов с React TypeScript Tailwind CSS и другими.
Для полного руководства по созданию полнофункциональных приложений с импортом HTML включая подробные примеры и лучшие практики смотрите /docs/bundler/fullstack.
Конфигурация
Изменение port и hostname
Для настройки порта и hostname на которых сервер будет слушать установите port и hostname в объекте опций.
Bun.serve({
port: 8080, // по умолчанию $BUN_PORT, $PORT, $NODE_PORT иначе 3000
hostname: "mydomain.com", // по умолчанию "0.0.0.0"
fetch(req) {
return new Response("404!");
},
});Для случайного выбора доступного порта установите port в 0.
const server = Bun.serve({
port: 0, // случайный порт
fetch(req) {
return new Response("404!");
},
});
// server.port это случайно выбранный порт
console.log(server.port);Вы можете просмотреть выбранный порт получив доступ к свойству port объекта сервера или получив доступ к свойству url.
console.log(server.port); // 3000
console.log(server.url); // http://localhost:3000Настройка порта по умолчанию
Bun поддерживает несколько опций и переменных окружения для настройки порта по умолчанию. Порт по умолчанию используется когда опция port не установлена.
- Флаг CLI
--port
bun --port=4002 server.ts- Переменная окружения
BUN_PORT
BUN_PORT=4002 bun server.ts- Переменная окружения
PORT
PORT=4002 bun server.ts- Переменная окружения
NODE_PORT
NODE_PORT=4002 bun server.tsСокеты доменов Unix
Для прослушивания сокета домена Unix передайте опцию unix с путём к сокету.
Bun.serve({
unix: "/tmp/my-socket.sock", // путь к сокету
fetch(req) {
return new Response(`404!`);
},
});Сокеты абстрактного пространства имён
Bun поддерживает сокеты абстрактного пространства имён Linux. Для использования сокета абстрактного пространства имён добавьте префикс null байт к пути unix.
Bun.serve({
unix: "\0my-abstract-socket", // сокет абстрактного пространства имён
fetch(req) {
return new Response(`404!`);
},
});В отличие от сокетов доменов Unix сокеты абстрактного пространства имён не привязаны к файловой системе и автоматически удаляются когда последняя ссылка на сокет закрывается.
idleTimeout
Для настройки таймаута бездействия установите поле idleTimeout в Bun.serve.
Bun.serve({
// 10 секунд:
idleTimeout: 10,
fetch(req) {
return new Response("Bun!");
},
});Это максимальное время в течение которого соединение может быть бездействующим перед тем как сервер закроет его. Соединение бездействует если нет отправленных или полученных данных.
Синтаксис export default
До сих пор примеры на этой странице использовали явный API Bun.serve. Bun также поддерживает альтернативный синтаксис.
import { type Serve } from "bun";
export default {
fetch(req) {
return new Response("Bun!");
},
} satisfies Serve;Вместо передачи опций сервера в Bun.serve используйте export default. Этот файл может быть выполнен как есть; когда Bun видит файл с экспортом default содержащим обработчик fetch он передаёт его в Bun.serve под капотом.
Горячая перезагрузка маршрутов
Обновляйте маршруты без перезапуска сервера используя server.reload():
const server = Bun.serve({
routes: {
"/api/version": () => Response.json({ version: "1.0.0" }),
},
});
// Развёртывание новых маршрутов без простоя
server.reload({
routes: {
"/api/version": () => Response.json({ version: "2.0.0" }),
},
});Методы жизненного цикла сервера
server.stop()
Для остановки сервера от приёма новых соединений:
const server = Bun.serve({
fetch(req) {
return new Response("Привет!");
},
});
// Плавная остановка сервера (ожидает запросы в процессе выполнения)
await server.stop();
// Принудительная остановка и закрытие всех активных соединений
await server.stop(true);По умолчанию stop() позволяет запросам в процессе выполнения и WebSocket-соединениям завершиться. Передайте true для немедленного завершения всех соединений.
server.ref() и server.unref()
Управляйте тем держит ли сервер процесс Bun активным:
// Не держать процесс активным если сервер единственное что работает
server.unref();
// Восстановить поведение по умолчанию - держать процесс активным
server.ref();server.reload()
Обновите обработчики сервера без перезапуска:
const server = Bun.serve({
routes: {
"/api/version": Response.json({ version: "v1" }),
},
fetch(req) {
return new Response("v1");
},
});
// Обновление до нового обработчика
server.reload({
routes: {
"/api/version": Response.json({ version: "v2" }),
},
fetch(req) {
return new Response("v2");
},
});Это полезно для разработки и горячей перезагрузки. Только fetch, error и routes могут быть обновлены.
Управление для каждого запроса
server.timeout(Request, seconds)
Установите пользовательский таймаут бездействия для отдельных запросов:
const server = Bun.serve({
async fetch(req, server) {
// Установка 60-секундного таймаута для этого запроса
server.timeout(req, 60);
// Если они займут больше 60 секунд на отправку тела запрос будет прерван
await req.text();
return new Response("Готово!");
},
});Передайте 0 для отключения таймаута для запроса.
server.requestIP(Request)
Получите информацию о IP-адресе и порте клиента:
const server = Bun.serve({
fetch(req, server) {
const address = server.requestIP(req);
if (address) {
return new Response(`IP клиента: ${address.address}, Порт: ${address.port}`);
}
return new Response("Неизвестный клиент");
},
});Возвращает null для закрытых запросов или сокетов доменов Unix.
Метрики сервера
server.pendingRequests и server.pendingWebSockets
Мониторьте активность сервера с помощью встроенных счётчиков:
const server = Bun.serve({
fetch(req, server) {
return new Response(
`Активные запросы: ${server.pendingRequests}\n` + `Активные WebSocket: ${server.pendingWebSockets}`,
);
},
});server.subscriberCount(topic)
Получите количество подписчиков для темы WebSocket:
const server = Bun.serve({
fetch(req, server) {
const chatUsers = server.subscriberCount("chat");
return new Response(`${chatUsers} пользователей в чате`);
},
websocket: {
message(ws) {
ws.subscribe("chat");
},
},
});Бенчмарки
Ниже представлены реализации Bun и Node.js простого HTTP-сервера который отвечает Bun! на каждый входящий Request.
Bun.serve({
fetch(req: Request) {
return new Response("Bun!");
},
port: 3000,
});require("http")
.createServer((req, res) => res.end("Bun!"))
.listen(8080);Сервер Bun.serve может обрабатывать примерно в 2.5 раза больше запросов в секунду чем Node.js на Linux.
| Runtime | Запросов в секунду |
|---|---|
| Node 16 | ~64,000 |
| Bun | ~160,000 |
Практический пример: REST API
Вот базовый REST API с базой данных использующий маршрутизатор Bun с нулевыми зависимостями:
import type { Post } from "./types.ts";
import { Database } from "bun:sqlite";
const db = new Database("posts.db");
db.exec(`
CREATE TABLE IF NOT EXISTS posts (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
content TEXT NOT NULL,
created_at TEXT NOT NULL
)
`);
Bun.serve({
routes: {
// Список постов
"/api/posts": {
GET: () => {
const posts = db.query("SELECT * FROM posts").all();
return Response.json(posts);
},
// Создание поста
POST: async req => {
const post: Omit<Post, "id" | "created_at"> = await req.json();
const id = crypto.randomUUID();
db.query(
`INSERT INTO posts (id, title, content, created_at)
VALUES (?, ?, ?, ?)`,
).run(id, post.title, post.content, new Date().toISOString());
return Response.json({ id, ...post }, { status: 201 });
},
},
// Получение поста по ID
"/api/posts/:id": req => {
const post = db.query("SELECT * FROM posts WHERE id = ?").get(req.params.id);
if (!post) {
return new Response("Не найдено", { status: 404 });
}
return Response.json(post);
},
},
error(error) {
console.error(error);
return new Response("Внутренняя ошибка сервера", { status: 500 });
},
});export interface Post {
id: string;
title: string;
content: string;
created_at: string;
}Справочник
interface Server extends Disposable {
/**
* Остановка сервера от приёма новых соединений.
* @param closeActiveConnections Если true немедленно завершает все соединения
* @returns Promise который разрешается когда сервер остановлен
*/
stop(closeActiveConnections?: boolean): Promise<void>;
/**
* Обновление обработчиков без перезапуска сервера.
* Только обработчики fetch и error могут быть обновлены.
*/
reload(options: Serve): void;
/**
* Выполнение запроса к работающему серверу.
* Полезно для тестирования или внутренней маршрутизации.
*/
fetch(request: Request | string): Response | Promise<Response>;
/**
* Обновление HTTP-запроса до WebSocket-соединения.
* @returns true если обновление успешно false если не удалось
*/
upgrade<T = undefined>(
request: Request,
options?: {
headers?: Bun.HeadersInit;
data?: T;
},
): boolean;
/**
* Публикация сообщения всем WebSocket-клиентам подписанным на тему.
* @returns Отправленные байты 0 если отброшено -1 если применено обратное давление
*/
publish(
topic: string,
data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer,
compress?: boolean,
): ServerWebSocketSendStatus;
/**
* Получение количества WebSocket-клиентов подписанных на тему.
*/
subscriberCount(topic: string): number;
/**
* Получение IP-адреса и порта клиента.
* @returns null для закрытых запросов или сокетов Unix
*/
requestIP(request: Request): SocketAddress | null;
/**
* Установка пользовательского таймаута бездействия для запроса.
* @param seconds Таймаут в секундах 0 для отключения
*/
timeout(request: Request, seconds: number): void;
/**
* Держать процесс активным пока сервер работает.
*/
ref(): void;
/**
* Разрешить выход процесса если сервер единственное что работает.
*/
unref(): void;
/** Количество запросов в процессе выполнения */
readonly pendingRequests: number;
/** Количество активных WebSocket-соединений */
readonly pendingWebSockets: number;
/** URL сервера включая протокол hostname и порт */
readonly url: URL;
/** Порт на котором слушает сервер */
readonly port: number;
/** Hostname к которому привязан сервер */
readonly hostname: string;
/** Находится ли сервер в режиме разработки */
readonly development: boolean;
/** Идентификатор экземпляра сервера */
readonly id: string;
}
interface WebSocketHandler<T = undefined> {
/** Максимальный размер сообщения WebSocket в байтах */
maxPayloadLength?: number;
/** Байты очереждённых сообщений перед применением обратного давления */
backpressureLimit?: number;
/** Закрывать ли соединение при достижении лимита обратного давления */
closeOnBackpressureLimit?: boolean;
/** Вызывается когда обратное давление снято */
drain?(ws: ServerWebSocket<T>): void | Promise<void>;
/** Секунды до таймаута бездействия */
idleTimeout?: number;
/** Включить сжатие deflate для каждого сообщения */
perMessageDeflate?:
| boolean
| {
compress?: WebSocketCompressor | boolean;
decompress?: WebSocketCompressor | boolean;
};
/** Отправка ping-фреймов для поддержания соединения */
sendPings?: boolean;
/** Получает ли сервер свои опубликованные сообщения */
publishToSelf?: boolean;
/** Вызывается при открытии соединения */
open?(ws: ServerWebSocket<T>): void | Promise<void>;
/** Вызывается при получении сообщения */
message(ws: ServerWebSocket<T>, message: string | Buffer): void | Promise<void>;
/** Вызывается при закрытии соединения */
close?(ws: ServerWebSocket<T>, code: number, reason: string): void | Promise<void>;
/** Вызывается при получении ping-фрейма */
ping?(ws: ServerWebSocket<T>, data: Buffer): void | Promise<void>;
/** Вызывается при получении pong-фрейма */
pong?(ws: ServerWebSocket<T>, data: Buffer): void | Promise<void>;
}
interface TLSOptions {
/** Цепочка сертификатов CA */
ca?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;
/** Сертификат сервера */
cert?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;
/** Путь к файлу параметров DH */
dhParamsFile?: string;
/** Закрытый ключ */
key?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;
/** Уменьшение использования памяти TLS */
lowMemoryMode?: boolean;
/** Пассфраза закрытого ключа */
passphrase?: string;
/** Флаги опций OpenSSL */
secureOptions?: number;
/** Имя сервера для SNI */
serverName?: string;
}