initial commit

This commit is contained in:
i2p
2026-08-27 21:09:14 +00:00
commit a5b6d59437
12681 changed files with 3253832 additions and 0 deletions
+76
View File
@@ -0,0 +1,76 @@
---
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 installs dependencies with `bun`.
```sh terminal icon="terminal"
bun create astro
```
```txt
astro Launch sequence initiated.
╭─────╮ Houston:
│ ◠ ◡ ◠ We're glad to have you on board.
╰─────╯
dir Where should we create your new project?
./fumbling-field
tmpl How would you like to start your new project?
Use blog template
deps Install dependencies?
Yes
git Initialize a new git repository?
Yes
✔ Project initialized!
■ Template copied
■ Dependencies installed
■ 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. q + ENTER 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 runs the dev server with Node.js. To use the Bun runtime instead, pass the `--bun` flag.
```sh terminal icon="terminal"
bunx --bun astro dev
```
```txt
astro v7.2.2 ready in 200 ms
┃ Local http://localhost:4321/
┃ Network use --host to expose
```
---
Open [http://localhost:4321](http://localhost:4321) in your browser to see the result. Astro hot-reloads the app as you edit your source files.
<Frame>
<img src="https://i.imgur.com/Dswiu6w.png" caption="An Astro starter app running on Bun" />
</Frame>
---
See the [Astro docs](https://docs.astro.build/en/getting-started/).
+162
View File
@@ -0,0 +1,162 @@
---
title: Create a Discord bot
sidebarTitle: "Discord.js with Bun"
mode: center
---
Discord.js runs on Bun with no extra setup. This guide builds a bot that answers a `/ping` slash command: you register the command once, then start the bot and use it in your server. If this is your first bot, copy each block as you reach it.
---
Create a folder for your bot and set it up with `bun init`. Pick the defaults when it asks.
```sh terminal icon="terminal"
mkdir my-bot
cd my-bot
bun init
```
---
Add Discord.js to the project.
```sh terminal icon="terminal"
bun add discord.js
```
---
Your bot needs its own account, which you create in Discord's developer portal. Open the [developer portal](https://discord.com/developers/applications), sign in, and create an **Application**. Discord adds a bot user to every new application automatically; it's on the **Bot** tab. The Discord.js guide's [setup walkthrough](https://discordjs.guide/legacy/preparations/app-setup) has screenshots if you get lost.
Copy two values from the portal:
- The **token** on the **Bot** tab: click **Reset Token** to generate it (Discord only shows it once). It's the password your code uses to log in, so treat it like one and keep it to yourself.
- The **Application ID** on the **General Information** tab. Discord uses it to tie your commands to this app.
---
A bot can't do anything in a server until you invite it. In the portal's **OAuth2** section, generate an invite URL with the `bot` and `applications.commands` scopes, open it, and add the bot to a server you manage. Slash commands need the `applications.commands` scope, so include it. A personal server you make for testing is a good place to start, and the Discord.js [guide to adding a bot](https://discordjs.guide/legacy/preparations/adding-your-app) walks through it with screenshots.
---
You also need your server's ID to register the command there. In Discord, turn on **Settings > Advanced > Developer Mode**, then right-click your server's icon and choose **Copy Server ID**.
---
Save all three values in `.env.local`. Bun reads this file on startup and loads it into `process.env`, so nothing secret lives in your code.
```ini .env.local icon="settings"
DISCORD_TOKEN=your-bot-token
DISCORD_CLIENT_ID=your-application-id
DISCORD_GUILD_ID=your-server-id
```
---
Add `.env.local` to your `.gitignore` before you commit anything. Anyone who reads the token can control your bot, so the token should never land in version control.
```txt .gitignore icon="file-code"
node_modules
.env.local
```
---
Discord has to know about a command before anyone can use it. Register `/ping` with a short script named `deploy-commands.ts`.
```ts deploy-commands.ts icon="/icons/typescript.svg"
import { REST, Routes, SlashCommandBuilder } from "discord.js";
const { DISCORD_TOKEN, DISCORD_CLIENT_ID, DISCORD_GUILD_ID } = process.env;
if (!DISCORD_TOKEN || !DISCORD_CLIENT_ID || !DISCORD_GUILD_ID) {
throw new Error("Set DISCORD_TOKEN, DISCORD_CLIENT_ID, and DISCORD_GUILD_ID in .env.local");
}
// the commands you want to register
const commands = [new SlashCommandBuilder().setName("ping").setDescription("Replies with Pong!").toJSON()];
const rest = new REST().setToken(DISCORD_TOKEN);
// register them in your test server
await rest.put(Routes.applicationGuildCommands(DISCORD_CLIENT_ID, DISCORD_GUILD_ID), { body: commands });
console.log("Registered /ping");
```
Run it once.
```sh terminal icon="terminal"
bun run deploy-commands.ts
```
You only run this again when you add a command or change its name or description, not every time the bot starts. Registering to your server instead of globally keeps the command scoped to where you're testing.
---
Now the bot itself. Save it as `bot.ts`.
```ts bot.ts icon="/icons/typescript.svg"
import { Client, Events, GatewayIntentBits } from "discord.js";
const { DISCORD_TOKEN } = process.env;
if (!DISCORD_TOKEN) {
throw new Error("Set DISCORD_TOKEN in .env.local");
}
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
// runs once, right after the bot connects
client.once(Events.ClientReady, readyClient => {
console.log(`Logged in as ${readyClient.user.tag}`);
});
// runs every time someone uses a slash command
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === "ping") {
await interaction.reply("Pong!");
}
});
client.login(DISCORD_TOKEN);
```
The ready handler logs a line once the bot connects. After that, `interactionCreate` runs whenever someone uses a slash command; it confirms the command was `/ping` and replies with `Pong!`.
---
Start the bot with `bun run`.
```sh terminal icon="terminal"
bun run bot.ts
```
The first connection takes a few seconds. Once the login line prints, switch to Discord and type `/ping` in your server.
```txt
Logged in as my-bot#1234
```
The bot replies with `Pong!`. You've got a working Discord bot.
---
To add another command, define it in `deploy-commands.ts`, run that script again, and add an `if` branch for its name in `bot.ts`. The [Discord.js docs](https://discord.js.org/docs) cover command options, permissions, buttons, embeds, and the rest of the API.
---
When you deploy, there's no build or bundling step. Bun runs `bot.ts` and every file it imports directly, so you ship your source as-is and start it with the same `bun run bot.ts` you use while developing.
`deploy-commands.ts` registers `/ping` in your test server, which is the right scope while you're building. To publish the bot to every server it joins, register globally instead: change the route to `Routes.applicationCommands(DISCORD_CLIENT_ID)`. Global registration doesn't use a server, so you can also drop `DISCORD_GUILD_ID` from the script's check and from `.env.local`.
To keep the bot online and bring it back after a crash or reboot, run it under a process manager.
<Columns cols={2}>
<Card title="systemd" href="/guides/ecosystem/systemd" icon="server">
Run your bot as a Linux daemon
</Card>
<Card title="PM2" href="/guides/ecosystem/pm2" icon="cog">
Manage your bot with PM2
</Card>
</Columns>
+149
View File
@@ -0,0 +1,149 @@
---
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_ the application, define a `Dockerfile`. It lists 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
# 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" ]
```
---
Next, add a `.dockerignore` file. It uses a syntax similar to `.gitignore` and lists the files and directories to exclude from every stage of the Docker build. For example:
```txt .dockerignore icon="docker"
node_modules
Dockerfile*
docker-compose*
.dockerignore
.git
.gitignore
README.md
LICENSE
.vscode
Makefile
helm-charts
.env
.editorconfig
.idea
coverage*
```
---
Run `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 names the image, and `--pull` tells Docker to download the latest version of the base image (`oven/bun`). The initial build takes longer, since Docker downloads 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
```
---
Now start a running _container_ from the `bun-hello-world` image with `docker run`. The `-d` flag runs it in _detached_ mode, and `-p 3000:3000` maps the container's port 3000 to port 3000 on your machine.
The `run` command prints 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 your application's response.
---
To stop the container, run `docker stop <container-id>`.
```sh terminal icon="terminal"
docker stop 7f03e212a15ede8644379bce11a13589f563d3909a9640446c5bbefce993678d
```
---
If you can't find the container ID, `docker ps` lists 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
```
---
See the [Docker documentation](https://docs.docker.com/) for more advanced usage.
+195
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.
---
Create a fresh project with `bun init` and install Drizzle.
```sh terminal icon="terminal"
bun init -y
bun add drizzle-orm
bun add -D drizzle-kit
```
---
Then connect to a SQLite database with 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({ client: 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.all<{ text: string }>(query);
console.log(result);
```
---
Then run `index.ts` with Bun. Bun creates `sqlite.db` and executes the query.
```sh terminal icon="terminal"
bun run index.ts
```
```txt
[
{
text: "hello world",
}
]
```
---
Now give the database a 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"),
});
```
---
Generate an initial SQL migration with the `drizzle-kit` CLI.
```sh terminal icon="terminal"
bunx drizzle-kit generate --dialect sqlite --schema ./schema.ts
```
---
The command creates a `drizzle` directory containing a `.sql` migration file and a `meta` directory.
```txt File Tree icon="folder-tree"
drizzle
├── 0000_ordinary_beyonder.sql
└── meta
├── 0000_snapshot.json
└── _journal.json
```
---
Execute these migrations with a `migrate.ts` script. It connects 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({ client: sqlite });
migrate(db, { migrationsFolder: "./drizzle" });
```
---
Run the script with `bun` to execute the migration.
```sh terminal icon="terminal"
bun run migrate.ts
```
---
Now add some data to the 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";
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.
```
---
The database now has a schema and some sample data. Query it with Drizzle by replacing 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 you 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,
}
]
```
---
See the [Drizzle docs](https://orm.drizzle.team/docs/overview).
+31
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 web framework built on 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 an 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 on port ${app.server?.port}...`);
```
---
Elysia is a server framework with Express-like syntax, type inference, middleware, file uploads, and plugins for JWT authentication and OpenAPI documentation. It's one of the [fastest Bun web frameworks](https://github.com/SaltyAom/bun-http-framework-benchmark).
See the Elysia [documentation](https://elysiajs.com/quick-start.html).
+40
View File
@@ -0,0 +1,40 @@
---
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 in Bun without changes. 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>See [Node.js compatibility](/runtime/nodejs-compat#node-http) for details.</Note>
```sh terminal icon="terminal"
bun add express
```
---
To define an 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
```
+262
View File
@@ -0,0 +1,262 @@
---
title: Use Gel with Bun
sidebarTitle: Gel with Bun
mode: center
---
Gel (formerly EdgeDB) is a graph-relational database built on Postgres. It provides a declarative schema language, migrations system, and object-oriented query language. It also supports raw SQL queries. It solves object-relational mapping at the database layer, so your application code doesn't need an ORM library.
---
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-gel-app
cd my-gel-app
bun init -y
```
---
Initialize a Gel instance for the project with the Gel CLI. The `gel project init` command creates a `gel.toml` file in the project root.
```sh terminal icon="terminal"
gel project init
```
```txt
No `gel.toml` (or `edgedb.toml`) found in `/Users/colinmcd94/Documents/bun/fun/examples/my-gel-app` or above
Initializing new project...
Checking Gel versions...
┌─────────────────────┬──────────────────────────────────────────────────────────────────┐
│ 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 │
│ Branch │ main │
└─────────────────────┴──────────────────────────────────────────────────────────────────┘
Version x.y+6d5921b is already downloaded
Initializing Gel instance 'my_gel_app'...
Applying migrations...
Everything is up to date. Revision initial
Writing gel.local.toml for configuration
Project initialized.
To connect to my_gel_app, run `gel`
```
---
To check that the database is running, open a REPL and run a query.
```sh terminal icon="terminal"
gel
my_gel_app:main> select 1 + 1;
```
```txt
{2}
```
Then run `\quit` to exit the REPL.
```sh terminal icon="terminal"
my_gel_app:main> \quit
```
---
Next, define a schema. The `gel project init` command already created a `dbschema/default.gel` file to hold it.
```txt File Tree icon="folder-tree"
dbschema
├── default.gel
├── extensions.gel
├── futures.gel
└── migrations
```
---
Open that file and paste the following contents.
```ts default.gel 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 dbschema/migrations/00001-m1uwekr.edgeql, id: m1uwekrn4ni4qs7ul7hfar4xemm5kkxlpswolcoyqj3xdhweomwjrq
```
```sh terminal icon="terminal"
gel migrate
```
```txt
Applying m1uwekrn4ni4qs7ul7hfar4xemm5kkxlpswolcoyqj3xdhweomwjrq (00001-m1uwekr.edgeql)
... parsed
... applied
```
---
With the schema applied, query the database with Gel's JavaScript client library. Install the client library and Gel's codegen CLI, then 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. The script inserts a few movies with the `.execute()` method, using EdgeQL's `for` expression to turn the bulk insert into a single 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 several code generation tools for TypeScript. To write typesafe queries against the seeded database, generate the EdgeQL query builder with `@gel/generate`.
```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 @gel/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`, import the generated query builder from `./dbschema/edgeql-js` and write a 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 }[]
```
---
Run the file with Bun to see the movies you 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,
}
]
```
---
See the [Gel docs](https://docs.geldata.com/).
+53
View File
@@ -0,0 +1,53 @@
---
title: Build an HTTP server using Hono and Bun
sidebarTitle: Hono with Bun
mode: center
---
[Hono](https://github.com/honojs/hono) is a lightweight 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
create-hono version 0.19.4
✔ Using target directory … myapp
✔ Which template do you want to use? bun
✔ Do you want to install project dependencies? Yes
✔ Which package manager do you want to use? bun
✔ Cloning the template
✔ Installing project dependencies
🎉 Copied project files
Get started with: cd myapp
```
```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 [getting started with Bun](https://hono.dev/docs/getting-started/bun) guide.
+92
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 with Bun with no extra configuration. This guide assumes you've already installed MongoDB and are running it as a background process or service on your development machine. See the [MongoDB installation 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`, declare and export an `Animal` model.
```ts schema.ts icon="/icons/typescript.svg"
import * as mongoose from "mongoose";
const animalSchema = new mongoose.Schema(
{
name: { 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);
```
---
In `index.ts`, import `Animal`, connect to MongoDB, and add some data to the 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({
name: "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();
```
---
Run the file with `bun run`.
```bash terminal icon="terminal"
bun run index.ts
```
```txt
Moo!
```
---
As you build your application, refer to the official [MongoDB](https://www.mongodb.com/docs) and [Mongoose](https://mongoosejs.com/docs/) docs.
+234
View File
@@ -0,0 +1,234 @@
---
title: Use Neon Postgres through Drizzle ORM
sidebarTitle: Neon Drizzle with Bun
mode: center
---
[Neon](https://neon.com/) is a fully managed serverless Postgres. Neon separates compute and storage to offer features like autoscaling, branching and bottomless storage. You can use Neon from Bun directly with 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 it with `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.com/docs/connect/connect-from-any-app) to it.
```ini .env.local icon="settings"
DATABASE_URL=postgresql://username:[email protected]/neondb?sslmode=require
```
---
In `db.ts`, connect to the Neon database with 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({ client: 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",
}
]
```
---
Define a schema for the database with 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(),
name: text("name").notNull(),
bio: text("bio"),
createdAt: timestamp("created_at").notNull().defaultNow(),
});
```
---
Then use the `drizzle-kit` CLI to generate an initial SQL migration.
```sh
bunx drizzle-kit generate --dialect postgresql --schema ./schema.ts --out ./drizzle
```
---
The command creates a `drizzle` directory containing a `.sql` migration file and a `meta` directory.
```txt File Tree icon="folder-tree"
drizzle
├── 0000_aspiring_post.sql
└── meta
├── 0000_snapshot.json
└── _journal.json
```
---
Execute these migrations with a `migrate.ts` script. The script opens 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();
```
---
Run the script with `bun`.
```sh terminal icon="terminal"
bun run migrate.ts
```
```txt
Migration completed
```
---
Now add some data to the 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([
{
name: "J.R.R. Tolkien",
bio: "The creator of Middle-earth and author of The Lord of the Rings.",
},
{
name: "George R.R. Martin",
bio: "The author of the epic fantasy series A Song of Ice and Fire.",
},
{
name: "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
```
---
The database now has a schema and sample data. Query it with Drizzle by replacing 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. It prints the three authors you inserted.
```sh terminal icon="terminal"
bun run index.ts
```
```txt
[
{
id: 1,
name: "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,
name: "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,
name: "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.com/docs/serverless/serverless-driver) for a complete overview.
Refer to the [Drizzle website](https://orm.drizzle.team/docs/overview) for complete documentation.
@@ -0,0 +1,60 @@
---
title: Use Neon's Serverless Postgres with Bun
sidebarTitle: Neon Serverless Postgres with Bun
mode: center
---
[Neon](https://neon.com/) is a fully managed serverless Postgres. Neon separates compute and storage to offer features such as autoscaling, branching, and bottomless storage.
---
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.com/docs/connect/connect-from-any-app) to it.
```ini .env.local icon="settings"
DATABASE_URL=postgresql://username:[email protected]/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 with `bun ./index.ts`. It prints the Postgres version 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.com/docs/serverless/serverless-driver) for a complete overview of the serverless driver.
+101
View File
@@ -0,0 +1,101 @@
---
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, and API routes. Bun installs packages fast and can run Next.js development and production servers.
---
<Steps>
<Step title="Create a new Next.js app">
Use the interactive CLI to scaffold a new Next.js project and install its 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) in your browser to see the result. Changes you make to `app/page.tsx` are hot-reloaded in the browser.
</Step>
<Step title="Update scripts in package.json">
Prefix the Next.js CLI commands in your `package.json` scripts with `bun --bun` so that Bun executes the Next.js CLI for `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
<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 basic 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>
---
Refer to the [Next.js documentation](https://nextjs.org/docs) for more on building and deploying Next.js applications.
+120
View File
@@ -0,0 +1,120 @@
---
title: Build an app with Nuxt and Bun
sidebarTitle: Nuxt with Bun
mode: center
---
Bun supports [Nuxt](https://nuxt.com) with no extra configuration. Initialize a Nuxt app with the official `create-nuxt` CLI.
```sh terminal icon="terminal"
bun create nuxt@latest my-nuxt-app
```
```txt
┌ Welcome to Nuxt!
◇ Templates loaded
◇ Which template would you like to use?
│ minimal Minimal starter with a single app.vue.
◇ Creating project in my-nuxt-app
◇ Downloaded minimal template
◇ Which package manager would you like to use?
│ bun
◇ Initialize git repository?
│ Yes
◇ Dependencies installed
◇ Git repository initialized
◇ Would you like to browse and install modules?
│ No
└ ✨ Nuxt project has been created with the minimal template.
╭── 👉 Next steps ─────╮
│ │
cd my-nuxt-app │
bun run dev │
│ │
╰──────────────────────╯
```
---
To start the dev server, run `bun --bun run dev` from the project root. This executes the `nuxt dev` command 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
● Nuxt 4.5.2 (with Nitro 2.13.4, Vite 8.2.1 and Vue 3.5.41)
➜ Local: http://localhost:3000/
➜ Network: use --host to expose
➜ DevTools: press Shift + Alt + D in the browser (v3.4.1)
✔ Vite client built in 95ms
✔ Vite server built in 33ms
[nitro] ✔ Nuxt Nitro server built in 948ms
Vite server warmed up in 3ms
Vite client warmed up in 9ms
```
---
Once the dev server starts, open [http://localhost:3000](http://localhost:3000) to see the app. It renders Nuxt's built-in `NuxtWelcome` template component.
To start developing your app, replace `<NuxtWelcome />` in `app/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 builds, the default preset is compatible with Bun, but the [Bun preset](https://nitro.build/deploy/runtimes/bun) generates better optimized builds.
```ts nuxt.config.ts icon="/icons/typescript.svg"
export default defineNuxtConfig({
nitro: {
preset: "bun", // [!code ++]
},
});
```
Alternatively, set the preset with an environment variable:
```sh terminal icon="terminal"
NITRO_PRESET=bun bun run build
```
<Note>
Some packages provide Bun-specific exports that Nitro does not bundle correctly with the default preset. Use the Bun
preset so those packages work in production builds.
</Note>
After building, start the server:
```sh terminal icon="terminal"
bun run ./.output/server/index.mjs
```
---
Refer to the [Nuxt website](https://nuxt.com/docs) for complete documentation.
+50
View File
@@ -0,0 +1,50 @@
---
title: Run Bun as a daemon with PM2
sidebarTitle: PM2 with Bun
mode: center
---
[PM2](https://pm2.keymetrics.io/) is a process manager that runs your applications as daemons (background processes).
PM2 offers process monitoring, automatic restarts, and scaling. It keeps your application running when you deploy it to a cloud-hosted virtual private server (VPS).
---
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, run:
```bash terminal icon="terminal"
pm2 start --interpreter ~/.bun/bin/bun index.ts
```
---
### With a configuration file
Alternatively, create a file named `pm2.config.cjs` in your project directory and add the following content.
```js pm2.config.cjs 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, start your application with PM2.
```bash terminal icon="terminal"
pm2 start pm2.config.cjs
```
---
Your JavaScript/TypeScript web server now runs as a daemon with PM2, using Bun as the interpreter.
+164
View File
@@ -0,0 +1,164 @@
---
title: Use Prisma Postgres with Bun
sidebarTitle: Prisma Postgres with Bun
mode: center
---
<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 Postgres driver adapter (`@prisma/adapter-pg`) as dependencies.
```bash terminal icon="terminal"
bun add -d prisma
bun add @prisma/client @prisma/adapter-pg
```
</Step>
<Step title="Initialize Prisma with PostgreSQL">
Use the Prisma CLI with `bunx` to initialize the schema and migration directory, with PostgreSQL as the database.
```bash terminal icon="terminal"
bunx --bun prisma init --db
```
This creates a basic schema. Update it to use the Rust-free client optimized for Bun: open `prisma/schema.prisma` and modify the generator block, then add a `User` model.
```prisma prisma/schema.prisma icon="/icons/ecosystem/prisma.svg"
generator client {
provider = "prisma-client"
output = "../generated/prisma" // [!code --]
output = "./generated" // [!code ++]
engineType = "client" // [!code ++]
runtime = "bun" // [!code ++]
}
datasource db {
provider = "postgresql"
}
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 the initial migration.
The command writes a `.sql` migration file to `prisma/migrations` and executes it against your Postgres database. Bun [does not load `.env` automatically](/runtime/environment-variables) when it runs a CLI with `--bun`. The `prisma.config.ts` generated by `prisma init` reads `DATABASE_URL` from the environment, so pass `--env-file=.env` explicitly.
```bash terminal icon="terminal"
bun run --bun --env-file=.env prisma migrate dev --name init
```
```txt
Loaded Prisma config from prisma.config.ts.
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.
```
</Step>
<Step title="Generate Prisma Client">
`prisma migrate dev` does not generate the _Prisma client_, so generate it with the Prisma CLI. The client provides a fully typed API for reading and writing to the database.
```sh terminal icon="terminal"
bun run --bun --env-file=.env prisma generate
```
</Step>
<Step title="Initialize Prisma Client with the Postgres adapter">
Create a new file `prisma/db.ts` that initializes the PrismaClient with the Postgres adapter.
```ts prisma/db.ts icon="/icons/typescript.svg"
import { PrismaClient } from "./generated/client";
import { PrismaPg } from "@prisma/adapter-pg";
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! });
export const prisma = new PrismaClient({ adapter });
```
</Step>
<Step title="Create a test script">
Write a script that creates a new user, then counts the 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">
Run the script with `bun run`. Each run creates a new user.
```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>
---
Prisma Postgres is now set up with Bun. Refer to the [official Prisma Postgres docs](https://www.prisma.io/docs/postgres) as you continue to develop your application.
+158
View File
@@ -0,0 +1,158 @@
---
title: Use Prisma with Bun
sidebarTitle: Prisma ORM with Bun
mode: center
---
<Note>
Prisma's dynamically loaded subcommands, such as `prisma dev`, require npm to be installed alongside Bun. The commands
used in this guide (`prisma init`, `prisma migrate`, and `prisma generate`) are built into the CLI. Generated code
works with Bun using the `prisma-client` generator.
</Note>
<Steps>
<Step title="Create a new project">
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">
Use the Prisma CLI with `bunx` to initialize the schema and migration directory. This guide uses a local SQLite database file. `prisma init` writes its connection string, `DATABASE_URL="file:./dev.db"`, to `.env`.
```bash terminal icon="terminal"
bunx --bun prisma init --datasource-provider sqlite
```
This creates a basic schema. Open `prisma/schema.prisma`, update the generator block to use the Rust-free client with the `bun` runtime, and add a `User` model.
```prisma prisma/schema.prisma icon="/icons/ecosystem/prisma.svg"
generator client {
provider = "prisma-client"
output = "../generated/prisma" // [!code --]
output = "./generated" // [!code ++]
engineType = "client" // [!code ++]
runtime = "bun" // [!code ++]
}
datasource db {
provider = "sqlite"
}
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">
Generate and run the initial migration. The command writes a `.sql` migration file to `prisma/migrations`, creates a new SQLite database, and runs the migration against it. Bun [does not load `.env` automatically](/runtime/environment-variables) when it runs a CLI with `--bun`. The `prisma.config.ts` generated by `prisma init` reads `DATABASE_URL` from the environment, so pass `--env-file=.env` explicitly.
```bash terminal icon="terminal"
bun run --bun --env-file=.env prisma migrate dev --name init
```
```txt
Loaded Prisma config from prisma.config.ts.
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.
```
</Step>
<Step title="Generate Prisma Client">
`prisma migrate dev` does not generate the _Prisma client_, so generate it with the Prisma CLI. The client provides a fully typed API for reading and writing to your database.
```sh terminal icon="terminal"
bun run --bun --env-file=.env prisma generate
```
</Step>
<Step title="Initialize Prisma Client with LibSQL">
Create a new file `prisma/db.ts` that initializes 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">
Write a script that creates a new user, then counts the 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">
Run the script with `bun run`. Each run creates a new user.
```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>
---
Prisma is now set up with Bun. See the [Prisma docs](https://www.prisma.io/docs/orm/prisma-client) as you build out your application.
+114
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 installs dependencies with `bun`.
```sh terminal icon="terminal"
bun create qwik
```
```txt
............
.::: :--------:.
.:::: .:-------:.
.:::::. .:-------.
::::::. .:------.
::::::. :-----:
::::::. .:-----.
:::::::. .-----.
::::::::.. ---:.
.:::::::::. :-:.
..::::::::::::
...::::
┌ Let's create a Qwik App ✨ (v1.20.0)
◇ 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
│ Playground App (Qwik City + Qwik)
◇ 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 v7.3.1 ssr ready in 433 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help
```
---
Open [http://localhost:5173](http://localhost:5173) in your browser to see the result. Qwik hot-reloads 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>
---
See the [Qwik docs](https://qwik.dev/docs/getting-started/) to learn more.
+53
View File
@@ -0,0 +1,53 @@
---
title: Build a React app with Bun
sidebarTitle: React with Bun
mode: center
---
Bun has built-in support for `.jsx` and `.tsx` files. React works with Bun.
Create a new React app with `bun init --react`. This gives you a template with a React app and an 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 starts 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 creates a `dist` directory with the built app and its assets.
```txt File Tree icon="folder-tree"
├── src/
│ ├── index.ts # 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
├── bun-env.d.ts # Type declarations for .svg and .css imports
├── bunfig.toml # Bun configuration
└── bun.lock # Lock file
```
+98
View File
@@ -0,0 +1,98 @@
---
title: Build an app with Remix and Bun
sidebarTitle: Remix with Bun
mode: center
---
[Remix 3](https://github.com/remix-run/remix) is a web framework built from standalone packages: a router, HTML rendering, sessions, form parsing, and more. These packages are distributed together as the `remix` package. Remix 3 is published under the `next` tag on npm while it is in beta. Its server runs on Bun as-is.
<Note>This guide covers Remix 3. To create a Remix 2 project, use `create-remix` instead.</Note>
---
Scaffold a new project with the Remix CLI.
```sh terminal icon="terminal"
bunx remix@next new my-remix-app
```
```txt
• Prepare target directory...
✓ Prepare target directory
• Generate scaffold files...
✓ Generate scaffold files
• Finalize package.json...
✓ Finalize package.json
Created My Remix App at my-remix-app
```
Then install its dependencies.
```sh terminal icon="terminal"
cd my-remix-app
bun install
```
---
The generated `server.ts` creates a `node:http` server that hands every request to the app's router. Bun runs it directly; pass `--watch` to restart the server whenever a file it imports changes.
```sh terminal icon="terminal"
bun --watch server.ts
```
```txt
Server listening on http://localhost:44100
```
Open [http://localhost:44100](http://localhost:44100) to see the starter page. The routes live in `app/routes.ts` and `app/router.ts`. `app/actions/home-page.tsx` renders the starter home page.
---
The `scripts` generated in `package.json` run the server with Node.js and the `remix/node-tsx` TypeScript loader. Bun runs TypeScript itself, so point the scripts at `bun` instead.
```json package.json icon="file-json"
{
"scripts": {
"dev": "NODE_ENV=development node --watch --import remix/node-tsx server.ts", // [!code --]
"dev": "bun --watch server.ts", // [!code ++]
"start": "NODE_ENV=production node --import remix/node-tsx server.ts", // [!code --]
"start": "NODE_ENV=production bun server.ts" // [!code ++]
}
}
```
```sh terminal icon="terminal"
bun run dev
```
```txt
$ bun --watch server.ts
Server listening on http://localhost:44100
```
<Note>
The generated `hmr` script (`hmr.ts`) also loads `remix/node-tsx`, which relies on `module.registerHooks()`. Bun does
not implement that API yet, so the script fails under Bun. See [Node.js
compatibility](/runtime/nodejs-compat#node-module). `bun --watch` restarts the server on changes instead.
</Note>
---
The router is a `fetch` handler, so you can also serve the app with [`Bun.serve()`](/runtime/http/server) instead of `node:http`.
```ts server.ts icon="/icons/typescript.svg"
import { router } from "./app/router.ts";
const server = Bun.serve({
port: 44100,
fetch: request => router.fetch(request),
});
console.log(`Server listening on ${server.url}`);
```
---
See the Remix [documentation](https://github.com/remix-run/remix/tree/main/docs) to learn more.
+62
View File
@@ -0,0 +1,62 @@
---
title: Add Sentry to a Bun app
sidebarTitle: Sentry with Bun
mode: center
---
[Sentry](https://sentry.io) is an error tracking and performance monitoring platform. Its Bun SDK, `@sentry/bun`, instruments your application to automatically collect error and performance data.
If you don't have a Sentry account and project yet, create one at [sentry.io](https://sentry.io/signup/), then return to this page.
---
First, install the Sentry Bun SDK.
```sh terminal icon="terminal"
bun add @sentry/bun
```
---
Then initialize the SDK with your Sentry DSN in its own 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,
});
```
---
Start your app with [`--preload`](/runtime) so this file runs before any of your app's modules. Bun evaluates a file's `import`s before its own code, so calling `Sentry.init()` at the top of your entry file would still run after everything that file imports.
```sh terminal icon="terminal"
bun --preload ./sentry.ts index.ts
```
---
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 the error's title opens a page with details, where you can mark it as resolved.
---
To learn more about the Sentry Bun SDK, see the [Sentry documentation](https://docs.sentry.io/platforms/javascript/guides/bun).
+62
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`. Pass the `--solidstart` flag to create a SolidStart project and `--ts` for TypeScript support. When prompted for a SolidStart version, select `2 (Stable)`. 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.9.0
◇ Which version of SolidStart?
│ 2 (Stable)
◇ Which template would you like to use?
│ basic
◇ Project created 🎉
◇ To get started, run: ─╮
│ │
│ cd my-app │
│ bun install │
│ bun dev │
│ │
├────────────────────────╯
```
---
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
$ vite dev
VITE v8.1.4 ready in 818 ms
➜ Local: http://localhost:3000/
➜ Network: use --host to expose
```
Open [localhost:3000](http://localhost:3000). The development server automatically hot-reloads changes you make to `src/routes/index.tsx`.
---
See the [SolidStart docs](https://docs.solidjs.com/solid-start) to learn more.
+49
View File
@@ -0,0 +1,49 @@
---
title: Server-side render (SSR) a React component
sidebarTitle: "SSR React with Bun"
mode: center
---
Install `react` and `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!" />);
```
---
Combine this with `Bun.serve()` to get an 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 include an [SSR optimization](https://github.com/react/react/pull/25597) that takes advantage of Bun's "direct" `ReadableStream` implementation. If you run into an error like `export named 'renderToReadableStream' not found`, install version `19` of `react` and `react-dom`, or import from `react-dom/server.browser` instead of `react-dom/server`. See [react/react#28941](https://github.com/react/react/issues/28941) for details.
+148
View File
@@ -0,0 +1,148 @@
---
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 the Svelte 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. The dependencies are already installed, so you don't need to run `bun install`.
Then start the development server with `bun --bun run dev`.
To run the dev server with Node.js instead of Bun, 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>
---
Edit and save `src/routes/+page.svelte` and the dev server hot-reloads your changes in the browser.
---
To build for production, you need a SvelteKit adapter. We recommend `svelte-adapter-bun`; install it with `bun add -D svelte-adapter-bun`.
Then make the following changes to your `vite.config.ts` (or `vite.config.js`). If your project configures SvelteKit in a `svelte.config.js` instead, swap the adapter import there.
```ts vite.config.ts icon="/icons/typescript.svg"
import adapter from "@sveltejs/adapter-auto"; // [!code --]
import adapter from "svelte-adapter-bun"; // [!code ++]
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
sveltekit({
compilerOptions: {
// Force runes mode for the project, except for libraries. Can be removed in svelte 6.
runes: ({ filename }) => (filename.split(/[/\\]/).includes("node_modules") ? undefined : true),
},
// 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(),
}),
],
});
```
---
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
✔ done
```
---
Then start the production server with `bun ./build/index.js`. It listens on port `3000` by default; set the `PORT` environment variable to change it.
```sh terminal icon="terminal"
bun ./build/index.js
```
```txt
Listening on http://0.0.0.0:3000/
```
+117
View File
@@ -0,0 +1,117 @@
---
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. It manages the startup and control of system processes and services.
---
To run a Bun application as a daemon with **systemd**, create a _service file_ in `/etc/systemd/system/`.
```sh terminal icon="terminal"
cd /etc/systemd/system
touch my-app.service
```
---
Here is a typical service file that runs an application on system start. Use it as a template for your own service. Replace `YOUR_USER` with the name of the user to run the application as. To run as `root`, replace `YOUR_USER` with `root` and `/home/YOUR_USER` with `/root` (root's home directory). For security reasons, we don't recommend running as `root`.
Refer to the [systemd documentation](https://www.freedesktop.org/software/systemd/man/systemd.service.html) for details on each setting.
```ini my-app.service icon="file-code"
[Unit]
# describe the app
Description=My App
# start the app after the network management stack has started
# (this does not wait for the network to be up, see https://systemd.io/NETWORK_ONLINE)
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, non-`root` users cannot listen on ports 80 or 443 by default. To allow Bun to listen on these ports when run by a non-`root` user, use the following command. The command requires `sudo` permissions. This step isn't necessary when running as `root`.
```bash terminal icon="terminal"
setcap CAP_NET_BIND_SERVICE=+eip /home/YOUR_USER/.bun/bin/bun
```
The command attaches the capability to the `bun` binary itself. Replacing the binary, for example with `bun upgrade`, removes the capability. Re-run the command after upgrading. Alternatively, add `AmbientCapabilities=CAP_NET_BIND_SERVICE` to the `[Service]` section of the service file instead.
---
With the service file configured, _enable_ the service. Once enabled, it starts automatically on reboot. Enabling the service requires `sudo` permissions.
```bash terminal icon="terminal"
systemctl enable my-app
```
---
To start the service without rebooting, _start_ it manually.
```bash terminal icon="terminal"
systemctl start my-app
```
---
Check the status of your application with `systemctl status`. If the app started successfully, the output looks like this:
```bash terminal icon="terminal"
systemctl status my-app
```
```txt
● my-app.service - My App
Loaded: loaded (/etc/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 index.ts
```
---
To update the service, edit the service file, then reload the daemon.
```bash terminal icon="terminal"
systemctl daemon-reload
```
---
For a complete guide to service unit configuration, see the [systemd.service documentation](https://www.freedesktop.org/software/systemd/man/systemd.service.html). Or use 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
```
+789
View File
@@ -0,0 +1,789 @@
---
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 and [Vite](https://vite.dev/). It supports full-document SSR, streaming, server functions, and bundling.
---
<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 start the Vite dev server with Bun.
```sh terminal icon="terminal"
cd my-tanstack-app
bun --bun run dev
```
</Step>
<Step title="Update scripts in package.json">
In the scripts field of your `package.json`, prefix the Vite CLI commands with `bun --bun` so that Bun runs the Vite CLI for `dev`, `build`, and `preview`.
```json package.json icon="file-json"
{
"scripts": {
"dev": "bun --bun vite dev", // [!code ++]
"build": "bun --bun vite build", // [!code ++]
"preview": "bun --bun vite preview" // [!code ++]
}
}
```
</Step>
</Steps>
---
## Hosting
To host your TanStack Start app in production, use [Nitro](https://nitro.build/) or a custom Bun server.
<Tabs>
<Tab title="Nitro">
<Steps>
<Step title="Add Nitro to your project">
Add [Nitro](https://nitro.build/) to your project 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>}>
Add the Nitro plugin to your `vite.config.ts` file.
```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">
Use one of the following guides to deploy your app to a hosting provider.
<Note>
When deploying to Vercel, either add `"bunVersion": "1.x"` to your `vercel.json` file, or set the Bun version in 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 is based on [TanStack's Bun template](https://github.com/TanStack/router/blob/main/examples/react/start-bun/server.ts). It gives you fine-grained control over static asset serving: the server preloads small files into memory and serves larger files on-demand. You can configure the limits on what the server preloads.
</Note>
<Steps>
<Step title="Create the production server">
Create a `server.ts` file in your project root:
```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 listens on port 3000 by default; set the `PORT` environment variable to change it.
</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 hosting documentation](https://tanstack.com/start/latest/docs/framework/react/guide/hosting)
+85
View File
@@ -0,0 +1,85 @@
---
title: Bun Redis with Upstash
sidebarTitle: Upstash with Bun
mode: center
---
[Upstash](https://upstash.com/) is a fully managed Redis database as a service. It works with the Redis® API, so you can connect with Bun's native Redis client.
<Note>TLS is enabled by default for all Upstash Redis databases.</Note>
---
<Steps>
<Step title="Create a new project">
Create a new project with `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; the URL starts with `rediss://`.
<Frame>
![Upstash Redis database page](/images/guides/upstash-1.png)
</Frame>
</Step>
<Step title="Connect using Bun's 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, create a custom client with `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">
Use the Redis client to read and write keys in your Upstash 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 handles connections automatically. You don't need to connect or disconnect manually for basic operations.
</Step>
</Steps>
+88
View File
@@ -0,0 +1,88 @@
---
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](/bundler/fullstack).
</Note>
---
Vite works with Bun with no extra configuration. 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
◇ Which linter to use?
│ Oxlint
◇ Install with bun and start now?
│ No
◇ 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": "tsc -b && vite build",
"lint": "oxlint",
"preview": "vite preview"
},
// ...
```
---
Now you can start the development server with `bun run dev`.
```bash terminal icon="terminal"
bun run dev
```
---
Build your app for production.
```sh terminal icon="terminal"
bunx --bun vite build
```
---
For more information, see the [Vite documentation](https://vite.dev/guide/).