feat: Complete zCode CLI X with Telegram bot integration

- Add full Telegram bot functionality with Z.AI API integration
- Implement 4 tools: Bash, FileEdit, WebSearch, Git
- Add 3 agents: Code Reviewer, Architect, DevOps Engineer
- Add 6 skills for common coding tasks
- Add systemd service file for 24/7 operation
- Add nginx configuration for HTTPS webhook
- Add comprehensive documentation
- Implement WebSocket server for real-time updates
- Add logging system with Winston
- Add environment validation

🤖 zCode CLI X - Agentic coder with Z.AI + Telegram integration
This commit is contained in:
admin
2026-05-05 09:01:26 +00:00
Unverified
parent 4a7035dd92
commit 875c7f9b91
24688 changed files with 3224957 additions and 221 deletions

View File

@@ -0,0 +1,105 @@
Default to using Bun instead of Node.js.
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
- Use `bun test` instead of `jest` or `vitest`
- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
- Use `bunx <package> <command>` instead of `npx <package> <command>`
- Bun automatically loads .env, so don't use dotenv.
## APIs
- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
- `Bun.redis` for Redis. Don't use `ioredis`.
- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
- `WebSocket` is built-in. Don't use `ws`.
- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
- Bun.$`ls` instead of execa.
## Testing
Use `bun test` to run tests.
```ts#index.test.ts
import { test, expect } from "bun:test";
test("hello world", () => {
expect(1).toBe(1);
});
```
## Frontend
Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
Server:
```ts#index.ts
import index from "./index.html"
Bun.serve({
routes: {
"/": index,
"/api/users/:id": {
GET: (req) => {
return new Response(JSON.stringify({ id: req.params.id }));
},
},
},
// optional websocket support
websocket: {
open: (ws) => {
ws.send("Hello, world!");
},
message: (ws, message) => {
ws.send(message);
},
close: (ws) => {
// handle close
}
},
development: {
hmr: true,
console: true,
}
})
```
HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
```html#index.html
<html>
<body>
<h1>Hello, world!</h1>
<script type="module" src="./frontend.tsx"></script>
</body>
</html>
```
With the following `frontend.tsx`:
```tsx#frontend.tsx
import React from "react";
import { createRoot } from "react-dom/client";
// import .css files directly and it works
import './index.css';
const root = createRoot(document.body);
export default function Frontend() {
return <h1>Hello, world!</h1>;
}
root.render(<Frontend />);
```
Then, run index.ts
```sh
bun --hot ./index.ts
```
For more information, read the Bun API docs in `docs/**.mdx`.

View File

@@ -0,0 +1,33 @@
# TypeScript types for Bun
<p align="center">
<a href="https://bun.com"><img src="https://bun.com/logo@2x.png" alt="Logo"></a>
</p>
These are the type definitions for Bun's JavaScript runtime APIs.
# Installation
Install the `@types/bun` npm package:
```bash
# yarn/npm/pnpm work too
# @types/bun is an ordinary npm package
bun add -D @types/bun
```
That's it! VS Code and TypeScript automatically load `@types/*` packages into your project, so the `Bun` global and all `bun:*` modules should be available immediately.
# Contributing
The `@types/bun` package is a shim that loads `bun-types`. The `bun-types` package lives in the Bun repo under `packages/bun-types`.
To add a new file, add it under `packages/bun-types`. Then add a [triple-slash directive](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html) pointing to it inside [./index.d.ts](./index.d.ts).
```diff
+ /// <reference path="./newfile.d.ts" />
```
```bash
bun build
```

8712
~/.npm-cache/bun-types@1.3.11@@@1/bun.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
import * as BunModule from "bun";
declare global {
export import Bun = BunModule;
}

View File

@@ -0,0 +1,74 @@
/**
* The `bun:bundle` module provides compile-time utilities for dead-code elimination.
*
* @example
* ```ts
* import { feature } from "bun:bundle";
*
* if (feature("SUPER_SECRET")) {
* console.log("Secret feature enabled!");
* } else {
* console.log("Normal mode");
* }
* ```
*
* Enable feature flags via CLI:
* ```bash
* # During build
* bun build --feature=SUPER_SECRET index.ts
*
* # At runtime
* bun run --feature=SUPER_SECRET index.ts
*
* # In tests
* bun test --feature=SUPER_SECRET
* ```
*
* @module bun:bundle
*/
declare module "bun:bundle" {
/**
* Registry for type-safe feature flags.
*
* Augment this interface to get autocomplete and type checking for your feature flags:
*
* @example
* ```ts
* // env.d.ts
* declare module "bun:bundle" {
* interface Registry {
* features: "DEBUG" | "PREMIUM" | "BETA";
* }
* }
* ```
*
* Now `feature()` only accepts `"DEBUG"`, `"PREMIUM"`, or `"BETA"`:
* ```ts
* feature("DEBUG"); // OK
* feature("TYPO"); // Type error
* ```
*/
interface Registry {}
/**
* Check if a feature flag is enabled at compile time.
*
* This function is replaced with a boolean literal (`true` or `false`) at bundle time,
* enabling dead-code elimination. The bundler will remove unreachable branches.
*
* @param flag - The name of the feature flag to check
* @returns `true` if the flag was passed via `--feature=FLAG`, `false` otherwise
*
* @example
* ```ts
* import { feature } from "bun:bundle";
*
* // With --feature=DEBUG, this becomes: if (true) { ... }
* // Without --feature=DEBUG, this becomes: if (false) { ... }
* if (feature("DEBUG")) {
* console.log("Debug mode enabled");
* }
* ```
*/
function feature(flag: Registry extends { features: infer Features extends string } ? Features : string): boolean;
}

View File

@@ -0,0 +1,184 @@
declare module "bun" {
/** @deprecated This type is unused in Bun's types and might be removed in the near future */
type Platform =
| "aix"
| "android"
| "darwin"
| "freebsd"
| "haiku"
| "linux"
| "openbsd"
| "sunos"
| "win32"
| "cygwin"
| "netbsd";
/** @deprecated This type is unused in Bun's types and might be removed in the near future */
type Architecture = "arm" | "arm64" | "ia32" | "mips" | "mipsel" | "ppc" | "ppc64" | "s390" | "s390x" | "x64";
/** @deprecated This type is unused in Bun's types and might be removed in the near future */
type UncaughtExceptionListener = (error: Error, origin: UncaughtExceptionOrigin) => void;
/**
* Most of the time the unhandledRejection will be an Error, but this should not be relied upon
* as *anything* can be thrown/rejected, it is therefore unsafe to assume that the value is an Error.
*
* @deprecated This type is unused in Bun's types and might be removed in the near future
*/
type UnhandledRejectionListener = (reason: unknown, promise: Promise<unknown>) => void;
/** @deprecated This type is unused in Bun's types and might be removed in the near future */
type MultipleResolveListener = (type: MultipleResolveType, promise: Promise<unknown>, value: unknown) => void;
/**
* Consume all data from a {@link ReadableStream} until it closes or errors.
*
* Concatenate the chunks into a single {@link ArrayBuffer}.
*
* Each chunk must be a TypedArray or an ArrayBuffer. If you need to support
* chunks of different types, consider {@link readableStreamToBlob}
*
* @param stream The stream to consume.
* @returns A promise that resolves with the concatenated chunks or the concatenated chunks as a {@link Uint8Array}.
*
* @deprecated Use {@link ReadableStream.bytes}
*/
function readableStreamToBytes(
stream: ReadableStream<ArrayBufferView | ArrayBufferLike>,
): Promise<Uint8Array<ArrayBuffer>> | Uint8Array<ArrayBuffer>;
/**
* Consume all data from a {@link ReadableStream} until it closes or errors.
*
* Concatenate the chunks into a single {@link Blob}.
*
* @param stream The stream to consume.
* @returns A promise that resolves with the concatenated chunks as a {@link Blob}.
*
* @deprecated Use {@link ReadableStream.blob}
*/
function readableStreamToBlob(stream: ReadableStream): Promise<Blob>;
/**
* Consume all data from a {@link ReadableStream} until it closes or errors.
*
* Concatenate the chunks into a single string. Chunks must be a TypedArray or an ArrayBuffer. If you need to support chunks of different types, consider {@link readableStreamToBlob}.
*
* @param stream The stream to consume.
* @returns A promise that resolves with the concatenated chunks as a {@link String}.
*
* @deprecated Use {@link ReadableStream.text}
*/
function readableStreamToText(stream: ReadableStream): Promise<string>;
/**
* Consume all data from a {@link ReadableStream} until it closes or errors.
*
* Concatenate the chunks into a single string and parse as JSON. Chunks must be a TypedArray or an ArrayBuffer. If you need to support chunks of different types, consider {@link readableStreamToBlob}.
*
* @param stream The stream to consume.
* @returns A promise that resolves with the concatenated chunks as a {@link String}.
*
* @deprecated Use {@link ReadableStream.json}
*/
function readableStreamToJSON(stream: ReadableStream): Promise<any>;
interface BunMessageEvent<T> {
/**
* @deprecated
*/
initMessageEvent(
type: string,
bubbles?: boolean,
cancelable?: boolean,
data?: any,
origin?: string,
lastEventId?: string,
source?: null,
): void;
}
/**
* @deprecated Use {@link Serve.Options Bun.Serve.Options<T, R>} instead
*/
type ServeOptions<T = undefined, R extends string = never> = Serve.Options<T, R>;
/** @deprecated Use {@link SQL.Query Bun.SQL.Query} */
type SQLQuery<T = any> = SQL.Query<T>;
/** @deprecated Use {@link SQL.TransactionContextCallback Bun.SQL.TransactionContextCallback} */
type SQLTransactionContextCallback<T> = SQL.TransactionContextCallback<T>;
/** @deprecated Use {@link SQL.SavepointContextCallback Bun.SQL.SavepointContextCallback} */
type SQLSavepointContextCallback<T> = SQL.SavepointContextCallback<T>;
/** @deprecated Use {@link SQL.Options Bun.SQL.Options} */
type SQLOptions = SQL.Options;
/**
* @deprecated Renamed to `ErrorLike`
*/
type Errorlike = ErrorLike;
/** @deprecated This is unused in Bun's types and may be removed in the future */
type ShellFunction = (input: Uint8Array<ArrayBuffer>) => Uint8Array<ArrayBuffer>;
interface TLSOptions {
/**
* File path to a TLS key
*
* To enable TLS, this option is required.
*
* @deprecated since v0.6.3 - Use `key: Bun.file(path)` instead.
*/
keyFile?: string;
/**
* File path to a TLS certificate
*
* To enable TLS, this option is required.
*
* @deprecated since v0.6.3 - Use `cert: Bun.file(path)` instead.
*/
certFile?: string;
/**
* File path to a .pem file for a custom root CA
*
* @deprecated since v0.6.3 - Use `ca: Bun.file(path)` instead.
*/
caFile?: string;
}
/** @deprecated This type is unused in Bun's declarations and may be removed in the future */
type ReadableIO = ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined;
}
declare namespace NodeJS {
interface Process {
/**
* @deprecated This is deprecated; use the "node:assert" module instead.
*/
assert(value: unknown, message?: string | Error): asserts value;
}
}
interface CustomEvent<T = any> {
/** @deprecated */
initCustomEvent(type: string, bubbles?: boolean, cancelable?: boolean, detail?: T): void;
}
interface DOMException {
/** @deprecated */
readonly code: number;
}
/**
* @deprecated Renamed to `BuildMessage`
*/
declare var BuildError: typeof BuildMessage;
/**
* @deprecated Renamed to `ResolveMessage`
*/
declare var ResolveError: typeof ResolveMessage;

View File

@@ -0,0 +1,187 @@
declare module "bun" {
type HMREventNames =
| "beforeUpdate"
| "afterUpdate"
| "beforeFullReload"
| "beforePrune"
| "invalidate"
| "error"
| "ws:disconnect"
| "ws:connect";
/**
* The event names for the dev server
*/
type HMREvent = `bun:${HMREventNames}` | (string & {});
}
interface ImportMeta {
/**
* Hot module replacement APIs. This value is `undefined` in production and
* can be used in an `if` statement to check if HMR APIs are available
*
* ```ts
* if (import.meta.hot) {
* // HMR APIs are available
* }
* ```
*
* However, this check is usually not needed as Bun will dead-code-eliminate
* calls to all of the HMR APIs in production builds.
*
* https://bun.com/docs/bundler/hmr
*/
hot: {
/**
* `import.meta.hot.data` maintains state between module instances during
* hot replacement, enabling data transfer from previous to new versions.
* When `import.meta.hot.data` is written to, Bun will mark this module as
* capable of self-accepting (equivalent of calling `accept()`).
*
* @example
* ```ts
* const root = import.meta.hot.data.root ??= createRoot(elem);
* root.render(<App />); // re-use an existing root
* ```
*
* In production, `data` is inlined to be `{}`. This is handy because Bun
* knows it can minify `{}.prop ??= value` into `value` in production.
*/
data: any;
/**
* Indicate that this module can be replaced simply by re-evaluating the
* file. After a hot update, importers of this module will be
* automatically patched.
*
* When `import.meta.hot.accept` is not used, the page will reload when
* the file updates, and a console message shows which files were checked.
*
* @example
* ```ts
* import { getCount } from "./foo";
*
* console.log("count is ", getCount());
*
* import.meta.hot.accept();
* ```
*/
accept(): void;
/**
* Indicate that this module can be replaced by evaluating the new module,
* and then calling the callback with the new module. In this mode, the
* importers do not get patched. This is to match Vite, which is unable
* to patch their import statements. Prefer using `import.meta.hot.accept()`
* without an argument as it usually makes your code easier to understand.
*
* When `import.meta.hot.accept` is not used, the page will reload when
* the file updates, and a console message shows which files were checked.
*
* @example
* ```ts
* export const count = 0;
*
* import.meta.hot.accept((newModule) => {
* if (newModule) {
* // newModule is undefined when SyntaxError happened
* console.log('updated: count is now ', newModule.count)
* }
* });
* ```
*
* In production, calls to this are dead-code-eliminated.
*/
accept(cb: (newModule: any | undefined) => void): void;
/**
* Indicate that a dependency's module can be accepted. When the dependency
* is updated, the callback will be called with the new module.
*
* When `import.meta.hot.accept` is not used, the page will reload when
* the file updates, and a console message shows which files were checked.
*
* @example
* ```ts
* import.meta.hot.accept('./foo', (newModule) => {
* if (newModule) {
* // newModule is undefined when SyntaxError happened
* console.log('updated: count is now ', newModule.count)
* }
* });
* ```
*/
accept(specifier: string, callback: (newModule: any) => void): void;
/**
* Indicate that a dependency's module can be accepted. This variant
* accepts an array of dependencies, where the callback will receive
* the one updated module, and `undefined` for the rest.
*
* When `import.meta.hot.accept` is not used, the page will reload when
* the file updates, and a console message shows which files were checked.
*/
accept(specifiers: string[], callback: (newModules: (any | undefined)[]) => void): void;
/**
* Attach an on-dispose callback. This is called:
* - Just before the module is replaced with another copy (before the next is loaded)
* - After the module is detached (removing all imports to this module)
*
* This callback is not called on route navigation or when the browser tab closes.
*
* Returning a promise will delay module replacement until the module is
* disposed. All dispose callbacks are called in parallel.
*/
dispose(cb: (data: any) => void | Promise<void>): void;
/**
* No-op
* @deprecated
*/
decline(): void;
// NOTE TO CONTRIBUTORS ////////////////////////////////////////
// Callback is currently never called for `.prune()` //
// so the types are commented out until we support it. //
////////////////////////////////////////////////////////////////
// /**
// * Attach a callback that is called when the module is removed from the module graph.
// *
// * This can be used to clean up resources that were created when the module was loaded.
// * Unlike `import.meta.hot.dispose()`, this pairs much better with `accept` and `data` to manage stateful resources.
// *
// * @example
// * ```ts
// * export const ws = (import.meta.hot.data.ws ??= new WebSocket(location.origin));
// *
// * import.meta.hot.prune(() => {
// * ws.close();
// * });
// * ```
// */
// prune(callback: () => void): void;
/**
* Listen for an event from the dev server
*
* For compatibility with Vite, event names are also available via vite:* prefix instead of bun:*.
*
* https://bun.com/docs/bundler/hmr#import-meta-hot-on-and-off
* @param event The event to listen to
* @param callback The callback to call when the event is emitted
*/
on(event: Bun.HMREvent, callback: () => void): void;
/**
* Stop listening for an event from the dev server
*
* For compatibility with Vite, event names are also available via vite:* prefix instead of bun:*.
*
* https://bun.com/docs/bundler/hmr#import-meta-hot-on-and-off
* @param event The event to stop listening to
* @param callback The callback to stop listening to
*/
off(event: Bun.HMREvent, callback: () => void): void;
};
}

View File

@@ -0,0 +1,28 @@
<p align="center">
<a href="https://bun.com">
<img src="https://github.com/user-attachments/assets/50282090-adfd-4ddb-9e27-c30753c6b161" alt="Logo" height="170" />
</a>
</p>
<h1 align="center">Bun Documentation</h1>
Official documentation for Bun: the fast, all-in-one JavaScript runtime.
## Development
Install the [Mintlify CLI](https://www.npmjs.com/package/mint) to preview the documentation locally:
```bash
bun install -g mint
```
Run the development server:
```bash
mint dev
```
The site will be available at `http://localhost:3000`.
## Contributing
Contributions are welcome! Please open an issue or submit a pull request.

View File

@@ -0,0 +1,447 @@
---
title: Bytecode Caching
description: Speed up JavaScript execution with bytecode caching in Bun's bundler
---
Bytecode caching is a build-time optimization that dramatically improves application startup time by pre-compiling your JavaScript to bytecode. For example, when compiling TypeScript's `tsc` with bytecode enabled, startup time improves by **2x**.
## Usage
### Basic usage (CommonJS)
Enable bytecode caching with the `--bytecode` flag. Without `--format`, this defaults to CommonJS:
```bash terminal icon="terminal"
bun build ./index.ts --target=bun --bytecode --outdir=./dist
```
This generates two files:
- `dist/index.js` - Your bundled JavaScript (CommonJS)
- `dist/index.jsc` - The bytecode cache file
At runtime, Bun automatically detects and uses the `.jsc` file:
```bash terminal icon="terminal"
bun ./dist/index.js # Automatically uses index.jsc
```
### With standalone executables
When creating executables with `--compile`, bytecode is embedded into the binary. Both ESM and CommonJS formats are supported:
```bash terminal icon="terminal"
# ESM (requires --compile)
bun build ./cli.ts --compile --bytecode --format=esm --outfile=mycli
# CommonJS (works with or without --compile)
bun build ./cli.ts --compile --bytecode --outfile=mycli
```
The resulting executable contains both the code and bytecode, giving you maximum performance in a single file.
### ESM bytecode
ESM bytecode requires `--compile` because Bun embeds module metadata (import/export information) in the compiled binary. This metadata allows the JavaScript engine to skip parsing entirely at runtime.
Without `--compile`, ESM bytecode would still require parsing the source to analyze module dependencies—defeating the purpose of bytecode caching.
### Combining with other optimizations
Bytecode works great with minification and source maps:
```bash terminal icon="terminal"
bun build --compile --bytecode --minify --sourcemap ./cli.ts --outfile=mycli
```
- `--minify` reduces code size before generating bytecode (less code -> less bytecode)
- `--sourcemap` preserves error reporting (errors still point to original source)
- `--bytecode` eliminates parsing overhead
## Performance impact
The performance improvement scales with your codebase size:
| Application size | Typical startup improvement |
| ------------------------- | --------------------------- |
| Small CLI (< 100 KB) | 1.5-2x faster |
| Medium-large app (> 5 MB) | 2.5x-4x faster |
Larger applications benefit more because they have more code to parse.
## When to use bytecode
### Great for:
#### CLI tools
- Invoked frequently (linters, formatters, git hooks)
- Startup time is the entire user experience
- Users notice the difference between 90ms and 45ms startup
- Example: TypeScript compiler, Prettier, ESLint
#### Build tools and task runners
- Run hundreds or thousands of times during development
- Milliseconds saved per run compound quickly
- Developer experience improvement
- Example: Build scripts, test runners, code generators
#### Standalone executables
- Distributed to users who care about snappy performance
- Single-file distribution is convenient
- File size less important than startup time
- Example: CLIs distributed via npm or as binaries
### Skip it for:
- ❌ **Small scripts**
- ❌ **Code that runs once**
- ❌ **Development builds**
- ❌ **Size-constrained environments**
## Limitations
### Version compatibility
Bytecode is **not portable across Bun versions**. The bytecode format is tied to JavaScriptCore's internal representation, which changes between versions.
When you update Bun, you must regenerate bytecode:
```bash terminal icon="terminal"
# After updating Bun
bun build --bytecode ./index.ts --outdir=./dist
```
If bytecode doesn't match the current Bun version, it's automatically ignored and your code falls back to parsing the JavaScript source. Your app still runs - you just lose the performance optimization.
**Best practice**: Generate bytecode as part of your CI/CD build process. Don't commit `.jsc` files to git. Regenerate them whenever you update Bun.
### Source code still required
- The `.js` file (your bundled source code)
- The `.jsc` file (the bytecode cache)
At runtime:
1. Bun loads the `.js` file, sees a `@bytecode` pragma, and checks the `.jsc` file
2. Bun loads the `.jsc` file
3. Bun validates the bytecode hash matches the source
4. If valid, Bun uses the bytecode
5. If invalid, Bun falls back to parsing the source
### Bytecode is not obfuscation
Bytecode **does not obscure your source code**. It's an optimization, not a security measure.
## Production deployment
### Docker
Include bytecode generation in your Dockerfile:
```dockerfile Dockerfile icon="docker"
FROM oven/bun:1 AS builder
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun build --bytecode --minify --sourcemap \
--target=bun \
--outdir=./dist \
--compile \
./src/server.ts --outfile=./dist/server
FROM oven/bun:1 AS runner
WORKDIR /app
COPY --from=builder /dist/server /app/server
CMD ["./server"]
```
The bytecode is architecture-independent.
### CI/CD
Generate bytecode during your build pipeline:
```yaml workflow.yml icon="file-code"
# GitHub Actions
- name: Build with bytecode
run: |
bun install
bun build --bytecode --minify \
--outdir=./dist \
--target=bun \
./src/index.ts
```
## Debugging
### Verify bytecode is being used
Check that the `.jsc` file exists:
```bash terminal icon="terminal"
ls -lh dist/
```
```txt
-rw-r--r-- 1 user staff 245K index.js
-rw-r--r-- 1 user staff 1.1M index.jsc
```
The `.jsc` file should be 2-8x larger than the `.js` file.
To log if bytecode is being used, set `BUN_JSC_verboseDiskCache=1` in your environment.
On success, it will log something like:
```txt
[Disk cache] cache hit for sourceCode
```
If you see a cache miss, it will log something like:
```txt
[Disk cache] cache miss for sourceCode
```
It's normal for it it to log a cache miss multiple times since Bun doesn't currently bytecode cache JavaScript code used in builtin modules.
### Common issues
**Bytecode silently ignored**: Usually caused by a Bun version update. The cache version doesn't match, so bytecode is rejected. Regenerate to fix.
**File size too large**: This is expected. Consider:
- Using `--minify` to reduce code size before bytecode generation
- Compressing `.jsc` files for network transfer (gzip/brotli)
- Evaluating if the startup performance gain is worth the size increase
## What is bytecode?
When you run JavaScript, the JavaScript engine doesn't execute your source code directly. Instead, it goes through several steps:
1. **Parsing**: The engine reads your JavaScript source code and converts it into an Abstract Syntax Tree (AST)
2. **Bytecode compilation**: The AST is compiled into bytecode - a lower-level representation that's faster to execute
3. **Execution**: The bytecode is executed by the engine's interpreter or JIT compiler
Bytecode is an intermediate representation - it's lower-level than JavaScript source code, but higher-level than machine code. Think of it as assembly language for a virtual machine. Each bytecode instruction represents a single operation like "load this variable," "add two numbers," or "call this function."
This happens **every single time** you run your code. If you have a CLI tool that runs 100 times a day, your code gets parsed 100 times. If you have a serverless function with frequent cold starts, parsing happens on every cold start.
With bytecode caching, Bun moves steps 1 and 2 to the build step. At runtime, the engine loads the pre-compiled bytecode and jumps straight to execution.
### Why lazy parsing makes this even better
Modern JavaScript engines use a clever optimization called **lazy parsing**. They don't parse all your code upfront - instead, functions are only parsed when they're first called:
```js
// Without bytecode caching:
function rarely_used() {
// This 500-line function is only parsed
// when it's actually called
}
function main() {
console.log("Starting app");
// rarely_used() is never called, so it's never parsed
}
```
This means parsing overhead isn't just a startup cost - it happens throughout your application's lifetime as different code paths execute. With bytecode caching, **all functions are pre-compiled**, even the ones that are lazily parsed. The parsing work happens once at build time instead of being distributed throughout your application's execution.
## The bytecode format
### Inside a .jsc file
A `.jsc` file contains a serialized bytecode structure. Understanding what's inside helps explain both the performance benefits and the file size tradeoff.
**Header section** (validated on every load):
- **Cache version**: A hash tied to the JavaScriptCore framework version. This ensures bytecode generated with one version of Bun only runs with that exact version.
- **Code block type tag**: Identifies whether this is a Program, Module, Eval, or Function code block.
**SourceCodeKey** (validates bytecode matches source):
- **Source code hash**: A hash of the original JavaScript source code. Bun verifies this matches before using the bytecode.
- **Source code length**: The exact length of the source, for additional validation.
- **Compilation flags**: Critical compilation context like strict mode, whether it's a script vs module, eval context type, etc. The same source code compiled with different flags produces different bytecode.
**Bytecode instructions**:
- **Instruction stream**: The actual bytecode opcodes - the compiled representation of your JavaScript. This is a variable-length sequence of bytecode instructions.
- **Metadata table**: Each opcode has associated metadata - things like profiling counters, type hints, and execution counts (even if not yet populated).
- **Jump targets**: Pre-computed addresses for control flow (if/else, loops, switch statements).
- **Switch tables**: Optimized lookup tables for switch statements.
**Constants and identifiers**:
- **Constant pool**: All literal values in your code - numbers, strings, booleans, null, undefined. These are stored as actual JavaScript values (JSValues) so they don't need to be parsed from source at runtime.
- **Identifier table**: All variable and function names used in the code. Stored as deduplicated strings.
- **Source code representation markers**: Flags indicating how constants should be represented (as integers, doubles, big ints, etc.).
**Function metadata** (for each function in your code):
- **Register allocation**: How many registers (local variables) the function needs - `thisRegister`, `scopeRegister`, `numVars`, `numCalleeLocals`, `numParameters`.
- **Code features**: A bitmask of function characteristics: is it a constructor? an arrow function? does it use `super`? does it have tail calls? These affect how the function is executed.
- **Lexically scoped features**: Strict mode and other lexical context.
- **Parse mode**: The mode in which the function was parsed (normal, async, generator, async generator).
**Nested structures**:
- **Function declarations and expressions**: Each nested function gets its own bytecode block, recursively. A file with 100 functions has 100 separate bytecode blocks, all nested in the structure.
- **Exception handlers**: Try/catch/finally blocks with their boundaries and handler addresses pre-computed.
- **Expression info**: Maps bytecode positions back to source code locations for error reporting and debugging.
### What bytecode does NOT contain
Importantly, **bytecode does not embed your source code**. Instead:
- The JavaScript source is stored separately (in the `.js` file)
- The bytecode only stores a hash and length of the source
- At load time, Bun validates the bytecode matches the current source code
This is why you need to deploy both the `.js` and `.jsc` files. The `.jsc` file is useless without its corresponding `.js` file.
## The tradeoff: file size
Bytecode files are significantly larger than source code - typically 2-8x larger.
### Why is bytecode so much larger?
**Bytecode instructions are verbose**:
A single line of minified JavaScript might compile to dozens of bytecode instructions. For example:
```js
const sum = arr.reduce((a, b) => a + b, 0);
```
Compiles to bytecode that:
- Loads the `arr` variable
- Gets the `reduce` property
- Creates the arrow function (which itself has bytecode)
- Loads the initial value `0`
- Sets up the call with the right number of arguments
- Actually performs the call
- Stores the result in `sum`
Each of these steps is a separate bytecode instruction with its own metadata.
**Constant pools store everything**:
Every string literal, number, property name - everything gets stored in the constant pool. Even if your source code has `"hello"` a hundred times, the constant pool stores it once, but the identifier table and constant references add overhead.
**Per-function metadata**:
Each function - even small one-line functions - gets its own complete metadata:
- Register allocation info
- Code features bitmask
- Parse mode
- Exception handlers
- Expression info for debugging
A file with 1,000 small functions has 1,000 sets of metadata.
**Profiling data structures**:
Even though profiling data isn't populated yet, the _structures_ to hold profiling data are allocated. This includes:
- Value profile slots (tracking what types flow through each operation)
- Array profile slots (tracking array access patterns)
- Binary arithmetic profile slots (tracking number types in math operations)
- Unary arithmetic profile slots
These take up space even when empty.
**Pre-computed control flow**:
Jump targets, switch tables, and exception handler boundaries are all pre-computed and stored. This makes execution faster but increases file size.
### Mitigation strategies
**Compression**:
Bytecode compresses extremely well with gzip/brotli (60-70% compression). The repetitive structure and metadata compress efficiently.
**Minification first**:
Using `--minify` before bytecode generation helps:
- Shorter identifiers → smaller identifier table
- Dead code elimination → less bytecode generated
- Constant folding → fewer constants in the pool
**The tradeoff**:
You're trading 2-4x larger files for 2-4x faster startup. For CLIs, this is usually worth it. For long-running servers where a few megabytes of disk space don't matter, it's even less of an issue.
## Versioning and portability
### Cross-architecture portability: ✅
Bytecode is **architecture-independent**. You can:
- Build on macOS ARM64, deploy to Linux x64
- Build on Linux x64, deploy to AWS Lambda ARM64
- Build on Windows x64, deploy to macOS ARM64
The bytecode contains abstract instructions that work on any architecture. Architecture-specific optimizations happen during JIT compilation at runtime, not in the cached bytecode.
### Cross-version portability: ❌
Bytecode is **not stable across Bun versions**. Here's why:
**Bytecode format changes**:
JavaScriptCore's bytecode format evolves. New opcodes get added, old ones get removed or changed, metadata structures change. Each version of JavaScriptCore has a different bytecode format.
**Version validation**:
The cache version in the `.jsc` file header is a hash of the JavaScriptCore framework. When Bun loads bytecode:
1. It extracts the cache version from the `.jsc` file
2. It computes the current JavaScriptCore version
3. If they don't match, the bytecode is **silently rejected**
4. Bun falls back to parsing the `.js` source code
Your application still runs - you just lose the performance optimization.
**Graceful degradation**:
This design means bytecode caching "fails open" - if anything goes wrong (version mismatch, corrupted file, missing file), your code still runs normally. You might see slower startup, but you won't see errors.
## Unlinked vs. linked bytecode
JavaScriptCore makes a crucial distinction between "unlinked" and "linked" bytecode. This separation is what makes bytecode caching possible:
### Unlinked bytecode (what's cached)
The bytecode saved in `.jsc` files is **unlinked bytecode**. It contains:
- The compiled bytecode instructions
- Structural information about the code
- Constants and identifiers
- Control flow information
But it **doesn't** contain:
- Pointers to actual runtime objects
- JIT-compiled machine code
- Profiling data from previous runs
- Call link information (which functions call which)
Unlinked bytecode is **immutable and shareable**. Multiple executions of the same code can all reference the same unlinked bytecode.
### Linked bytecode (runtime execution)
When Bun runs bytecode, it "links" it - creating a runtime wrapper that adds:
- **Call link information**: As your code runs, the engine learns which functions call which and optimizes those call sites.
- **Profiling data**: The engine tracks how many times each instruction executes, what types of values flow through the code, array access patterns, etc.
- **JIT compilation state**: References to baseline JIT or optimizing JIT (DFG/FTL) compiled versions of hot code.
- **Runtime objects**: Pointers to actual JavaScript objects, prototypes, scopes, etc.
This linked representation is created fresh every time you run your code. This allows:
1. **Caching the expensive work** (parsing and compilation to unlinked bytecode)
2. **Still collecting runtime profiling data** to guide optimizations
3. **Still applying JIT optimizations** based on actual execution patterns
Bytecode caching moves expensive work (parsing and compiling to bytecode) from runtime to build time. For applications that start frequently, this can halve your startup time at the cost of larger files on disk.
For production CLIs and serverless deployments, the combination of `--bytecode --minify --sourcemap` gives you the best performance while maintaining debuggability.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,304 @@
---
title: esbuild
description: Migration guide from esbuild to Bun's bundler
---
Bun's bundler API is inspired heavily by esbuild. Migrating to Bun's bundler from esbuild should be relatively painless. This guide will briefly explain why you might consider migrating to Bun's bundler and provide a side-by-side API comparison reference for those who are already familiar with esbuild's API.
There are a few behavioral differences to note.
<Note>
**Bundling by default.** Unlike esbuild, Bun always bundles by default. This is why the `--bundle` flag isn't
necessary in the Bun example. To transpile each file individually, use `Bun.Transpiler`.
</Note>
<Note>
**It's just a bundler.** Unlike esbuild, Bun's bundler does not include a built-in development server or file watcher.
It's just a bundler. The bundler is intended for use in conjunction with `Bun.serve` and other runtime APIs to achieve
the same effect. As such, all options relating to HTTP/file watching are not applicable.
</Note>
## Performance
With a performance-minded API coupled with the extensively optimized Zig-based JS/TS parser, Bun's bundler is 1.75x faster than esbuild on esbuild's three.js benchmark.
<Info>Bundling 10 copies of three.js from scratch, with sourcemaps and minification</Info>
## CLI API
Bun and esbuild both provide a command-line interface.
```bash terminal icon="terminal"
# esbuild
esbuild <entrypoint> --outdir=out --bundle
# bun
bun build <entrypoint> --outdir=out
```
In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Other flags like `--outdir <path>` do accept an argument; these flags can be written as `--outdir out` or `--outdir=out`. Some flags like `--define` can be specified several times: `--define foo=bar --define bar=baz`.
| esbuild | bun build | Notes |
| ---------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--bundle` | n/a | Bun always bundles, use `--no-bundle` to disable this behavior. |
| `--define:K=V` | `--define K=V` | Small syntax difference; no colon.<br/>`esbuild --define:foo=bar`<br/>`bun build --define foo=bar` |
| `--external:<pkg>` | `--external <pkg>` | Small syntax difference; no colon.<br/>`esbuild --external:react`<br/>`bun build --external react` |
| `--format` | `--format` | Bun supports `"esm"` and `"cjs"` currently, but more module formats are planned. esbuild defaults to `"iife"`. |
| `--loader:.ext=loader` | `--loader .ext:loader` | Bun supports a different set of built-in loaders than esbuild; see Bundler > Loaders for a complete reference. The esbuild loaders `dataurl`, `binary`, `base64`, `copy`, and `empty` are not yet implemented.<br/><br/>The syntax for `--loader` is slightly different.<br/>`esbuild app.ts --bundle --loader:.svg=text`<br/>`bun build app.ts --loader .svg:text` |
| `--minify` | `--minify` | No differences |
| `--outdir` | `--outdir` | No differences |
| `--outfile` | `--outfile` | No differences |
| `--packages` | `--packages` | No differences |
| `--platform` | `--target` | Renamed to `--target` for consistency with tsconfig. Does not support `neutral`. |
| `--serve` | n/a | Not applicable |
| `--sourcemap` | `--sourcemap` | No differences |
| `--splitting` | `--splitting` | No differences |
| `--target` | n/a | Not supported. Bun's bundler performs no syntactic down-leveling at this time. |
| `--watch` | `--watch` | No differences |
| `--allow-overwrite` | n/a | Overwriting is never allowed |
| `--analyze` | n/a | Not supported |
| `--asset-names` | `--asset-naming` | Renamed for consistency with naming in JS API |
| `--banner` | `--banner` | Only applies to js bundles |
| `--footer` | `--footer` | Only applies to js bundles |
| `--certfile` | n/a | Not applicable |
| `--charset=utf8` | n/a | Not supported |
| `--chunk-names` | `--chunk-naming` | Renamed for consistency with naming in JS API |
| `--color` | n/a | Always enabled |
| `--drop` | `--drop` | |
| n/a | `--feature` | Bun-specific. Enable feature flags for compile-time dead-code elimination via `import { feature } from "bun:bundle"` |
| `--entry-names` | `--entry-naming` | Renamed for consistency with naming in JS API |
| `--global-name` | n/a | Not applicable, Bun does not support `iife` output at this time |
| `--ignore-annotations` | `--ignore-dce-annotations` | |
| `--inject` | n/a | Not supported |
| `--jsx` | `--jsx-runtime <runtime>` | Supports `"automatic"` (uses jsx transform) and `"classic"` (uses `React.createElement`) |
| `--jsx-dev` | n/a | Bun reads `compilerOptions.jsx` from `tsconfig.json` to determine a default. If `compilerOptions.jsx` is `"react-jsx"`, or if `NODE_ENV=production`, Bun will use the jsx transform. Otherwise, it uses `jsxDEV`. The bundler does not support `preserve`. |
| `--jsx-factory` | `--jsx-factory` | |
| `--jsx-fragment` | `--jsx-fragment` | |
| `--jsx-import-source` | `--jsx-import-source` | |
| `--jsx-side-effects` | n/a | JSX is always assumed to be side-effect-free |
| `--keep-names` | n/a | Not supported |
| `--keyfile` | n/a | Not applicable |
| `--legal-comments` | n/a | Not supported |
| `--log-level` | n/a | Not supported. This can be set in `bunfig.toml` as `logLevel`. |
| `--log-limit` | n/a | Not supported |
| `--log-override:X=Y` | n/a | Not supported |
| `--main-fields` | n/a | Not supported |
| `--mangle-cache` | n/a | Not supported |
| `--mangle-props` | n/a | Not supported |
| `--mangle-quoted` | n/a | Not supported |
| `--metafile` | n/a | Not supported |
| `--minify-whitespace` | `--minify-whitespace` | |
| `--minify-identifiers` | `--minify-identifiers` | |
| `--minify-syntax` | `--minify-syntax` | |
| `--out-extension` | n/a | Not supported |
| `--outbase` | `--root` | |
| `--preserve-symlinks` | n/a | Not supported |
| `--public-path` | `--public-path` | |
| `--pure` | n/a | Not supported |
| `--reserve-props` | n/a | Not supported |
| `--resolve-extensions` | n/a | Not supported |
| `--servedir` | n/a | Not applicable |
| `--source-root` | n/a | Not supported |
| `--sourcefile` | n/a | Not supported. Bun does not support stdin input yet. |
| `--sourcemap` | `--sourcemap` | No differences |
| `--sources-content` | n/a | Not supported |
| `--supported` | n/a | Not supported |
| `--tree-shaking` | n/a | Always true |
| `--tsconfig` | `--tsconfig-override` | |
| `--version` | n/a | Run `bun --version` to see the version of Bun. |
## JavaScript API
| esbuild.build() | Bun.build() | Notes |
| ------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `absWorkingDir` | n/a | Always set to `process.cwd()` |
| `alias` | n/a | Not supported |
| `allowOverwrite` | n/a | Always false |
| `assetNames` | `naming.asset` | Uses same templating syntax as esbuild, but `[ext]` must be included explicitly.<br/><br/>`ts<br/>Bun.build({<br/> entrypoints: ["./index.tsx"],<br/> naming: {<br/> asset: "[name].[ext]",<br/> },<br/>});<br/>` |
| `banner` | n/a | Not supported |
| `bundle` | n/a | Always true. Use `Bun.Transpiler` to transpile without bundling. |
| `charset` | n/a | Not supported |
| `chunkNames` | `naming.chunk` | Uses same templating syntax as esbuild, but `[ext]` must be included explicitly.<br/><br/>`ts<br/>Bun.build({<br/> entrypoints: ["./index.tsx"],<br/> naming: {<br/> chunk: "[name].[ext]",<br/> },<br/>});<br/>` |
| `color` | n/a | Bun returns logs in the `logs` property of the build result. |
| `conditions` | n/a | Not supported. Export conditions priority is determined by `target`. |
| `define` | `define` | |
| `drop` | n/a | Not supported |
| `entryNames` | `naming` or `naming.entry` | Bun supports a `naming` key that can either be a string or an object. Uses same templating syntax as esbuild, but `[ext]` must be included explicitly.<br/><br/>`ts<br/>Bun.build({<br/> entrypoints: ["./index.tsx"],<br/> // when string, this is equivalent to entryNames<br/> naming: "[name].[ext]",<br/><br/> // granular naming options<br/> naming: {<br/> entry: "[name].[ext]",<br/> asset: "[name].[ext]",<br/> chunk: "[name].[ext]",<br/> },<br/>});<br/>` |
| `entryPoints` | `entrypoints` | Capitalization difference |
| `external` | `external` | No differences |
| `footer` | n/a | Not supported |
| `format` | `format` | Only supports `"esm"` currently. Support for `"cjs"` and `"iife"` is planned. |
| `globalName` | n/a | Not supported |
| `ignoreAnnotations` | n/a | Not supported |
| `inject` | n/a | Not supported |
| `jsx` | `jsx` | Not supported in JS API, configure in `tsconfig.json` |
| `jsxDev` | `jsxDev` | Not supported in JS API, configure in `tsconfig.json` |
| `jsxFactory` | `jsxFactory` | Not supported in JS API, configure in `tsconfig.json` |
| `jsxFragment` | `jsxFragment` | Not supported in JS API, configure in `tsconfig.json` |
| `jsxImportSource` | `jsxImportSource` | Not supported in JS API, configure in `tsconfig.json` |
| `jsxSideEffects` | `jsxSideEffects` | Not supported in JS API, configure in `tsconfig.json` |
| `keepNames` | n/a | Not supported |
| `legalComments` | n/a | Not supported |
| `loader` | `loader` | Bun supports a different set of built-in loaders than esbuild; see Bundler > Loaders for a complete reference. The esbuild loaders `dataurl`, `binary`, `base64`, `copy`, and `empty` are not yet implemented. |
| `logLevel` | n/a | Not supported |
| `logLimit` | n/a | Not supported |
| `logOverride` | n/a | Not supported |
| `mainFields` | n/a | Not supported |
| `mangleCache` | n/a | Not supported |
| `mangleProps` | n/a | Not supported |
| `mangleQuoted` | n/a | Not supported |
| `metafile` | n/a | Not supported |
| `minify` | `minify` | In Bun, `minify` can be a boolean or an object.<br/><br/>`ts<br/>await Bun.build({<br/> entrypoints: ['./index.tsx'],<br/> // enable all minification<br/> minify: true<br/><br/> // granular options<br/> minify: {<br/> identifiers: true,<br/> syntax: true,<br/> whitespace: true<br/> }<br/>})<br/>` |
| `minifyIdentifiers` | `minify.identifiers` | See `minify` |
| `minifySyntax` | `minify.syntax` | See `minify` |
| `minifyWhitespace` | `minify.whitespace` | See `minify` |
| `nodePaths` | n/a | Not supported |
| `outExtension` | n/a | Not supported |
| `outbase` | `root` | Different name |
| `outdir` | `outdir` | No differences |
| `outfile` | `outfile` | No differences |
| `packages` | n/a | Not supported, use `external` |
| `platform` | `target` | Supports `"bun"`, `"node"` and `"browser"` (the default). Does not support `"neutral"`. |
| `plugins` | `plugins` | Bun's plugin API is a subset of esbuild's. Some esbuild plugins will work out of the box with Bun. |
| `preserveSymlinks` | n/a | Not supported |
| `publicPath` | `publicPath` | No differences |
| `pure` | n/a | Not supported |
| `reserveProps` | n/a | Not supported |
| `resolveExtensions` | n/a | Not supported |
| `sourceRoot` | n/a | Not supported |
| `sourcemap` | `sourcemap` | Supports `"inline"`, `"external"`, and `"none"` |
| `sourcesContent` | n/a | Not supported |
| `splitting` | `splitting` | No differences |
| `stdin` | n/a | Not supported |
| `supported` | n/a | Not supported |
| `target` | n/a | No support for syntax downleveling |
| `treeShaking` | n/a | Always true |
| `tsconfig` | n/a | Not supported |
| `write` | n/a | Set to true if `outdir`/`outfile` is set, otherwise false |
## Plugin API
Bun's plugin API is designed to be esbuild compatible. Bun doesn't support esbuild's entire plugin API surface, but the core functionality is implemented. Many third-party esbuild plugins will work out of the box with Bun.
<Note>
Long term, we aim for feature parity with esbuild's API, so if something doesn't work please file an issue to help us
prioritize.
</Note>
Plugins in Bun and esbuild are defined with a builder object.
```ts title="myPlugin.ts" icon="/icons/typescript.svg"
import type { BunPlugin } from "bun";
const myPlugin: BunPlugin = {
name: "my-plugin",
setup(builder) {
// define plugin
},
};
```
The builder object provides some methods for hooking into parts of the bundling process. Bun implements `onStart`, `onEnd`, `onResolve`, and `onLoad`. It does not yet implement the esbuild hooks `onDispose` and `resolve`. `initialOptions` is partially implemented, being read-only and only having a subset of esbuild's options; use `config` (same thing but with Bun's `BuildConfig` format) instead.
```ts title="myPlugin.ts" icon="/icons/typescript.svg"
import type { BunPlugin } from "bun";
const myPlugin: BunPlugin = {
name: "my-plugin",
setup(builder) {
builder.onStart(() => {
/* called when the bundle starts */
});
builder.onResolve(
{
/* onResolve.options */
},
args => {
return {
/* onResolve.results */
};
},
);
builder.onLoad(
{
/* onLoad.options */
},
args => {
return {
/* onLoad.results */
};
},
);
builder.onEnd(result => {
/* called when the bundle is complete */
});
},
};
```
### onResolve
<Tabs>
<Tab title="options">
- 🟢 `filter`
- 🟢 `namespace`
</Tab>
<Tab title="arguments">
- 🟢 `path`
- 🟢 `importer`
- 🔴 `namespace`
- 🔴 `resolveDir`
- 🔴 `kind`
- 🔴 `pluginData`
</Tab>
<Tab title="results">
- 🟢 `namespace`
- 🟢 `path`
- 🔴 `errors`
- 🔴 `external`
- 🔴 `pluginData`
- 🔴 `pluginName`
- 🔴 `sideEffects`
- 🔴 `suffix`
- 🔴 `warnings`
- 🔴 `watchDirs`
- 🔴 `watchFiles`
</Tab>
</Tabs>
### onLoad
<Tabs>
<Tab title="options">
- 🟢 `filter`
- 🟢 `namespace`
</Tab>
<Tab title="arguments">
- 🟢 `path`
- 🔴 `namespace`
- 🔴 `suffix`
- 🔴 `pluginData`
</Tab>
<Tab title="results">
- 🟢 `contents`
- 🟢 `loader`
- 🔴 `errors`
- 🔴 `pluginData`
- 🔴 `pluginName`
- 🔴 `resolveDir`
- 🔴 `warnings`
- 🔴 `watchDirs`
- 🔴 `watchFiles`
</Tab>
</Tabs>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,229 @@
---
title: Hot reloading
description: Hot Module Replacement (HMR) for Bun's development server
---
Hot Module Replacement (HMR) allows you to update modules in a running application without needing a full page reload. This preserves the application state and improves the development experience.
<Note>HMR is enabled by default when using Bun's full-stack development server.</Note>
## `import.meta.hot` API Reference
Bun implements a client-side HMR API modeled after [Vite's `import.meta.hot` API](https://vite.dev/guide/api-hmr). It can be checked for with `if (import.meta.hot)`, tree-shaking it in production.
```ts title="index.ts" icon="/icons/typescript.svg"
if (import.meta.hot) {
// HMR APIs are available.
}
```
However, this check is often not needed as Bun will dead-code-eliminate calls to all of the HMR APIs in production builds.
```ts title="index.ts" icon="/icons/typescript.svg"
// This entire function call will be removed in production!
import.meta.hot.dispose(() => {
console.log("dispose");
});
```
<Warning>
For this to work, Bun forces these APIs to be called without indirection. That means the following do not work:
```ts title="index.ts" icon="/icons/typescript.svg"
// INVALID: Assigning `hot` to a variable
const hot = import.meta.hot;
hot.accept();
// INVALID: Assigning `import.meta` to a variable
const meta = import.meta;
meta.hot.accept();
console.log(meta.hot.data);
// INVALID: Passing to a function
doSomething(import.meta.hot.dispose);
// OK: The full phrase "import.meta.hot.<API>" must be called directly:
import.meta.hot.accept();
// OK: `data` can be passed to functions:
doSomething(import.meta.hot.data);
```
</Warning>
<Note>
The HMR API is still a work in progress. Some features are missing. HMR can be disabled in `Bun.serve` by setting the development option to `{ hmr: false }`.
</Note>
## API Methods
| Method | Status | Notes |
| ------------------ | ------ | --------------------------------------------------------------------- |
| `hot.accept()` | ✅ | Indicate that a hot update can be replaced gracefully. |
| `hot.data` | ✅ | Persist data between module evaluations. |
| `hot.dispose()` | ✅ | Add a callback function to run when a module is about to be replaced. |
| `hot.invalidate()` | ❌ | |
| `hot.on()` | ✅ | Attach an event listener |
| `hot.off()` | ✅ | Remove an event listener from `on`. |
| `hot.send()` | ❌ | |
| `hot.prune()` | 🚧 | NOTE: Callback is currently never called. |
| `hot.decline()` | ✅ | No-op to match Vite's `import.meta.hot` |
## import.meta.hot.accept()
The `accept()` method indicates that a module can be hot-replaced. When called without arguments, it indicates that this module can be replaced simply by re-evaluating the file. After a hot update, importers of this module will be automatically patched.
```ts title="index.ts" icon="/icons/typescript.svg"
// index.ts
import { getCount } from "./foo.ts";
console.log("count is ", getCount());
import.meta.hot.accept();
export function getNegativeCount() {
return -getCount();
}
```
This creates a hot-reloading boundary for all of the files that `index.ts` imports. That means whenever `foo.ts` or any of its dependencies are saved, the update will bubble up to `index.ts` will re-evaluate. Files that import `index.ts` will then be patched to import the new version of `getNegativeCount()`. If only `index.ts` is updated, only the one file will be re-evaluated, and the counter in `foo.ts` is reused.
This may be used in combination with `import.meta.hot.data` to transfer state from the previous module to the new one.
<Info>
When no modules call `import.meta.hot.accept()` (and there isn't React Fast Refresh or a plugin calling it for you),
the page will reload when the file updates, and a console warning shows which files were invalidated. This warning is
safe to ignore if it makes more sense to rely on full page reloads.
</Info>
### With callback
When provided one callback, `import.meta.hot.accept` will function how it does in Vite. Instead of patching the importers of this module, it will call the callback with the new module.
```ts title="index.ts" icon="/icons/typescript.svg"
export const count = 0;
import.meta.hot.accept(newModule => {
if (newModule) {
// newModule is undefined when SyntaxError happened
console.log("updated: count is now ", newModule.count);
}
});
```
<Tip>
Prefer using `import.meta.hot.accept()` without an argument as it usually makes your code easier to understand.
</Tip>
### Accepting other modules
```ts title="index.ts" icon="/icons/typescript.svg"
import { count } from "./foo";
import.meta.hot.accept("./foo", () => {
if (!newModule) return;
console.log("updated: count is now ", count);
});
```
Indicates that a dependency's module can be accepted. When the dependency is updated, the callback will be called with the new module.
### With multiple dependencies
```ts title="index.ts" icon="/icons/typescript.svg"
import.meta.hot.accept(["./foo", "./bar"], newModules => {
// newModules is an array where each item corresponds to the updated module
// or undefined if that module had a syntax error
});
```
Indicates that multiple dependencies' modules can be accepted. This variant accepts an array of dependencies, where the callback will receive the updated modules, and `undefined` for any that had errors.
## import.meta.hot.data
`import.meta.hot.data` maintains state between module instances during hot replacement, enabling data transfer from previous to new versions. When `import.meta.hot.data` is written into, Bun will also mark this module as capable of self-accepting (equivalent of calling `import.meta.hot.accept()`).
```tsx title="index.tsx" icon="/icons/typescript.svg"
import { createRoot } from "react-dom/client";
import { App } from "./app";
const root = (import.meta.hot.data.root ??= createRoot(elem));
root.render(<App />); // re-use an existing root
```
In production, `data` is inlined to be `{}`, meaning it cannot be used as a state holder.
<Tip>
The above pattern is recommended for stateful modules because Bun knows it can minify `{}.prop ??= value` into `value`
in production.
</Tip>
## import.meta.hot.dispose()
Attaches an on-dispose callback. This is called:
- Just before the module is replaced with another copy (before the next is loaded)
- After the module is detached (removing all imports to this module, see `import.meta.hot.prune()`)
```ts title="index.ts" icon="/icons/typescript.svg"
const sideEffect = setupSideEffect();
import.meta.hot.dispose(() => {
sideEffect.cleanup();
});
```
<Warning>This callback is not called on route navigation or when the browser tab closes.</Warning>
Returning a promise will delay module replacement until the module is disposed. All dispose callbacks are called in parallel.
## import.meta.hot.prune()
Attaches an on-prune callback. This is called when all imports to this module are removed, but the module was previously loaded.
This can be used to clean up resources that were created when the module was loaded. Unlike `import.meta.hot.dispose()`, this pairs much better with `accept` and `data` to manage stateful resources. A full example managing a WebSocket:
```ts title="index.ts" icon="/icons/typescript.svg"
import { something } from "./something";
// Initialize or re-use a WebSocket connection
export const ws = (import.meta.hot.data.ws ??= new WebSocket(location.origin));
// If the module's import is removed, clean up the WebSocket connection.
import.meta.hot.prune(() => {
ws.close();
});
```
<Info>
If `dispose` was used instead, the WebSocket would close and re-open on every hot update. Both versions of the code
will prevent page reloads when imported files are updated.
</Info>
## import.meta.hot.on() and off()
`on()` and `off()` are used to listen for events from the HMR runtime. Event names are prefixed with a prefix so that plugins do not conflict with each other.
```ts title="index.ts" icon="/icons/typescript.svg"
import.meta.hot.on("bun:beforeUpdate", () => {
console.log("before a hot update");
});
```
When a file is replaced, all of its event listeners are automatically removed.
### Built-in events
| Event | Emitted when |
| ---------------------- | ----------------------------------------------------------------------------------------------- |
| `bun:beforeUpdate` | before a hot update is applied. |
| `bun:afterUpdate` | after a hot update is applied. |
| `bun:beforeFullReload` | before a full page reload happens. |
| `bun:beforePrune` | before prune callbacks are called. |
| `bun:invalidate` | when a module is invalidated with `import.meta.hot.invalidate()` |
| `bun:error` | when a build or runtime error occurs |
| `bun:ws:disconnect` | when the HMR WebSocket connection is lost. This can indicate the development server is offline. |
| `bun:ws:connect` | when the HMR WebSocket connects or re-connects. |
<Note>For compatibility with Vite, the above events are also available via `vite:*` prefix instead of `bun:*`.</Note>

View File

@@ -0,0 +1,498 @@
---
title: HTML & static sites
description: Build static sites, landing pages, and web applications with Bun's bundler
---
Bun's bundler has first-class support for HTML. Build static sites, landing pages, and web applications with zero configuration. Just point Bun at your HTML file and it handles everything else.
```html title="index.html" icon="file-code"
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="./styles.css" />
<script src="./app.ts" type="module"></script>
</head>
<body>
<img src="./logo.png" />
</body>
</html>
```
To get started, pass HTML files to `bun`.
```bash terminal icon="terminal"
bun ./index.html
```
```
Bun v1.3.3
ready in 6.62ms
→ http://localhost:3000/
Press h + Enter to show shortcuts
```
Bun's development server provides powerful features with zero configuration:
- **Automatic Bundling** - Bundles and serves your HTML, JavaScript, and CSS
- **Multi-Entry Support** - Handles multiple HTML entry points and glob entry points
- **Modern JavaScript** - TypeScript & JSX support out of the box
- **Smart Configuration** - Reads `tsconfig.json` for paths, JSX options, experimental decorators, and more
- **Plugins** - Plugins for TailwindCSS and more
- **ESM & CommonJS** - Use ESM and CommonJS in your JavaScript, TypeScript, and JSX files
- **CSS Bundling & Minification** - Bundles CSS from `<link>` tags and `@import` statements
- **Asset Management** - Automatic copying & hashing of images and assets; Rewrites asset paths in JavaScript, CSS, and HTML
## Single Page Apps (SPA)
When you pass a single `.html` file to Bun, Bun will use it as a fallback route for all paths. This makes it perfect for single page apps that use client-side routing:
```bash terminal icon="terminal"
bun index.html
```
```
Bun v1.3.3
ready in 6.62ms
→ http://localhost:3000/
Press h + Enter to show shortcuts
```
Your React or other SPA will work out of the box — no configuration needed. All routes like `/about`, `/users/123`, etc. will serve the same HTML file, letting your client-side router handle the navigation.
```html title="index.html" icon="file-code"
<!doctype html>
<html>
<head>
<title>My SPA</title>
<script src="./app.tsx" type="module"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
```
## Multi-page apps (MPA)
Some projects have several separate routes or HTML files as entry points. To support multiple entry points, pass them all to `bun`:
```bash terminal icon="terminal"
bun ./index.html ./about.html
```
```txt
Bun v1.3.3
ready in 6.62ms
→ http://localhost:3000/
Routes:
/ ./index.html
/about ./about.html
Press h + Enter to show shortcuts
```
This will serve:
- `index.html` at `/`
- `about.html` at `/about`
### Glob patterns
To specify multiple files, you can use glob patterns that end in `.html`:
```bash terminal icon="terminal"
bun ./**/*.html
```
```
Bun v1.3.3
ready in 6.62ms
→ http://localhost:3000/
Routes:
/ ./index.html
/about ./about.html
Press h + Enter to show shortcuts
```
### Path normalization
The base path is chosen from the longest common prefix among all the files.
```bash terminal icon="terminal"
bun ./index.html ./about/index.html ./about/foo/index.html
```
```
Bun v1.3.3
ready in 6.62ms
→ http://localhost:3000/
Routes:
/ ./index.html
/about ./about/index.html
/about/foo ./about/foo/index.html
Press h + Enter to show shortcuts
```
## JavaScript, TypeScript, and JSX
Bun's transpiler natively implements JavaScript, TypeScript, and JSX support. Learn more about loaders in Bun.
<Note>Bun's transpiler is also used at runtime.</Note>
### ES Modules & CommonJS
You can use ESM and CJS in your JavaScript, TypeScript, and JSX files. Bun will handle the transpilation and bundling automatically.
There is no pre-build or separate optimization step. It's all done at the same time.
Learn more about module resolution in Bun.
## CSS
Bun's CSS parser is also natively implemented (clocking in around 58,000 lines of Zig).
It's also a CSS bundler. You can use `@import` in your CSS files to import other CSS files.
For example:
<CodeGroup>
```css styles.css icon="file-code"
@import "./abc.css";
.container {
background-color: blue;
}
```
```css abc.css icon="file-code"
body {
background-color: red;
}
```
</CodeGroup>
This outputs:
```css styles.css icon="file-code"
body {
background-color: red;
}
.container {
background-color: blue;
}
```
### Referencing local assets in CSS
You can reference local assets in your CSS files.
```css styles.css icon="file-code"
body {
background-image: url("./logo.png");
}
```
This will copy `./logo.png` to the output directory and rewrite the path in the CSS file to include a content hash.
```css styles.css icon="file-code"
body {
background-image: url("./logo-[ABC123].png");
}
```
### Importing CSS in JavaScript
To associate a CSS file with a JavaScript file, you can import it in your JavaScript file.
```ts app.ts icon="/icons/typescript.svg"
import "./styles.css";
import "./more-styles.css";
```
This generates `./app.css` and `./app.js` in the output directory. All CSS files imported from JavaScript will be bundled into a single CSS file per entry point. If you import the same CSS file from multiple JavaScript files, it will only be included once in the output CSS file.
## Plugins
The dev server supports plugins.
### Tailwind CSS
To use TailwindCSS, install the `bun-plugin-tailwind` plugin:
```bash terminal icon="terminal"
# Or any npm client
bun install --dev bun-plugin-tailwind
```
Then, add the plugin to your `bunfig.toml`:
```toml title="bunfig.toml" icon="settings"
[serve.static]
plugins = ["bun-plugin-tailwind"]
```
Then, reference TailwindCSS in your HTML via `<link>` tag, `@import` in CSS, or import in JavaScript.
<Tabs>
<Tab title="index.html">
```html title="index.html" icon="file-code"
<!-- Reference TailwindCSS in your HTML -->
<link rel="stylesheet" href="tailwindcss" />
```
</Tab>
<Tab title="styles.css">
```css title="styles.css" icon="file-code"
@import "tailwindcss";
```
</Tab>
<Tab title="app.ts">
```ts title="app.ts" icon="/icons/typescript.svg"
import "tailwindcss";
```
</Tab>
</Tabs>
<Info>Only one of those are necessary, not all three.</Info>
## Inline environment variables
Bun can replace `process.env.*` references in your JavaScript and TypeScript with their actual values at build time. This is useful for injecting configuration like API URLs or feature flags into your frontend code.
### Dev server (runtime)
To inline environment variables when using `bun ./index.html`, configure the `env` option in your `bunfig.toml`:
```toml title="bunfig.toml" icon="settings"
[serve.static]
env = "PUBLIC_*" # only inline env vars starting with PUBLIC_ (recommended)
# env = "inline" # inline all environment variables
# env = "disable" # disable env var replacement (default)
```
<Note>
This only works with literal `process.env.FOO` references, not `import.meta.env` or indirect access like `const env =
process.env; env.FOO`.
If an environment variable is not set, you may see runtime errors like `ReferenceError: process
is not defined` in the browser.
</Note>
Then run the dev server:
```bash terminal icon="terminal"
PUBLIC_API_URL=https://api.example.com bun ./index.html
```
### Build for production
When building static HTML for production, use the `env` option to inline environment variables:
<Tabs>
<Tab title="CLI">
```bash terminal icon="terminal"
# Inline all environment variables
bun build ./index.html --outdir=dist --env=inline
# Only inline env vars with a specific prefix (recommended)
bun build ./index.html --outdir=dist --env=PUBLIC_*
```
</Tab>
<Tab title="API">
```ts title="build.ts" icon="/icons/typescript.svg"
// Inline all environment variables
await Bun.build({
entrypoints: ["./index.html"],
outdir: "./dist",
env: "inline", // [!code highlight]
});
// Only inline env vars with a specific prefix (recommended)
await Bun.build({
entrypoints: ["./index.html"],
outdir: "./dist",
env: "PUBLIC_*", // [!code highlight]
});
```
</Tab>
</Tabs>
### Example
Given this source file:
```ts title="app.ts" icon="/icons/typescript.svg"
const apiUrl = process.env.PUBLIC_API_URL;
console.log(`API URL: ${apiUrl}`);
```
And running with `PUBLIC_API_URL=https://api.example.com`:
```bash terminal icon="terminal"
PUBLIC_API_URL=https://api.example.com bun build ./index.html --outdir=dist --env=PUBLIC_*
```
The bundled output will contain:
```js title="dist/app.js" icon="/icons/javascript.svg"
const apiUrl = "https://api.example.com";
console.log(`API URL: ${apiUrl}`);
```
## Echo console logs from browser to terminal
Bun's dev server supports streaming console logs from the browser to the terminal.
To enable, pass the `--console` CLI flag.
```bash terminal icon="terminal"
bun ./index.html --console
```
```
Bun v1.3.3
ready in 6.62ms
→ http://localhost:3000/
Press h + Enter to show shortcuts
```
Each call to `console.log` or `console.error` will be broadcast to the terminal that started the server. This is useful to see errors from the browser in the same place you run your server. This is also useful for AI agents that watch terminal output.
Internally, this reuses the existing WebSocket connection from hot module reloading to send the logs.
## Edit files in the browser
Bun's frontend dev server has support for Automatic Workspace Folders in Chrome DevTools, which lets you save edits to files in the browser.
## Keyboard Shortcuts
While the server is running:
- `o + Enter` - Open in browser
- `c + Enter` - Clear console
- `q + Enter` (or `Ctrl+C`) - Quit server
## Build for Production
When you're ready to deploy, use `bun build` to create optimized production bundles:
<Tabs>
<Tab title="CLI">
```bash terminal icon="terminal"
bun build ./index.html --minify --outdir=dist
```
</Tab>
<Tab title="API">
```ts title="build.ts" icon="/icons/typescript.svg"
await Bun.build({
entrypoints: ["./index.html"],
outdir: "./dist",
minify: true,
});
```
</Tab>
</Tabs>
<Warning>
Currently, plugins are only supported through `Bun.build`'s API or through `bunfig.toml` with the frontend dev server
- not yet supported in `bun build`'s CLI.
</Warning>
### Watch Mode
You can run `bun build --watch` to watch for changes and rebuild automatically. This works nicely for library development.
<Info>You've never seen a watch mode this fast.</Info>
## Plugin API
Need more control? Configure the bundler through the JavaScript API and use Bun's builtin `HTMLRewriter` to preprocess HTML.
```ts title="build.ts" icon="/icons/typescript.svg"
await Bun.build({
entrypoints: ["./index.html"],
outdir: "./dist",
minify: true,
plugins: [
{
// A plugin that makes every HTML tag lowercase
name: "lowercase-html-plugin",
setup({ onLoad }) {
const rewriter = new HTMLRewriter().on("*", {
element(element) {
element.tagName = element.tagName.toLowerCase();
},
text(element) {
element.replace(element.text.toLowerCase());
},
});
onLoad({ filter: /\.html$/ }, async args => {
const html = await Bun.file(args.path).text();
return {
// Bun's bundler will scan the HTML for <script> tags, <link rel="stylesheet"> tags, and other assets
// and bundle them automatically
contents: rewriter.transform(html),
loader: "html",
};
});
},
},
],
});
```
## What Gets Processed?
Bun automatically handles all common web assets:
- **Scripts** (`<script src>`) are run through Bun's JavaScript/TypeScript/JSX bundler
- **Stylesheets** (`<link rel="stylesheet">`) are run through Bun's CSS parser & bundler
- **Images** (`<img>`, `<picture>`) are copied and hashed
- **Media** (`<video>`, `<audio>`, `<source>`) are copied and hashed
- Any `<link>` tag with an `href` attribute pointing to a local file is rewritten to the new path, and hashed
All paths are resolved relative to your HTML file, making it easy to organize your project however you want.
<Warning>
**This is a work in progress**
- Need more plugins
- Need more configuration options for things like asset handling
- Need a way to configure CORS, headers, etc.
{/* todo: find the correct link to link to as this 404's and there isn't any similar files */}
{/* If you want to submit a PR, most of the code is [here](https://github.com/oven-sh/bun/blob/main/src/bun.js/api/bun/html-rewriter.ts). You could even copy paste that file into your project and use it as a starting point. */}
</Warning>
## How this works
This is a small wrapper around Bun's support for HTML imports in JavaScript.
## Standalone HTML
You can bundle your entire frontend into a **single self-contained `.html` file** with no external dependencies using `--compile --target=browser`. All JavaScript, CSS, and images are inlined directly into the HTML.
```bash terminal icon="terminal"
bun build --compile --target=browser ./index.html --outdir=dist
```
Learn more in the [Standalone HTML docs](/docs/bundler/standalone-html).
## Adding a backend to your frontend
To add a backend to your frontend, you can use the "routes" option in `Bun.serve`.
Learn more in the full-stack docs.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,451 @@
---
title: Loaders
description: Built-in loaders for the Bun bundler and runtime
---
The Bun bundler implements a set of default loaders out of the box.
> As a rule of thumb: **the bundler and the runtime both support the same set of file types out of the box.**
`.js` `.cjs` `.mjs` `.mts` `.cts` `.ts` `.tsx` `.jsx` `.css` `.json` `.jsonc` `.toml` `.yaml` `.yml` `.txt` `.wasm` `.node` `.html` `.sh`
Bun uses the file extension to determine which built-in loader should be used to parse the file. Every loader has a name, such as `js`, `tsx`, or `json`. These names are used when building plugins that extend Bun with custom loaders.
You can explicitly specify which loader to use using the `'type'` import attribute.
```ts title="index.ts" icon="/icons/typescript.svg"
import my_toml from "./my_file" with { type: "toml" };
// or with dynamic imports
const { default: my_toml } = await import("./my_file", { with: { type: "toml" } });
```
## Built-in loaders
### `js`
**JavaScript loader.** Default for `.cjs` and `.mjs`.
Parses the code and applies a set of default transforms like dead-code elimination and tree shaking. Note that Bun does not attempt to down-convert syntax at the moment.
---
### `jsx`
**JavaScript + JSX loader.** Default for `.js` and `.jsx`.
Same as the `js` loader, but JSX syntax is supported. By default, JSX is down-converted to plain JavaScript; the details of how this is done depends on the `jsx*` compiler options in your `tsconfig.json`. Refer to the [TypeScript documentation on JSX](https://www.typescriptlang.org/tsconfig#jsx) for more information.
---
### `ts`
**TypeScript loader.** Default for `.ts`, `.mts`, and `.cts`.
Strips out all TypeScript syntax, then behaves identically to the `js` loader. Bun does not perform typechecking.
---
### `tsx`
**TypeScript + JSX loader.** Default for `.tsx`.
Transpiles both TypeScript and JSX to vanilla JavaScript.
---
### `json`
**JSON loader.** Default for `.json`.
JSON files can be directly imported.
```js
import pkg from "./package.json";
pkg.name; // => "my-package"
```
During bundling, the parsed JSON is inlined into the bundle as a JavaScript object.
```js
const pkg = {
name: "my-package",
// ... other fields
};
pkg.name;
```
If a `.json` file is passed as an entrypoint to the bundler, it will be converted to a `.js` module that `export default`s the parsed object.
<CodeGroup>
```json Input
{
"name": "John Doe",
"age": 35,
"email": "johndoe@example.com"
}
```
```js Output
export default {
name: "John Doe",
age: 35,
email: "johndoe@example.com",
};
```
</CodeGroup>
---
### `jsonc`
**JSON with Comments loader.** Default for `.jsonc`.
JSONC (JSON with Comments) files can be directly imported. Bun will parse them, stripping out comments and trailing commas.
```js
import config from "./config.jsonc";
console.log(config);
```
During bundling, the parsed JSONC is inlined into the bundle as a JavaScript object, identical to the `json` loader.
```js
var config = {
option: "value",
};
```
<Note>
Bun automatically uses the `jsonc` loader for `tsconfig.json`, `jsconfig.json`, `package.json`, and `bun.lock` files.
</Note>
---
### `toml`
**TOML loader.** Default for `.toml`.
TOML files can be directly imported. Bun will parse them with its fast native TOML parser.
```js
import config from "./bunfig.toml";
config.logLevel; // => "debug"
// via import attribute:
// import myCustomTOML from './my.config' with {type: "toml"};
```
During bundling, the parsed TOML is inlined into the bundle as a JavaScript object.
```js
var config = {
logLevel: "debug",
// ...other fields
};
config.logLevel;
```
If a `.toml` file is passed as an entrypoint, it will be converted to a `.js` module that `export default`s the parsed object.
<CodeGroup>
```toml Input
name = "John Doe"
age = 35
email = "johndoe@example.com"
```
```js Output
export default {
name: "John Doe",
age: 35,
email: "johndoe@example.com",
};
```
</CodeGroup>
---
### `yaml`
**YAML loader.** Default for `.yaml` and `.yml`.
YAML files can be directly imported. Bun will parse them with its fast native YAML parser.
```js
import config from "./config.yaml";
console.log(config);
// via import attribute:
import data from "./data.txt" with { type: "yaml" };
```
During bundling, the parsed YAML is inlined into the bundle as a JavaScript object.
```js
var config = {
name: "my-app",
version: "1.0.0",
// ...other fields
};
```
If a `.yaml` or `.yml` file is passed as an entrypoint, it will be converted to a `.js` module that `export default`s the parsed object.
<CodeGroup>
```yaml Input
name: John Doe
age: 35
email: johndoe@example.com
```
```js Output
export default {
name: "John Doe",
age: 35,
email: "johndoe@example.com",
};
```
</CodeGroup>
---
### `text`
**Text loader.** Default for `.txt`.
The contents of the text file are read and inlined into the bundle as a string. Text files can be directly imported. The file is read and returned as a string.
```js
import contents from "./file.txt";
console.log(contents); // => "Hello, world!"
// To import an html file as text
// The "type" attribute can be used to override the default loader.
import html from "./index.html" with { type: "text" };
```
When referenced during a build, the contents are inlined into the bundle as a string.
```js
var contents = `Hello, world!`;
console.log(contents);
```
If a `.txt` file is passed as an entrypoint, it will be converted to a `.js` module that `export default`s the file contents.
<CodeGroup>
```txt Input
Hello, world!
```
```js Output
export default "Hello, world!";
```
</CodeGroup>
---
### `napi`
**Native addon loader.** Default for `.node`.
In the runtime, native addons can be directly imported.
```js
import addon from "./addon.node";
console.log(addon);
```
<Note>In the bundler, `.node` files are handled using the file loader.</Note>
---
### `sqlite`
**SQLite loader.** Requires `with { "type": "sqlite" }` import attribute.
In the runtime and bundler, SQLite databases can be directly imported. This will load the database using `bun:sqlite`.
```js
import db from "./my.db" with { type: "sqlite" };
```
<Warning>This is only supported when the target is `bun`.</Warning>
By default, the database is external to the bundle (so that you can potentially use a database loaded elsewhere), so the database file on-disk won't be bundled into the final output.
You can change this behavior with the `"embed"` attribute:
```js
// embed the database into the bundle
import db from "./my.db" with { type: "sqlite", embed: "true" };
```
<Info>
When using a standalone executable, the database is embedded into the single-file executable.
Otherwise, the database to embed is copied into the `outdir` with a hashed filename.
</Info>
---
### `html`
**HTML loader.** Default for `.html`.
The `html` loader processes HTML files and bundles any referenced assets. It will:
- Bundle and hash referenced JavaScript files (`<script src="...">`)
- Bundle and hash referenced CSS files (`<link rel="stylesheet" href="...">`)
- Hash referenced images (`<img src="...">`)
- Preserve external URLs (by default, anything starting with `http://` or `https://`)
For example, given this HTML file:
```html title="src/index.html" icon="file-code"
<!DOCTYPE html>
<html>
<body>
<img src="./image.jpg" alt="Local image" />
<img src="https://example.com/image.jpg" alt="External image" />
<script type="module" src="./script.js"></script>
</body>
</html>
```
It will output a new HTML file with the bundled assets:
```html title="dist/index.html" icon="file-code"
<!DOCTYPE html>
<html>
<body>
<img src="./image-HASHED.jpg" alt="Local image" />
<img src="https://example.com/image.jpg" alt="External image" />
<script type="module" src="./output-ALSO-HASHED.js"></script>
</body>
</html>
```
Under the hood, it uses [`lol-html`](https://github.com/cloudflare/lol-html) to extract script and link tags as entrypoints, and other assets as external.
<Accordion title="List of supported HTML selectors">
Currently, the list of selectors is:
- `audio[src]`
- `iframe[src]`
- `img[src]`
- `img[srcset]`
- `link:not([rel~='stylesheet']):not([rel~='modulepreload']):not([rel~='manifest']):not([rel~='icon']):not([rel~='apple-touch-icon'])[href]`
- `link[as='font'][href], link[type^='font/'][href]`
- `link[as='image'][href]`
- `link[as='style'][href]`
- `link[as='video'][href], link[as='audio'][href]`
- `link[as='worker'][href]`
- `link[rel='icon'][href], link[rel='apple-touch-icon'][href]`
- `link[rel='manifest'][href]`
- `link[rel='stylesheet'][href]`
- `script[src]`
- `source[src]`
- `source[srcset]`
- `video[poster]`
- `video[src]`
</Accordion>
<Note>
**HTML Loader Behavior in Different Contexts**
The `html` loader behaves differently depending on how it's used:
- Static Build: When you run `bun build ./index.html`, Bun produces a static site with all assets bundled and hashed.
- Runtime: When you run `bun run server.ts` (where `server.ts` imports an HTML file), Bun bundles assets on-the-fly during development, enabling features like hot module replacement.
- Full-stack Build: When you run `bun build --target=bun server.ts` (where `server.ts` imports an HTML file), the import resolves to a manifest object that `Bun.serve` uses to efficiently serve pre-bundled assets in production.
</Note>
---
### `css`
**CSS loader.** Default for `.css`.
CSS files can be directly imported. The bundler will parse and bundle CSS files, handling `@import` statements and `url()` references.
```js
import "./styles.css";
```
During bundling, all imported CSS files are bundled together into a single `.css` file in the output directory.
```css
.my-class {
background: url("./image.png");
}
```
---
### `sh`
**Bun Shell loader.** Default for `.sh` files.
This loader is used to parse Bun Shell scripts. It's only supported when starting Bun itself, so it's not available in the bundler or in the runtime.
```bash
bun run ./script.sh
```
---
### `file`
**File loader.** Default for all unrecognized file types.
The file loader resolves the import as a path/URL to the imported file. It's commonly used for referencing media or font assets.
```js
// logo.ts
import logo from "./logo.svg";
console.log(logo);
```
In the runtime, Bun checks that the `logo.svg` file exists and converts it to an absolute path to the location of `logo.svg` on disk.
```bash
bun run logo.ts
# Output: /path/to/project/logo.svg
```
In the bundler, things are slightly different. The file is copied into `outdir` as-is, and the import is resolved as a relative path pointing to the copied file.
```js
// Output
var logo = "./logo.svg";
console.log(logo);
```
If a value is specified for `publicPath`, the import will use value as a prefix to construct an absolute path/URL.
| Public path | Resolved import |
| ---------------------------- | ---------------------------------- |
| `""` (default) | `/logo.svg` |
| `"/assets"` | `/assets/logo.svg` |
| `"https://cdn.example.com/"` | `https://cdn.example.com/logo.svg` |
<Note>
The location and file name of the copied file is determined by the value of `naming.asset`.
This loader is copied into the `outdir` as-is. The name of the copied file is determined using the value of `naming.asset`.
</Note>

View File

@@ -0,0 +1,328 @@
---
title: Macros
description: Run JavaScript functions at bundle-time with Bun macros
---
Macros are a mechanism for running JavaScript functions at bundle-time. The value returned from these functions are directly inlined into your bundle.
As a toy example, consider this simple function that returns a random number.
```ts title="random.ts" icon="/icons/typescript.svg"
export function random() {
return Math.random();
}
```
This is just a regular function in a regular file, but we can use it as a macro like so:
```tsx title="cli.tsx" icon="/icons/typescript.svg"
import { random } from "./random.ts" with { type: "macro" };
console.log(`Your random number is ${random()}`);
```
<Note>
Macros are indicated using import attribute syntax. If you haven't seen this syntax before, it's a Stage 3 TC39
proposal that lets you attach additional metadata to import statements.
</Note>
Now we'll bundle this file with `bun build`. The bundled file will be printed to stdout.
```bash terminal icon="terminal"
bun build ./cli.tsx
```
```js
console.log(`Your random number is ${0.6805550949689833}`);
```
As you can see, the source code of the `random` function occurs nowhere in the bundle. Instead, it is executed during bundling and function call (`random()`) is replaced with the result of the function. Since the source code will never be included in the bundle, macros can safely perform privileged operations like reading from a database.
## When to use macros
If you have several build scripts for small things where you would otherwise have a one-off build script, bundle-time code execution can be easier to maintain. It lives with the rest of your code, it runs with the rest of the build, it is automatically parallelized, and if it fails, the build fails too.
If you find yourself running a lot of code at bundle-time though, consider running a server instead.
## Import attributes
Bun Macros are import statements annotated using either:
- `with { type: 'macro' }` — an import attribute, a Stage 3 ECMA Script proposal
- `assert { type: 'macro' }` — an import assertion, an earlier incarnation of import attributes that has now been abandoned (but is already supported by a number of browsers and runtimes)
## Security considerations
Macros must explicitly be imported with `{ type: "macro" }` in order to be executed at bundle-time. These imports have no effect if they are not called, unlike regular JavaScript imports which may have side effects.
You can disable macros entirely by passing the `--no-macros` flag to Bun. It produces a build error like this:
```
error: Macros are disabled
foo();
^
./hello.js:3:1 53
```
To reduce the potential attack surface for malicious packages, macros cannot be invoked from inside `node_modules/**/*`. If a package attempts to invoke a macro, you'll see an error like this:
```
error: For security reasons, macros cannot be run from node_modules.
beEvil();
^
node_modules/evil/index.js:3:1 50
```
Your application code can still import macros from `node_modules` and invoke them.
```ts title="cli.tsx" icon="/icons/typescript.svg"
import { macro } from "some-package" with { type: "macro" };
macro();
```
## Export condition "macro"
When shipping a library containing a macro to npm or another package registry, use the `"macro"` export condition to provide a special version of your package exclusively for the macro environment.
```json title="package.json" icon="file-json"
{
"name": "my-package",
"exports": {
"import": "./index.js",
"require": "./index.js",
"default": "./index.js",
"macro": "./index.macro.js"
}
}
```
With this configuration, users can consume your package at runtime or at bundle-time using the same import specifier:
```ts title="index.ts" icon="/icons/typescript.svg"
import pkg from "my-package"; // runtime import
import { macro } from "my-package" with { type: "macro" }; // macro import
```
The first import will resolve to `./node_modules/my-package/index.js`, while the second will be resolved by Bun's bundler to `./node_modules/my-package/index.macro.js`.
## Execution
When Bun's transpiler sees a macro import, it calls the function inside the transpiler using Bun's JavaScript runtime and converts the return value from JavaScript into an AST node. These JavaScript functions are called at bundle-time, not runtime.
Macros are executed synchronously in the transpiler during the visiting phase—before plugins and before the transpiler generates the AST. They are executed in the order they are imported. The transpiler will wait for the macro to finish executing before continuing. The transpiler will also await any Promise returned by a macro.
Bun's bundler is multi-threaded. As such, macros execute in parallel inside of multiple spawned JavaScript "workers".
## Dead code elimination
The bundler performs dead code elimination after running and inlining macros. So given the following macro:
```ts title="returnFalse.ts" icon="/icons/typescript.svg"
export function returnFalse() {
return false;
}
```
...then bundling the following file will produce an empty bundle, provided that the minify syntax option is enabled.
```ts title="index.ts" icon="/icons/typescript.svg"
import { returnFalse } from "./returnFalse.ts" with { type: "macro" };
if (returnFalse()) {
console.log("This code is eliminated");
}
```
## Serializability
Bun's transpiler needs to be able to serialize the result of the macro so it can be inlined into the AST. All JSON-compatible data structures are supported:
```ts title="macro.ts" icon="/icons/typescript.svg"
export function getObject() {
return {
foo: "bar",
baz: 123,
array: [1, 2, { nested: "value" }],
};
}
```
Macros can be async, or return Promise instances. Bun's transpiler will automatically await the Promise and inline the result.
```ts title="macro.ts" icon="/icons/typescript.svg"
export async function getText() {
return "async value";
}
```
The transpiler implements special logic for serializing common data formats like `Response`, `Blob`, `TypedArray`.
- **TypedArray**: Resolves to a base64-encoded string.
- **Response**: Bun will read the `Content-Type` and serialize accordingly; for instance, a Response with type `application/json` will be automatically parsed into an object and `text/plain` will be inlined as a string. Responses with an unrecognized or undefined type will be base-64 encoded.
- **Blob**: As with Response, the serialization depends on the `type` property.
The result of `fetch` is `Promise<Response>`, so it can be directly returned.
```ts title="macro.ts" icon="/icons/typescript.svg"
export function getObject() {
return fetch("https://bun.com");
}
```
Functions and instances of most classes (except those mentioned above) are not serializable.
```ts title="macro.ts" icon="/icons/typescript.svg"
export function getText(url: string) {
// this doesn't work!
return () => {};
}
```
## Arguments
Macros can accept inputs, but only in limited cases. The value must be statically known. For example, the following is not allowed:
```ts title="index.ts" icon="/icons/typescript.svg"
import { getText } from "./getText.ts" with { type: "macro" };
export function howLong() {
// the value of `foo` cannot be statically known
const foo = Math.random() ? "foo" : "bar";
const text = getText(`https://example.com/${foo}`);
console.log("The page is ", text.length, " characters long");
}
```
However, if the value of `foo` is known at bundle-time (say, if it's a constant or the result of another macro) then it's allowed:
```ts title="index.ts" icon="/icons/typescript.svg"
import { getText } from "./getText.ts" with { type: "macro" };
import { getFoo } from "./getFoo.ts" with { type: "macro" };
export function howLong() {
// this works because getFoo() is statically known
const foo = getFoo();
const text = getText(`https://example.com/${foo}`);
console.log("The page is", text.length, "characters long");
}
```
This outputs:
```js
function howLong() {
console.log("The page is", 1322, "characters long");
}
export { howLong };
```
## Examples
### Embed latest git commit hash
```ts title="getGitCommitHash.ts" icon="/icons/typescript.svg"
export function getGitCommitHash() {
const { stdout } = Bun.spawnSync({
cmd: ["git", "rev-parse", "HEAD"],
stdout: "pipe",
});
return stdout.toString();
}
```
When we build it, the `getGitCommitHash` is replaced with the result of calling the function:
<CodeGroup>
```ts input
import { getGitCommitHash } from "./getGitCommitHash.ts" with { type: "macro" };
console.log(`The current Git commit hash is ${getGitCommitHash()}`);
```
```ts output
console.log(`The current Git commit hash is 3ee3259104e4507cf62c160f0ff5357ec4c7a7f8`);
```
</CodeGroup>
<Info>
You're probably thinking "Why not just use `process.env.GIT_COMMIT_HASH`?" Well, you can do that too. But can you do
this with an environment variable?
</Info>
### Make fetch() requests at bundle-time
In this example, we make an outgoing HTTP request using `fetch()`, parse the HTML response using `HTMLRewriter`, and return an object containing the title and meta tagsall at bundle-time.
```ts title="meta.ts" icon="/icons/typescript.svg"
export async function extractMetaTags(url: string) {
const response = await fetch(url);
const meta = {
title: "",
};
new HTMLRewriter()
.on("title", {
text(element) {
meta.title += element.text;
},
})
.on("meta", {
element(element) {
const name =
element.getAttribute("name") || element.getAttribute("property") || element.getAttribute("itemprop");
if (name) meta[name] = element.getAttribute("content");
},
})
.transform(response);
return meta;
}
```
The `extractMetaTags` function is erased at bundle-time and replaced with the result of the function call. This means that the fetch request happens at bundle-time, and the result is embedded in the bundle. Also, the branch throwing the error is eliminated since it's unreachable.
<CodeGroup>
```jsx input
import { extractMetaTags } from "./meta.ts" with { type: "macro" };
export const Head = () => {
const headTags = extractMetaTags("https://example.com");
if (headTags.title !== "Example Domain") {
throw new Error("Expected title to be 'Example Domain'");
}
return (
<head>
<title>{headTags.title}</title>
<meta name="viewport" content={headTags.viewport} />
</head>
);
};
```
```jsx output
export const Head = () => {
const headTags = {
title: "Example Domain",
viewport: "width=device-width, initial-scale=1",
};
return (
<head>
<title>{headTags.title}</title>
<meta name="viewport" content={headTags.viewport} />
</head>
);
};
```
</CodeGroup>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,477 @@
---
title: Plugins
description: Universal plugin API for extending Bun's runtime and bundler
---
Bun provides a universal plugin API that can be used to extend both the runtime and bundler.
Plugins intercept imports and perform custom loading logic: reading files, transpiling code, etc. They can be used to add support for additional file types, like `.scss` or `.yaml`. In the context of Bun's bundler, plugins can be used to implement framework-level features like CSS extraction, macros, and client-server code co-location.
## Lifecycle hooks
Plugins can register callbacks to be run at various points in the lifecycle of a bundle:
- `onStart()`: Run once the bundler has started a bundle
- `onResolve()`: Run before a module is resolved
- `onLoad()`: Run before a module is loaded
- `onBeforeParse()`: Run zero-copy native addons in the parser thread before a file is parsed
- `onEnd()`: Run after the bundle is complete
## Reference
A rough overview of the types (please refer to Bun's `bun.d.ts` for the full type definitions):
```ts title="bun.d.ts" icon="/icons/typescript.svg"
type PluginBuilder = {
onStart(callback: () => void): void;
onResolve: (
args: { filter: RegExp; namespace?: string },
callback: (args: { path: string; importer: string }) => {
path: string;
namespace?: string;
} | void,
) => void;
onLoad: (
args: { filter: RegExp; namespace?: string },
defer: () => Promise<void>,
callback: (args: { path: string }) => {
loader?: Loader;
contents?: string;
exports?: Record<string, any>;
},
) => void;
onEnd(callback: (result: BuildOutput) => void | Promise<void>): void;
config: BuildConfig;
};
type Loader =
| "js"
| "jsx"
| "ts"
| "tsx"
| "json"
| "jsonc"
| "toml"
| "yaml"
| "file"
| "napi"
| "wasm"
| "text"
| "css"
| "html";
```
## Usage
A plugin is defined as simple JavaScript object containing a `name` property and a `setup` function.
```ts title="myPlugin.ts" icon="/icons/typescript.svg"
import type { BunPlugin } from "bun";
const myPlugin: BunPlugin = {
name: "Custom loader",
setup(build) {
// implementation
},
};
```
This plugin can be passed into the `plugins` array when calling `Bun.build`.
```ts title="index.ts" icon="/icons/typescript.svg"
await Bun.build({
entrypoints: ["./app.ts"],
outdir: "./out",
plugins: [myPlugin],
});
```
## Plugin lifecycle
### Namespaces
`onLoad` and `onResolve` accept an optional `namespace` string. What is a namespace?
Every module has a namespace. Namespaces are used to prefix the import in transpiled code; for instance, a loader with a `filter: /\.yaml$/` and `namespace: "yaml:"` will transform an import from `./myfile.yaml` into `yaml:./myfile.yaml`.
The default namespace is `"file"` and it is not necessary to specify it, for instance: `import myModule from "./my-module.ts"` is the same as `import myModule from "file:./my-module.ts"`.
Other common namespaces are:
- `"bun"`: for Bun-specific modules (e.g. `"bun:test"`, `"bun:sqlite"`)
- `"node"`: for Node.js modules (e.g. `"node:fs"`, `"node:path"`)
### onStart
```ts
onStart(callback: () => void): Promise<void> | void;
```
Registers a callback to be run when the bundler starts a new bundle.
```ts title="index.ts" icon="/icons/typescript.svg"
import { plugin } from "bun";
plugin({
name: "onStart example",
setup(build) {
build.onStart(() => {
console.log("Bundle started!");
});
},
});
```
The callback can return a Promise. After the bundle process has initialized, the bundler waits until all `onStart()` callbacks have completed before continuing.
For example:
```ts title="index.ts" icon="/icons/typescript.svg"
const result = await Bun.build({
entrypoints: ["./app.ts"],
outdir: "./dist",
sourcemap: "external",
plugins: [
{
name: "Sleep for 10 seconds",
setup(build) {
build.onStart(async () => {
await Bun.sleep(10_000);
});
},
},
{
name: "Log bundle time to a file",
setup(build) {
build.onStart(async () => {
const now = Date.now();
await Bun.$`echo ${now} > bundle-time.txt`;
});
},
},
],
});
```
In the above example, Bun will wait until the first `onStart()` (sleeping for 10 seconds) has completed, as well as the second `onStart()` (writing the bundle time to a file).
<Note>
`onStart()` callbacks (like every other lifecycle callback) do not have the ability to modify the `build.config`
object. If you want to mutate `build.config`, you must do so directly in the `setup()` function.
</Note>
### onResolve
```ts
onResolve(
args: { filter: RegExp; namespace?: string },
callback: (args: { path: string; importer: string }) => {
path: string;
namespace?: string;
} | void,
): void;
```
To bundle your project, Bun walks down the dependency tree of all modules in your project. For each imported module, Bun actually has to find and read that module. The "finding" part is known as "resolving" a module.
The `onResolve()` plugin lifecycle callback allows you to configure how a module is resolved.
The first argument to `onResolve()` is an object with a `filter` and `namespace` property. The `filter` is a regular expression which is run on the import string. Effectively, these allow you to filter which modules your custom resolution logic will apply to.
The second argument to `onResolve()` is a callback which is run for each module import Bun finds that matches the filter and namespace defined in the first argument.
The callback receives as input the path to the matching module. The callback can return a new path for the module. Bun will read the contents of the new path and parse it as a module.
For example, redirecting all imports to `images/` to `./public/images/`:
```ts title="index.ts" icon="/icons/typescript.svg"
import { plugin } from "bun";
plugin({
name: "onResolve example",
setup(build) {
build.onResolve({ filter: /.*/, namespace: "file" }, args => {
if (args.path.startsWith("images/")) {
return {
path: args.path.replace("images/", "./public/images/"),
};
}
});
},
});
```
### onLoad
```ts
onLoad(
args: { filter: RegExp; namespace?: string },
defer: () => Promise<void>,
callback: (args: { path: string, importer: string, namespace: string, kind: ImportKind }) => {
loader?: Loader;
contents?: string;
exports?: Record<string, any>;
},
): void;
```
After Bun's bundler has resolved a module, it needs to read the contents of the module and parse it.
The `onLoad()` plugin lifecycle callback allows you to modify the contents of a module before it is read and parsed by Bun.
Like `onResolve()`, the first argument to `onLoad()` allows you to filter which modules this invocation of `onLoad()` will apply to.
The second argument to `onLoad()` is a callback which is run for each matching module before Bun loads the contents of the module into memory.
This callback receives as input the path to the matching module, the importer of the module (the module that imported the module), the namespace of the module, and the kind of the module.
The callback can return a new `contents` string for the module as well as a new `loader`.
For example:
```ts title="index.ts" icon="/icons/typescript.svg"
import { plugin } from "bun";
const envPlugin: BunPlugin = {
name: "env plugin",
setup(build) {
build.onLoad({ filter: /env/, namespace: "file" }, args => {
return {
contents: `export default ${JSON.stringify(process.env)}`,
loader: "js",
};
});
},
});
Bun.build({
entrypoints: ["./app.ts"],
outdir: "./dist",
plugins: [envPlugin],
});
// import env from "env"
// env.FOO === "bar"
```
This plugin will transform all imports of the form `import env from "env"` into a JavaScript module that exports the current environment variables.
#### .defer()
One of the arguments passed to the `onLoad` callback is a `defer` function. This function returns a Promise that is resolved when all other modules have been loaded.
This allows you to delay execution of the `onLoad` callback until all other modules have been loaded.
This is useful for returning contents of a module that depends on other modules.
<Accordion title="Example: tracking and reporting unused exports">
```ts title="index.ts" icon="/icons/typescript.svg"
import { plugin } from "bun";
plugin({
name: "track imports",
setup(build) {
const transpiler = new Bun.Transpiler();
let trackedImports: Record<string, number> = {};
// Each module that goes through this onLoad callback
// will record its imports in `trackedImports`
build.onLoad({ filter: /\.ts/ }, async ({ path }) => {
const contents = await Bun.file(path).arrayBuffer();
const imports = transpiler.scanImports(contents);
for (const i of imports) {
trackedImports[i.path] = (trackedImports[i.path] || 0) + 1;
}
return undefined;
});
build.onLoad({ filter: /stats\.json/ }, async ({ defer }) => {
// Wait for all files to be loaded, ensuring
// that every file goes through the above `onLoad()` function
// and their imports tracked
await defer();
// Emit JSON containing the stats of each import
return {
contents: `export default ${JSON.stringify(trackedImports)}`,
loader: "json",
};
});
},
});
```
</Accordion>
<Warning>
The `.defer()` function currently has the limitation that it can only be called once per `onLoad` callback.
</Warning>
## Native plugins
One of the reasons why Bun's bundler is so fast is that it is written in native code and leverages multi-threading to load and parse modules in parallel.
However, one limitation of plugins written in JavaScript is that JavaScript itself is single-threaded.
Native plugins are written as NAPI modules and can be run on multiple threads. This allows native plugins to run much faster than JavaScript plugins.
In addition, native plugins can skip unnecessary work such as the UTF-8 -> UTF-16 conversion needed to pass strings to JavaScript.
These are the following lifecycle hooks which are available to native plugins:
- `onBeforeParse()`: Called on any thread before a file is parsed by Bun's bundler.
Native plugins are NAPI modules which expose lifecycle hooks as C ABI functions.
To create a native plugin, you must export a C ABI function which matches the signature of the native lifecycle hook you want to implement.
### Creating a native plugin in Rust
Native plugins are NAPI modules which expose lifecycle hooks as C ABI functions.
To create a native plugin, you must export a C ABI function which matches the signature of the native lifecycle hook you want to implement.
```bash terminal icon="terminal"
bun add -g @napi-rs/cli
napi new
```
Then install this crate:
```bash terminal icon="terminal"
cargo add bun-native-plugin
```
Now, inside the `lib.rs` file, we'll use the `bun_native_plugin::bun` proc macro to define a function which will implement our native plugin.
Here's an example implementing the `onBeforeParse` hook:
```rust title="lib.rs" icon="/icons/rust.svg"
use bun_native_plugin::{define_bun_plugin, OnBeforeParse, bun, Result, anyhow, BunLoader};
use napi_derive::napi;
/// Define the plugin and its name
define_bun_plugin!("replace-foo-with-bar");
/// Here we'll implement `onBeforeParse` with code that replaces all occurrences of
/// `foo` with `bar`.
///
/// We use the #[bun] macro to generate some of the boilerplate code.
///
/// The argument of the function (`handle: &mut OnBeforeParse`) tells
/// the macro that this function implements the `onBeforeParse` hook.
#[bun]
pub fn replace_foo_with_bar(handle: &mut OnBeforeParse) -> Result<()> {
// Fetch the input source code.
let input_source_code = handle.input_source_code()?;
// Get the Loader for the file
let loader = handle.output_loader();
let output_source_code = input_source_code.replace("foo", "bar");
handle.set_output_source_code(output_source_code, BunLoader::BUN_LOADER_JSX);
Ok(())
}
```
And to use it in `Bun.build()`:
```ts title="index.ts" icon="/icons/typescript.svg"
import myNativeAddon from "./my-native-addon";
Bun.build({
entrypoints: ["./app.tsx"],
plugins: [
{
name: "my-plugin",
setup(build) {
build.onBeforeParse(
{
namespace: "file",
filter: "**/*.tsx",
},
{
napiModule: myNativeAddon,
symbol: "replace_foo_with_bar",
// external: myNativeAddon.getSharedState()
},
);
},
},
],
});
```
### onBeforeParse
```ts
onBeforeParse(
args: { filter: RegExp; namespace?: string },
callback: { napiModule: NapiModule; symbol: string; external?: unknown },
): void;
```
This lifecycle callback is run immediately before a file is parsed by Bun's bundler.
As input, it receives the file's contents and can optionally return new source code.
<Info>This callback can be called from any thread and so the napi module implementation must be thread-safe.</Info>
### onEnd
```ts
onEnd(callback: (result: BuildOutput) => void | Promise<void>): void;
```
Registers a callback to be run after the bundle is complete. The callback receives the [`BuildOutput`](/docs/bundler#outputs) object containing the build results, including output files and any build messages.
```ts title="index.ts" icon="/icons/typescript.svg"
const result = await Bun.build({
entrypoints: ["./app.ts"],
outdir: "./dist",
plugins: [
{
name: "onEnd example",
setup(build) {
build.onEnd(result => {
console.log(`Build completed with ${result.outputs.length} files`);
for (const log of result.logs) {
console.log(log);
}
});
},
},
],
});
```
The callback can return a `Promise`. The build output promise from `Bun.build()` will not resolve until all `onEnd()` callbacks have completed.
```ts title="index.ts" icon="/icons/typescript.svg"
const result = await Bun.build({
entrypoints: ["./app.ts"],
outdir: "./dist",
plugins: [
{
name: "Upload to S3",
setup(build) {
build.onEnd(async result => {
if (!result.success) return;
for (const output of result.outputs) {
await uploadToS3(output);
}
});
},
},
],
});
```

View File

@@ -0,0 +1,314 @@
---
title: Standalone HTML
description: Bundle a single-page app into a single self-contained .html file with no external dependencies
---
Bun can bundle your entire frontend into a **single `.html` file** with zero external dependencies. JavaScript, TypeScript, JSX, CSS, images, fonts, videos, WASM — everything gets inlined into one file.
```bash terminal icon="terminal"
bun build --compile --target=browser ./index.html --outdir=dist
```
The output is a completely self-contained HTML document. No relative paths. No external files. No server required. Just one `.html` file that works anywhere a browser can open it.
## One file. Upload anywhere. It just works.
The output is a single `.html` file you can put anywhere:
- **Upload it to S3** or any static file host — no directory structure to maintain, just one file
- **Double-click it from your desktop** — it opens in the browser and works offline, no localhost server needed
- **Embed it in your webview** — No need to deal with relative files
- **Insert it in an `<iframe>`** — embed interactive content in another page with a single file URL
- **Serve it from anywhere** — any HTTP server, CDN, or file share. One file, zero configuration.
There's nothing to install, no `node_modules` to deploy, no build artifacts to coordinate, no relative paths to think about. The entire app — framework code, stylesheets, images, everything — lives in that one file.
## Truly one file
Normally, distributing a web page means managing a folder of assets — the HTML, the JavaScript bundles, the CSS files, the images. Move the HTML without the rest and everything breaks. Browsers have tried to solve this before: Safari's `.webarchive` and `.mhtml` are supposed to save a page as a single file, but in practice they unpack into a folder of loose files on your computer — defeating the purpose.
Standalone HTML is different. The output is a plain `.html` file. Not an archive. Not a folder. One file, with everything inside it. Every image, every font, every line of CSS and JavaScript is embedded directly in the HTML using standard `<style>` tags, `<script>` tags, and `data:` URIs. Any browser can open it. Any server can host it. Any file host can store it.
This makes it practical to distribute web pages the same way you'd distribute a PDF — as a single file you can move, copy, upload, or share without worrying about broken paths or missing assets.
## Quick start
<CodeGroup>
```html index.html icon="file-code"
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<div id="root"></div>
<script src="./app.tsx"></script>
</body>
</html>
```
```tsx app.tsx icon="/icons/typescript.svg"
import React from "react";
import { createRoot } from "react-dom/client";
function App() {
return <h1>Hello from a single HTML file!</h1>;
}
createRoot(document.getElementById("root")!).render(<App />);
```
```css styles.css icon="file-code"
body {
margin: 0;
font-family: system-ui, sans-serif;
background: #f5f5f5;
}
```
</CodeGroup>
```bash terminal icon="terminal"
bun build --compile --target=browser ./index.html --outdir=dist
```
Open `dist/index.html` — the React app works with no server.
## Everything gets inlined
Bun inlines every local asset it finds in your HTML. If it has a relative path, it gets embedded into the output file. This isn't limited to images and stylesheets — it works with any file type.
### What gets inlined
| In your source | In the output |
| ------------------------------------------------ | ------------------------------------------------------------------------ |
| `<script src="./app.tsx">` | `<script type="module">...bundled code...</script>` |
| `<link rel="stylesheet" href="./styles.css">` | `<style>...bundled CSS...</style>` |
| `<img src="./logo.png">` | `<img src="data:image/png;base64,...">` |
| `<img src="./icon.svg">` | `<img src="data:image/svg+xml;base64,...">` |
| `<video src="./demo.mp4">` | `<video src="data:video/mp4;base64,...">` |
| `<audio src="./click.wav">` | `<audio src="data:audio/wav;base64,...">` |
| `<source src="./clip.webm">` | `<source src="data:video/webm;base64,...">` |
| `<video poster="./thumb.jpg">` | `<video poster="data:image/jpeg;base64,...">` |
| `<link rel="icon" href="./favicon.ico">` | `<link rel="icon" href="data:image/x-icon;base64,...">` |
| `<link rel="manifest" href="./app.webmanifest">` | `<link rel="manifest" href="data:application/manifest+json;base64,...">` |
| CSS `url("./bg.png")` | CSS `url(data:image/png;base64,...)` |
| CSS `@import "./reset.css"` | Flattened into the `<style>` tag |
| CSS `url("./font.woff2")` | CSS `url(data:font/woff2;base64,...)` |
| JS `import "./styles.css"` | Merged into the `<style>` tag |
Images, fonts, WASM binaries, videos, audio files, SVGs — any file referenced by a relative path gets base64-encoded into a `data:` URI and embedded directly in the HTML. The MIME type is automatically detected from the file extension.
External URLs (like CDN links or absolute URLs) are left untouched.
## Using with React
React apps work out of the box. Bun handles JSX transpilation and npm package resolution automatically.
```bash terminal icon="terminal"
bun install react react-dom
```
<CodeGroup>
```html index.html icon="file-code"
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>My App</title>
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<div id="root"></div>
<script src="./app.tsx"></script>
</body>
</html>
```
```tsx app.tsx icon="/icons/typescript.svg"
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import { Counter } from "./components/Counter.tsx";
function App() {
return (
<main>
<h1>Single-file React App</h1>
<Counter />
</main>
);
}
createRoot(document.getElementById("root")!).render(<App />);
```
```tsx components/Counter.tsx icon="/icons/typescript.svg"
import React, { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}
```
</CodeGroup>
```bash terminal icon="terminal"
bun build --compile --target=browser ./index.html --outdir=dist
```
All of React, your components, and your CSS are bundled into `dist/index.html`. Upload that one file anywhere and it works.
## Using with Tailwind CSS
Install the plugin and reference Tailwind in your HTML or CSS:
```bash terminal icon="terminal"
bun install --dev bun-plugin-tailwind
```
<CodeGroup>
```html index.html icon="file-code"
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="tailwindcss" />
</head>
<body class="bg-gray-100 flex items-center justify-center min-h-screen">
<div id="root"></div>
<script src="./app.tsx"></script>
</body>
</html>
```
```tsx app.tsx icon="/icons/typescript.svg"
import React from "react";
import { createRoot } from "react-dom/client";
function App() {
return (
<div className="bg-white rounded-lg shadow-lg p-8 max-w-md">
<h1 className="text-2xl font-bold text-gray-800">Hello Tailwind</h1>
<p className="text-gray-600 mt-2">This is a single HTML file.</p>
</div>
);
}
createRoot(document.getElementById("root")!).render(<App />);
```
</CodeGroup>
Build with the plugin using the JavaScript API:
```ts build.ts icon="/icons/typescript.svg"
await Bun.build({
entrypoints: ["./index.html"],
compile: true,
target: "browser",
outdir: "./dist",
plugins: [require("bun-plugin-tailwind")],
});
```
```bash terminal icon="terminal"
bun run build.ts
```
The generated Tailwind CSS is inlined directly into the HTML file as a `<style>` tag.
## How it works
When you pass `--compile --target=browser` with an HTML entrypoint, Bun:
1. Parses the HTML and discovers all `<script>`, `<link>`, `<img>`, `<video>`, `<audio>`, `<source>`, and other asset references
2. Bundles all JavaScript/TypeScript/JSX into a single module
3. Bundles all CSS (including `@import` chains and CSS imported from JS) into a single stylesheet
4. Converts every relative asset reference into a base64 `data:` URI
5. Inlines the bundled JS as `<script type="module">` before `</body>`
6. Inlines the bundled CSS as `<style>` in `<head>`
7. Outputs a single `.html` file with no external dependencies
## Minification
Add `--minify` to minify the JavaScript and CSS:
```bash terminal icon="terminal"
bun build --compile --target=browser --minify ./index.html --outdir=dist
```
Or via the API:
```ts build.ts icon="/icons/typescript.svg"
await Bun.build({
entrypoints: ["./index.html"],
compile: true,
target: "browser",
outdir: "./dist",
minify: true,
});
```
## JavaScript API
You can use `Bun.build()` to produce standalone HTML programmatically:
```ts build.ts icon="/icons/typescript.svg"
const result = await Bun.build({
entrypoints: ["./index.html"],
compile: true,
target: "browser",
outdir: "./dist", // optional — omit to get output as BuildArtifact
minify: true,
});
if (!result.success) {
console.error("Build failed:");
for (const log of result.logs) {
console.error(log);
}
} else {
console.log("Built:", result.outputs[0].path);
}
```
When `outdir` is omitted, the output is available as a `BuildArtifact` in `result.outputs`:
```ts icon="/icons/typescript.svg"
const result = await Bun.build({
entrypoints: ["./index.html"],
compile: true,
target: "browser",
});
const html = await result.outputs[0].text();
await Bun.write("output.html", html);
```
## Multiple HTML files
You can pass multiple HTML files as entrypoints. Each produces its own standalone HTML file:
```bash terminal icon="terminal"
bun build --compile --target=browser ./index.html ./about.html --outdir=dist
```
## Environment variables
Use `--env` to inline environment variables into the bundled JavaScript:
```bash terminal icon="terminal"
API_URL=https://api.example.com bun build --compile --target=browser --env=inline ./index.html --outdir=dist
```
References to `process.env.API_URL` in your JavaScript are replaced with the literal value at build time.
## Limitations
- **Code splitting** is not supported — `--splitting` cannot be used with `--compile --target=browser`
- **Large assets** increase file size since they're base64-encoded (33% overhead vs the raw binary)
- **External URLs** (CDN links, absolute URLs) are left as-is — only relative paths are inlined

View File

@@ -0,0 +1,75 @@
---
title: Feedback
description: Share feedback, bug reports, and feature requests
mode: center
---
Whether you've found a bug, have a performance issue, or just want to suggest an improvement, here's how you can open a helpful issue:
<Callout icon="discord">For general questions, please join our [Discord](https://bun.com/discord).</Callout>
## Reporting Issues
<Steps>
<Step title="Upgrade Bun">
Try upgrading Bun to the latest version with `bun upgrade`. This might fix your problem without having to open an issue.
```bash terminal icon="terminal"
bun upgrade
```
You can also try the latest canary release, which includes the most recent changes and bug fixes that haven't been released in a stable version yet.
```bash terminal icon="terminal"
bun upgrade --canary
# To revert back to the stable
bun upgrade --stable
```
If the issue still persists after upgrading, continue to the next step.
</Step>
<Step title="Review Existing Issues">
First take a minute to check if the issue has already been reported. Don't open a new issue if it has already been reported, it saves time for everyone and helps us focus on fixing things faster.
- 🔍 [**Search existing issues**](https://github.com/oven-sh/bun/issues)
- 💬 [**Check discussions**](https://github.com/oven-sh/bun/discussions)
If you find a related issue, add a 👍 reaction or comment with extra details instead of opening a new one.
</Step>
<Step title="Report the Issue">
If no one has reported the issue, please open a new issue or suggest an improvement.
- 🐞 [**Report a Bug**](https://github.com/oven-sh/bun/issues/new?template=2-bug-report.yml)
- ⚡ [**Suggest an Improvement**](https://github.com/oven-sh/bun/issues/new?template=4-feature-request.yml)
Please provide as much detail as possible, including:
- A clear and concise title
- A code example or steps to reproduce the issue
- The version of Bun you are using (run `bun --version`)
- A detailed description of the issue (what happened, what you expected to happen, and what actually happened)
- The operating system and version you are using
<Note>
- For MacOS and Linux: copy the output of `uname -mprs`
- For Windows: copy the output of this command in the powershell console:
`"$([Environment]::OSVersion | ForEach-Object VersionString) $(if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" })"`
</Note>
</Step>
</Steps>
The Bun team will review the issue and get back to you as soon as possible!
---
## Use `bun feedback`
Alternatively, you can use `bun feedback` to share feedback, bug reports, and feature requests directly with the Bun team.
```bash terminal icon="terminal"
bun feedback "Love the new release!"
bun feedback report.txt details.log
echo "please document X" | bun feedback --email you@example.com
```
You can provide feedback as text arguments, file paths, or piped input.

View File

@@ -0,0 +1,29 @@
---
title: Convert an ArrayBuffer to an array of numbers
sidebarTitle: "ArrayBuffer to Array"
mode: center
---
To retrieve the contents of an `ArrayBuffer` as an array of numbers, create a [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) over of the buffer. and use the [`Array.from()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) method to convert it to an array.
```ts
const buf = new ArrayBuffer(64);
const arr = new Uint8Array(buf);
arr.length; // 64
arr[0]; // 0 (instantiated with all zeros)
```
---
The `Uint8Array` class supports array indexing and iteration. However if you wish to convert the instance to a regular `Array`, use `Array.from()`. (This will likely be slower than using the `Uint8Array` directly.)
```ts
const buf = new ArrayBuffer(64);
const uintArr = new Uint8Array(buf);
const regularArr = Array.from(uintArr);
// number[]
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,26 @@
---
title: Convert an ArrayBuffer to a Blob
sidebarTitle: "ArrayBuffer to Blob"
mode: center
---
A [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) can be constructed from an array of "chunks", where each chunk is a string, binary data structure, or another `Blob`.
```ts
const buf = new ArrayBuffer(64);
const blob = new Blob([buf]);
```
---
By default the `type` of the resulting `Blob` will be unset. This can be set manually.
```ts
const buf = new ArrayBuffer(64);
const blob = new Blob([buf], { type: "application/octet-stream" });
blob.type; // => "application/octet-stream"
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,27 @@
---
title: Convert an ArrayBuffer to a Buffer
sidebarTitle: "ArrayBuffer to Buffer"
mode: center
---
The Node.js [`Buffer`](https://nodejs.org/api/buffer.html) API predates the introduction of `ArrayBuffer` into the JavaScript language. Bun implements both.
Use the static `Buffer.from()` method to create a `Buffer` from an `ArrayBuffer`.
```ts
const arrBuffer = new ArrayBuffer(64);
const nodeBuffer = Buffer.from(arrBuffer);
```
---
To create a `Buffer` that only views a portion of the underlying buffer, pass the offset and length to the constructor.
```ts
const arrBuffer = new ArrayBuffer(64);
const nodeBuffer = Buffer.from(arrBuffer, 0, 16); // view first 16 bytes
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,17 @@
---
title: Convert an ArrayBuffer to a string
sidebarTitle: "ArrayBuffer to string"
mode: center
---
Bun implements the Web-standard [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) class for converting between binary data types and strings.
```ts
const buf = new ArrayBuffer(64);
const decoder = new TextDecoder();
const str = decoder.decode(buf);
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,41 @@
---
title: Convert an ArrayBuffer to a Uint8Array
sidebarTitle: "ArrayBuffer to Uint8Array"
mode: center
---
A `Uint8Array` is a _typed array_, meaning it is a mechanism for viewing the data in an underlying `ArrayBuffer`.
```ts
const buffer = new ArrayBuffer(64);
const arr = new Uint8Array(buffer);
```
---
Instances of other typed arrays can be created similarly.
```ts
const buffer = new ArrayBuffer(64);
const arr1 = new Uint8Array(buffer);
const arr2 = new Uint16Array(buffer);
const arr3 = new Uint32Array(buffer);
const arr4 = new Float32Array(buffer);
const arr5 = new Float64Array(buffer);
const arr6 = new BigInt64Array(buffer);
const arr7 = new BigUint64Array(buffer);
```
---
To create a typed array that only views a portion of the underlying buffer, pass the offset and length to the constructor.
```ts
const buffer = new ArrayBuffer(64);
const arr = new Uint8Array(buffer, 0, 16); // view first 16 bytes
```
---
See [Docs > API > Utils](/docs/runtime/utils) for more useful utilities.

View File

@@ -0,0 +1,16 @@
---
title: Convert a Blob to an ArrayBuffer
sidebarTitle: "Blob to ArrayBuffer"
mode: center
---
The [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) class provides a number of methods for consuming its contents in different formats, including `.arrayBuffer()`.
```ts
const blob = new Blob(["hello world"]);
const buf = await blob.arrayBuffer();
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,16 @@
---
title: Convert a Blob to a DataView
sidebarTitle: "Blob to DataView"
mode: center
---
The [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) class provides a number of methods for consuming its contents in different formats. This snippets reads the contents to an `ArrayBuffer`, then creates a `DataView` from the buffer.
```ts
const blob = new Blob(["hello world"]);
const arr = new DataView(await blob.arrayBuffer());
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,16 @@
---
title: Convert a Blob to a ReadableStream
sidebarTitle: "Blob to ReadableStream"
mode: center
---
The [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) class provides a number of methods for consuming its contents in different formats, including `.stream()`. This returns `Promise<ReadableStream>`.
```ts
const blob = new Blob(["hello world"]);
const stream = await blob.stream();
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,17 @@
---
title: Convert a Blob to a string
sidebarTitle: "Blob to string"
mode: center
---
The [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) class provides a number of methods for consuming its contents in different formats, including `.text()`.
```ts
const blob = new Blob(["hello world"]);
const str = await blob.text();
// => "hello world"
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,16 @@
---
title: Convert a Blob to a Uint8Array
sidebarTitle: "Blob to Uint8Array"
mode: center
---
The [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) class provides a number of methods for consuming its contents in different formats. This snippets reads the contents to an `ArrayBuffer`, then creates a `Uint8Array` from the buffer.
```ts
const blob = new Blob(["hello world"]);
const arr = new Uint8Array(await blob.arrayBuffer());
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,16 @@
---
title: Convert a Buffer to an ArrayBuffer
sidebarTitle: "Buffer to ArrayBuffer"
mode: center
---
The Node.js [`Buffer`](https://nodejs.org/api/buffer.html) class provides a way to view and manipulate data in an underlying `ArrayBuffer`, which is available via the `buffer` property.
```ts
const nodeBuf = Buffer.alloc(64);
const arrBuf = nodeBuf.buffer;
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,16 @@
---
title: Convert a Buffer to a blob
sidebarTitle: "Buffer to Blob"
mode: center
---
A [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) can be constructed from an array of "chunks", where each chunk is a string, binary data structure (including `Buffer`), or another `Blob`.
```ts
const buf = Buffer.from("hello");
const blob = new Blob([buf]);
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,43 @@
---
title: Convert a Buffer to a ReadableStream
sidebarTitle: "Buffer to ReadableStream"
mode: center
---
The naive approach to creating a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) from a [`Buffer`](https://nodejs.org/api/buffer.html) is to use the `ReadableStream` constructor and enqueue the entire array as a single chunk. For a large buffer, this may be undesirable as this approach does not "streaming" the data in smaller chunks.
```ts
const buf = Buffer.from("hello world");
const stream = new ReadableStream({
start(controller) {
controller.enqueue(buf);
controller.close();
},
});
```
---
To stream the data in smaller chunks, first create a `Blob` instance from the `Buffer`. Then use the [`Blob.stream()`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/stream) method to create a `ReadableStream` that streams the data in chunks of a specified size.
```ts
const buf = Buffer.from("hello world");
const blob = new Blob([buf]);
const stream = blob.stream();
```
---
The chunk size can be set by passing a number to the `.stream()` method.
```ts
const buf = Buffer.from("hello world");
const blob = new Blob([buf]);
// set chunk size of 1024 bytes
const stream = blob.stream(1024);
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,27 @@
---
title: Convert a Buffer to a string
sidebarTitle: "Buffer to string"
mode: center
---
The [`Buffer`](https://nodejs.org/api/buffer.html) class provides a built-in `.toString()` method that converts a `Buffer` to a string.
```ts
const buf = Buffer.from("hello");
const str = buf.toString();
// => "hello"
```
---
You can optionally specify an encoding and byte range.
```ts
const buf = Buffer.from("hello world!");
const str = buf.toString("utf8", 0, 5);
// => "hello"
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,16 @@
---
title: Convert a Buffer to a Uint8Array
sidebarTitle: "Buffer to Uint8Array"
mode: center
---
The Node.js [`Buffer`](https://nodejs.org/api/buffer.html) class extends [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), so no conversion is needed. All properties and methods on `Uint8Array` are available on `Buffer`.
```ts
const buf = Buffer.alloc(64);
buf instanceof Uint8Array; // => true
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,17 @@
---
title: Convert a DataView to a string
sidebarTitle: "DataView to string"
mode: center
---
If a [`DataView`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) contains ASCII-encoded text, you can convert it to a string using the [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) class.
```ts
const dv: DataView = ...;
const decoder = new TextDecoder();
const str = decoder.decode(dv);
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,27 @@
---
title: Convert a Uint8Array to an ArrayBuffer
sidebarTitle: "Uint8Array to ArrayBuffer"
mode: center
---
A [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) is a _typed array_ class, meaning it is a mechanism for viewing data in an underlying `ArrayBuffer`. The underlying `ArrayBuffer` is accessible via the `buffer` property.
```ts
const arr = new Uint8Array(64);
arr.buffer; // => ArrayBuffer(64)
```
---
The `Uint8Array` may be a view over a _subset_ of the data in the underlying `ArrayBuffer`. In this case, the `buffer` property will return the entire buffer, and the `byteOffset` and `byteLength` properties will indicate the subset.
```ts
const arr = new Uint8Array(64, 16, 32);
arr.buffer; // => ArrayBuffer(64)
arr.byteOffset; // => 16
arr.byteLength; // => 32
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,18 @@
---
title: Convert a Uint8Array to a Blob
sidebarTitle: "Uint8Array to Blob"
mode: center
---
A [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) can be constructed from an array of "chunks", where each chunk is a string, binary data structure (including `Uint8Array`), or another `Blob`.
```ts
const arr = new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x6f]);
const blob = new Blob([arr]);
console.log(await blob.text());
// => "hello"
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,16 @@
---
title: Convert a Uint8Array to a Buffer
sidebarTitle: "Uint8Array to Buffer"
mode: center
---
The [`Buffer`](https://nodejs.org/api/buffer.html) class extends [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) with a number of additional methods. Use `Buffer.from()` to create a `Buffer` instance from a `Uint8Array`.
```ts
const arr: Uint8Array = ...
const buf = Buffer.from(arr);
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,16 @@
---
title: Convert a Uint8Array to a DataView
sidebarTitle: "Uint8Array to DataView"
mode: center
---
A [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) is a _typed array_ class, meaning it is a mechanism for viewing data in an underlying `ArrayBuffer`. The following snippet creates a [`DataView`] instance over the same range of data as the `Uint8Array`.
```ts
const arr: Uint8Array = ...
const dv = new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,43 @@
---
title: Convert a Uint8Array to a ReadableStream
sidebarTitle: "Uint8Array to ReadableStream"
mode: center
---
The naive approach to creating a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) from a [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) is to use the [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) constructor and enqueue the entire array as a single chunk. For larger chunks, this may be undesirable as it isn't actually "streaming" the data.
```ts
const arr = new Uint8Array(64);
const stream = new ReadableStream({
start(controller) {
controller.enqueue(arr);
controller.close();
},
});
```
---
To stream the data in smaller chunks, first create a `Blob` instance from the `Uint8Array`. Then use the [`Blob.stream()`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/stream) method to create a `ReadableStream` that streams the data in chunks of a specified size.
```ts
const arr = new Uint8Array(64);
const blob = new Blob([arr]);
const stream = blob.stream();
```
---
The chunk size can be set by passing a number to the `.stream()` method.
```ts
const arr = new Uint8Array(64);
const blob = new Blob([arr]);
// set chunk size of 1024 bytes
const stream = blob.stream(1024);
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,18 @@
---
title: Convert a Uint8Array to a string
sidebarTitle: "Uint8Array to string"
mode: center
---
Bun implements the Web-standard [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) class for converting from binary data types like `Uint8Array` and strings.
```ts
const arr = new Uint8Array([104, 101, 108, 108, 111]);
const decoder = new TextDecoder();
const str = decoder.decode(arr);
// => "hello"
```
---
See [Docs > API > Binary Data](/docs/runtime/binary-data#conversion) for complete documentation on manipulating binary data with Bun.

View File

@@ -0,0 +1,204 @@
---
title: Deploy a Bun application on AWS Lambda
sidebarTitle: Deploy on AWS Lambda
mode: center
---
[AWS Lambda](https://aws.amazon.com/lambda/) is a serverless compute service that lets you run code without provisioning or managing servers.
In this guide, we will deploy a Bun HTTP server to AWS Lambda using a `Dockerfile`.
<Note>
Before continuing, make sure you have:
- A Bun application ready for deployment
- An [AWS account](https://aws.amazon.com/)
- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html) installed and configured
- [Docker](https://docs.docker.com/get-started/get-docker/) installed and added to your `PATH`
</Note>
---
<Steps>
<Step title="Create a new Dockerfile">
Make sure you're in the directory containing your project, then create a new `Dockerfile` in the root of your project. This file contains the instructions to initialize the container, copy your local project files into it, install dependencies, and start the application.
```docker Dockerfile icon="docker"
# Use the official AWS Lambda adapter image to handle the Lambda runtime
FROM public.ecr.aws/awsguru/aws-lambda-adapter:0.9.0 AS aws-lambda-adapter
# Use the official Bun image to run the application
FROM oven/bun:debian AS bun_latest
# Copy the Lambda adapter into the container
COPY --from=aws-lambda-adapter /lambda-adapter /opt/extensions/lambda-adapter
# Set the port to 8080. This is required for the AWS Lambda adapter.
ENV PORT=8080
# Set the work directory to `/var/task`. This is the default work directory for Lambda.
WORKDIR "/var/task"
# Copy the package.json and bun.lock into the container
COPY package.json bun.lock ./
# Install the dependencies
RUN bun install --production --frozen-lockfile
# Copy the rest of the application into the container
COPY . /var/task
# Run the application.
CMD ["bun", "index.ts"]
```
<Note>
Make sure that the start command corresponds to your application's entry point. This can also be `CMD ["bun", "run", "start"]` if you have a start script in your `package.json`.
This image installs dependencies and runs your app with Bun inside a container. If your app doesn't have dependencies, you can omit the `RUN bun install --production --frozen-lockfile` line.
</Note>
Create a new `.dockerignore` file in the root of your project. This file contains the files and directories that should be _excluded_ from the container image, such as `node_modules`. This makes your builds faster and smaller:
```docker .dockerignore icon="Docker"
node_modules
Dockerfile*
.dockerignore
.git
.gitignore
README.md
LICENSE
.vscode
.env
# Any other files or directories you want to exclude
```
</Step>
<Step title="Build the Docker image">
Make sure you're in the directory containing your `Dockerfile`, then build the Docker image. In this case, we'll call the image `bun-lambda-demo` and tag it as `latest`.
```bash terminal icon="terminal"
# cd /path/to/your/app
docker build --provenance=false --platform linux/amd64 -t bun-lambda-demo:latest .
```
</Step>
<Step title="Create an ECR repository">
To push the image to AWS Lambda, we first need to create an [ECR repository](https://aws.amazon.com/ecr/) to push the image to.
By running the following command, we:
- Create an ECR repository named `bun-lambda-demo` in the `us-east-1` region
- Get the repository URI, and export the repository URI as an environment variable. This is optional, but make the next steps easier.
```bash terminal icon="terminal"
export ECR_URI=$(aws ecr create-repository --repository-name bun-lambda-demo --region us-east-1 --query 'repository.repositoryUri' --output text)
echo $ECR_URI
```
```txt
[id].dkr.ecr.us-east-1.amazonaws.com/bun-lambda-demo
```
<Note>
If you're using IAM Identity Center (SSO) or have configured AWS CLI with profiles, you'll need to add the `--profile` flag to your AWS CLI commands.
For example, if your profile is named `my-sso-app`, use `--profile my-sso-app`. Check your AWS CLI configuration with `aws configure list-profiles` to see available profiles.
```bash terminal icon="terminal"
export ECR_URI=$(aws ecr create-repository --repository-name bun-lambda-demo --region us-east-1 --profile my-sso-app --query 'repository.repositoryUri' --output text)
echo $ECR_URI
```
</Note>
</Step>
<Step title="Authenticate with the ECR repository">
Log in to the ECR repository:
```bash terminal icon="terminal"
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin $ECR_URI
```
```txt
Login Succeeded
```
<Note>
If using a profile, use the `--profile` flag:
```bash terminal icon="terminal"
aws ecr get-login-password --region us-east-1 --profile my-sso-app | docker login --username AWS --password-stdin $ECR_URI
```
</Note>
</Step>
<Step title="Tag and push the docker image to the ECR repository">
Make sure you're in the directory containing your `Dockerfile`, then tag the docker image with the ECR repository URI.
```bash terminal icon="terminal"
docker tag bun-lambda-demo:latest ${ECR_URI}:latest
```
Then, push the image to the ECR repository.
```bash terminal icon="terminal"
docker push ${ECR_URI}:latest
```
</Step>
<Step title="Create an AWS Lambda function">
Go to **AWS Console** > **Lambda** > [**Create Function**](https://us-east-1.console.aws.amazon.com/lambda/home?region=us-east-1#/create/function?intent=authorFromImage) > Select **Container image**
<Warning>Make sure you've selected the right region, this URL defaults to `us-east-1`.</Warning>
<Frame>
![Create Function](https://bun.com/images/guides/lambda1.png)
</Frame>
Give the function a name, like `my-bun-function`.
</Step>
<Step title="Select the container image">
Then, go to the **Container image URI** section, click on **Browse images**. Select the image we just pushed to the ECR repository.
<Frame>
![Select Container Repository](https://bun.com/images/guides/lambda2.png)
</Frame>
Then, select the `latest` image, and click on **Select image**.
<Frame>
![Select Container Image](https://bun.com/images/guides/lambda3.png)
</Frame>
</Step>
<Step title="Configure the function">
To get a public URL for the function, we need to go to **Additional configurations** > **Networking** > **Function URL**.
Set this to **Enable**, with Auth Type **NONE**.
<Frame>
![Set the Function URL](https://bun.com/images/guides/lambda4.png)
</Frame>
</Step>
<Step title="Create the function">
Click on **Create function** at the bottom of the page, this will create the function.
<Frame>
![Create Function](https://bun.com/images/guides/lambda6.png)
</Frame>
</Step>
<Step title="Get the function URL">
Once the function has been created you'll be redirected to the function's page, where you can see the function URL in the **"Function URL"** section.
<Frame>
![Function URL](https://bun.com/images/guides/lambda5.png)
</Frame>
</Step>
<Step title="Test the function">
🥳 Your app is now live! To test the function, you can either go to the **Test** tab, or call the function URL directly.
```bash terminal icon="terminal"
curl -X GET https://[your-function-id].lambda-url.us-east-1.on.aws/
```
```txt
Hello from Bun on Lambda!
```
</Step>
</Steps>

View File

@@ -0,0 +1,161 @@
---
title: Deploy a Bun application on DigitalOcean
sidebarTitle: Deploy on DigitalOcean
mode: center
---
[DigitalOcean](https://www.digitalocean.com/) is a cloud platform that provides a range of services for building and deploying applications.
In this guide, we will deploy a Bun HTTP server to DigitalOcean using a `Dockerfile`.
<Note>
Before continuing, make sure you have:
- A Bun application ready for deployment
- A [DigitalOcean account](https://www.digitalocean.com/)
- [DigitalOcean CLI](https://docs.digitalocean.com/reference/doctl/how-to/install/#step-1-install-doctl) installed and configured
- [Docker](https://docs.docker.com/get-started/get-docker/) installed and added to your `PATH`
</Note>
---
<Steps>
<Step title="Create a new DigitalOcean Container Registry">
Create a new Container Registry to store the Docker image.
<Tabs>
<Tab title="Through the DigitalOcean dashboard">
In the DigitalOcean dashboard, go to [**Container Registry**](https://cloud.digitalocean.com/registry), and enter the details for the new registry.
<Frame>
![DigitalOcean registry dashboard](https://bun.com/images/guides/digitalocean-7.png)
</Frame>
Make sure the details are correct, then click **Create Registry**.
</Tab>
<Tab title="Through the DigitalOcean CLI">
```bash terminal icon="terminal"
doctl registry create bun-digitalocean-demo
```
```txt
Name Endpoint Region slug
bun-digitalocean-demo registry.digitalocean.com/bun-digitalocean-demo sfo2
```
</Tab>
</Tabs>
You should see the new registry in the [**DigitalOcean registry dashboard**](https://cloud.digitalocean.com/registry):
<Frame>
![DigitalOcean registry dashboard](https://bun.com/images/guides/digitalocean-1.png)
</Frame>
</Step>
<Step title="Create a new Dockerfile">
Make sure you're in the directory containing your project, then create a new `Dockerfile` in the root of your project. This file contains the instructions to initialize the container, copy your local project files into it, install dependencies, and start the application.
```docker Dockerfile icon="docker"
# Use the official Bun image to run the application
FROM oven/bun:debian
# Set the work directory to `/app`
WORKDIR /app
# Copy the package.json and bun.lock into the container
COPY package.json bun.lock ./
# Install the dependencies
RUN bun install --production --frozen-lockfile
# Copy the rest of the application into the container
COPY . .
# Expose the port (DigitalOcean will set PORT env var)
EXPOSE 8080
# Run the application
CMD ["bun", "index.ts"]
```
<Note>
Make sure that the start command corresponds to your application's entry point. This can also be `CMD ["bun", "run", "start"]` if you have a start script in your `package.json`.
This image installs dependencies and runs your app with Bun inside a container. If your app doesn't have dependencies, you can omit the `RUN bun install --production --frozen-lockfile` line.
</Note>
Create a new `.dockerignore` file in the root of your project. This file contains the files and directories that should be _excluded_ from the container image, such as `node_modules`. This makes your builds faster and smaller:
```docker .dockerignore icon="Docker"
node_modules
Dockerfile*
.dockerignore
.git
.gitignore
README.md
LICENSE
.vscode
.env
# Any other files or directories you want to exclude
```
</Step>
<Step title="Authenticate Docker with DigitalOcean registry">
Before building and pushing the Docker image, authenticate Docker with the DigitalOcean Container Registry:
```bash terminal icon="terminal"
doctl registry login
```
```txt
Successfully authenticated with registry.digitalocean.com
```
<Note>
This command authenticates Docker with DigitalOcean's registry using your DigitalOcean credentials. Without this step, the build and push command will fail with a 401 authentication error.
</Note>
</Step>
<Step title="Build and push the Docker image to the DigitalOcean registry">
Make sure you're in the directory containing your `Dockerfile`, then build and push the Docker image to the DigitalOcean registry in one command:
```bash terminal icon="terminal"
docker buildx build --platform=linux/amd64 -t registry.digitalocean.com/bun-digitalocean-demo/bun-digitalocean-demo:latest --push .
```
<Note>
If you're building on an ARM Mac (M1/M2), you must use `docker buildx` with `--platform=linux/amd64` to ensure compatibility with DigitalOcean's infrastructure. Using `docker build` without the platform flag will create an ARM64 image that won't run on DigitalOcean.
</Note>
Once the image is pushed, you should see it in the [**DigitalOcean registry dashboard**](https://cloud.digitalocean.com/registry):
<Frame>
![DigitalOcean registry dashboard](https://bun.com/images/guides/digitalocean-2.png)
</Frame>
</Step>
<Step title="Create a new DigitalOcean App Platform project">
In the DigitalOcean dashboard, go to [**App Platform**](https://cloud.digitalocean.com/apps) > **Create App**. We can create a project directly from the container image.
<Frame>
![DigitalOcean App Platform project dashboard](https://bun.com/images/guides/digitalocean-3.png)
</Frame>
Make sure the details are correct, then click **Next**.
<Frame>
![DigitalOcean App Platform service dashboard](https://bun.com/images/guides/digitalocean-4.png)
</Frame>
Review and configure resource settings, then click **Create app**.
<Frame>
![DigitalOcean App Platform service dashboard](https://bun.com/images/guides/digitalocean-6.png)
</Frame>
</Step>
<Step title="Visit your live application">
🥳 Your app is now live! Once the app is created, you should see it in the App Platform dashboard with the public URL.
<Frame>
![DigitalOcean App Platform app dashboard](https://bun.com/images/guides/digitalocean-5.png)
</Frame>
</Step>
</Steps>

View File

@@ -0,0 +1,194 @@
---
title: Deploy a Bun application on Google Cloud Run
sidebarTitle: Deploy on Google Cloud Run
mode: center
---
[Google Cloud Run](https://cloud.google.com/run) is a managed platform for deploying and scaling serverless applications. Google handles the infrastructure for you.
In this guide, we will deploy a Bun HTTP server to Google Cloud Run using a `Dockerfile`.
<Note>
Before continuing, make sure you have:
- A Bun application ready for deployment
- A [Google Cloud account](https://cloud.google.com/) with billing enabled
- [Google Cloud CLI](https://cloud.google.com/sdk/docs/install) installed and configured
</Note>
---
<Steps>
<Step title={<span>Initialize <code>gcloud</code> by select/creating a project</span>}>
Make sure that you've initialized the Google Cloud CLI. This command logs you in, and prompts you to either select an existing project or create a new one.
For more help with the Google Cloud CLI, see the [official documentation](https://docs.cloud.google.com/sdk/gcloud/reference/init).
```bash terminal icon="terminal"
gcloud init
```
```txt
Welcome! This command will take you through the configuration of gcloud.
You must sign in to continue. Would you like to sign in (Y/n)? Y
You are signed in as [email@example.com].
Pick cloud project to use:
[1] existing-bun-app-1234
[2] Enter a project ID
[3] Create a new project
Please enter numeric choice or text value (must exactly match list item): 3
Enter a Project ID. my-bun-app
Your current project has been set to: [my-bun-app]
The Google Cloud CLI is configured and ready to use!
```
</Step>
<Step title="(Optional) Store your project info in environment variables">
Set variables for your project ID and number so they're easier to reuse in the following steps.
```bash terminal icon="terminal"
PROJECT_ID=$(gcloud projects list --format='value(projectId)' --filter='name="my bun app"')
PROJECT_NUMBER=$(gcloud projects list --format='value(projectNumber)' --filter='name="my bun app"')
echo $PROJECT_ID $PROJECT_NUMBER
```
```txt
my-bun-app-... [PROJECT_NUMBER]
```
</Step>
<Step title="Link a billing account">
List your available billing accounts and link one to your project:
```bash terminal icon="terminal"
gcloud billing accounts list
```
```txt
ACCOUNT_ID NAME OPEN MASTER_ACCOUNT_ID
[BILLING_ACCOUNT_ID] My Billing Account True
```
Link your billing account to your project. Replace `[BILLING_ACCOUNT_ID]` with the ID of your billing account.
```bash terminal icon="terminal"
gcloud billing projects link $PROJECT_ID --billing-account=[BILLING_ACCOUNT_ID]
```
```txt
billingAccountName: billingAccounts/[BILLING_ACCOUNT_ID]
billingEnabled: true
name: projects/my-bun-app-.../billingInfo
projectId: my-bun-app-...
```
</Step>
<Step title="Enable APIs and configure IAM roles">
Activate the necessary services and grant Cloud Build permissions:
```bash terminal icon="terminal"
gcloud services enable run.googleapis.com cloudbuild.googleapis.com
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member=serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
--role=roles/run.builder
```
<Note>
These commands enable Cloud Run (`run.googleapis.com`) and Cloud Build (`cloudbuild.googleapis.com`), which are required for deploying from source. Cloud Run runs your containerized app, while Cloud Build handles building and packaging it.
The IAM binding grants the Compute Engine service account (`$PROJECT_NUMBER-compute@developer.gserviceaccount.com`) permission to build and deploy images on your behalf.
</Note>
</Step>
<Step title="Add a Dockerfile">
Create a new `Dockerfile` in the root of your project. This file contains the instructions to initialize the container, copy your local project files into it, install dependencies, and start the application.
```docker Dockerfile icon="docker"
# Use the official Bun image to run the application
FROM oven/bun:latest
# Copy the package.json and bun.lock into the container
COPY package.json bun.lock ./
# Install the dependencies
# Install the dependencies
RUN bun install --production --frozen-lockfile
# Copy the rest of the application into the container
COPY . .
# Run the application
CMD ["bun", "index.ts"]
```
<Note>
Make sure that the start command corresponds to your application's entry point. This can also be `CMD ["bun", "run", "start"]` if you have a start script in your `package.json`.
This image installs dependencies and runs your app with Bun inside a container. If your app doesn't have dependencies, you can omit the `RUN bun install --production --frozen-lockfile` line.
</Note>
Create a new `.dockerignore` file in the root of your project. This file contains the files and directories that should be _excluded_ from the container image, such as `node_modules`. This makes your builds faster and smaller:
```docker .dockerignore icon="Docker"
node_modules
Dockerfile*
.dockerignore
.git
.gitignore
README.md
LICENSE
.vscode
.env
# Any other files or directories you want to exclude
```
</Step>
<Step title="Deploy your service">
Make sure you're in the directory containing your `Dockerfile`, then deploy directly from your local source:
<Note>
Update the `--region` flag to your preferred region. You can also omit this flag to get an interactive prompt to
select a region.
</Note>
```bash terminal icon="terminal"
gcloud run deploy my-bun-app --source . --region=us-west1 --allow-unauthenticated
```
```txt
Deploying from source requires an Artifact Registry Docker repository to store built containers. A repository named
[cloud-run-source-deploy] in region [us-west1] will be created.
Do you want to continue (Y/n)? Y
Building using Dockerfile and deploying container to Cloud Run service [my-bun-app] in project [my-bun-app-...] region [us-west1]
✓ Building and deploying... Done.
✓ Validating Service...
✓ Uploading sources...
✓ Building Container... Logs are available at [https://console.cloud.google.com/cloud-build/builds...].
✓ Creating Revision...
✓ Routing traffic...
✓ Setting IAM Policy...
Done.
Service [my-bun-app] revision [my-bun-app-...] has been deployed and is serving 100 percent of traffic.
Service URL: https://my-bun-app-....us-west1.run.app
```
</Step>
<Step title="Visit your live application">
🎉 Your Bun application is now live!
Visit the Service URL (`https://my-bun-app-....us-west1.run.app`) to confirm everything works as expected.
</Step>
</Steps>

View File

@@ -0,0 +1,145 @@
---
title: Deploy a Bun application on Railway
description: Deploy Bun applications to Railway with this step-by-step guide covering CLI and dashboard methods, optional PostgreSQL setup, and automatic SSL configuration.
sidebarTitle: Deploy on Railway
mode: center
---
Railway is an infrastructure platform where you can provision infrastructure, develop with that infrastructure locally, and then deploy to the cloud. It enables instant deployments from GitHub with zero configuration, automatic SSL, and built-in database provisioning.
This guide walks through deploying a Bun application with a PostgreSQL database (optional), which is exactly what the template below provides.
You can either follow this guide step-by-step or simply deploy the pre-configured template with one click:
<a
href="https://railway.com/deploy/bun-react-postgres?referralCode=Bun&utm_medium=integration&utm_source=template&utm_campaign=bun"
target="_blank"
>
<img src="https://railway.com/button.svg" alt="Deploy on Railway" />
</a>
---
**Prerequisites**:
- A Bun application ready for deployment
- A [Railway account](https://railway.app/)
- Railway CLI (for CLI deployment method)
- A GitHub account (for Dashboard deployment method)
---
## Method 1: Deploy via CLI
<Steps>
<Step title="Step 1">
Ensure sure you have the Railway CLI installed.
```bash terminal icon="terminal"
bun install -g @railway/cli
```
</Step>
<Step title="Step 2">
Log into your Railway account.
```bash terminal icon="terminal"
railway login
```
</Step>
<Step title="Step 3">
After successfully authenticating, initialize a new project.
```bash terminal icon="terminal"
railway init
```
</Step>
<Step title="Step 4">
After initializing the project, add a new database and service.
<Note>Step 4 is only necessary if your application uses a database. If you don't need PostgreSQL, skip to Step 5.</Note>
```bash terminal icon="terminal"
# Add PostgreSQL database. Make sure to add this first!
railway add --database postgres
# Add your application service.
railway add --service bun-react-db --variables DATABASE_URL=\${{Postgres.DATABASE_URL}}
```
</Step>
<Step title="Step 5">
After the services have been created and connected, deploy the application to Railway. By default, services are only accessible within Railway's private network. To make your app publicly accessible, you need to generate a public domain.
```bash terminal icon="terminal"
# Deploy your application
railway up
# Generate public domain
railway domain
```
</Step>
</Steps>
Your app is now live! Railway auto-deploys on every GitHub push.
---
## Method 2: Deploy via Dashboard
<Steps>
<Step title="Step 1">
Create a new project
1. Go to [Railway Dashboard](http://railway.com/dashboard?utm_medium=integration&utm_source=docs&utm_campaign=bun)
2. Click **"+ New"** → **"GitHub repo"**
3. Choose your repository
</Step>
<Step title="Step 2">
Add a PostgreSQL database, and connect this database to the service
<Note>Step 2 is only necessary if your application uses a database. If you don't need PostgreSQL, skip to Step 3.</Note>
1. Click **"+ New"** → **"Database"** → **"Add PostgreSQL"**
2. After the database has been created, select your service (not the database)
3. Go to **"Variables"** tab
4. Click **"+ New Variable"** → **"Add Reference"**
5. Select `DATABASE_URL` from postgres
</Step>
<Step title="Step 3">
Generate a public domain
1. Select your service
2. Go to **"Settings"** tab
3. Under **"Networking"**, click **"Generate Domain"**
</Step>
</Steps>
Your app is now live! Railway auto-deploys on every GitHub push.
---
## Configuration (Optional)
By default, Railway uses [Nixpacks](https://docs.railway.com/guides/build-configuration#nixpacks-options) to automatically detect and build your Bun application with zero configuration.
However, using the [Railpack](https://docs.railway.com/guides/build-configuration#railpack) application builder provides better Bun support, and will always support the latest version of Bun. The pre-configured templates use Railpack by default.
To enable Railpack in a custom project, add the following to your `railway.json`:
```json railway.json icon="file-json"
{
"$schema": "https://railway.com/railway.schema.json",
"build": {
"builder": "RAILPACK"
}
}
```
For more build configuration settings, check out the [Railway documentation](https://docs.railway.com/guides/build-configuration).

View File

@@ -0,0 +1,82 @@
---
title: Deploy a Bun application on Render
sidebarTitle: Deploy on Render
mode: center
---
[Render](https://render.com/) is a cloud platform that lets you flexibly build, deploy, and scale your apps.
It offers features like auto deploys from GitHub, a global CDN, private networks, automatic HTTPS setup, and managed PostgreSQL and Redis.
Render supports Bun natively. You can deploy Bun apps as web services, background workers, cron jobs, and more.
---
As an example, let's deploy a simple Express HTTP server to Render.
<Steps>
<Step title="Step 1">
Create a new GitHub repo named `myapp`. Git clone it locally.
```sh
git clone git@github.com:my-github-username/myapp.git
cd myapp
```
</Step>
<Step title="Step 2">
Add the Express library.
```sh
bun add express
```
</Step>
<Step title="Step 3">
Define a simple server with Express:
```ts app.ts icon="/icons/typescript.svg"
import express from "express";
const app = express();
const port = process.env.PORT || 3001;
app.get("/", (req, res) => {
res.send("Hello World!");
});
app.listen(port, () => {
console.log(`Listening on port ${port}...`);
});
```
</Step>
<Step title="Step 4">
Commit your changes and push to GitHub.
```sh terminal icon="terminal"
git add app.ts bun.lock package.json
git commit -m "Create simple Express app"
git push origin main
```
</Step>
<Step title="Step 5">
In your [Render Dashboard](https://dashboard.render.com/), click `New` > `Web Service` and connect your `myapp` repo.
</Step>
<Step title="Step 6">
In the Render UI, provide the following values during web service creation:
| | |
| ----------------- | ------------- |
| **Runtime** | `Node` |
| **Build Command** | `bun install` |
| **Start Command** | `bun app.ts` |
</Step>
</Steps>
That's it! Your web service will be live at its assigned `onrender.com` URL as soon as the build finishes.
You can view the [deploy logs](https://docs.render.com/logging#logs-for-an-individual-deploy-or-job) for details. Refer to [Render's documentation](https://docs.render.com/deploys) for a complete overview of deploying on Render.

View File

@@ -0,0 +1,97 @@
---
title: Deploy a Bun application on Vercel
sidebarTitle: Deploy on Vercel
mode: center
---
[Vercel](https://vercel.com/) is a cloud platform that lets you build, deploy, and scale your apps.
<Warning>
The Bun runtime is in Beta; certain features (e.g., automatic source maps, byte-code caching, metrics on
`node:http/https`) are not yet supported.
</Warning>
<Note>
`Bun.serve` is currently not supported on Vercel Functions. Use Bun with frameworks supported by Vercel, like Next.js,
Express, Hono, or Nitro.
</Note>
---
<Steps>
<Step title="Configure Bun in vercel.json">
To enable the Bun runtime for your Functions, add a `bunVersion` field in your `vercel.json` file:
```json vercel.json icon="file-json"
{
"bunVersion": "1.x" // [!code ++]
}
```
Vercel automatically detects this configuration and runs your application on Bun. The value has to be `"1.x"`, Vercel handles the minor version internally.
For best results, match your local Bun version with the version used by Vercel.
</Step>
<Step title="Next.js configuration">
If youre deploying a **Next.js** project (including ISR), update your `package.json` scripts to use the Bun runtime:
```json package.json icon="file-json"
{
"scripts": {
"dev": "bun --bun next dev", // [!code ++]
"build": "bun --bun next build" // [!code ++]
}
}
```
<Note>
The `--bun` flag runs the Next.js CLI under Bun. Bundling (via Turbopack or Webpack) remains unchanged, but all commands execute within the Bun runtime.
</Note>
This ensures both local development and builds use Bun.
</Step>
<Step title="Deploy your app">
Connect your repository to Vercel, or deploy from the CLI:
```bash terminal icon="terminal"
# Using bunx (no global install)
bunx vercel login
bunx vercel deploy
```
Or install the Vercel CLI globally:
```bash terminal icon="terminal"
bun i -g vercel
vercel login
vercel deploy
```
[Learn more in the Vercel Deploy CLI documentation →](https://vercel.com/docs/cli/deploy)
</Step>
<Step title="Verify the runtime">
To confirm your deployment uses Bun, log the Bun version:
```ts index.ts icon="/icons/typescript.svg"
console.log("runtime", process.versions.bun);
```
```txt
runtime 1.3.3
```
[See the Vercel Bun Runtime documentation for feature support →](https://vercel.com/docs/functions/runtimes/bun#feature-support)
</Step>
</Steps>
---
- [Fluid compute](https://vercel.com/docs/fluid-compute): Both Bun and Node.js runtimes run on Fluid compute and support the same core Vercel Functions features.
- [Middleware](https://vercel.com/docs/routing-middleware): To run Routing Middleware with Bun, set the runtime to `nodejs`:
```ts middleware.ts icon="/icons/typescript.svg"
export const config = { runtime: "nodejs" }; // [!code ++]
```

View File

@@ -0,0 +1,82 @@
---
title: Build an app with Astro and Bun
sidebarTitle: "Astro with Bun"
mode: center
---
Initialize a fresh Astro app with `bun create astro`. The `create-astro` package detects when you are using `bunx` and will automatically install dependencies using `bun`.
```sh terminal icon="terminal"
bun create astro
```
```txt
╭─────╮ Houston:
│ ◠ ◡ ◠ We're glad to have you on board.
╰─────╯
astro v3.1.4 Launch sequence initiated.
dir Where should we create your new project?
./fumbling-field
tmpl How would you like to start your new project?
Use blog template
✔ Template copied
deps Install dependencies?
Yes
✔ Dependencies installed
ts Do you plan to write TypeScript?
Yes
use How strict should TypeScript be?
Strict
✔ TypeScript customized
git Initialize a new git repository?
Yes
✔ Git initialized
next Liftoff confirmed. Explore your project!
Enter your project directory using cd ./fumbling-field
Run `bun run dev` to start the dev server. CTRL+C to stop.
Add frameworks like react or tailwind using astro add.
Stuck? Join us at https://astro.build/chat
╭─────╮ Houston:
│ ◠ ◡ ◠ Good luck out there, astronaut! 🚀
╰─────╯
```
---
Start the dev server with `bunx`.
By default, Bun will run the dev server with Node.js. To use the Bun runtime instead, use the `--bun` flag.
```sh terminal icon="terminal"
bunx --bun astro dev
```
```txt
🚀 astro v3.1.4 started in 200ms
┃ Local http://localhost:4321/
┃ Network use --host to expose
```
---
Open [http://localhost:4321](http://localhost:4321) with your browser to see the result. Astro will hot-reload your app as you edit your source files.
<Frame>
<img src="https://i.imgur.com/Dswiu6w.png" caption="An Astro v3 starter app running on Bun" />
</Frame>
---
Refer to the [Astro docs](https://docs.astro.build/en/getting-started/) for complete documentation.

View File

@@ -0,0 +1,80 @@
---
title: Create a Discord bot
sidebarTitle: "Discord.js with Bun"
mode: center
---
Discord.js works out of the box with Bun. Let's write a simple bot. First create a directory and initialize it with `bun init`.
```sh terminal icon="terminal"
mkdir my-bot
cd my-bot
bun init
```
---
Now install Discord.js.
```sh terminal icon="terminal"
bun add discord.js
```
---
Before we go further, we need to go to the [Discord developer portal](https://discord.com/developers/applications), login/signup, create a new _Application_, then create a new _Bot_ within that application. Follow the [official guide](https://discordjs.guide/legacy/preparations/app-setup#creating-your-bot) for step-by-step instructions.
---
Once complete, you'll be presented with your bot's _private key_. Let's add this to a file called `.env.local`. Bun automatically reads this file and loads it into `process.env`.
<Note>This is an example token that has already been invalidated.</Note>
```ini .env.local icon="settings"
DISCORD_TOKEN=NzkyNzE1NDU0MTk2MDg4ODQy.X-hvzA.Ovy4MCQywSkoMRRclStW4xAYK7I
```
---
Be sure to add `.env.local` to your `.gitignore`! It is dangerous to check your bot's private key into version control.
```txt .gitignore icon="file-code"
node_modules
.env.local
```
---
Now let's actually write our bot in a new file called `bot.ts`.
```ts bot.ts icon="/icons/typescript.svg"
// import discord.js
import { Client, Events, GatewayIntentBits } from "discord.js";
// create a new Client instance
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
// listen for the client to be ready
client.once(Events.ClientReady, c => {
console.log(`Ready! Logged in as ${c.user.tag}`);
});
// login with the token from .env.local
client.login(process.env.DISCORD_TOKEN);
```
---
Now we can run our bot with `bun run`. It may take a several seconds for the client to initialize the first time you run the file.
```sh terminal icon="terminal"
bun run bot.ts
```
```txt
Ready! Logged in as my-bot#1234
```
---
You're up and running with a bare-bones Discord.js bot! This is a basic guide to setting up your bot with Bun; we recommend the [official discord.js docs](https://discordjs.guide/) for complete information on the `discord.js` API.

View File

@@ -0,0 +1,151 @@
---
title: Containerize a Bun application with Docker
sidebarTitle: Docker with Bun
mode: center
---
<Note>
This guide assumes you already have [Docker Desktop](https://www.docker.com/products/docker-desktop/) installed.
</Note>
[Docker](https://www.docker.com) is a platform for packaging and running an application as a lightweight, portable _container_ that encapsulates all the necessary dependencies.
---
To _containerize_ our application, we define a `Dockerfile`. This file contains a list of instructions to initialize the container, copy our local project files into it, install dependencies, and starts the application.
```docker Dockerfile icon="docker"
# use the official Bun image
# see all versions at https://hub.docker.com/r/oven/bun/tags
FROM oven/bun:1 AS base
WORKDIR /usr/src/app
# install dependencies into temp directory
# this will cache them and speed up future builds
FROM base AS install
RUN mkdir -p /temp/dev
COPY package.json bun.lock /temp/dev/
RUN cd /temp/dev && bun install --frozen-lockfile
# install with --production (exclude devDependencies)
RUN mkdir -p /temp/prod
COPY package.json bun.lock /temp/prod/
RUN cd /temp/prod && bun install --frozen-lockfile --production
# copy node_modules from temp directory
# then copy all (non-ignored) project files into the image
FROM base AS prerelease
COPY --from=install /temp/dev/node_modules node_modules
COPY . .
# [optional] tests & build
ENV NODE_ENV=production
RUN bun test
RUN bun run build
# copy production dependencies and source code into final image
FROM base AS release
COPY --from=install /temp/prod/node_modules node_modules
COPY --from=prerelease /usr/src/app/index.ts .
COPY --from=prerelease /usr/src/app/package.json .
# run the app
USER bun
EXPOSE 3000/tcp
ENTRYPOINT [ "bun", "run", "index.ts" ]
```
---
Now that you have your docker image, let's look at `.dockerignore` which has the same syntax as `.gitignore`, here you need to specify the files/directories that must not go in any stage of the docker build. An example for a ignore file is
```txt .dockerignore icon="docker"
node_modules
Dockerfile*
docker-compose*
.dockerignore
.git
.gitignore
README.md
LICENSE
.vscode
Makefile
helm-charts
.env
.editorconfig
.idea
coverage*
```
---
We'll now use `docker build` to convert this `Dockerfile` into a _Docker image_, a self-contained template containing all the dependencies and configuration required to run the application.
The `-t` flag lets us specify a name for the image, and `--pull` tells Docker to automatically download the latest version of the base image (`oven/bun`). The initial build will take longer, as Docker will download all the base images and dependencies.
```bash terminal icon="terminal"
docker build --pull -t bun-hello-world .
```
```txt
[+] Building 0.9s (21/21) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 37B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 35B 0.0s
=> [internal] load metadata for docker.io/oven/bun:1 0.8s
=> [auth] oven/bun:pull token for registry-1.docker.io 0.0s
=> [base 1/2] FROM docker.io/oven/bun:1@sha256:373265748d3cd3624cb3f3ee6004f45b1fc3edbd07a622aeeec17566d2756997 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 155B 0.0s
# ...lots of commands...
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:360663f7fdcd6f11e8e94761d5592e2e4dfc8d167f034f15cd5a863d5dc093c4 0.0s
=> => naming to docker.io/library/bun-hello-world 0.0s
```
---
We've built a new _Docker image_. Now let's use that image to spin up an actual, running _container_.
We'll use `docker run` to start a new container using the `bun-hello-world` image. It will be run in _detached_ mode (`-d`) and we'll map the container's port 3000 to our local machine's port 3000 (`-p 3000:3000`).
The `run` command prints a string representing the _container ID_.
```sh terminal icon="terminal"
docker run -d -p 3000:3000 bun-hello-world
```
```txt
7f03e212a15ede8644379bce11a13589f563d3909a9640446c5bbefce993678d
```
---
The container is now running in the background. Visit [localhost:3000](http://localhost:3000). You should see a `Hello, World!` message.
---
To stop the container, we'll use `docker stop <container-id>`.
```sh terminal icon="terminal"
docker stop 7f03e212a15ede8644379bce11a13589f563d3909a9640446c5bbefce993678d
```
---
If you can't find the container ID, you can use `docker ps` to list all running containers.
```sh terminal icon="terminal"
docker ps
```
```txt
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7f03e212a15e bun-hello-world "bun run index.ts" 2 minutes ago Up 2 minutes 0.0.0.0:3000->3000/tcp flamboyant_cerf
```
---
That's it! Refer to the [Docker documentation](https://docs.docker.com/) for more advanced usage.

View File

@@ -0,0 +1,195 @@
---
title: Use Drizzle ORM with Bun
sidebarTitle: Drizzle with Bun
mode: center
---
Drizzle is an ORM that supports both a SQL-like "query builder" API and an ORM-like [Queries API](https://orm.drizzle.team/docs/rqb). It supports the `bun:sqlite` built-in module.
---
Let's get started by creating a fresh project with `bun init` and installing Drizzle.
```sh terminal icon="terminal"
bun init -y
bun add drizzle-orm
bun add -D drizzle-kit
```
---
Then we'll connect to a SQLite database using the `bun:sqlite` module and create the Drizzle database instance.
```ts db.ts icon="/icons/typescript.svg"
import { drizzle } from "drizzle-orm/bun-sqlite";
import { Database } from "bun:sqlite";
const sqlite = new Database("sqlite.db");
export const db = drizzle(sqlite);
```
---
To see the database in action, add these lines to `index.ts`.
```ts index.ts icon="/icons/typescript.svg"
import { db } from "./db";
import { sql } from "drizzle-orm";
const query = sql`select "hello world" as text`;
const result = db.get<{ text: string }>(query);
console.log(result);
```
---
Then run `index.ts` with Bun. Bun will automatically create `sqlite.db` and execute the query.
```sh terminal icon="terminal"
bun run index.ts
```
```txt
{
text: "hello world"
}
```
---
Lets give our database a proper schema. Create a `schema.ts` file and define a `movies` table.
```ts schema.ts icon="/icons/typescript.svg"
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
export const movies = sqliteTable("movies", {
id: integer("id").primaryKey(),
title: text("name"),
releaseYear: integer("release_year"),
});
```
---
We can use the `drizzle-kit` CLI to generate an initial SQL migration.
```sh terminal icon="terminal"
bunx drizzle-kit generate --dialect sqlite --schema ./schema.ts
```
---
This creates a new `drizzle` directory containing a `.sql` migration file and `meta` directory.
```txt File Tree icon="folder-tree"
drizzle
├── 0000_ordinary_beyonder.sql
└── meta
├── 0000_snapshot.json
└── _journal.json
```
---
We can execute these migrations with a simple `migrate.ts` script.
This script creates a new connection to a SQLite database that writes to `sqlite.db`, then executes all unexecuted migrations in the `drizzle` directory.
```ts migrate.ts icon="/icons/typescript.svg"
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
import { drizzle } from "drizzle-orm/bun-sqlite";
import { Database } from "bun:sqlite";
const sqlite = new Database("sqlite.db");
const db = drizzle(sqlite);
migrate(db, { migrationsFolder: "./drizzle" });
```
---
We can run this script with `bun` to execute the migration.
```sh terminal icon="terminal"
bun run migrate.ts
```
---
Now that we have a database, let's add some data to it. Create a `seed.ts` file with the following contents.
```ts seed.ts icon="/icons/typescript.svg"
import { db } from "./db";
import * as schema from "./schema";
await db.insert(schema.movies).values([
{
title: "The Matrix",
releaseYear: 1999,
},
{
title: "The Matrix Reloaded",
releaseYear: 2003,
},
{
title: "The Matrix Revolutions",
releaseYear: 2003,
},
]);
console.log(`Seeding complete.`);
```
---
Then run this file.
```sh terminal icon="terminal"
bun run seed.ts
```
```txt
Seeding complete.
```
---
We finally have a database with a schema and some sample data. Let's use Drizzle to query it. Replace the contents of `index.ts` with the following.
```ts index.ts icon="/icons/typescript.svg"
import * as schema from "./schema";
import { db } from "./db";
const result = await db.select().from(schema.movies);
console.log(result);
```
---
Then run the file. You should see the three movies we inserted.
```sh terminal icon="terminal"
bun run index.ts
```
```txt
[
{
id: 1,
title: "The Matrix",
releaseYear: 1999
}, {
id: 2,
title: "The Matrix Reloaded",
releaseYear: 2003
}, {
id: 3,
title: "The Matrix Revolutions",
releaseYear: 2003
}
]
```
---
Refer to the [Drizzle website](https://orm.drizzle.team/docs/overview) for complete documentation.

View File

@@ -0,0 +1,31 @@
---
title: Build an HTTP server using Elysia and Bun
sidebarTitle: Elysia with Bun
mode: center
---
[Elysia](https://elysiajs.com) is a Bun-first performance focused web framework that takes full advantage of Bun's HTTP, file system, and hot reloading APIs. Get started with `bun create`.
```bash terminal icon="terminal"
bun create elysia myapp
cd myapp
bun run dev
```
---
To define a simple HTTP route and start a server with Elysia:
```ts server.ts icon="/icons/typescript.svg"
import { Elysia } from "elysia";
const app = new Elysia().get("/", () => "Hello Elysia").listen(8080);
console.log(`🦊 Elysia is running at on port ${app.server?.port}...`);
```
---
Elysia is a full-featured server framework with Express-like syntax, type inference, middleware, file uploads, and plugins for JWT authentication, tRPC, and more. It's also is one of the [fastest Bun web frameworks](https://github.com/SaltyAom/bun-http-framework-benchmark).
Refer to the Elysia [documentation](https://elysiajs.com/quick-start.html) for more information.

View File

@@ -0,0 +1,43 @@
---
title: Build an HTTP server using Express and Bun
sidebarTitle: Express with Bun
mode: center
---
Express and other major Node.js HTTP libraries should work out of the box. Bun implements the [`node:http`](https://nodejs.org/api/http.html) and [`node:https`](https://nodejs.org/api/https.html) modules that these libraries rely on.
<Note>
Refer to the [Runtime > Node.js APIs](/docs/runtime/nodejs-compat#node-http) page for more detailed compatibility
information.
</Note>
```sh terminal icon="terminal"
bun add express
```
---
To define a simple HTTP route and start a server with Express:
```ts server.ts icon="/icons/typescript.svg"
import express from "express";
const app = express();
const port = 8080;
app.get("/", (req, res) => {
res.send("Hello World!");
});
app.listen(port, () => {
console.log(`Listening on port ${port}...`);
});
```
---
To start the server on `localhost`:
```sh terminal icon="terminal"
bun server.ts
```

View File

@@ -0,0 +1,261 @@
---
title: Use Gel with Bun
sidebarTitle: Gel with Bun
mode: center
---
Gel (formerly EdgeDB) is a graph-relational database powered by Postgres under the hood. It provides a declarative schema language, migrations system, and object-oriented query language, in addition to supporting raw SQL queries. It solves the object-relational mapping problem at the database layer, eliminating the need for an ORM library in your application code.
---
First, [install Gel](https://docs.geldata.com/learn/installation) if you haven't already.
<CodeGroup>
```sh Linux/macOS terminal icon="terminal"
curl https://www.geldata.com/sh --proto "=https" -sSf1 | sh
```
```sh Windows terminal icon="windows"
irm https://www.geldata.com/ps1 | iex
```
```sh Homebrew terminal icon="terminal"
brew install geldata/tap/gel-cli
```
</CodeGroup>
---
Use `bun init` to create a fresh project.
```sh terminal icon="terminal"
mkdir my-edgedb-app
cd my-edgedb-app
bun init -y
```
---
We'll use the Gel CLI to initialize a Gel instance for our project. This creates a `gel.toml` file in our project root.
```sh terminal icon="terminal"
gel project init
```
```txt
No `gel.toml` found in `/Users/colinmcd94/Documents/bun/fun/examples/my-gel-app` or above
Do you want to initialize a new project? [Y/n]
> Y
Specify the name of Gel instance to use with this project [default: my_gel_app]:
> my_gel_app
Checking Gel versions...
Specify the version of Gel to use with this project [default: x.y]:
> x.y
┌─────────────────────┬──────────────────────────────────────────────────────────────────┐
│ Project directory │ /Users/colinmcd94/Documents/bun/fun/examples/my-gel-app │
│ Project config │ /Users/colinmcd94/Documents/bun/fun/examples/my-gel-app/gel.toml│
│ Schema dir (empty) │ /Users/colinmcd94/Documents/bun/fun/examples/my-gel-app/dbschema│
│ Installation method │ portable package │
│ Version │ x.y+6d5921b │
│ Instance name │ my_gel_app │
└─────────────────────┴──────────────────────────────────────────────────────────────────┘
Version x.y+6d5921b is already downloaded
Initializing Gel instance...
Applying migrations...
Everything is up to date. Revision initial
Project initialized.
To connect to my_gel_app, run `gel`
```
---
To see if the database is running, let's open a REPL and run a simple query.
```sh terminal icon="terminal"
gel
gel> select 1 + 1;
```
```txt
2
```
Then run `\quit` to exit the REPL.
```sh terminal icon="terminal"
gel> \quit
```
---
With the project initialized, we can define a schema. The `gel project init` command already created a `dbschema/default.esdl` file to contain our schema.
```txt File Tree icon="folder-tree"
dbschema
├── default.esdl
└── migrations
```
---
Open that file and paste the following contents.
```ts default.esdl icon="file-code"
module default {
type Movie {
required title: str;
releaseYear: int64;
}
};
```
---
Then generate and apply an initial migration.
```sh terminal icon="terminal"
gel migration create
```
```txt
Created /Users/colinmcd94/Documents/bun/fun/examples/my-gel-app/dbschema/migrations/00001.edgeql, id: m1uwekrn4ni4qs7ul7hfar4xemm5kkxlpswolcoyqj3xdhweomwjrq
```
```sh terminal icon="terminal"
gel migrate
```
```txt
Applied m1uwekrn4ni4qs7ul7hfar4xemm5kkxlpswolcoyqj3xdhweomwjrq (00001.edgeql)
```
---
With our schema applied, let's execute some queries using Gel's JavaScript client library. We'll install the client library and Gel's codegen CLI, and create a `seed.ts`.file.
```sh terminal icon="terminal"
bun add gel
bun add -D @gel/generate
touch seed.ts
```
---
Paste the following code into `seed.ts`.
The client auto-connects to the database. We insert a couple movies using the `.execute()` method. We will use EdgeQL's `for` expression to turn this bulk insert into a single optimized query.
```ts seed.ts icon="/icons/typescript.svg"
import { createClient } from "gel";
const client = createClient();
const INSERT_MOVIE = `
with movies := <array<tuple<title: str, year: int64>>>$movies
for movie in array_unpack(movies) union (
insert Movie {
title := movie.title,
releaseYear := movie.year,
}
)
`;
const movies = [
{ title: "The Matrix", year: 1999 },
{ title: "The Matrix Reloaded", year: 2003 },
{ title: "The Matrix Revolutions", year: 2003 },
];
await client.execute(INSERT_MOVIE, { movies });
console.log(`Seeding complete.`);
process.exit();
```
---
Then run this file with Bun.
```sh terminal icon="terminal"
bun run seed.ts
```
```txt
Seeding complete.
```
---
Gel implements a number of code generation tools for TypeScript. To query our newly seeded database in a typesafe way, we'll use `@gel/generate` to code-generate the EdgeQL query builder.
```sh terminal icon="terminal"
bunx @gel/generate edgeql-js
```
```txt
Generating query builder...
Detected tsconfig.json, generating TypeScript files.
To override this, use the --target flag.
Run `npx @edgedb/generate --help` for full options.
Introspecting database schema...
Writing files to ./dbschema/edgeql-js
Generation complete! 🤘
Checking the generated query builder into version control
is not recommended. Would you like to update .gitignore to ignore
the query builder directory? The following line will be added:
dbschema/edgeql-js
[y/n] (leave blank for "y")
> y
```
---
In `index.ts`, we can import the generated query builder from `./dbschema/edgeql-js` and write a simple select query.
```ts index.ts icon="/icons/typescript.svg"
import { createClient } from "gel";
import e from "./dbschema/edgeql-js";
const client = createClient();
const query = e.select(e.Movie, () => ({
title: true,
releaseYear: true,
}));
const results = await query.run(client);
console.log(results);
results; // { title: string, releaseYear: number | null }[]
```
---
Running the file with Bun, we can see the list of movies we inserted.
```sh terminal icon="terminal"
bun run index.ts
```
```txt
[
{
title: "The Matrix",
releaseYear: 1999
}, {
title: "The Matrix Reloaded",
releaseYear: 2003
}, {
title: "The Matrix Revolutions",
releaseYear: 2003
}
]
```
---
For complete documentation, refer to the [Gel docs](https://docs.geldata.com/).

View File

@@ -0,0 +1,47 @@
---
title: Build an HTTP server using Hono and Bun
sidebarTitle: Hono with Bun
mode: center
---
[Hono](https://github.com/honojs/hono) is a lightweight ultrafast web framework designed for the edge.
```ts server.ts icon="/icons/typescript.svg"
import { Hono } from "hono";
const app = new Hono();
app.get("/", c => c.text("Hono!"));
export default app;
```
---
Use `create-hono` to get started with one of Hono's project templates. Select `bun` when prompted for a template.
```sh terminal icon="terminal"
bun create hono myapp
```
```txt
✔ Which template do you want to use? bun
cloned honojs/starter#main to /path/to/myapp
✔ Copied project files
```
```sh terminal icon="terminal"
cd myapp
bun install
```
---
Then start the dev server and visit [localhost:3000](http://localhost:3000).
```sh terminal icon="terminal"
bun run dev
```
---
Refer to Hono's guide on [getting started with Bun](https://hono.dev/getting-started/bun) for more information.

View File

@@ -0,0 +1,92 @@
---
title: Read and write data to MongoDB using Mongoose and Bun
sidebarTitle: Mongoose with Bun
mode: center
---
MongoDB and Mongoose work out of the box with Bun. This guide assumes you've already installed MongoDB and are running it as background process/service on your development machine. Follow [this guide](https://www.mongodb.com/docs/manual/installation/) for details.
---
Once MongoDB is running, create a directory and initialize it with `bun init`.
```sh terminal icon="terminal"
mkdir mongoose-app
cd mongoose-app
bun init
```
---
Then add Mongoose as a dependency.
```sh terminal icon="terminal"
bun add mongoose
```
---
In `schema.ts` we'll declare and export a simple `Animal` model.
```ts schema.ts icon="/icons/typescript.svg"
import * as mongoose from "mongoose";
const animalSchema = new mongoose.Schema(
{
title: { type: String, required: true },
sound: { type: String, required: true },
},
{
methods: {
speak() {
console.log(`${this.sound}!`);
},
},
},
);
export type Animal = mongoose.InferSchemaType<typeof animalSchema>;
export const Animal = mongoose.model("Animal", animalSchema);
```
---
Now from `index.ts` we can import `Animal`, connect to MongoDB, and add some data to our database.
```ts index.ts icon="/icons/typescript.svg"
import * as mongoose from "mongoose";
import { Animal } from "./schema";
// connect to database
await mongoose.connect("mongodb://127.0.0.1:27017/mongoose-app");
// create new Animal
const cow = new Animal({
title: "Cow",
sound: "Moo",
});
await cow.save(); // saves to the database
// read all Animals
const animals = await Animal.find();
animals[0].speak(); // logs "Moo!"
// disconnect
await mongoose.disconnect();
```
---
Let's run this with `bun run`.
```bash terminal icon="terminal"
bun run index.ts
```
```txt
Moo!
```
---
This is a simple introduction to using Mongoose with TypeScript and Bun. As you build your application, refer to the official [MongoDB](https://www.mongodb.com/docs) and [Mongoose](https://mongoosejs.com/docs/) sites for complete documentation.

View File

@@ -0,0 +1,234 @@
---
title: Use Neon Postgres through Drizzle ORM
sidebarTitle: Neon Drizzle with Bun
mode: center
---
[Neon](https://neon.tech/) is a fully managed serverless Postgres, separating compute and storage to offer features like autoscaling, branching and bottomless storage. Neon can be used from Bun directly using the `@neondatabase/serverless` driver or through an ORM like `Drizzle`.
Drizzle ORM supports both a SQL-like "query builder" API and an ORM-like [Queries API](https://orm.drizzle.team/docs/rqb). Get started by creating a project directory, initializing the directory using `bun init`, and installing Drizzle and the [Neon serverless driver](https://github.com/neondatabase/serverless/).
```sh terminal icon="terminal"
mkdir bun-drizzle-neon
cd bun-drizzle-neon
bun init -y
bun add drizzle-orm @neondatabase/serverless
bun add -D drizzle-kit
```
---
Create a `.env.local` file and add your [Neon Postgres connection string](https://neon.tech/docs/connect/connect-from-any-app) to it.
```ini .env.local icon="settings"
DATABASE_URL=postgresql://usertitle:password@ep-adj-noun-guid.us-east-1.aws.neon.tech/neondb?sslmode=require
```
---
We will connect to the Neon database using the Neon serverless driver, wrapped in a Drizzle database instance.
```ts db.ts icon="/icons/typescript.svg"
import { neon } from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-http";
// Bun automatically loads the DATABASE_URL from .env.local
// Refer to: https://bun.com/docs/runtime/environment-variables for more information
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql);
```
---
To see the database in action, add these lines to `index.ts`.
```ts index.ts icon="/icons/typescript.svg"
import { db } from "./db";
import { sql } from "drizzle-orm";
const query = sql`select 'hello world' as text`;
const result = await db.execute(query);
console.log(result.rows);
```
---
Then run `index.ts` with Bun.
```sh terminal icon="terminal"
bun run index.ts
```
```txt
[
{
text: "hello world",
}
]
```
---
We can define a schema for our database using Drizzle ORM primitives. Create a `schema.ts` file and add this code.
```ts schema.ts icon="/icons/typescript.svg"
import { pgTable, integer, serial, text, timestamp } from "drizzle-orm/pg-core";
export const authors = pgTable("authors", {
id: serial("id").primaryKey(),
title: text("name").notNull(),
bio: text("bio"),
createdAt: timestamp("created_at").notNull().defaultNow(),
});
```
---
We then use the `drizzle-kit` CLI to generate an initial SQL migration.
```sh
bunx drizzle-kit generate --dialect postgresql --schema ./schema.ts --out ./drizzle
```
---
This creates a new `drizzle` directory containing a `.sql` migration file and `meta` directory.
```txt File Tree icon="folder-tree"
drizzle
├── 0000_aspiring_post.sql
└── meta
├── 0000_snapshot.json
└── _journal.json
```
---
We can execute these migrations with a simple `migrate.ts` script. This script creates a new connection to the Neon database and executes all unexecuted migrations in the `drizzle` directory.
```ts migrate.ts
import { db } from "./db";
import { migrate } from "drizzle-orm/neon-http/migrator";
const main = async () => {
try {
await migrate(db, { migrationsFolder: "drizzle" });
console.log("Migration completed");
} catch (error) {
console.error("Error during migration:", error);
process.exit(1);
}
};
main();
```
---
We can run this script with `bun` to execute the migration.
```sh terminal icon="terminal"
bun run migrate.ts
```
```txt
Migration completed
```
---
We can now add some data to our database. Create a `seed.ts` file with the following contents.
```ts seed.ts icon="/icons/typescript.svg"
import { db } from "./db";
import * as schema from "./schema";
async function seed() {
await db.insert(schema.authors).values([
{
title: "J.R.R. Tolkien",
bio: "The creator of Middle-earth and author of The Lord of the Rings.",
},
{
title: "George R.R. Martin",
bio: "The author of the epic fantasy series A Song of Ice and Fire.",
},
{
title: "J.K. Rowling",
bio: "The creator of the Harry Potter series.",
},
]);
}
async function main() {
try {
await seed();
console.log("Seeding completed");
} catch (error) {
console.error("Error during seeding:", error);
process.exit(1);
}
}
main();
```
---
Then run this file.
```sh terminal icon="terminal"
bun run seed.ts
```
```txt
Seeding completed
```
---
We now have a database with a schema and sample data. We can use Drizzle to query it. Replace the contents of `index.ts` with the following.
```ts index.ts icon="/icons/typescript.svg"
import * as schema from "./schema";
import { db } from "./db";
const result = await db.select().from(schema.authors);
console.log(result);
```
---
Then run the file. You should see the three authors we inserted.
```sh terminal icon="terminal"
bun run index.ts
```
```txt
[
{
id: 1,
title: "J.R.R. Tolkien",
bio: "The creator of Middle-earth and author of The Lord of the Rings.",
createdAt: 2024-05-11T10:28:46.029Z,
}, {
id: 2,
title: "George R.R. Martin",
bio: "The author of the epic fantasy series A Song of Ice and Fire.",
createdAt: 2024-05-11T10:28:46.029Z,
}, {
id: 3,
title: "J.K. Rowling",
bio: "The creator of the Harry Potter series.",
createdAt: 2024-05-11T10:28:46.029Z,
}
]
```
---
This example used the Neon serverless driver's SQL-over-HTTP functionality. Neon's serverless driver also exposes `Client` and `Pool` constructors to enable sessions, interactive transactions, and node-postgres compatibility. Refer to [Neon's documentation](https://neon.tech/docs/serverless/serverless-driver) for a complete overview.
Refer to the [Drizzle website](https://orm.drizzle.team/docs/overview) for more documentation on using the Drizzle ORM.

View File

@@ -0,0 +1,60 @@
---
title: Use Neon's Serverless Postgres with Bun
sidebarTitle: Neon Serverless Postgres with Bun
mode: center
---
[Neon](https://neon.tech/) is a fully managed serverless Postgres. Neon separates compute and storage to offer modern developer features such as autoscaling, branching, bottomless storage, and more.
---
Get started by creating a project directory, initializing the directory using `bun init`, and adding the [Neon serverless driver](https://github.com/neondatabase/serverless/) as a project dependency.
```sh terminal icon="terminal"
mkdir bun-neon-postgres
cd bun-neon-postgres
bun init -y
bun add @neondatabase/serverless
```
---
Create a `.env.local` file and add your [Neon Postgres connection string](https://neon.tech/docs/connect/connect-from-any-app) to it.
```ini .env.local icon="settings"
DATABASE_URL=postgresql://usertitle:password@ep-adj-noun-guid.us-east-1.aws.neon.tech/neondb?sslmode=require
```
---
Paste the following code into your project's `index.ts` file.
```ts index.ts icon="/icons/typescript.svg"
import { neon } from "@neondatabase/serverless";
// Bun automatically loads the DATABASE_URL from .env.local
// Refer to: https://bun.com/docs/runtime/environment-variables for more information
const sql = neon(process.env.DATABASE_URL);
const rows = await sql`SELECT version()`;
console.log(rows[0].version);
```
---
Start the program using `bun ./index.ts`. The Postgres version should be printed to the console.
```sh terminal icon="terminal"
bun ./index.ts
```
```txt
PostgreSQL 16.2 on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
```
---
This example used the Neon serverless driver's SQL-over-HTTP functionality. Neon's serverless driver also exposes `Client` and `Pool` constructors to enable sessions, interactive transactions, and node-postgres compatibility.
Refer to [Neon's documentation](https://neon.tech/docs/serverless/serverless-driver) for a complete overview of the serverless driver.

View File

@@ -0,0 +1,103 @@
---
title: Build an app with Next.js and Bun
sidebarTitle: Next.js with Bun
mode: center
---
[Next.js](https://nextjs.org/) is a React framework for building full-stack web applications. It supports server-side rendering, static site generation, API routes, and more. Bun provides fast package installation and can run Next.js development and production servers.
---
<Steps>
<Step title="Create a new Next.js app">
Use the interactive CLI to create a new Next.js app. This will scaffold a new Next.js project and automatically install dependencies.
```sh terminal icon="terminal"
bun create next-app@latest my-bun-app
```
</Step>
<Step title="Start the dev server">
Change to the project directory and run the dev server with Bun.
```sh terminal icon="terminal"
cd my-bun-app
bun --bun run dev
```
This starts the Next.js dev server with Bun's runtime.
Open [`http://localhost:3000`](http://localhost:3000) with your browser to see the result. Any changes you make to `app/page.tsx` will be hot-reloaded in the browser.
</Step>
<Step title="Update scripts in package.json">
Modify the scripts field in your `package.json` by prefixing the Next.js CLI commands with `bun --bun`. This ensures that Bun executes the Next.js CLI for common tasks like `dev`, `build`, and `start`.
```json package.json icon="file-json"
{
"scripts": {
"dev": "bun --bun next dev", // [!code ++]
"build": "bun --bun next build", // [!code ++]
"start": "bun --bun next start", // [!code ++]
}
}
```
</Step>
</Steps>
---
## Hosting
Next.js applications on Bun can be deployed to various platforms.
<Columns cols={3}>
<Card title="Vercel" href="/guides/deployment/vercel" icon="/icons/ecosystem/vercel.svg">
Deploy on Vercel
</Card>
<Card title="Railway" href="/guides/deployment/railway" icon="/icons/ecosystem/railway.svg">
Deploy on Railway
</Card>
<Card title="DigitalOcean" href="/guides/deployment/digital-ocean" icon="/icons/ecosystem/digitalocean.svg">
Deploy on DigitalOcean
</Card>
<Card title="AWS Lambda" href="/guides/deployment/aws-lambda" icon="/icons/ecosystem/aws.svg">
Deploy on AWS Lambda
</Card>
<Card title="Google Cloud Run" href="/guides/deployment/google-cloud-run" icon="/icons/ecosystem/gcp.svg">
Deploy on Google Cloud Run
</Card>
<Card title="Render" href="/guides/deployment/render" icon="/icons/ecosystem/render.svg">
Deploy on Render
</Card>
</Columns>
---
## Templates
<Columns cols={2}>
<Card
title="Bun + Next.js Basic Starter"
img="/images/templates/bun-nextjs-basic.png"
href="https://github.com/bun-templates/bun-nextjs-basic"
arrow="true"
cta="Go to template"
>
A simple App Router starter with Bun, Next.js, and Tailwind CSS.
</Card>
<Card
title="Todo App with Next.js + Bun"
img="/images/templates/bun-nextjs-todo.png"
href="https://github.com/bun-templates/bun-nextjs-todo"
arrow="true"
cta="Go to template"
>
A full-stack todo application built with Bun, Next.js, and PostgreSQL.
</Card>
</Columns>
---
[→ See Next.js's official documentation](https://nextjs.org/docs) for more information on building and deploying Next.js applications.

View File

@@ -0,0 +1,96 @@
---
title: Build an app with Nuxt and Bun
sidebarTitle: Nuxt with Bun
mode: center
---
Bun supports [Nuxt](https://nuxt.com) out of the box. Initialize a Nuxt app with official `nuxi` CLI.
```sh terminal icon="terminal"
bunx nuxi init my-nuxt-app
```
```txt
✔ Which package manager would you like to use?
bun
◐ Installing dependencies...
bun install v1.3.3 (16b4bf34)
+ @nuxt/devtools@0.8.2
+ nuxt@3.7.0
785 packages installed [2.67s]
✔ Installation completed.
✔ Types generated in .nuxt
✨ Nuxt project has been created with the v3 template. Next steps:
cd my-nuxt-app
Start development server with bun run dev
```
---
To start the dev server, run `bun --bun run dev` from the project root. This will execute the `nuxt dev` command (as defined in the `"dev"` script in `package.json`).
<Note>
The `nuxt` CLI uses Node.js by default; passing the `--bun` flag forces the dev server to use the Bun runtime instead.
</Note>
```sh terminal icon="terminal"
cd my-nuxt-app
bun --bun run dev
```
```txt
nuxt dev
Nuxi 3.6.5
Nuxt 3.6.5 with Nitro 2.5.2
> Local: http://localhost:3000/
> Network: http://192.168.0.21:3000/
> Network: http://[fd8a:d31d:481c:4883:1c64:3d90:9f83:d8a2]:3000/
✔ Nuxt DevTools is enabled v0.8.0 (experimental)
Vite client warmed up in 547ms
✔ Nitro built in 244 ms
```
---
Once the dev server spins up, open [http://localhost:3000](http://localhost:3000) to see the app. The app will render Nuxt's built-in `NuxtWelcome` template component.
To start developing your app, replace `<NuxtWelcome />` in `app.vue` with your own UI.
<Frame>
![Demo Nuxt app running on
localhost](https://github.com/oven-sh/bun/assets/3084745/2c683ecc-3298-4bb0-b8c0-cf4cfaea1daa)
</Frame>
---
For production build, while the default preset is already compatible with Bun, you can also use [Bun preset](https://nitro.build/deploy/runtimes/bun) to generate better optimized builds.
```ts nuxt.config.ts icon="/icons/typescript.svg"
export default defineNuxtConfig({
nitro: {
preset: "bun", // [!code ++]
},
});
```
Alternatively, you can set the preset via environment variable:
```sh terminal icon="terminal"
NITRO_PRESET=bun bun run build
```
<Note>
Some packages provide Bun-specific exports that Nitro will not bundle correctly using the default preset. In this
case, you need to use Bun preset so that the packages will work correctly in production builds.
</Note>
After building with bun, run:
```sh terminal icon="terminal"
bun run ./.output/server/index.mjs
```
---
Refer to the [Nuxt website](https://nuxt.com/docs) for complete documentation.

View File

@@ -0,0 +1,55 @@
---
title: Run Bun as a daemon with PM2
sidebarTitle: PM2 with Bun
mode: center
---
[PM2](https://pm2.keymetrics.io/) is a popular process manager that manages and runs your applications as daemons (background processes).
It offers features like process monitoring, automatic restarts, and easy scaling. Using a process manager is common when deploying a Bun application on a cloud-hosted virtual private server (VPS), as it:
- Keeps your Node.js application running continuously.
- Ensure high availability and reliability of your application.
- Monitor and manage multiple processes with ease.
- Simplify the deployment process.
---
You can use PM2 with Bun in two ways: as a CLI option or in a configuration file.
### With `--interpreter`
To start your application with PM2 and Bun as the interpreter, open your terminal and run the following command:
```bash terminal icon="terminal"
pm2 start --interpreter ~/.bun/bin/bun index.ts
```
---
### With a configuration file
Alternatively, you can create a PM2 configuration file. Create a file named `pm2.config.js` in your project directory and add the following content.
```js pm2.config.js icon="file-code"
module.exports = {
name: "app", // Name of your application
script: "index.ts", // Entry point of your application
interpreter: "bun", // Bun interpreter
env: {
PATH: `${process.env.HOME}/.bun/bin:${process.env.PATH}`, // Add "~/.bun/bin/bun" to PATH
},
};
```
---
After saving the file, you can start your application with PM2
```bash terminal icon="terminal"
pm2 start pm2.config.js
```
---
Thats it! Your JavaScript/TypeScript web server is now running as a daemon with PM2 using Bun as the interpreter.

View File

@@ -0,0 +1,169 @@
---
title: Use Prisma Postgres with Bun
sidebarTitle: Prisma Postgres with Bun
mode: center
---
<Note>
**Note** — At the moment Prisma needs Node.js to be installed to run certain generation code. Make sure Node.js is
installed in the environment where you're running `bunx prisma` commands.
</Note>
<Steps>
<Step title="Create a new project">
First, create a directory and initialize it with `bun init`.
```bash terminal icon="terminal"
mkdir prisma-postgres-app
cd prisma-postgres-app
bun init
```
</Step>
<Step title="Install Prisma dependencies">
Then install the Prisma CLI (`prisma`), Prisma Client (`@prisma/client`), and the accelerate extension as dependencies.
```bash terminal icon="terminal"
bun add -d prisma
bun add @prisma/client @prisma/extension-accelerate
```
</Step>
<Step title="Initialize Prisma with PostgreSQL">
We'll use the Prisma CLI with `bunx` to initialize our schema and migration directory. We'll be using PostgreSQL as our database.
```bash terminal icon="terminal"
bunx --bun prisma init --db
```
This creates a basic schema. We need to update it to use the new Rust-free client with Bun optimization. Open `prisma/schema.prisma` and modify the generator block, then add a simple `User` model.
```prisma prisma/schema.prisma icon="/icons/ecosystem/prisma.svg"
generator client {
provider = "prisma-client"
output = "./generated" // [!code ++]
engineType = "client" // [!code ++]
runtime = "bun" // [!code ++]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User { // [!code ++]
id Int @id @default(autoincrement()) // [!code ++]
email String @unique // [!code ++]
name String? // [!code ++]
} // [!code ++]
```
</Step>
<Step title="Configure database connection">
Set up your Postgres database URL in the `.env` file.
```ini .env icon="settings"
DATABASE_URL="postgresql://username:password@localhost:5432/mydb?schema=public"
```
</Step>
<Step title="Create and run database migration">
Then generate and run initial migration.
This will generate a `.sql` migration file in `prisma/migrations`, and execute the migration against your Postgres database.
```bash terminal icon="terminal"
bunx --bun prisma migrate dev --name init
```
```txt
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": PostgreSQL database "mydb", schema "public" at "localhost:5432"
Applying migration `20250114141233_init`
The following migration(s) have been created and applied from new schema changes:
prisma/migrations/
└─ 20250114141233_init/
└─ migration.sql
Your database is now in sync with your schema.
✔ Generated Prisma Client (6.17.1) to ./generated in 18ms
```
</Step>
<Step title="Generate Prisma Client">
As indicated in the output, Prisma re-generates our _Prisma client_ whenever we execute a new migration. The client provides a fully typed API for reading and writing from our database. You can manually re-generate the client with the Prisma CLI.
```sh terminal icon="terminal"
bunx --bun prisma generate
```
</Step>
<Step title="Initialize Prisma Client with Accelerate">
Now we need to create a Prisma client instance. Create a new file `prisma/db.ts` to initialize the PrismaClient with the Postgres adapter.
```ts prisma/db.ts icon="/icons/typescript.svg"
import { PrismaClient } from "./generated/client";
import { withAccelerate } from '@prisma/extension-accelerate'
export const prisma = new PrismaClient().$extends(withAccelerate())
```
</Step>
<Step title="Create a test script">
Let's write a simple script to create a new user, then count the number of users in the database.
```ts index.ts icon="/icons/typescript.svg"
import { prisma } from "./prisma/db";
// create a new user
await prisma.user.create({
data: {
name: "John Dough",
email: `john-${Math.random()}@example.com`,
},
});
// count the number of users
const count = await prisma.user.count();
console.log(`There are ${count} users in the database.`);
```
</Step>
<Step title="Run and test the application">
Let's run this script with `bun run`. Each time we run it, a new user is created.
```bash terminal icon="terminal"
bun run index.ts
```
```txt
There are 1 users in the database.
```
```bash terminal icon="terminal"
bun run index.ts
```
```txt
There are 2 users in the database.
```
```bash terminal icon="terminal"
bun run index.ts
```
```txt
There are 3 users in the database.
```
</Step>
</Steps>
---
That's it! Now that you've set up Prisma Postgres using Bun, we recommend referring to the [official Prisma Postgres docs](https://www.prisma.io/docs/postgres) as you continue to develop your application.

View File

@@ -0,0 +1,164 @@
---
title: Use Prisma with Bun
sidebarTitle: Prisma ORM with Bun
mode: center
---
<Note>
**Note** — Prisma's dynamic subcommand loading system currently requires npm to be installed alongside Bun. This
affects certain CLI commands like `prisma init`, `prisma migrate`, etc. Generated code works perfectly with Bun using
the new `prisma-client` generator.
</Note>
<Steps>
<Step title="Create a new project">
Prisma works out of the box with Bun. First, create a directory and initialize it with `bun init`.
```bash terminal icon="terminal"
mkdir prisma-app
cd prisma-app
bun init
```
</Step>
<Step title="Install Prisma dependencies">
Then install the Prisma CLI (`prisma`), Prisma Client (`@prisma/client`), and the LibSQL adapter as dependencies.
```bash terminal icon="terminal"
bun add -d prisma
bun add @prisma/client @prisma/adapter-libsql
```
</Step>
<Step title="Initialize Prisma with SQLite">
We'll use the Prisma CLI with `bunx` to initialize our schema and migration directory. For simplicity we'll be using an in-memory SQLite database.
```bash terminal icon="terminal"
bunx --bun prisma init --datasource-provider sqlite
```
This creates a basic schema. We need to update it to use the new Rust-free client with Bun optimization. Open `prisma/schema.prisma` and modify the generator block, then add a simple `User` model.
```prisma prisma/schema.prisma icon="/icons/ecosystem/prisma.svg"
generator client {
provider = "prisma-client" // [!code ++]
output = "./generated" // [!code ++]
engineType = "client" // [!code ++]
runtime = "bun" // [!code ++]
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model User { // [!code ++]
id Int @id @default(autoincrement()) // [!code ++]
email String @unique // [!code ++]
name String? // [!code ++]
} // [!code ++]
```
</Step>
<Step title="Create and run database migration">
Then generate and run initial migration.
This will generate a `.sql` migration file in `prisma/migrations`, create a new SQLite instance, and execute the migration against the new instance.
```bash terminal icon="terminal"
bunx --bun prisma migrate dev --name init
```
```txt
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": SQLite database "dev.db" at "file:./dev.db"
SQLite database dev.db created at file:./dev.db
Applying migration `20251014141233_init`
The following migration(s) have been created and applied from new schema changes:
prisma/migrations/
└─ 20251014141233_init/
└─ migration.sql
Your database is now in sync with your schema.
✔ Generated Prisma Client (6.17.1) to ./generated in 18ms
```
</Step>
<Step title="Generate Prisma Client">
As indicated in the output, Prisma re-generates our _Prisma client_ whenever we execute a new migration. The client provides a fully typed API for reading and writing from our database. You can manually re-generate the client with the Prisma CLI.
```sh terminal icon="terminal"
bunx --bun prisma generate
```
</Step>
<Step title="Initialize Prisma Client with LibSQL">
Now we need to create a Prisma client instance. Create a new file `prisma/db.ts` to initialize the PrismaClient with the LibSQL adapter.
```ts prisma/db.ts icon="/icons/typescript.svg"
import { PrismaClient } from "./generated/client";
import { PrismaLibSQL } from "@prisma/adapter-libsql";
const adapter = new PrismaLibSQL({ url: process.env.DATABASE_URL || "" });
export const prisma = new PrismaClient({ adapter });
```
</Step>
<Step title="Create a test script">
Let's write a simple script to create a new user, then count the number of users in the database.
```ts index.ts icon="/icons/typescript.svg"
import { prisma } from "./prisma/db";
// create a new user
await prisma.user.create({
data: {
name: "John Dough",
email: `john-${Math.random()}@example.com`,
},
});
// count the number of users
const count = await prisma.user.count();
console.log(`There are ${count} users in the database.`);
```
</Step>
<Step title="Run and test the application">
Let's run this script with `bun run`. Each time we run it, a new user is created.
```bash terminal icon="terminal"
bun run index.ts
```
```txg
Created john-0.12802932895402364@example.com
There are 1 users in the database.
```
```bash terminal icon="terminal"
bun run index.ts
```
```txt
Created john-0.8671308799782803@example.com
There are 2 users in the database.
```
```bash terminal icon="terminal"
bun run index.ts
```
```txt
Created john-0.4465968383115295@example.com
There are 3 users in the database.
```
</Step>
</Steps>
---
That's it! Now that you've set up Prisma using Bun, we recommend referring to the [official Prisma docs](https://www.prisma.io/docs/orm/prisma-client) as you continue to develop your application.

View File

@@ -0,0 +1,114 @@
---
title: Build an app with Qwik and Bun
sidebarTitle: Qwik with Bun
mode: center
---
Initialize a new Qwik app with `bunx create-qwik`.
The `create-qwik` package detects when you are using `bunx` and will automatically install dependencies using `bun`.
```sh terminal icon="terminal"
bun create qwik
```
```txts
............
.::: :--------:.
.:::: .:-------:.
.:::::. .:-------.
::::::. .:------.
::::::. :-----:
::::::. .:-----.
:::::::. .-----.
::::::::.. ---:.
.:::::::::. :-:.
..::::::::::::
...::::
┌ Let's create a Qwik App ✨ (v1.2.10)
◇ Where would you like to create your new project? (Use '.' or './' for current directory)
│ ./my-app
● Creating new project in /path/to/my-app ... 🐇
◇ Select a starter
│ Basic App
◇ Would you like to install bun dependencies?
│ Yes
◇ Initialize a new git repository?
│ No
◇ Finishing the install. Wanna hear a joke?
│ Yes
○ ────────────────────────────────────────────────────────╮
│ │
│ How do you know if theres an elephant under your bed? │
│ Your head hits the ceiling! │
│ │
├──────────────────────────────────────────────────────────╯
◇ App Created 🐰
◇ Installed bun dependencies 📋
○ Result ─────────────────────────────────────────────╮
│ │
│ Success! Project created in my-app directory │
│ │
│ Integrations? Add Netlify, Cloudflare, Tailwind... │
│ bun qwik add │
│ │
│ Relevant docs: │
│ https://qwik.dev/docs/getting-started/ │
│ │
│ Questions? Start the conversation at: │
│ https://qwik.dev/chat │
│ https://twitter.com/QwikDev │
│ │
│ Presentations, Podcasts and Videos: │
│ https://qwik.dev/media/ │
│ │
│ Next steps: │
│ cd my-app │
│ bun start │
│ │
│ │
├──────────────────────────────────────────────────────╯
└ Happy coding! 🎉
```
---
Run `bun run dev` to start the development server.
```sh terminal icon="terminal"
bun run dev
```
```txt
$ vite--mode ssr
VITE v4.4.7 ready in 1190 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h to show help
```
---
Open [http://localhost:5173](http://localhost:5173) with your browser to see the result. Qwik will hot-reload your app as you edit your source files.
<Frame>![Qwik screenshot](https://github.com/oven-sh/bun/assets/3084745/ec35f2f7-03dd-4c90-851e-fb4ad150bb28)</Frame>
---
Refer to the [Qwik docs](https://qwik.dev/docs/getting-started/) for complete documentation.

View File

@@ -0,0 +1,52 @@
---
title: Build a React app with Bun
sidebarTitle: React with Bun
mode: center
---
Bun supports `.jsx` and `.tsx` files out of the box. React just works with Bun.
Create a new React app with `bun init --react`. This gives you a template with a simple React app and a simple API server together in one full-stack app.
```bash terminal icon="terminal"
# Create a new React app
bun init --react
# Run the app in development mode
bun dev
# Build as a static site for production
bun run build
# Run the server in production
bun start
```
---
### Hot Reloading
Run `bun dev` to start the app in development mode. This will start the API server and the React app with hot reloading.
### Full-Stack App
Run `bun start` to start the API server and frontend together in one process.
### Static Site
Run `bun run build` to build the app as a static site. This will create a `dist` directory with the built app and all the assets.
```txt File Tree icon="folder-tree"
├── src/
│ ├── index.tsx # Server entry point with API routes
│ ├── frontend.tsx # React app entry point with HMR
│ ├── App.tsx # Main React component
│ ├── APITester.tsx # Component for testing API endpoints
│ ├── index.html # HTML template
│ ├── index.css # Styles
│ └── *.svg # Static assets
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
├── bunfig.toml # Bun configuration
└── bun.lock # Lock file
```

View File

@@ -0,0 +1,97 @@
---
title: Build an app with Remix and Bun
sidebarTitle: Remix with Bun
mode: center
---
<Note>
Currently the Remix development server (`remix dev`) relies on Node.js APIs that Bun does not yet implement. The guide
below uses Bun to initialize a project and install dependencies, but it uses Node.js to run the dev server.
</Note>
---
Initialize a Remix app with `create-remix`.
```sh terminal icon="terminal"
bun create remix
```
```txt
remix v1.19.3 💿 Let's build a better website...
dir Where should we create your new project?
./my-app
◼ Using basic template See https://remix.run/docs/en/main/guides/templates#templates for more
✔ Template copied
git Initialize a new git repository?
Yes
deps Install dependencies with bun?
Yes
✔ Dependencies installed
✔ Git initialized
done That's it!
Enter your project directory using cd ./my-app
Check out README.md for development and deploy instructions.
```
---
To start the dev server, run `bun run dev` from the project root. This will start the dev server using the `remix dev` command. Note that Node.js will be used to run the dev server.
```sh terminal icon="terminal"
cd my-app
bun run dev
```
```txt
$ remix dev
💿 remix dev
info building...
info built (263ms)
Remix App Server started at http://localhost:3000 (http://172.20.0.143:3000)
```
---
Open [http://localhost:3000](http://localhost:3000) to see the app. Any changes you make to `app/routes/_index.tsx` will be hot-reloaded in the browser.
<Frame>
![Remix app running on localhost](https://github.com/oven-sh/bun/assets/3084745/c26f1059-a5d4-4c0b-9a88-d9902472fd77)
</Frame>
---
To build and start your app, run `bun run build`
```sh terminal icon="terminal"
bun run build
```
```txt
$ remix build
info building... (NODE_ENV=production)
info built (158ms)
```
Then `bun run start` from the project root.
```sh terminal icon="terminal"
bun start
```
```txt
$ remix-serve ./build/index.js
[remix-serve] http://localhost:3000 (http://192.168.86.237:3000)
```
---
Read the [Remix docs](https://remix.run/) for more information on how to build apps with Remix.

View File

@@ -0,0 +1,54 @@
---
title: Add Sentry to a Bun app
sidebarTitle: Sentry with Bun
mode: center
---
[Sentry](https://sentry.io) is a developer-first error tracking and performance monitoring platform. Sentry has a first-class SDK for Bun, `@sentry/bun`, that instruments your Bun application to automatically collect error and performance data.
Don't already have an account and Sentry project established? Head over to [sentry.io](https://sentry.io/signup/), then return to this page.
---
To start using Sentry with Bun, first install the Sentry Bun SDK.
```sh terminal icon="terminal"
bun add @sentry/bun
```
---
Then, initialize the Sentry SDK with your Sentry DSN in your app's entry file. You can find your DSN in your Sentry project settings.
```ts sentry.ts icon="/icons/typescript.svg"
import * as Sentry from "@sentry/bun";
// Ensure to call this before importing any other modules!
Sentry.init({
dsn: "__SENTRY_DSN__",
// Add Performance Monitoring by setting tracesSampleRate
// We recommend adjusting this value in production
tracesSampleRate: 1.0,
});
```
---
You can verify that Sentry is working by capturing a test error:
```ts sentry.ts icon="/icons/typescript.svg"
setTimeout(() => {
try {
foo();
} catch (e) {
Sentry.captureException(e);
}
}, 99);
```
To view and resolve the recorded error, log into [sentry.io](https://sentry.io/) and open your project. Clicking on the error's title will open a page where you can see detailed information and mark it as resolved.
---
To learn more about Sentry and using the Sentry Bun SDK, view the [Sentry documentation](https://docs.sentry.io/platforms/javascript/guides/bun).

View File

@@ -0,0 +1,62 @@
---
title: Build an app with SolidStart and Bun
sidebarTitle: "SolidStart with Bun"
mode: center
---
Initialize a SolidStart app with `create-solid`. You can specify the `--solidstart` flag to create a SolidStart project, and `--ts` for TypeScript support. When prompted for a template, select `basic` for a minimal starter app.
```sh terminal icon="terminal"
bun create solid my-app --solidstart --ts
```
```txt
Create-Solid v0.6.11
◇ Project Name
│ my-app
◇ Which template would you like to use?
│ basic
◇ Project created 🎉
◇ To get started, run: ─╮
│ │
│ cd my-app │
│ bun install │
│ bun dev │
│ │
├────────────────────────╯
```
---
As instructed by the `create-solid` CLI, install the dependencies.
```sh terminal icon="terminal"
cd my-app
bun install
```
Then run the development server with `bun dev`.
```sh terminal icon="terminal"
bun dev
```
```txt
$ vinxi dev
vinxi v0.5.8
vinxi starting dev server
➜ Local: http://localhost:3000/
➜ Network: use --host to expose
```
Open [localhost:3000](http://localhost:3000). Any changes you make to `src/routes/index.tsx` will be hot-reloaded automatically.
---
Refer to the [SolidStart website](https://docs.solidjs.com/solid-start) for complete framework documentation.

View File

@@ -0,0 +1,49 @@
---
title: Server-side render (SSR) a React component
sidebarTitle: "SSR React with Bun"
mode: center
---
To get started, install `react` & `react-dom`:
```sh terminal icon="terminal"
# Any package manager can be used
bun add react react-dom
```
---
To render a React component to an HTML stream server-side (SSR):
```tsx ssr-react.tsx icon="file-code"
import { renderToReadableStream } from "react-dom/server";
function Component(props: { message: string }) {
return (
<body>
<h1>{props.message}</h1>
</body>
);
}
const stream = await renderToReadableStream(<Component message="Hello from server!" />);
```
---
Combining this with `Bun.serve()`, we get a simple SSR HTTP server:
```tsx server.tsx icon="/icons/typescript.svg"
Bun.serve({
async fetch() {
const stream = await renderToReadableStream(<Component message="Hello from server!" />);
return new Response(stream, {
headers: { "Content-Type": "text/html" },
});
},
});
```
---
React `19` and later includes an [SSR optimization](https://github.com/facebook/react/pull/25597) that takes advantage of Bun's "direct" `ReadableStream` implementation. If you run into an error like `export named 'renderToReadableStream' not found`, please make sure to install version `19` of `react` & `react-dom`, or import from `react-dom/server.browser` instead of `react-dom/server`. See [facebook/react#28941](https://github.com/facebook/react/issues/28941) for more information.

View File

@@ -0,0 +1,54 @@
---
title: Build an HTTP server using StricJS and Bun
sidebarTitle: "StricJS with Bun"
mode: center
---
[StricJS](https://github.com/bunsvr) is a Bun framework for building high-performance web applications and APIs.
- **Fast** — Stric is one of the fastest Bun frameworks. See [benchmark](https://github.com/bunsvr/benchmark) for more details.
- **Minimal** — The basic components like `@stricjs/router` and `@stricjs/utils` are under 50kB and require no external dependencies.
- **Extensible** — Stric includes with a plugin system, dependency injection, and optional optimizations for handling requests.
---
Use `bun init` to create an empty project.
```bash terminal icon="terminal"
mkdir myapp
cd myapp
bun init
bun add @stricjs/router @stricjs/utils
```
---
To implement a simple HTTP server with StricJS:
```ts index.ts icon="file-code"
import { Router } from "@stricjs/router";
export default new Router().get("/", () => new Response("Hi"));
```
---
To serve static files from `/public`:
```ts index.ts icon="file-code"
import { dir } from "@stricjs/utils";
export default new Router().get("/", () => new Response("Hi")).get("/*", dir("./public"));
```
---
Run the file in watch mode to start the development server.
```bash terminal icon="terminal"
bun --watch run index.ts
```
---
For more info, see Stric's [documentation](https://stricjs.netlify.app).

View File

@@ -0,0 +1,138 @@
---
title: Build an app with SvelteKit and Bun
sidebarTitle: "SvelteKit with Bun"
mode: center
---
Use `sv create my-app` to create a SvelteKit project with SvelteKit CLI. Answer the prompts to select a template and set up your development environment.
```sh terminal icon="terminal"
bunx sv create my-app
```
```txt
┌ Welcome to the Svelte CLI! (v0.5.7)
◇ Which template would you like?
│ SvelteKit demo
◇ Add type checking with Typescript?
│ Yes, using Typescript syntax
◆ Project created
◇ What would you like to add to your project?
│ none
◇ Which package manager do you want to install dependencies with?
│ bun
◇ Successfully installed dependencies
◇ Project next steps ─────────────────────────────────────────────────────╮
│ │
│ 1: cd my-app │
│ 2: git init && git add -A && git commit -m "Initial commit" (optional) │
│ 3: bun run dev -- --open │
│ │
│ To close the dev server, hit Ctrl-C │
│ │
│ Stuck? Visit us at https://svelte.dev/chat │
│ │
├──────────────────────────────────────────────────────────────────────────╯
└ You're all set!
```
---
Once the project is initialized, `cd` into the new project. You don't need to run 'bun install' since the dependencies are already installed.
Then start the development server with `bun --bun run dev`.
To run the dev server with Node.js instead of Bun, you can omit the `--bun` flag.
```sh terminal icon="terminal"
cd my-app
bun --bun run dev
```
```txt
$ vite dev
Forced re-optimization of dependencies
VITE v5.4.10 ready in 424 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help
```
---
Visit [http://localhost:5173](http://localhost:5173/) in a browser to see the template app.
<Frame>
![SvelteKit app running](https://github.com/oven-sh/bun/assets/3084745/7c76eae8-78f9-44fa-9f15-1bd3ca1a47c0)
</Frame>
---
If you edit and save `src/routes/+page.svelte`, you should see your changes hot-reloaded in the browser.
---
To build for production, you'll need to add the right SvelteKit adapter. Currently we recommend the
`bun add -D svelte-adapter-bun`.
Now, make the following changes to your `svelte.config.js`.
```js svelte.config.js icon="file-code"
import adapter from "@sveltejs/adapter-auto"; // [!code --]
import adapter from "svelte-adapter-bun"; // [!code ++]
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://svelte.dev/docs/kit/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
adapter: adapter(),
},
};
export default config;
```
---
To build a production bundle:
```sh terminal icon="terminal"
bun --bun run build
```
```txt
$ vite build
vite v5.4.10 building SSR bundle for production...
"confetti" is imported from external module "@neoconfetti/svelte" but never used in "src/routes/sverdle/+page.svelte".
✓ 130 modules transformed.
vite v5.4.10 building for production...
✓ 148 modules transformed.
...
✓ built in 231ms
...
✓ built in 899ms
Run npm run preview to preview your production build locally.
> Using svelte-adapter-bun
✔ Start server with: bun ./build/index.js
✔ done
```

View File

@@ -0,0 +1,114 @@
---
title: Run Bun as a daemon with systemd
sidebarTitle: "systemd with Bun"
mode: center
---
[systemd](https://systemd.io) is an init system and service manager for Linux operating systems that manages the startup and control of system processes and services.
---
To run a Bun application as a daemon using **systemd** you'll need to create a _service file_ in `/lib/systemd/system/`.
```sh terminal icon="terminal"
cd /lib/systemd/system
touch my-app.service
```
---
Here is a typical service file that runs an application on system start. You can use this as a template for your own service. Replace `YOUR_USER` with the name of the user you want to run the application as. To run as `root`, replace `YOUR_USER` with `root`, though this is generally not recommended for security reasons.
Refer to the [systemd documentation](https://www.freedesktop.org/software/systemd/man/systemd.service.html) for more information on each setting.
```ini my-app.service icon="file-code"
[Unit]
# describe the app
Description=My App
# start the app after the network is available
After=network.target
[Service]
# usually you'll use 'simple'
# one of https://www.freedesktop.org/software/systemd/man/systemd.service.html#Type=
Type=simple
# which user to use when starting the app
User=YOUR_USER
# path to your application's root directory
WorkingDirectory=/home/YOUR_USER/path/to/my-app
# the command to start the app
# requires absolute paths
ExecStart=/home/YOUR_USER/.bun/bin/bun run index.ts
# restart policy
# one of {no|on-success|on-failure|on-abnormal|on-watchdog|on-abort|always}
Restart=always
[Install]
# start the app automatically
WantedBy=multi-user.target
```
---
If your application starts a webserver, note that non-`root` users are not able to listen on ports 80 or 443 by default. To permanently allow Bun to listen on these ports when executed by a non-`root` user, use the following command. This step isn't necessary when running as `root`.
```bash terminal icon="terminal"
setcap CAP_NET_BIND_SERVICE=+eip ~/.bun/bin/bun
```
---
With the service file configured, you can now _enable_ the service. Once enabled, it will start automatically on reboot. This requires `sudo` permissions.
```bash terminal icon="terminal"
systemctl enable my-app
```
---
To start the service without rebooting, you can manually _start_ it.
```bash terminal icon="terminal"
systemctl start my-app
```
---
Check the status of your application with `systemctl status`. If you've started your app successfully, you should see something like this:
```bash terminal icon="terminal"
systemctl status my-app
```
```txt
● my-app.service - My App
Loaded: loaded (/lib/systemd/system/my-app.service; enabled; preset: enabled)
Active: active (running) since Thu 2023-10-12 11:34:08 UTC; 1h 8min ago
Main PID: 309641 (bun)
Tasks: 3 (limit: 503)
Memory: 40.9M
CPU: 1.093s
CGroup: /system.slice/my-app.service
└─309641 /home/YOUR_USER/.bun/bin/bun run /home/YOUR_USER/application/index.ts
```
---
To update the service, edit the contents of the service file, then reload the daemon.
```bash terminal icon="terminal"
systemctl daemon-reload
```
---
For a complete guide on the service unit configuration, you can check [this page](https://www.freedesktop.org/software/systemd/man/systemd.service.html). Or refer to this cheatsheet of common commands:
```bash terminal icon="terminal"
systemctl daemon-reload # tell systemd that some files got changed
systemctl enable my-app # enable the app (to allow auto-start)
systemctl disable my-app # disable the app (turns off auto-start)
systemctl start my-app # start the app if is stopped
systemctl stop my-app # stop the app
systemctl restart my-app # restart the app
```

View File

@@ -0,0 +1,791 @@
---
title: Use TanStack Start with Bun
sidebarTitle: TanStack Start with Bun
mode: center
---
[TanStack Start](https://tanstack.com/start/latest) is a full-stack framework powered by TanStack Router. It supports full-document SSR, streaming, server functions, bundling and more, powered by TanStack Router and [Vite](https://vite.dev/).
---
<Steps>
<Step title="Create a new TanStack Start app">
Use the interactive CLI to create a new TanStack Start app.
```sh terminal icon="terminal"
bunx @tanstack/cli create my-tanstack-app
```
</Step>
<Step title="Start the dev server">
Change to the project directory and run the dev server with Bun.
```sh terminal icon="terminal"
cd my-tanstack-app
bun --bun run dev
```
This starts the Vite dev server with Bun.
</Step>
<Step title="Update scripts in package.json">
Modify the scripts field in your `package.json` by prefixing the Vite CLI commands with `bun --bun`. This ensures that Bun executes the Vite CLI for common tasks like `dev`, `build`, and `preview`.
```json package.json icon="file-json"
{
"scripts": {
"dev": "bun --bun vite dev", // [!code ++]
"build": "bun --bun vite build", // [!code ++]
"serve": "bun --bun vite preview" // [!code ++]
}
}
```
</Step>
</Steps>
---
## Hosting
To host your TanStack Start app, you can use [Nitro](https://nitro.build/) or a custom Bun server for production deployments.
<Tabs>
<Tab title="Nitro">
<Steps>
<Step title="Add Nitro to your project">
Add [Nitro](https://nitro.build/) to your project. This tool allows you to deploy your TanStack Start app to different platforms.
```sh terminal icon="terminal"
bun add nitro
```
</Step>
<Step title={<span>Update your <code>vite.config.ts</code> file</span>}>
Update your `vite.config.ts` file to include the necessary plugins for TanStack Start with Bun.
```ts vite.config.ts icon="/icons/typescript.svg"
// other imports...
import { nitro } from "nitro/vite"; // [!code ++]
const config = defineConfig({
plugins: [
tanstackStart(),
nitro({ preset: "bun" }), // [!code ++]
// other plugins...
],
});
export default config;
```
<Note>
The `bun` preset is optional, but it configures the build output specifically for Bun's runtime.
</Note>
</Step>
<Step title="Update the start command">
Make sure `build` and `start` scripts are present in your `package.json` file:
```json package.json icon="file-json"
{
"scripts": {
"build": "bun --bun vite build", // [!code ++]
// The .output files are created by Nitro when you run `bun run build`.
// Not necessary when deploying to Vercel.
"start": "bun run .output/server/index.mjs" // [!code ++]
}
}
```
<Note>
You do **not** need the custom `start` script when deploying to Vercel.
</Note>
</Step>
<Step title="Deploy your app">
Check out one of our guides to deploy your app to a hosting provider.
<Note>
When deploying to Vercel, you can either add the `"bunVersion": "1.x"` to your `vercel.json` file, or add it to the `nitro` config in your `vite.config.ts` file:
<Warning>
Do **not** use the `bun` Nitro preset when deploying to Vercel.
</Warning>
```ts vite.config.ts icon="/icons/typescript.svg"
export default defineConfig({
plugins: [
tanstackStart(),
nitro({
preset: "bun", // [!code --]
vercel: { // [!code ++]
functions: { // [!code ++]
runtime: "bun1.x", // [!code ++]
}, // [!code ++]
}, // [!code ++]
}),
],
});
```
</Note>
</Step>
</Steps>
</Tab>
<Tab title="Custom Server">
<Note>
This custom server implementation is based on [TanStack's Bun template](https://github.com/TanStack/router/blob/main/examples/react/start-bun/server.ts). It provides fine-grained control over static asset serving, including configurable memory management that preloads small files into memory for fast serving while serving larger files on-demand. This approach is useful when you need precise control over resource usage and asset loading behavior in production deployments.
</Note>
<Steps>
<Step title="Create the production server">
Create a `server.ts` file in your project root with the following custom server implementation:
```ts server.ts icon="/icons/typescript.svg" expandable
/**
* TanStack Start Production Server with Bun
*
* A high-performance production server for TanStack Start applications that
* implements intelligent static asset loading with configurable memory management.
*
* Features:
* - Hybrid loading strategy (preload small files, serve large files on-demand)
* - Configurable file filtering with include/exclude patterns
* - Memory-efficient response generation
* - Production-ready caching headers
*
* Environment Variables:
*
* PORT (number)
* - Server port number
* - Default: 3000
*
* ASSET_PRELOAD_MAX_SIZE (number)
* - Maximum file size in bytes to preload into memory
* - Files larger than this will be served on-demand from disk
* - Default: 5242880 (5MB)
* - Example: ASSET_PRELOAD_MAX_SIZE=5242880 (5MB)
*
* ASSET_PRELOAD_INCLUDE_PATTERNS (string)
* - Comma-separated list of glob patterns for files to include
* - If specified, only matching files are eligible for preloading
* - Patterns are matched against filenames only, not full paths
* - Example: ASSET_PRELOAD_INCLUDE_PATTERNS="*.js,*.css,*.woff2"
*
* ASSET_PRELOAD_EXCLUDE_PATTERNS (string)
* - Comma-separated list of glob patterns for files to exclude
* - Applied after include patterns
* - Patterns are matched against filenames only, not full paths
* - Example: ASSET_PRELOAD_EXCLUDE_PATTERNS="*.map,*.txt"
*
* ASSET_PRELOAD_VERBOSE_LOGGING (boolean)
* - Enable detailed logging of loaded and skipped files
* - Default: false
* - Set to "true" to enable verbose output
*
* ASSET_PRELOAD_ENABLE_ETAG (boolean)
* - Enable ETag generation for preloaded assets
* - Default: true
* - Set to "false" to disable ETag support
*
* ASSET_PRELOAD_ENABLE_GZIP (boolean)
* - Enable Gzip compression for eligible assets
* - Default: true
* - Set to "false" to disable Gzip compression
*
* ASSET_PRELOAD_GZIP_MIN_SIZE (number)
* - Minimum file size in bytes required for Gzip compression
* - Files smaller than this will not be compressed
* - Default: 1024 (1KB)
*
* ASSET_PRELOAD_GZIP_MIME_TYPES (string)
* - Comma-separated list of MIME types eligible for Gzip compression
* - Supports partial matching for types ending with "/"
* - Default: text/,application/javascript,application/json,application/xml,image/svg+xml
*
* Usage:
* bun run server.ts
*/
import path from 'node:path'
// Configuration
const SERVER_PORT = Number(process.env.PORT ?? 3000)
const CLIENT_DIRECTORY = './dist/client'
const SERVER_ENTRY_POINT = './dist/server/server.js'
// Logging utilities for professional output
const log = {
info: (message: string) => {
console.log(`[INFO] ${message}`)
},
success: (message: string) => {
console.log(`[SUCCESS] ${message}`)
},
warning: (message: string) => {
console.log(`[WARNING] ${message}`)
},
error: (message: string) => {
console.log(`[ERROR] ${message}`)
},
header: (message: string) => {
console.log(`\n${message}\n`)
},
}
// Preloading configuration from environment variables
const MAX_PRELOAD_BYTES = Number(
process.env.ASSET_PRELOAD_MAX_SIZE ?? 5 * 1024 * 1024, // 5MB default
)
// Parse comma-separated include patterns (no defaults)
const INCLUDE_PATTERNS = (process.env.ASSET_PRELOAD_INCLUDE_PATTERNS ?? '')
.split(',')
.map((s) => s.trim())
.filter(Boolean)
.map((pattern: string) => convertGlobToRegExp(pattern))
// Parse comma-separated exclude patterns (no defaults)
const EXCLUDE_PATTERNS = (process.env.ASSET_PRELOAD_EXCLUDE_PATTERNS ?? '')
.split(',')
.map((s) => s.trim())
.filter(Boolean)
.map((pattern: string) => convertGlobToRegExp(pattern))
// Verbose logging flag
const VERBOSE = process.env.ASSET_PRELOAD_VERBOSE_LOGGING === 'true'
// Optional ETag feature
const ENABLE_ETAG = (process.env.ASSET_PRELOAD_ENABLE_ETAG ?? 'true') === 'true'
// Optional Gzip feature
const ENABLE_GZIP = (process.env.ASSET_PRELOAD_ENABLE_GZIP ?? 'true') === 'true'
const GZIP_MIN_BYTES = Number(process.env.ASSET_PRELOAD_GZIP_MIN_SIZE ?? 1024) // 1KB
const GZIP_TYPES = (
process.env.ASSET_PRELOAD_GZIP_MIME_TYPES ??
'text/,application/javascript,application/json,application/xml,image/svg+xml'
)
.split(',')
.map((v) => v.trim())
.filter(Boolean)
/**
* Convert a simple glob pattern to a regular expression
* Supports * wildcard for matching any characters
*/
function convertGlobToRegExp(globPattern: string): RegExp {
// Escape regex special chars except *, then replace * with .*
const escapedPattern = globPattern
.replace(/[-/\\^$+?.()|[\]{}]/g, '\\$&')
.replace(/\*/g, '.*')
return new RegExp(`^${escapedPattern}$`, 'i')
}
/**
* Compute ETag for a given data buffer
*/
function computeEtag(data: Uint8Array): string {
const hash = Bun.hash(data)
return `W/"${hash.toString(16)}-${data.byteLength.toString()}"`
}
/**
* Metadata for preloaded static assets
*/
interface AssetMetadata {
route: string
size: number
type: string
}
/**
* In-memory asset with ETag and Gzip support
*/
interface InMemoryAsset {
raw: Uint8Array
gz?: Uint8Array
etag?: string
type: string
immutable: boolean
size: number
}
/**
* Result of static asset preloading process
*/
interface PreloadResult {
routes: Record<string, (req: Request) => Response | Promise<Response>>
loaded: AssetMetadata[]
skipped: AssetMetadata[]
}
/**
* Check if a file is eligible for preloading based on configured patterns
*/
function isFileEligibleForPreloading(relativePath: string): boolean {
const fileName = relativePath.split(/[/\\]/).pop() ?? relativePath
// If include patterns are specified, file must match at least one
if (INCLUDE_PATTERNS.length > 0) {
if (!INCLUDE_PATTERNS.some((pattern) => pattern.test(fileName))) {
return false
}
}
// If exclude patterns are specified, file must not match any
if (EXCLUDE_PATTERNS.some((pattern) => pattern.test(fileName))) {
return false
}
return true
}
/**
* Check if a MIME type is compressible
*/
function isMimeTypeCompressible(mimeType: string): boolean {
return GZIP_TYPES.some((type) =>
type.endsWith('/') ? mimeType.startsWith(type) : mimeType === type,
)
}
/**
* Conditionally compress data based on size and MIME type
*/
function compressDataIfAppropriate(
data: Uint8Array,
mimeType: string,
): Uint8Array | undefined {
if (!ENABLE_GZIP) return undefined
if (data.byteLength < GZIP_MIN_BYTES) return undefined
if (!isMimeTypeCompressible(mimeType)) return undefined
try {
return Bun.gzipSync(data.buffer as ArrayBuffer)
} catch {
return undefined
}
}
/**
* Create response handler function with ETag and Gzip support
*/
function createResponseHandler(
asset: InMemoryAsset,
): (req: Request) => Response {
return (req: Request) => {
const headers: Record<string, string> = {
'Content-Type': asset.type,
'Cache-Control': asset.immutable
? 'public, max-age=31536000, immutable'
: 'public, max-age=3600',
}
if (ENABLE_ETAG && asset.etag) {
const ifNone = req.headers.get('if-none-match')
if (ifNone && ifNone === asset.etag) {
return new Response(null, {
status: 304,
headers: { ETag: asset.etag },
})
}
headers.ETag = asset.etag
}
if (
ENABLE_GZIP &&
asset.gz &&
req.headers.get('accept-encoding')?.includes('gzip')
) {
headers['Content-Encoding'] = 'gzip'
headers['Content-Length'] = String(asset.gz.byteLength)
const gzCopy = new Uint8Array(asset.gz)
return new Response(gzCopy, { status: 200, headers })
}
headers['Content-Length'] = String(asset.raw.byteLength)
const rawCopy = new Uint8Array(asset.raw)
return new Response(rawCopy, { status: 200, headers })
}
}
/**
* Create composite glob pattern from include patterns
*/
function createCompositeGlobPattern(): Bun.Glob {
const raw = (process.env.ASSET_PRELOAD_INCLUDE_PATTERNS ?? '')
.split(',')
.map((s) => s.trim())
.filter(Boolean)
if (raw.length === 0) return new Bun.Glob('**/*')
if (raw.length === 1) return new Bun.Glob(raw[0])
return new Bun.Glob(`{${raw.join(',')}}`)
}
/**
* Initialize static routes with intelligent preloading strategy
* Small files are loaded into memory, large files are served on-demand
*/
async function initializeStaticRoutes(
clientDirectory: string,
): Promise<PreloadResult> {
const routes: Record<string, (req: Request) => Response | Promise<Response>> =
{}
const loaded: AssetMetadata[] = []
const skipped: AssetMetadata[] = []
log.info(`Loading static assets from ${clientDirectory}...`)
if (VERBOSE) {
console.log(
`Max preload size: ${(MAX_PRELOAD_BYTES / 1024 / 1024).toFixed(2)} MB`,
)
if (INCLUDE_PATTERNS.length > 0) {
console.log(
`Include patterns: ${process.env.ASSET_PRELOAD_INCLUDE_PATTERNS ?? ''}`,
)
}
if (EXCLUDE_PATTERNS.length > 0) {
console.log(
`Exclude patterns: ${process.env.ASSET_PRELOAD_EXCLUDE_PATTERNS ?? ''}`,
)
}
}
let totalPreloadedBytes = 0
try {
const glob = createCompositeGlobPattern()
for await (const relativePath of glob.scan({ cwd: clientDirectory })) {
const filepath = path.join(clientDirectory, relativePath)
const route = `/${relativePath.split(path.sep).join(path.posix.sep)}`
try {
// Get file metadata
const file = Bun.file(filepath)
// Skip if file doesn't exist or is empty
if (!(await file.exists()) || file.size === 0) {
continue
}
const metadata: AssetMetadata = {
route,
size: file.size,
type: file.type || 'application/octet-stream',
}
// Determine if file should be preloaded
const matchesPattern = isFileEligibleForPreloading(relativePath)
const withinSizeLimit = file.size <= MAX_PRELOAD_BYTES
if (matchesPattern && withinSizeLimit) {
// Preload small files into memory with ETag and Gzip support
const bytes = new Uint8Array(await file.arrayBuffer())
const gz = compressDataIfAppropriate(bytes, metadata.type)
const etag = ENABLE_ETAG ? computeEtag(bytes) : undefined
const asset: InMemoryAsset = {
raw: bytes,
gz,
etag,
type: metadata.type,
immutable: true,
size: bytes.byteLength,
}
routes[route] = createResponseHandler(asset)
loaded.push({ ...metadata, size: bytes.byteLength })
totalPreloadedBytes += bytes.byteLength
} else {
// Serve large or filtered files on-demand
routes[route] = () => {
const fileOnDemand = Bun.file(filepath)
return new Response(fileOnDemand, {
headers: {
'Content-Type': metadata.type,
'Cache-Control': 'public, max-age=3600',
},
})
}
skipped.push(metadata)
}
} catch (error: unknown) {
if (error instanceof Error && error.name !== 'EISDIR') {
log.error(`Failed to load ${filepath}: ${error.message}`)
}
}
}
// Show detailed file overview only when verbose mode is enabled
if (VERBOSE && (loaded.length > 0 || skipped.length > 0)) {
const allFiles = [...loaded, ...skipped].sort((a, b) =>
a.route.localeCompare(b.route),
)
// Calculate max path length for alignment
const maxPathLength = Math.min(
Math.max(...allFiles.map((f) => f.route.length)),
60,
)
// Format file size with KB and actual gzip size
const formatFileSize = (bytes: number, gzBytes?: number) => {
const kb = bytes / 1024
const sizeStr = kb < 100 ? kb.toFixed(2) : kb.toFixed(1)
if (gzBytes !== undefined) {
const gzKb = gzBytes / 1024
const gzStr = gzKb < 100 ? gzKb.toFixed(2) : gzKb.toFixed(1)
return {
size: sizeStr,
gzip: gzStr,
}
}
// Rough gzip estimation (typically 30-70% compression) if no actual gzip data
const gzipKb = kb * 0.35
return {
size: sizeStr,
gzip: gzipKb < 100 ? gzipKb.toFixed(2) : gzipKb.toFixed(1),
}
}
if (loaded.length > 0) {
console.log('\n📁 Preloaded into memory:')
console.log(
'Path │ Size │ Gzip Size',
)
loaded
.sort((a, b) => a.route.localeCompare(b.route))
.forEach((file) => {
const { size, gzip } = formatFileSize(file.size)
const paddedPath = file.route.padEnd(maxPathLength)
const sizeStr = `${size.padStart(7)} kB`
const gzipStr = `${gzip.padStart(7)} kB`
console.log(`${paddedPath} │ ${sizeStr} │ ${gzipStr}`)
})
}
if (skipped.length > 0) {
console.log('\n💾 Served on-demand:')
console.log(
'Path │ Size │ Gzip Size',
)
skipped
.sort((a, b) => a.route.localeCompare(b.route))
.forEach((file) => {
const { size, gzip } = formatFileSize(file.size)
const paddedPath = file.route.padEnd(maxPathLength)
const sizeStr = `${size.padStart(7)} kB`
const gzipStr = `${gzip.padStart(7)} kB`
console.log(`${paddedPath} │ ${sizeStr} │ ${gzipStr}`)
})
}
}
// Show detailed verbose info if enabled
if (VERBOSE) {
if (loaded.length > 0 || skipped.length > 0) {
const allFiles = [...loaded, ...skipped].sort((a, b) =>
a.route.localeCompare(b.route),
)
console.log('\n📊 Detailed file information:')
console.log(
'Status │ Path │ MIME Type │ Reason',
)
allFiles.forEach((file) => {
const isPreloaded = loaded.includes(file)
const status = isPreloaded ? 'MEMORY' : 'ON-DEMAND'
const reason =
!isPreloaded && file.size > MAX_PRELOAD_BYTES
? 'too large'
: !isPreloaded
? 'filtered'
: 'preloaded'
const route =
file.route.length > 30
? file.route.substring(0, 27) + '...'
: file.route
console.log(
`${status.padEnd(12)} │ ${route.padEnd(30)} │ ${file.type.padEnd(28)} │ ${reason.padEnd(10)}`,
)
})
} else {
console.log('\n📊 No files found to display')
}
}
// Log summary after the file list
console.log() // Empty line for separation
if (loaded.length > 0) {
log.success(
`Preloaded ${String(loaded.length)} files (${(totalPreloadedBytes / 1024 / 1024).toFixed(2)} MB) into memory`,
)
} else {
log.info('No files preloaded into memory')
}
if (skipped.length > 0) {
const tooLarge = skipped.filter((f) => f.size > MAX_PRELOAD_BYTES).length
const filtered = skipped.length - tooLarge
log.info(
`${String(skipped.length)} files will be served on-demand (${String(tooLarge)} too large, ${String(filtered)} filtered)`,
)
}
} catch (error) {
log.error(
`Failed to load static files from ${clientDirectory}: ${String(error)}`,
)
}
return { routes, loaded, skipped }
}
/**
* Initialize the server
*/
async function initializeServer() {
log.header('Starting Production Server')
// Load TanStack Start server handler
let handler: { fetch: (request: Request) => Response | Promise<Response> }
try {
const serverModule = (await import(SERVER_ENTRY_POINT)) as {
default: { fetch: (request: Request) => Response | Promise<Response> }
}
handler = serverModule.default
log.success('TanStack Start application handler initialized')
} catch (error) {
log.error(`Failed to load server handler: ${String(error)}`)
process.exit(1)
}
// Build static routes with intelligent preloading
const { routes } = await initializeStaticRoutes(CLIENT_DIRECTORY)
// Create Bun server
const server = Bun.serve({
port: SERVER_PORT,
routes: {
// Serve static assets (preloaded or on-demand)
...routes,
// Fallback to TanStack Start handler for all other routes
'/*': (req: Request) => {
try {
return handler.fetch(req)
} catch (error) {
log.error(`Server handler error: ${String(error)}`)
return new Response('Internal Server Error', { status: 500 })
}
},
},
// Global error handler
error(error) {
log.error(
`Uncaught server error: ${error instanceof Error ? error.message : String(error)}`,
)
return new Response('Internal Server Error', { status: 500 })
},
})
log.success(`Server listening on http://localhost:${String(server.port)}`)
}
// Initialize the server
initializeServer().catch((error: unknown) => {
log.error(`Failed to start server: ${String(error)}`)
process.exit(1)
})
```
</Step>
<Step title="Update package.json scripts">
Add a `start` script to run the custom server:
```json package.json icon="file-json"
{
"scripts": {
"build": "bun --bun vite build",
"start": "bun run server.ts" // [!code ++]
}
}
```
</Step>
<Step title="Build and run">
Build your application and start the server:
```sh terminal icon="terminal"
bun run build
bun run start
```
The server will start on port 3000 by default (configurable via `PORT` environment variable).
</Step>
</Steps>
</Tab>
</Tabs>
<Columns cols={3}>
<Card title="Vercel" href="/guides/deployment/vercel" icon="/icons/ecosystem/vercel.svg">
Deploy on Vercel
</Card>
<Card title="Render" href="/guides/deployment/render" icon="/icons/ecosystem/render.svg">
Deploy on Render
</Card>
<Card title="Railway" href="/guides/deployment/railway" icon="/icons/ecosystem/railway.svg">
Deploy on Railway
</Card>
<Card title="DigitalOcean" href="/guides/deployment/digital-ocean" icon="/icons/ecosystem/digitalocean.svg">
Deploy on DigitalOcean
</Card>
<Card title="AWS Lambda" href="/guides/deployment/aws-lambda" icon="/icons/ecosystem/aws.svg">
Deploy on AWS Lambda
</Card>
<Card title="Google Cloud Run" href="/guides/deployment/google-cloud-run" icon="/icons/ecosystem/gcp.svg">
Deploy on Google Cloud Run
</Card>
</Columns>
---
## Templates
<Columns cols={2}>
<Card
title="Todo App with Tanstack + Bun"
img="/images/templates/bun-tanstack-todo.png"
href="https://github.com/bun-templates/bun-tanstack-todo"
arrow="true"
cta="Go to template"
>
A Todo application built with Bun, TanStack Start, and PostgreSQL.
</Card>
<Card
title="Bun + TanStack Start Application"
img="/images/templates/bun-tanstack-basic.png"
href="https://github.com/bun-templates/bun-tanstack-basic"
arrow="true"
cta="Go to template"
>
A TanStack Start template using Bun with SSR and file-based routing.
</Card>
<Card
title="Basic Bun + Tanstack Starter"
img="/images/templates/bun-tanstack-start.png"
href="https://github.com/bun-templates/bun-tanstack-start"
arrow="true"
cta="Go to template"
>
The basic TanStack starter using the Bun runtime and Bun's file APIs.
</Card>
</Columns>
---
[→ See TanStack Start's official documentation](https://tanstack.com/start/latest/docs/framework/react/guide/hosting) for more information on hosting.

View File

@@ -0,0 +1,87 @@
---
title: Bun Redis with Upstash
sidebarTitle: Upstash with Bun
mode: center
---
[Upstash](https://upstash.com/) is a fully managed Redis database as a service. Upstash works with the Redis® API, which means you can use Bun's native Redis client to connect to your Upstash database.
<Note>TLS is enabled by default for all Upstash Redis databases.</Note>
---
<Steps>
<Step title="Create a new project">
Create a new project by running `bun init`:
```sh terminal icon="terminal"
bun init bun-upstash-redis
cd bun-upstash-redis
```
</Step>
<Step title="Create an Upstash Redis database">
Go to the [Upstash dashboard](https://console.upstash.com/) and create a new Redis database. After completing the [getting started guide](https://upstash.com/docs/redis/overall/getstarted), you'll see your database page with connection information.
The database page displays two connection methods; HTTP and TLS. For Bun's Redis client, you need the **TLS** connection details. This URL starts with `rediss://`.
<Frame>
![Upstash Redis database page](https://bun.com/images/guides/upstash-1.png)
</Frame>
</Step>
<Step title="Connect using Bun's Redis client">
You can connect to Upstash by setting environment variables with Bun's default `redis` client.
Set the `REDIS_URL` environment variable in your `.env` file using the Redis endpoint (not the REST URL):
```ini .env icon="settings"
REDIS_URL=rediss://********@********.upstash.io:6379
```
Bun's Redis client reads connection information from `REDIS_URL` by default:
```ts index.ts icon="/icons/typescript.svg"
import { redis } from "bun";
// Reads from process.env.REDIS_URL automatically
await redis.set("counter", "0"); // [!code ++]
```
Alternatively, you can create a custom client using `RedisClient`:
```ts index.ts icon="/icons/typescript.svg"
import { RedisClient } from "bun";
const redis = new RedisClient(process.env.REDIS_URL); // [!code ++]
```
</Step>
<Step title="Use the Redis client">
You can now use the Redis client to interact with your Upstash Redis database:
```ts index.ts icon="/icons/typescript.svg"
import { redis } from "bun";
// Get a value
let counter = await redis.get("counter");
// Set a value if it doesn't exist
if (!counter) {
await redis.set("counter", "0");
}
// Increment the counter
await redis.incr("counter");
// Get the updated value
counter = await redis.get("counter");
console.log(counter);
```
```txt
1
```
The Redis client automatically handles connections in the background. No need to manually connect or disconnect for basic operations.
</Step>
</Steps>

View File

@@ -0,0 +1,77 @@
---
title: Build a frontend using Vite and Bun
sidebarTitle: "Vite with Bun"
mode: center
---
<Note>
You can use Vite with Bun, but many projects get faster builds & drop hundreds of dependencies by switching to [HTML
imports](/docs/bundler/html-static).
</Note>
---
Vite works out of the box with Bun. Get started with one of Vite's templates.
```bash terminal icon="terminal"
bun create vite my-app
```
```txt
✔ Select a framework: React
✔ Select a variant: TypeScript + SWC
Scaffolding project in /path/to/my-app...
```
---
Then `cd` into the project directory and install dependencies.
```bash terminal icon="terminal"
cd my-app
bun install
```
---
Start the development server with the `vite` CLI using `bunx`.
The `--bun` flag tells Bun to run Vite's CLI using `bun` instead of `node`; by default Bun respects Vite's `#!/usr/bin/env node` [shebang line](<https://en.wikipedia.org/wiki/Shebang_(Unix)>).
```bash terminal icon="terminal"
bunx --bun vite
```
---
To simplify this command, update the `"dev"` script in `package.json` to the following.
```json package.json icon="file-json"
"scripts": {
"dev": "vite", // [!code --]
"dev": "bunx --bun vite", // [!code ++]
"build": "vite build",
"serve": "vite preview"
},
// ...
```
---
Now you can start the development server with `bun run dev`.
```bash terminal icon="terminal"
bun run dev
```
---
The following command will build your app for production.
```sh terminal icon="terminal"
bunx --bun vite build
```
---
This is a stripped down guide to get you started with Vite + Bun. For more information, see the [Vite documentation](https://vite.dev/guide/).

View File

@@ -0,0 +1,71 @@
---
title: Extract links from a webpage using HTMLRewriter
sidebarTitle: Extract links using HTMLRewriter
mode: center
---
## Extract links from a webpage
Bun's [HTMLRewriter](/docs/runtime/html-rewriter) API can be used to efficiently extract links from HTML content. It works by chaining together CSS selectors to match the elements, text, and attributes you want to process. This is a simple example of how to extract links from a webpage. You can pass `.transform` a `Response`, `Blob`, or `string`.
```ts extract-links.ts icon="/icons/typescript.svg"
async function extractLinks(url: string) {
const links = new Set<string>();
const response = await fetch(url);
const rewriter = new HTMLRewriter().on("a[href]", {
element(el) {
const href = el.getAttribute("href");
if (href) {
links.add(href);
}
},
});
// Wait for the response to be processed
await rewriter.transform(response).blob();
console.log([...links]); // ["https://bun.com", "/docs", ...]
}
// Extract all links from the Bun website
await extractLinks("https://bun.com");
```
---
## Convert relative URLs to absolute
When scraping websites, you often want to convert relative URLs (like `/docs`) to absolute URLs. Here's how to handle URL resolution:
{/* prettier-ignore */}
```ts extract-links.ts icon="/icons/typescript.svg"
async function extractLinksFromURL(url: string) {
const response = await fetch(url);
const links = new Set<string>();
const rewriter = new HTMLRewriter().on("a[href]", {
element(el) {
const href = el.getAttribute("href");
if (href) {
// Convert relative URLs to absolute // [!code ++]
try { // [!code ++]
const absoluteURL = new URL(href, url).href; // [!code ++]
links.add(absoluteURL);
} catch { // [!code ++]
links.add(href); // [!code ++]
} // [!code ++]
}
},
});
// Wait for the response to be processed
await rewriter.transform(response).blob();
return [...links];
}
const websiteLinks = await extractLinksFromURL("https://example.com");
```
---
See [Docs > API > HTMLRewriter](/docs/runtime/html-rewriter) for complete documentation on HTML transformation with Bun.

View File

@@ -0,0 +1,97 @@
---
title: Extract social share images and Open Graph tags
sidebarTitle: OpenGraph tags
mode: center
---
## Extract social share images and Open Graph tags
Bun's [HTMLRewriter](/docs/runtime/html-rewriter) API can be used to efficiently extract social share images and Open Graph metadata from HTML content. This is particularly useful for building link preview features, social media cards, or web scrapers. We can use HTMLRewriter to match CSS selectors to HTML elements, text, and attributes we want to process.
```ts extract-social-meta.ts icon="/icons/typescript.svg"
interface SocialMetadata {
title?: string;
description?: string;
image?: string;
url?: string;
siteName?: string;
type?: string;
}
async function extractSocialMetadata(url: string): Promise<SocialMetadata> {
const metadata: SocialMetadata = {};
const response = await fetch(url);
const rewriter = new HTMLRewriter()
// Extract Open Graph meta tags
.on('meta[property^="og:"]', {
element(el) {
const property = el.getAttribute("property");
const content = el.getAttribute("content");
if (property && content) {
// Convert "og:image" to "image" etc.
const key = property.replace("og:", "") as keyof SocialMetadata;
metadata[key] = content;
}
},
})
// Extract Twitter Card meta tags as fallback
.on('meta[name^="twitter:"]', {
element(el) {
const name = el.getAttribute("name");
const content = el.getAttribute("content");
if (name && content) {
const key = name.replace("twitter:", "") as keyof SocialMetadata;
// Only use Twitter Card data if we don't have OG data
if (!metadata[key]) {
metadata[key] = content;
}
}
},
})
// Fallback to regular meta tags
.on('meta[name="description"]', {
element(el) {
const content = el.getAttribute("content");
if (content && !metadata.description) {
metadata.description = content;
}
},
})
// Fallback to title tag
.on("title", {
text(text) {
if (!metadata.title) {
metadata.title = text.text;
}
},
});
// Process the response
await rewriter.transform(response).blob();
// Convert relative image URLs to absolute
if (metadata.image && !metadata.image.startsWith("http")) {
try {
metadata.image = new URL(metadata.image, url).href;
} catch {
// Keep the original URL if parsing fails
}
}
return metadata;
}
```
```ts Example Usage icon="/icons/typescript.svg"
// Example usage
const metadata = await extractSocialMetadata("https://bun.com");
console.log(metadata);
// {
// title: "Bun — A fast all-in-one JavaScript runtime",
// description: "Bundle, transpile, install and run JavaScript & TypeScript projects — all in Bun. Bun is a fast all-in-one JavaScript runtime & toolkit designed for speed, complete with a bundler, test runner, and Node.js-compatible package manager.",
// image: "https://bun.com/share.jpg",
// type: "website",
// ...
// }
```

View File

@@ -0,0 +1,69 @@
---
title: Start a cluster of HTTP servers
description: Run multiple HTTP servers concurrently via the "reusePort" option to share the same port across multiple processes
sidebarTitle: Start a cluster of HTTP servers
mode: center
---
To run multiple HTTP servers concurrently, use the `reusePort` option in `Bun.serve()` which shares the same port across multiple processes.
This automatically load balances incoming requests across multiple instances of Bun.
```ts server.ts icon="/icons/typescript.svg"
import { serve } from "bun";
const id = Math.random().toString(36).slice(2);
serve({
port: process.env.PORT || 8080,
development: false,
// Share the same port across multiple processes
// This is the important part!
reusePort: true,
async fetch(request) {
return new Response("Hello from Bun #" + id + "!\n");
},
});
```
---
<Note>
**Linux only** &mdash; Windows and macOS ignore the `reusePort` option. This is an operating system limitation with
`SO_REUSEPORT`, unfortunately.
</Note>
After saving the file, start your servers on the same port.
Under the hood, this uses the Linux `SO_REUSEPORT` and `SO_REUSEADDR` socket options to ensure fair load balancing across multiple processes. [Learn more about `SO_REUSEPORT` and `SO_REUSEADDR`](https://lwn.net/Articles/542629/)
```ts cluster.ts icon="/icons/typescript.svg"
import { spawn } from "bun";
const cpus = navigator.hardwareConcurrency; // Number of CPU cores
const buns = new Array(cpus);
for (let i = 0; i < cpus; i++) {
buns[i] = spawn({
cmd: ["bun", "./server.ts"],
stdout: "inherit",
stderr: "inherit",
stdin: "inherit",
});
}
function kill() {
for (const bun of buns) {
bun.kill();
}
}
process.on("SIGINT", kill);
process.on("exit", kill);
```
---
Bun has also implemented the `node:cluster` module, but this is a faster, simple, and limited alternative.

View File

@@ -0,0 +1,35 @@
---
title: fetch with unix domain sockets in Bun
sidebarTitle: Fetch with unix domain sockets
mode: center
---
In Bun, the `unix` option in `fetch()` lets you send HTTP requests over a [unix domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket).
```ts fetch-unix.ts icon="/icons/typescript.svg"
const unix = "/var/run/docker.sock";
const response = await fetch("http://localhost/info", { unix });
const body = await response.json();
console.log(body); // { ... }
```
---
The `unix` option is a string that specifies the local file path to a unix domain socket. The `fetch()` function will use the socket to send the request to the server instead of using a TCP network connection. `https` is also supported by using the `https://` protocol in the URL instead of `http://`.
To send a `POST` request to an API endpoint over a unix domain socket:
```ts fetch-unix.ts icon="/icons/typescript.svg"
const response = await fetch("https://hostname/a/path", {
unix: "/var/run/path/to/unix.sock",
method: "POST",
body: JSON.stringify({ message: "Hello from Bun!" }),
headers: {
"Content-Type": "application/json",
},
});
const body = await response.json();
```

View File

@@ -0,0 +1,26 @@
---
title: Send an HTTP request using fetch
sidebarTitle: Fetch with Bun
mode: center
---
Bun implements the Web-standard [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) API for sending HTTP requests. To send a simple `GET` request to a URL:
```ts fetch.ts icon="/icons/typescript.svg"
const response = await fetch("https://bun.com");
const html = await response.text(); // HTML string
```
---
To send a `POST` request to an API endpoint.
```ts fetch.ts icon="/icons/typescript.svg"
const response = await fetch("https://bun.com/api", {
method: "POST",
body: JSON.stringify({ message: "Hello from Bun!" }),
headers: { "Content-Type": "application/json" },
});
const body = await response.json();
```

View File

@@ -0,0 +1,97 @@
---
title: Upload files via HTTP using FormData
sidebarTitle: Upload files via HTTP using FormData
mode: center
---
To upload files via HTTP with Bun, use the [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) API. Let's start with a HTTP server that serves a simple HTML web form.
```ts index.ts icon="/icons/typescript.svg"
const server = Bun.serve({
port: 4000,
async fetch(req) {
const url = new URL(req.url);
// return index.html for root path
if (url.pathname === "/")
return new Response(Bun.file("index.html"), {
headers: {
"Content-Type": "text/html",
},
});
return new Response("Not Found", { status: 404 });
},
});
console.log(`Listening on http://localhost:${server.port}`);
```
---
We can define our HTML form in another file, `index.html`.
```html index.html icon="file-code"
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Form</title>
</head>
<body>
<form action="/action" method="post" enctype="multipart/form-data">
<input type="text" name="name" placeholder="Name" />
<input type="file" name="profilePicture" />
<input type="submit" value="Submit" />
</form>
</body>
</html>
```
---
At this point, we can run the server and visit [`localhost:4000`](http://localhost:4000) to see our form.
```bash
bun run index.ts
Listening on http://localhost:4000
```
---
Our form will send a `POST` request to the `/action` endpoint with the form data. Let's handle that request in our server.
First we use the [`.formData()`](https://developer.mozilla.org/en-US/docs/Web/API/Request/formData) method on the incoming `Request` to asynchronously parse its contents to a `FormData` instance. Then we can use the [`.get()`](https://developer.mozilla.org/en-US/docs/Web/API/FormData/get) method to extract the value of the `name` and `profilePicture` fields. Here `name` corresponds to a `string` and `profilePicture` is a `Blob`.
Finally, we write the `Blob` to disk using [`Bun.write()`](/docs/runtime/file-io#writing-files-bun-write).
{/* prettier-ignore */}
```ts index.ts icon="/icons/typescript.svg"
const server = Bun.serve({
port: 4000,
async fetch(req) {
const url = new URL(req.url);
// return index.html for root path
if (url.pathname === "/")
return new Response(Bun.file("index.html"), {
headers: {
"Content-Type": "text/html",
},
});
// parse formdata at /action // [!code ++]
if (url.pathname === "/action") { // [!code ++]
const formdata = await req.formData(); // [!code ++]
const name = formdata.get("name"); // [!code ++]
const profilePicture = formdata.get("profilePicture"); // [!code ++]
if (!profilePicture) throw new Error("Must upload a profile picture."); // [!code ++]
// write profilePicture to disk // [!code ++]
await Bun.write("profilePicture.png", profilePicture); // [!code ++]
return new Response("Success"); // [!code ++]
} // [!code ++]
return new Response("Not Found", { status: 404 });
},
});
```

View File

@@ -0,0 +1,28 @@
---
title: Hot reload an HTTP server
sidebarTitle: Hot reload an HTTP server
mode: center
---
Bun supports the [`--hot`](/docs/runtime/watch-mode#hot-mode) flag to run a file with hot reloading enabled. When any module or file changes, Bun re-runs the file.
```sh terminal icon="terminal"
bun --hot run index.ts
```
---
Bun detects when you are running an HTTP server with `Bun.serve()`. It reloads your fetch handler when source files change, _without_ restarting the `bun` process. This makes hot reloads nearly instantaneous.
<Note>
Note that this doesn't reload the page on your browser.
</Note>
```ts index.ts icon="/icons/typescript.svg"
Bun.serve({
port: 3000,
fetch(req) {
return new Response("Hello world");
},
});
```

View File

@@ -0,0 +1,50 @@
---
title: Proxy HTTP requests using fetch()
sidebarTitle: Proxy HTTP requests using fetch()
mode: center
---
In Bun, `fetch` supports sending requests through an HTTP or HTTPS proxy. This is useful on corporate networks or when you need to ensure a request is sent through a specific IP address.
```ts proxy.ts icon="/icons/typescript.svg"
await fetch("https://example.com", {
// The URL of the proxy server
proxy: "https://username:password@proxy.example.com:8080",
});
```
---
The `proxy` option can be a URL string or an object with `url` and optional `headers`. The URL can include the username and password if the proxy requires authentication. It can be `http://` or `https://`.
---
## Custom proxy headers
To send custom headers to the proxy server (useful for proxy authentication tokens, custom routing, etc.), use the object format:
```ts proxy-headers.ts icon="/icons/typescript.svg"
await fetch("https://example.com", {
proxy: {
url: "https://proxy.example.com:8080",
headers: {
"Proxy-Authorization": "Bearer my-token",
"X-Proxy-Region": "us-east-1",
},
},
});
```
The `headers` property accepts a plain object or a `Headers` instance. These headers are sent directly to the proxy server in `CONNECT` requests (for HTTPS targets) or in the proxy request (for HTTP targets).
If you provide a `Proxy-Authorization` header, it will override any credentials specified in the proxy URL.
---
## Environment variables
You can also set the `$HTTP_PROXY` or `$HTTPS_PROXY` environment variable to the proxy URL. This is useful when you want to use the same proxy for all requests.
```sh terminal icon="terminal"
HTTPS_PROXY=https://username:password@proxy.example.com:8080 bun run index.ts
```

View File

@@ -0,0 +1,48 @@
---
title: Common HTTP server usage
sidebarTitle: HTTP Server with Bun
mode: center
---
This starts an HTTP server listening on port `3000`. It demonstrates basic routing with a number of common responses and also handles POST data from standard forms or as JSON.
See [`Bun.serve`](/docs/runtime/http/server) for details.
```ts server.ts icon="/icons/typescript.svg"
const server = Bun.serve({
async fetch(req) {
const path = new URL(req.url).pathname;
// respond with text/html
if (path === "/") return new Response("Welcome to Bun!");
// redirect
if (path === "/abc") return Response.redirect("/source", 301);
// send back a file (in this case, *this* file)
if (path === "/source") return new Response(Bun.file(import.meta.path));
// respond with JSON
if (path === "/api") return Response.json({ some: "buns", for: "you" });
// receive JSON data to a POST request
if (req.method === "POST" && path === "/api/post") {
const data = await req.json();
console.log("Received JSON:", data);
return Response.json({ success: true, data });
}
// receive POST data from a form
if (req.method === "POST" && path === "/form") {
const data = await req.formData();
console.log(data.get("someField"));
return new Response("Success");
}
// 404s
return new Response("Page not found", { status: 404 });
},
});
console.log(`Listening on ${server.url}`);
```

View File

@@ -0,0 +1,20 @@
---
title: Write a simple HTTP server
sidebarTitle: Simple HTTP Server with Bun
mode: center
---
This starts an HTTP server listening on port `3000`. It responds to all requests with a `Response` with status `200` and body `"Welcome to Bun!"`.
See [`Bun.serve`](/docs/runtime/http/server) for details.
```ts server.ts icon="/icons/typescript.svg"
const server = Bun.serve({
port: 3000,
fetch(request) {
return new Response("Welcome to Bun!");
},
});
console.log(`Listening on ${server.url}`);
```

View File

@@ -0,0 +1,91 @@
---
title: Server-Sent Events (SSE) with Bun
sidebarTitle: Server-Sent Events
mode: center
---
[Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) let you push a stream of text events to the browser over a single HTTP response. The client consumes them via [`EventSource`](https://developer.mozilla.org/en-US/docs/Web/API/EventSource).
In Bun, you can implement an SSE endpoint by returning a `Response` whose body is a streaming source and setting the `Content-Type` header to `text/event-stream`.
<Note>
`Bun.serve` closes idle connections after **10 seconds** by default. A quiet SSE stream counts as idle, so the
examples below call `server.timeout(req, 0)` to disable the timeout for the stream. See
[`idleTimeout`](/docs/runtime/http/server#idletimeout) for details.
</Note>
## Using an async generator
In Bun, `new Response` accepts an async generator function directly. This is usually the simplest way to write an SSE endpoint — each `yield` flushes a chunk to the client, and if the client disconnects, the generator's `finally` block runs so you can clean up.
```ts server.ts icon="/icons/typescript.svg"
Bun.serve({
port: 3000,
routes: {
"/events": (req, server) => {
// SSE streams are often quiet between events. By default,
// Bun.serve closes connections after 10 seconds of inactivity.
// Disable the idle timeout for this request so the stream
// stays open indefinitely.
server.timeout(req, 0);
return new Response(
async function* () {
yield `data: connected at ${Date.now()}\n\n`;
// Emit a tick every 5 seconds until the client disconnects.
// When the client goes away, the generator is returned
// (cancelled) and this loop stops automatically.
while (true) {
await Bun.sleep(5000);
yield `data: tick ${Date.now()}\n\n`;
}
},
{
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
},
},
);
},
},
});
```
## Using a `ReadableStream`
If your events originate from callbacks — message brokers, timers, external pushes — rather than a linear `await` flow, a `ReadableStream` often fits better. When the client disconnects, Bun calls the stream's `cancel()` method automatically, so you can release any resources you set up in `start()`.
```ts server.ts icon="/icons/typescript.svg"
Bun.serve({
port: 3000,
routes: {
"/events": (req, server) => {
server.timeout(req, 0);
let timer: Timer;
const stream = new ReadableStream({
start(controller) {
controller.enqueue(`data: connected at ${Date.now()}\n\n`);
timer = setInterval(() => {
controller.enqueue(`data: tick ${Date.now()}\n\n`);
}, 5000);
},
cancel() {
// Called automatically when the client disconnects.
clearInterval(timer);
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
},
});
},
},
});
```

View File

@@ -0,0 +1,50 @@
---
title: Stream a file as an HTTP Response
sidebarTitle: Stream file response
mode: center
---
This snippet reads a file from disk using [`Bun.file()`](/docs/runtime/file-io#reading-files-bun-file). This returns a `BunFile` instance, which can be passed directly into the `new Response` constructor.
```ts server.ts icon="/icons/typescript.svg"
const path = "/path/to/file.txt";
const file = Bun.file(path);
const resp = new Response(file);
```
---
The `Content-Type` is read from the file and automatically set on the `Response`.
```ts server.ts icon="/icons/typescript.svg"
new Response(Bun.file("./package.json")).headers.get("Content-Type");
// => application/json;charset=utf-8
new Response(Bun.file("./test.txt")).headers.get("Content-Type");
// => text/plain;charset=utf-8
new Response(Bun.file("./index.tsx")).headers.get("Content-Type");
// => text/javascript;charset=utf-8
new Response(Bun.file("./img.png")).headers.get("Content-Type");
// => image/png
```
---
Putting it all together with [`Bun.serve()`](/docs/runtime/http/server).
```ts server.ts icon="/icons/typescript.svg"
// static file server
Bun.serve({
async fetch(req) {
const path = new URL(req.url).pathname;
const file = Bun.file(path);
return new Response(file);
},
});
```
---
See [Docs > API > File I/O](/docs/runtime/file-io#writing-files-bun-write) for complete documentation of `Bun.write()`.

View File

@@ -0,0 +1,49 @@
---
title: Streaming HTTP Server with Async Iterators
sidebarTitle: Stream with iterators
mode: center
---
In Bun, [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) objects can accept an async generator function as their body. This allows you to stream data to the client as it becomes available, rather than waiting for the entire response to be ready.
```ts stream-iterator.ts icon="/icons/typescript.svg"
Bun.serve({
port: 3000,
fetch(req) {
return new Response(
// An async generator function
async function* () {
yield "Hello, ";
await Bun.sleep(100);
yield "world!";
// you can also yield a TypedArray or Buffer
yield new Uint8Array(["\n".charCodeAt(0)]);
},
{ headers: { "Content-Type": "text/plain" } },
);
},
});
```
---
You can pass any async iterable directly to `Response`:
```ts stream-iterator.ts icon="/icons/typescript.svg"
Bun.serve({
port: 3000,
fetch(req) {
return new Response(
{
[Symbol.asyncIterator]: async function* () {
yield "Hello, ";
await Bun.sleep(100);
yield "world!";
},
},
{ headers: { "Content-Type": "text/plain" } },
);
},
});
```

View File

@@ -0,0 +1,22 @@
---
title: Streaming HTTP Server with Node.js Streams
sidebarTitle: Stream with Node.js
mode: center
---
In Bun, [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) objects can accept a Node.js [`Readable`](https://nodejs.org/api/stream.html#stream_readable_streams).
This works because Bun's `Response` object allows any async iterable as its body. Node.js streams are async iterables, so you can pass them directly to `Response`.
```ts server.ts icon="/icons/typescript.svg"
import { Readable } from "stream";
import { serve } from "bun";
serve({
port: 3000,
fetch(req) {
return new Response(Readable.from(["Hello, ", "world!"]), {
headers: { "Content-Type": "text/plain" },
});
},
});
```

View File

@@ -0,0 +1,32 @@
---
title: Configure TLS on an HTTP server
sidebarTitle: Configure TLS
mode: center
---
Set the `tls` key to configure TLS. Both `key` and `cert` are required. The `key` should be the contents of your private key; `cert` should be the contents of your issued certificate. Use [`Bun.file()`](/docs/runtime/file-io#reading-files-bun-file) to read the contents.
```ts server.ts icon="/icons/typescript.svg"
const server = Bun.serve({
fetch: request => new Response("Welcome to Bun!"),
tls: {
cert: Bun.file("cert.pem"),
key: Bun.file("key.pem"),
},
});
```
---
By default Bun trusts the default Mozilla-curated list of well-known root CAs. To override this list, pass an array of certificates as `ca`.
```ts server.ts icon="/icons/typescript.svg"
const server = Bun.serve({
fetch: request => new Response("Welcome to Bun!"),
tls: {
cert: Bun.file("cert.pem"),
key: Bun.file("key.pem"),
ca: [Bun.file("ca1.pem"), Bun.file("ca2.pem")],
},
});
```

View File

@@ -0,0 +1,10 @@
---
title: Guides
description: A collection of code samples and walkthroughs for performing common tasks with Bun.
contextual: false
mode: center
---
import { GuidesList } from "/snippets/guides.jsx";
<GuidesList />

View File

@@ -0,0 +1,28 @@
---
title: Add a development dependency
sidebarTitle: Add a dev dependency
mode: center
---
To add an npm package as a development dependency, use `bun add --development`.
```sh terminal icon="terminal"
bun add zod --dev
bun add zod -d # shorthand
```
---
This will add the package to `devDependencies` in `package.json`.
```json
{
"devDependencies": {
"zod": "^3.0.0" // [!code ++]
}
}
```
---
See [Docs > Package manager](/docs/pm/cli/install) for complete documentation of Bun's package manager.

View File

@@ -0,0 +1,40 @@
---
title: Add a Git dependency
sidebarTitle: Add a Git dependency
mode: center
---
Bun supports directly adding GitHub repositories as dependencies of your project.
```sh terminal icon="terminal"
bun add github:lodash/lodash
```
---
This will add the following line to your `package.json`:
```json package.json icon="file-json"
{
"dependencies": {
"lodash": "github:lodash/lodash"
}
}
```
---
Bun supports a number of protocols for specifying Git dependencies.
```sh terminal icon="terminal"
bun add git+https://github.com/lodash/lodash.git
bun add git+ssh://github.com/lodash/lodash.git#4.17.21
bun add git@github.com:lodash/lodash.git
bun add github:colinhacks/zod
```
**Note:** GitHub dependencies download via HTTP tarball when possible for faster installation.
---
See [Docs > Package manager](/docs/pm/cli/install) for complete documentation of Bun's package manager.

View File

@@ -0,0 +1,27 @@
---
title: Add an optional dependency
sidebarTitle: Add an optional dependency
mode: center
---
To add an npm package as an optional dependency, use the `--optional` flag.
```sh terminal icon="terminal"
bun add zod --optional
```
---
This will add the package to `optionalDependencies` in `package.json`.
```json package.json icon="file-json"
{
"optionalDependencies": {
"zod": "^3.0.0" // [!code ++]
}
}
```
---
See [Docs > Package manager](/docs/pm/cli/install) for complete documentation of Bun's package manager.

View File

@@ -0,0 +1,45 @@
---
title: Add a peer dependency
sidebarTitle: Add a peer dependency
mode: center
---
To add an npm package as a peer dependency, use the `--peer` flag.
```sh terminal icon="terminal"
bun add @types/bun --peer
```
---
This will add the package to `peerDependencies` in `package.json`.
```json package.json icon="file-json"
{
"peerDependencies": {
"@types/bun": "^1.3.3" // [!code ++]
}
}
```
---
Running `bun install` will install peer dependencies by default, unless marked optional in `peerDependenciesMeta`.
{/* prettier-ignore */}
```json package.json icon="file-json"
{
"peerDependencies": {
"@types/bun": "^1.3.3"
},
"peerDependenciesMeta": {
"@types/bun": { // [!code ++]
"optional": true // [!code ++]
} // [!code ++]
}
}
```
---
See [Docs > Package manager](/docs/pm/cli/install) for complete documentation of Bun's package manager.

View File

@@ -0,0 +1,35 @@
---
title: Add a tarball dependency
sidebarTitle: Add a tarball dependency
mode: center
---
Bun's package manager can install any publicly available tarball URL as a dependency of your project.
```sh terminal icon="terminal"
bun add zod@https://registry.npmjs.org/zod/-/zod-3.21.4.tgz
```
---
Running this command will download, extract, and install the tarball to your project's `node_modules` directory. It will also add the following line to your `package.json`:
```json package.json icon="file-json"
{
"dependencies": {
"zod": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz" // [!code ++]
}
}
```
---
The package `"zod"` can now be imported as usual.
```ts
import { z } from "zod";
```
---
See [Docs > Package manager](/docs/pm/cli/install) for complete documentation of Bun's package manager.

View File

@@ -0,0 +1,44 @@
---
title: Add a dependency
sidebarTitle: Add a dependency
mode: center
---
To add an npm package as a dependency, use `bun add`.
```sh terminal icon="terminal"
bun add zod
```
---
This will add the package to `dependencies` in `package.json`. By default, the `^` range specifier will be used, to indicate that any future minor or patch versions are acceptable.
```json package.json icon="file-json"
{
"dependencies": {
"zod": "^3.0.0" // [!code ++]
}
}
```
---
To "pin" to an exact version of the package, use `--exact`. This will add the package to `dependencies` without the `^`, pinning your project to the exact version you installed.
```sh terminal icon="terminal"
bun add zod --exact
```
---
To specify an exact version or a tag:
```sh terminal icon="terminal"
bun add zod@3.0.0
bun add zod@next
```
---
See [Docs > Package manager](/docs/pm/cli/install) for complete documentation of Bun's package manager.

Some files were not shown because too many files have changed in this diff Show More