الإعداد الأساسي
const server = Bun.serve({
// `routes` يتطلب Bun v1.2.3+
routes: {
// المسارات الثابتة
"/api/status": new Response("OK"),
// المسارات الديناميكية
"/users/:id": req => {
return new Response(`Hello User ${req.params.id}!`);
},
// معالجات لكل طريقة HTTP
"/api/posts": {
GET: () => new Response("List posts"),
POST: async req => {
const body = await req.json();
return Response.json({ created: true, ...body });
},
},
// مسار بديل لجميع المسارات التي تبدأ بـ "/api/" ولم تتم مطابقتها
"/api/*": Response.json({ message: "Not found" }, { 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("Not Found", { status: 404 });
},
});
console.log(`Server running at ${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 فقط — إنها أداة تجميع واجهة أمامية كاملة الميزات، ومحول، ومجموعة أدوات مبنية باستخدام مجمع في Bun، ومحول JavaScript، ومحلل CSS. يمكنك استخدام هذا لبناء واجهات أمامية كاملة الميزات مع React و TypeScript و Tailwind CSS والمزيد.
للحصول على دليل كامل لبناء تطبيقات كاملة المكدس مع واردات HTML، بما في ذلك أمثلة مفصلة وأفضل الممارسات، راجع /docs/bundler/fullstack.
التكوين
تغيير port و 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. لاستخدام مقبس مساحة اسم مجرد، بادئ مسار unix بايت null.
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
حتى الآن، استخدمت الأمثلة في هذه الصفحة واجهة 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("Hello!");
},
});
// إيقاف الخادم بسلاسة (ينتظر الطلبات قيد التنفيذ)
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("Done!");
},
});مرر 0 لتعطيل المهلة لطلب.
server.requestIP(Request)
الحصول على معلومات IP والمنفذ للعميل:
const server = Bun.serve({
fetch(req, server) {
const address = server.requestIP(req);
if (address) {
return new Response(`Client IP: ${address.address}, Port: ${address.port}`);
}
return new Response("Unknown client");
},
});يرجع null للطلبات المغلقة أو مقابس مجال Unix.
مقاييس الخادم
server.pendingRequests و server.pendingWebSockets
راقب نشاط الخادم مع العدادات المدمجة:
const server = Bun.serve({
fetch(req, server) {
return new Response(
`Active requests: ${server.pendingRequests}\n` + `Active WebSockets: ${server.pendingWebSockets}`,
);
},
});server.subscriberCount(topic)
احصل على عدد المشتركين لموضوع WebSocket:
const server = Bun.serve({
fetch(req, server) {
const chatUsers = server.subscriberCount("chat");
return new Response(`${chatUsers} users in chat`);
},
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.
| وقت التشغيل | طلبات في الثانية |
|---|---|
| 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("Not Found", { status: 404 });
}
return Response.json(post);
},
},
error(error) {
console.error(error);
return new Response("Internal Server Error", { 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 Bytes المرسلة، 0 إذا تم إسقاطها، -1 إذا تم تطبيق backpressure
*/
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;
/** عدد طلبات HTTP قيد التنفيذ */
readonly pendingRequests: number;
/** عدد اتصالات WebSocket النشطة */
readonly pendingWebSockets: number;
/** عنوان الخادم بما في ذلك البروتوكول واسم المضيف والمنفذ */
readonly url: URL;
/** المنفذ الذي يستمع إليه الخادم */
readonly port: number;
/** اسم المضيف المرتبط به الخادم */
readonly hostname: string;
/** ما إذا كان الخادم في وضع التطوير */
readonly development: boolean;
/** معرف مثيل الخادم */
readonly id: string;
}
interface WebSocketHandler<T = undefined> {
/** الحد الأقصى لحجم رسالة WebSocket بالبايت */
maxPayloadLength?: number;
/** بايت الرسائل في قائمة الانتظار قبل تطبيق backpressure */
backpressureLimit?: number;
/** ما إذا كان يجب إغلاق الاتصال عند الوصول إلى حد backpressure */
closeOnBackpressureLimit?: boolean;
/** يُستدعى عند تخفيف backpressure */
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;
}