initial commit
This commit is contained in:
@@ -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/).
|
||||
@@ -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>
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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).
|
||||
@@ -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
|
||||
```
|
||||
@@ -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/).
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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>
|
||||

|
||||
</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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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 there’s 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></Frame>
|
||||
|
||||
---
|
||||
|
||||
See the [Qwik docs](https://qwik.dev/docs/getting-started/) to learn more.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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>
|
||||

|
||||
</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/
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
@@ -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)
|
||||
@@ -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>
|
||||

|
||||
</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>
|
||||
@@ -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/).
|
||||
Reference in New Issue
Block a user