Skip to content

Um zu beginnen, importieren Sie HTML-Dateien und übergeben Sie sie an die routes-Option in Bun.serve().

ts
import { serve } from "bun";
import dashboard from "./dashboard.html";
import homepage from "./index.html";

const server = serve({
  routes: {
    // ** HTML-Importe **
    // Bündele und route index.html nach "/". Dies verwendet HTMLRewriter, um
    // das HTML nach `<script>`- und `<link>`-Tags zu durchsuchen, führt Buns JavaScript
    // & CSS-Bundler darauf aus, transpiliert TypeScript, JSX und TSX,
    // wandelt CSS mit Buns CSS-Parser um und stellt das Ergebnis bereit.
    "/": homepage,
    // Bündele und route dashboard.html nach "/dashboard"
    "/dashboard": dashboard,

    // ** API-Endpunkte ** (Bun v1.2.3+ erforderlich)
    "/api/users": {
      async GET(req) {
        const users = await sql`SELECT * FROM users`;
        return Response.json(users);
      },
      async POST(req) {
        const { name, email } = await req.json();
        const [user] = await sql`INSERT INTO users (name, email) VALUES (${name}, ${email})`;
        return Response.json(user);
      },
    },
    "/api/users/:id": async req => {
      const { id } = req.params;
      const [user] = await sql`SELECT * FROM users WHERE id = ${id}`;
      return Response.json(user);
    },
  },

  // Entwicklungsmodus aktivieren für:
  // - Detaillierte Fehlermeldungen
  // - Hot Reloading (Bun v1.2.3+ erforderlich)
  development: true,
});

console.log(`Server läuft auf ${server.url}`);
bash
bun run app.ts

HTML-Routen

HTML-Importe als Routen

Das Web beginnt mit HTML, und so auch Buns Fullstack-dev-server.

Um Einstiegspunkte für Ihr Frontend anzugeben, importieren Sie HTML-Dateien in Ihre JavaScript/TypeScript/TSX/JSX-Dateien.

ts
import dashboard from "./dashboard.html";
import homepage from "./index.html";

Diese HTML-Dateien werden als Routen in Buns dev-server verwendet, den Sie an Bun.serve() übergeben können.

ts
Bun.serve({
  routes: {
    "/": homepage,
    "/dashboard": dashboard,
  },

  fetch(req) {
    // ... API-Anfragen
  },
});

Wenn Sie eine Anfrage an /dashboard oder / stellen, bündelt Bun automatisch die <script>- und <link>-Tags in den HTML-Dateien, stellt sie als statische Routen bereit und liefert das Ergebnis.

HTML-Verarbeitungsbeispiel

Eine index.html-Datei wie diese:

html
<!DOCTYPE html>
<html>
  <head>
    <title>Startseite</title>
    <link rel="stylesheet" href="./reset.css" />
    <link rel="stylesheet" href="./styles.css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="./sentry-and-preloads.ts"></script>
    <script type="module" src="./my-app.tsx"></script>
  </body>
</html>

Wird zu so etwas:

html
<!DOCTYPE html>
<html>
  <head>
    <title>Startseite</title>
    <link rel="stylesheet" href="/index-[hash].css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/index-[hash].js"></script>
  </body>
</html>

React-Integration

Um React in Ihrem Client-seitigen Code zu verwenden, importieren Sie react-dom/client und rendern Sie Ihre App.

src/backend.ts
ts
import dashboard from "../public/dashboard.html";
import { serve } from "bun";

serve({
  routes: {
    "/": dashboard,
  },
  async fetch(req) {
    // ... API-Anfragen
    return new Response("Hallo Welt");
  },
});
tsx
import { createRoot } from 'react-dom/client';
import App from './app';

const container = document.getElementById('root');
const root = createRoot(container!);
root.render(<App />);
html
<!DOCTYPE html>
<html>
  <head>
    <title>Dashboard</title>
    <link rel="stylesheet" href="../src/styles.css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="../src/frontend.tsx"></script>
  </body>
</html>
tsx
import { useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>Dashboard</h1>
      <button onClick={() => setCount(count + 1)}>Anzahl: {count}</button>
    </div>
  );
}

Entwicklungsmodus

Beim lokalen Erstellen aktivieren Sie den Entwicklungsmodus, indem Sie development: true in Bun.serve() setzen.

ts
import homepage from "./index.html";
import dashboard from "./dashboard.html";

Bun.serve({
  routes: {
    "/": homepage,
    "/dashboard": dashboard,
  },

  development: true,

  fetch(req) {
    // ... API-Anfragen
  },
});

Funktionen des Entwicklungsmodus

Wenn development auf true gesetzt ist, wird Bun:

  • Den SourceMap-Header in der Antwort einschließen, damit devtools den ursprünglichen Quellcode anzeigen können
  • Minifizierung deaktivieren
  • Assets bei jeder Anfrage an eine .html-Datei neu bündeln
  • Hot Module Reloading aktivieren (es sei denn, hmr: false ist gesetzt)
  • Console-Logs vom Browser zum Terminal weiterleiten

Erweiterte Entwicklungskonfiguration

Bun.serve() unterstützt das Weiterleiten von Console-Logs vom Browser zum Terminal.

Um dies zu aktivieren, übergeben Sie console: true im development-Objekt in Bun.serve().

ts
import homepage from "./index.html";

Bun.serve({
  // development kann auch ein Objekt sein.
  development: {
    // Hot Module Reloading aktivieren
    hmr: true,

    // Console-Logs vom Browser zum Terminal weiterleiten
    console: true,
  },

  routes: {
    "/": homepage,
  },
});

Wenn console: true gesetzt ist, streamt Bun Console-Logs vom Browser zum Terminal. Dies verwendet die bestehende WebSocket-Verbindung von HMR, um die Logs zu senden.

Entwicklung vs Produktion

FunktionEntwicklungProduktion
Source maps✅ Aktiviert❌ Deaktiviert
Minifizierung❌ Deaktiviert✅ Aktiviert
Hot reloading✅ Aktiviert❌ Deaktiviert
Asset-Bündelung🔄 Bei jeder Anfrage💾 Gecacht
Console-Logging🖥️ Browser → Terminal❌ Deaktiviert
Fehlerdetails📝 Detailliert🔒 Minimal

Produktionsmodus

Hot Reloading und development: true helfen Ihnen, schnell zu iterieren, aber in der Produktion sollte Ihr Server so schnell wie möglich sein und so wenige externe Abhängigkeiten wie möglich haben.

Ahead-of-Time-Bündelung (Empfohlen)

Ab Bun v1.2.17 können Sie Bun.build oder bun build verwenden, um Ihre Fullstack-Anwendung im Voraus zu bündeln.

bash
bun build --target=bun --production --outdir=dist ./src/index.ts

Wenn Buns Bundler einen HTML-Import von Server-seitigem Code sieht, bündelt er die referenzierten JavaScript/TypeScript/TSX/JSX- und CSS-Dateien in ein Manifest-Objekt, das Bun.serve() verwenden kann, um die Assets bereitzustellen.

src/backend.ts
ts
import { serve } from "bun";
import index from "./index.html";

serve({
  routes: { "/": index },
});

Runtime-Bündelung

Wenn das Hinzufügen eines Build-Schritts zu kompliziert ist, können Sie development: false in Bun.serve() setzen.

Dies wird:

  • In-Memory-Caching von gebündelten Assets aktivieren. Bun bündelt Assets faul bei der ersten Anfrage an eine .html-Datei und cached das Ergebnis im Speicher, bis der Server neu startet.
  • Cache-Control-Header und ETag-Header aktivieren
  • JavaScript/TypeScript/TSX/JSX-Dateien minifizieren
src/backend.ts
ts
import { serve } from "bun";
import homepage from "./index.html";

serve({
  routes: {
    "/": homepage,
  },

  // Produktionsmodus
  development: false,
});

API-Routen

HTTP-Methoden-Handler

Definieren Sie API-Endpunkte mit HTTP-Methoden-Handlern:

src/backend.ts
ts
import { serve } from "bun";

serve({
  routes: {
    "/api/users": {
      async GET(req) {
        // GET-Anfragen verarbeiten
        const users = await getUsers();
        return Response.json(users);
      },

      async POST(req) {
        // POST-Anfragen verarbeiten
        const userData = await req.json();
        const user = await createUser(userData);
        return Response.json(user, { status: 201 });
      },

      async PUT(req) {
        // PUT-Anfragen verarbeiten
        const userData = await req.json();
        const user = await updateUser(userData);
        return Response.json(user);
      },

      async DELETE(req) {
        // DELETE-Anfragen verarbeiten
        await deleteUser(req.params.id);
        return new Response(null, { status: 204 });
      },
    },
  },
});

Dynamische Routen

Verwenden Sie URL-Parameter in Ihren Routen:

ts
serve({
  routes: {
    // Einzelner Parameter
    "/api/users/:id": async req => {
      const { id } = req.params;
      const user = await getUserById(id);
      return Response.json(user);
    },

    // Mehrere Parameter
    "/api/users/:userId/posts/:postId": async req => {
      const { userId, postId } = req.params;
      const post = await getPostByUser(userId, postId);
      return Response.json(post);
    },

    // Wildcard-Routen
    "/api/files/*": async req => {
      const filePath = req.params["*"];
      const file = await getFile(filePath);
      return new Response(file);
    },
  },
});

Anfrageverarbeitung

src/backend.ts
ts
serve({
  routes: {
    "/api/data": {
      async POST(req) {
        // JSON-Body parsen
        const body = await req.json();

        // Header zugreifen
        const auth = req.headers.get("Authorization");

        // URL-Parameter zugreifen
        const { id } = req.params;

        // Query-Parameter zugreifen
        const url = new URL(req.url);
        const page = url.searchParams.get("page") || "1";

        // Antwort zurückgeben
        return Response.json({
          message: "Daten verarbeitet",
          page: parseInt(page),
          authenticated: !!auth,
        });
      },
    },
  },
});

Plugins

Buns Bundler-Plugins werden auch beim Bündeln statischer Routen unterstützt.

Um Plugins für Bun.serve zu konfigurieren, fügen Sie ein plugins-Array im [serve.static]-Abschnitt Ihrer bunfig.toml hinzu.

TailwindCSS-Plugin

Sie können TailwindCSS verwenden, indem Sie das tailwindcss-Paket und das bun-plugin-tailwind-Plugin installieren und hinzufügen.

bash
bun add tailwindcss bun-plugin-tailwind
[serve.static]
plugins = ["bun-plugin-tailwind"]

Dies ermöglicht Ihnen, TailwindCSS-Utility-Klassen in Ihren HTML- und CSS-Dateien zu verwenden. Alles, was Sie tun müssen, ist tailwindcss irgendwo zu importieren:

html
<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="tailwindcss" />
  </head>
  <!-- der Rest Ihres HTML... -->
</html>

Alternativ können Sie TailwindCSS in Ihrer CSS-Datei importieren:

css
@import "tailwindcss";

.custom-class {
  @apply bg-red-500 text-white;
}
html
<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="./style.css" />
  </head>
  <!-- der Rest Ihres HTML... -->
</html>

Benutzerdefinierte Plugins

Jede JS-Datei oder jedes Modul, das ein gültiges Bundler-Plugin-Objekt exportiert (im Wesentlichen ein Objekt mit einem name- und setup-Feld), kann in das plugins-Array eingefügt werden:

[serve.static]
plugins = ["./my-plugin-implementation.ts"]
my-plugin-implementation.ts
ts
import type { BunPlugin } from "bun";

const myPlugin: BunPlugin = {
  name: "my-custom-plugin",
  setup(build) {
    // Plugin-Implementierung
    build.onLoad({ filter: /\.custom$/ }, async args => {
      const text = await Bun.file(args.path).text();
      return {
        contents: `export default ${JSON.stringify(text)};`,
        loader: "js",
      };
    });
  },
};

export default myPlugin;

Bun wird jedes Plugin faul auflösen und laden und sie verwenden, um Ihre Routen zu bündeln.

NOTE

Dies befindet sich derzeit in `bunfig.toml`, um es möglich zu machen, statisch zu wissen, welche Plugins verwendet werden, wenn wir dies schließlich mit der `bun build`-CLI integrieren. Diese Plugins funktionieren in `Bun.build()`'s JS-API, werden aber noch nicht in der CLI unterstützt.

Inline-Umgebungsvariablen

Bun kann process.env.*-Referenzen in Ihrem Frontend-JavaScript und TypeScript zur Build-Zeit durch ihre tatsächlichen Werte ersetzen. Konfigurieren Sie die env-Option in Ihrer bunfig.toml:

[serve.static]
env = "PUBLIC_*"  # nur Inline-Umgebungsvariablen beginnend mit PUBLIC_ (empfohlen)
# env = "inline"  # alle Umgebungsvariablen inline
# env = "disable" # Umgebungsvariablen-Ersetzung deaktivieren (Standard)

Hinweis

Dies funktioniert nur mit literalen process.env.FOO-Referenzen, nicht import.meta.env oder indirektem Zugriff wie const env = process.env; env.FOO.

Wenn eine Umgebungsvariable nicht gesetzt ist, können Sie Laufzeitfehler wie ReferenceError: process is not defined im Browser sehen.

Siehe die HTML & statische Seiten-Dokumentation für weitere Details zur Build-Zeit-Konfiguration und Beispiele.

Wie es funktioniert

Bun verwendet HTMLRewriter, um nach <script>- und <link>-Tags in HTML-Dateien zu suchen, verwendet sie als Einstiegspunkte für Buns Bundler, generiert ein optimiertes Bündel für die JavaScript/TypeScript/TSX/JSX- und CSS-Dateien und stellt das Ergebnis bereit.

Verarbeitungspipeline

1. Script-Verarbeitung

  • Transpiliert TypeScript, JSX und TSX in <script>-Tags
  • Bündelt importierte Abhängigkeiten
  • Generiert Sourcemaps zum Debuggen
  • Minifiziert, wenn development in Bun.serve() nicht true ist
html
<script type="module" src="./counter.tsx"></script>

2. -Verarbeitung

  • Verarbeitet CSS-Importe und <link>-Tags
  • Verkettet CSS-Dateien
  • Schreibt URL- und Asset-Pfade um, um inhaltsadressierbare Hashes in URLs einzuschließen
html
<link rel="stylesheet" href="./styles.css" />

3. & Asset-Verarbeitung

  • Links zu Assets werden umgeschrieben, um inhaltsadressierbare Hashes in URLs einzuschließen
  • Kleine Assets in CSS-Dateien werden in data:-URLs inline eingefügt, was die Gesamtzahl der HTTP-Anfragen reduziert

4. HTML-Rewriting

  • Kombiniert alle <script>-Tags zu einem einzigen <script>-Tag mit einem inhaltsadressierbaren Hash in der URL
  • Kombiniert alle <link>-Tags zu einem einzigen <link>-Tag mit einem inhaltsadressierbaren Hash in der URL
  • Gibt eine neue HTML-Datei aus

5. Bereitstellen

  • Alle Ausgabedateien vom Bundler werden als statische Routen bereitgestellt, wobei intern derselbe Mechanismus verwendet wird, wie wenn Sie ein Response-Objekt an static in Bun.serve() übergeben.
  • Dies funktioniert ähnlich wie Bun.build HTML-Dateien verarbeitet.

Vollständiges Beispiel

Hier ist ein vollständiges Fullstack-Anwendungsbeispiel:

server.ts
ts
import { serve } from "bun";
import { Database } from "bun:sqlite";
import homepage from "./public/index.html";
import dashboard from "./public/dashboard.html";

// Datenbank initialisieren
const db = new Database("app.db");
db.exec(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  )
`);

const server = serve({
  routes: {
    // Frontend-Routen
    "/": homepage,
    "/dashboard": dashboard,

    // API-Routen
    "/api/users": {
      async GET() {
        const users = db.query("SELECT * FROM users").all();
        return Response.json(users);
      },

      async POST(req) {
        const { name, email } = await req.json();

        try {
          const result = db.query("INSERT INTO users (name, email) VALUES (?, ?) RETURNING *").get(name, email);

          return Response.json(result, { status: 201 });
        } catch (error) {
          return Response.json({ error: "E-Mail existiert bereits" }, { status: 400 });
        }
      },
    },

    "/api/users/:id": {
      async GET(req) {
        const { id } = req.params;
        const user = db.query("SELECT * FROM users WHERE id = ?").get(id);

        if (!user) {
          return Response.json({ error: "Benutzer nicht gefunden" }, { status: 404 });
        }

        return Response.json(user);
      },

      async DELETE(req) {
        const { id } = req.params;
        const result = db.query("DELETE FROM users WHERE id = ?").run(id);

        if (result.changes === 0) {
          return Response.json({ error: "Benutzer nicht gefunden" }, { status: 404 });
        }

        return new Response(null, { status: 204 });
      },
    },

    // Health-Check-Endpunkt
    "/api/health": {
      GET() {
        return Response.json({
          status: "ok",
          timestamp: new Date().toISOString(),
        });
      },
    },
  },

  // Entwicklungsmodus aktivieren
  development: {
    hmr: true,
    console: true,
  },

  // Fallback für nicht übereinstimmende Routen
  fetch(req) {
    return new Response("Nicht gefunden", { status: 404 });
  },
});

console.log(`🚀 Server läuft auf ${server.url}`);
html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Fullstack Bun App</title>
    <link rel="stylesheet" href="../src/styles.css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="../src/main.tsx"></script>
  </body>
</html>
tsx
import { createRoot } from "react-dom/client";
import { App } from "./App";

const container = document.getElementById("root")!;
const root = createRoot(container);
root.render(<App />);
tsx
import { useState, useEffect } from "react";

interface User {
  id: number;
  name: string;
  email: string;
  created_at: string;
}

export function App() {
  const [users, setUsers] = useState<User[]>([]);
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [loading, setLoading] = useState(false);

  const fetchUsers = async () => {
    const response = await fetch("/api/users");
    const data = await response.json();
    setUsers(data);
  };

  const createUser = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);

    try {
      const response = await fetch("/api/users", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ name, email }),
      });

      if (response.ok) {
        setName("");
        setEmail("");
        await fetchUsers();
      } else {
        const error = await response.json();
        alert(error.error);
      }
    } catch (error) {
      alert("Benutzer erstellen fehlgeschlagen");
    } finally {
      setLoading(false);
    }
  };

  const deleteUser = async (id: number) => {
    if (!confirm("Sind Sie sicher?")) return;

    try {
      const response = await fetch(`/api/users/${id}`, {
        method: "DELETE",
      });

      if (response.ok) {
        await fetchUsers();
      }
    } catch (error) {
      alert("Benutzer löschen fehlgeschlagen");
    }
  };

  useEffect(() => {
    fetchUsers();
  }, []);

  return (
    <div className="container">
      <h1>Benutzerverwaltung</h1>

      <form onSubmit={createUser} className="form">
        <input type="text" placeholder="Name" value={name} onChange={e => setName(e.target.value)} required />
        <input type="email" placeholder="E-Mail" value={email} onChange={e => setEmail(e.target.value)} required />
        <button type="submit" disabled={loading}>
          {loading ? "Erstellen..." : "Benutzer erstellen"}
        </button>
      </form>

      <div className="users">
        <h2>Benutzer ({users.length})</h2>
        {users.map(user => (
          <div key={user.id} className="user-card">
            <div>
              <strong>{user.name}</strong>
              <br />
              <span>{user.email}</span>
            </div>
            <button onClick={() => deleteUser(user.id)} className="delete-btn">
              Löschen
            </button>
          </div>
        ))}
      </div>
    </div>
  );
}
css
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, sans-serif;
  background: #f5f5f5;
  color: #333;
}

.container {
  max-width: 800px;
  margin: 0 auto;
  padding: 2rem;
}

h1 {
  color: #2563eb;
  margin-bottom: 2rem;
}

.form {
  background: white;
  padding: 1.5rem;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  margin-bottom: 2rem;
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}

.form input {
  flex: 1;
  min-width: 200px;
  padding: 0.75rem;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.form button {
  padding: 0.75rem 1.5rem;
  background: #2563eb;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.form button:hover {
  background: #1d4ed8;
}

.form button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.users {
  background: white;
  padding: 1.5rem;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.user-card {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem;
  border-bottom: 1px solid #eee;
}

.user-card:last-child {
  border-bottom: none;
}

.delete-btn {
  padding: 0.5rem 1rem;
  background: #dc2626;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.delete-btn:hover {
  background: #b91c1c;
}

Best Practices

Projektstruktur

my-app/
├── src/
│   ├── components/
│   │   ├── Header.tsx
│   │   └── UserList.tsx
│   ├── styles/
│   │   ├── globals.css
│   │   └── components.css
│   ├── utils/
│   │   └── api.ts
│   ├── App.tsx
│   └── main.tsx
├── public/
│   ├── index.html
│   ├── dashboard.html
│   └── favicon.ico
├── server/
│   ├── routes/
│   │   ├── users.ts
│   │   └── auth.ts
│   ├── db/
│   │   └── schema.sql
│   └── index.ts
├── bunfig.toml
└── package.json

Umgebungsbasierte Konfiguration

ts
export const config = {
  development: process.env.NODE_ENV !== "production",
  port: process.env.PORT || 3000,
  database: {
    url: process.env.DATABASE_URL || "./dev.db",
  },
  cors: {
    origin: process.env.CORS_ORIGIN || "*",
  },
};

Fehlerbehandlung

ts
export function errorHandler(error: Error, req: Request) {
  console.error("Serverfehler:", error);

  if (process.env.NODE_ENV === "production") {
    return Response.json({ error: "Interner Serverfehler" }, { status: 500 });
  }

  return Response.json(
    {
      error: error.message,
      stack: error.stack,
    },
    { status: 500 },
  );
}

API-Antworthelfer

ts
export function json(data: any, status = 200) {
  return Response.json(data, { status });
}

export function error(message: string, status = 400) {
  return Response.json({ error: message }, { status });
}

export function notFound(message = "Nicht gefunden") {
  return error(message, 404);
}

export function unauthorized(message = "Nicht autorisiert") {
  return error(message, 401);
}

Typsicherheit

ts
export interface User {
  id: number;
  name: string;
  email: string;
  created_at: string;
}

export interface CreateUserRequest {
  name: string;
  email: string;
}

export interface ApiResponse<T> {
  data?: T;
  error?: string;
}

Bereitstellung

Produktions-Build

bash
# Für Produktion erstellen
bun build --target=bun --production --outdir=dist ./server/index.ts

# Produktionsserver ausführen
NODE_ENV=production bun dist/index.js

Docker-Bereitstellung

Dockerfile
dockerfile
FROM oven/bun:1 as base
WORKDIR /usr/src/app

# Abhängigkeiten installieren
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile

# Quellcode kopieren
COPY . .

# Anwendung erstellen
RUN bun build --target=bun --production --outdir=dist ./server/index.ts

# Produktionsstufe
FROM oven/bun:1-slim
WORKDIR /usr/src/app
COPY --from=base /usr/src/app/dist ./
COPY --from=base /usr/src/app/public ./public

EXPOSE 3000
CMD ["bun", "index.js"]

Umgebungsvariablen

.env.production
ini
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://user:pass@localhost:5432/myapp
CORS_ORIGIN=https://myapp.com

Migration von anderen Frameworks

Von Express + Webpack

server.ts
ts
// Vorher (Express + Webpack)
app.use(express.static("dist"));
app.get("/api/users", (req, res) => {
  res.json(users);
});

// Nachher (Bun Fullstack)
serve({
  routes: {
    "/": homepage, // Ersetzt express.static
    "/api/users": {
      GET() {
        return Response.json(users);
      },
    },
  },
});

Von Next.js API-Routen

server.ts
ts
// Vorher (Next.js)
export default function handler(req, res) {
  if (req.method === 'GET') {
    res.json(users);
  }
}

// Nachher (Bun)
"/api/users": {
  GET() { return Response.json(users); }
}

Einschränkungen und zukünftige Pläne

Aktuelle Einschränkungen

  • bun build CLI-Integration ist noch nicht für Fullstack-Apps verfügbar
  • Auto-Discovery von API-Routen ist nicht implementiert
  • Server-Side Rendering (SSR) ist nicht integriert

Geplante Funktionen

  • Integration mit bun build CLI
  • Dateibasiertes Routing für API-Endpunkte
  • Integrierte SSR-Unterstützung
  • Erweitertes Plugin-Ökosystem

NOTE

Dies ist eine laufende Arbeit. Funktionen und APIs können sich ändern, während Bun weiterentwickelt wird.

Bun von www.bunjs.com.cn bearbeitet