How to Use Bun + Qwik to Build Blazing-Fast Tools

White sports car parked in an urban parking lot on a cloudy day.
White sports car parked in an urban parking lot on a cloudy day.

How to Use Bun + Qwik to Build Blazing-Fast Tools

How to Use Bun + Qwik to Build Blazing-Fast Tools

Speed matters. In an era where milliseconds dictate user experience and conversion rates, reaching for development tools that prioritize performance from the ground up isn’t just a luxury—it’s a necessity. This post will walk you through leveraging two of the most exciting, performance-centric technologies in the JavaScript ecosystem today: Bun and Qwik.

We’ll cover what makes them uniquely fast, how to set up a project combining their strengths, and build a simple, blazing-fast application together.

Why Bun + Qwik? The Need for Speed

Before we dive into the how, let’s understand the why.

Bun isn’t just a JavaScript runtime; it’s an all-in-one toolkit. Written in Zig, it’s designed from the ground up for speed, offering a faster alternative to Node.js and npm for development, testing, and production. It integrates a runtime, a package manager, a bundler, and a test runner into a single, cohesive, and incredibly fast executable.

Qwik is a new breed of web framework focused on “resumability” rather than “hydration.” This fundamental shift means Qwik applications start with virtually zero JavaScript downloaded and executed on the client, leading to almost instant interactive times (TTI). The server renders the HTML, and the client “resumes” execution exactly where the server left off, only downloading and executing JavaScript as needed, when needed.

Combine these two, and you get a development experience that’s fast, and a production application that’s even faster. Bun handles the backend, tooling, and build process with extreme efficiency, while Qwik ensures your frontend delivers unparalleled user experience by minimizing client-side overhead.

Bun: The All-in-One JavaScript Toolkit

Let’s get acquainted with Bun. If you don’t have it installed, you can do so quickly:

curl -fsSL https://bun.sh/install | bash

Once installed, Bun is ready to replace several tools in your workflow.

Bun as a Runtime

Bun can execute JavaScript and TypeScript files directly, just like Node.js.

// hello-bun.ts
console.log("Hello from Bun!");

const add = (a: number, b: number): number => a + b;
console.log(`2 + 3 = ${add(2, 3)}`);

To run this file:

bun run hello-bun.ts
Hello from Bun!
2 + 3 = 5

You can even create a basic HTTP server with Bun’s built-in APIs, which are designed for performance.

// bun-server.ts
Bun.serve({
  port: 3000,
  fetch(request: Request) {
    const url = new URL(request.url);
    if (url.pathname === "/hello") {
      return new Response("Hello, Bun HTTP server!", { status: 200 });
    }
    return new Response("404 Not Found", { status: 404 });
  },
});

console.log("Bun server listening on http://localhost:3000");

Run the server:

bun run bun-server.ts
Bun server listening on http://localhost:3000

Now, open your browser to http://localhost:3000/hello or use curl:

curl http://localhost:3000/hello
Hello, Bun HTTP server!

Bun as a Package Manager

Bun can install, add, remove, and update packages, often much faster than npm or Yarn. The syntax is familiar.

To initialize a new project:

mkdir bun-project && cd bun-project
bun init -y
bun init -y
bun.js install

  bun-project/
  ├── .gitignore
  ├── README.md
  ├── bunfig.toml
  ├── package.json
  └── tsconfig.json

6 files, 0 installs
Done.

To install dependencies:

bun add express
bun add express
Installed express@4.18.2

1 package installed [600.00ms]

Notice the speed!

Bun as a Bundler

Bun includes a highly performant bundler, compatible with esbuild and Rollup plugins, making it a great choice for both library and application bundling.

Let’s create a small example:

// src/entry.ts
import { capitalize } from "./utils";

console.log(capitalize("hello bun bundler"));
// src/utils.ts
export function capitalize(str: string): string {
  if (!str) return "";
  return str.charAt(0).toUpperCase() + str.slice(1);
}

Now, bundle it:

bun build ./src/entry.ts --outfile ./dist/bundle.js --target browser
bun build ./src/entry.ts --outfile ./dist/bundle.js --target browser
bun.js build

  dist/bundle.js  83B

1 file, 83B
Done.

This generates dist/bundle.js with the bundled code.

Qwik: The Resumable Framework

Now let’s turn our attention to Qwik. While Bun optimizes the development and build process, Qwik optimizes the runtime performance of your web application on the client.

Understanding Resumability vs. Hydration

Traditional SPA frameworks (React, Vue, Angular) use a process called “hydration.” The server renders the initial HTML, but then the client-side JavaScript framework must re-execute all the component logic, rebuild the virtual DOM, and attach event listeners. This “re-execution” is hydration and is a major source of slow Time To Interactive (TTI), as the browser must download, parse, and execute a large amount of JavaScript before the page becomes interactive.

Qwik, on the other hand, leverages “resumability.” The server renders the HTML and serializes the application’s state, component tree, and execution context directly into the HTML. When the client loads the page, it resumes execution exactly where the server left off, without re-executing any application logic. JavaScript is only downloaded and executed in tiny chunks, precisely when a user interacts with a specific part of the page. This means extremely low initial JavaScript download and near-instant interactivity.

Setting Up Your Blazing-Fast Project (Bun + Qwik)

The easiest way to get started with a Qwik project that inherently leverages Bun (especially for production builds and server-side rendering) is to use the official Qwik CLI.

bun create qwik@latest

When prompted, follow these steps:

  1. Where would you like to create your new project?: my-bun-qwik-app
  2. What kind of app would you like to create?: Basic App (Recommended for most projects)
  3. Install dependencies?: Yes, using bun
  4. Ready to start?: Yes, bun dev
bun create qwik@latest
✔ Where would you like to create your new project? … my-bun-qwik-app
✔ What kind of app would you like to create? › Basic App (Recommended for most projects)
✔ Install dependencies? › Yes, using bun
✔ Ready to start? › Yes, bun dev

  my-bun-qwik-app
  ├── src/
  │   ├── components/
  │   ├── entry.dev.tsx
  │   ├── entry.ssr.tsx
  │   ├── global.css
  │   └── routes/
  │       └── index.tsx
  ├── public/
  ├── package.json
  ├── tsconfig.json
  ├── ... (other config files)

Project created! Next steps:
- cd my-bun-qwik-app
- bun run dev # to start the dev server

Now, navigate into your new project directory:

cd my-bun-qwik-app

And start the development server:

bun run dev
bun run dev
vite v4.4.9 dev server running at:
> Local: http://localhost:5173/
> Network: use --host to expose
ready in 276ms.

You now have a Qwik application running with Bun powering its development server and dependency management.

Project Structure Overview

Let’s quickly look at the key parts of your new Qwik project:

  • src/routes/: This is where your page components live. Each .tsx file here corresponds to a route. index.tsx is your homepage.

  • src/components/: Reusable Qwik components.

  • src/entry.ssr.tsx: The server-side rendering entry point for Qwik. Bun will be used to run this for production.

  • src/entry.dev.tsx: The client-side entry point for development.

  • public/: Static assets.

  • package.json: Contains your scripts. Notice dev and build scripts.

    // package.json (excerpt)
    {
      "scripts": {
        "build": "qwik build",
        "build.ssr": "qwik build ssr",
        "build.client": "qwik build client",
        "build.preview": "qwik build preview",
        "dev": "vite --mode ssr",
        "preview": "qwik serve",
        "start": "bun run preview"
      }
    }

    qwik build uses Vite under the hood for bundling and Bun as the runtime for the SSR part if the adapter is configured. qwik serve uses a Bun adapter to run the production server.

Building a Simple Tool: A Blazing-Fast Qwik Counter

Let’s modify the default Qwik application to include a simple counter component. This will demonstrate how Qwik handles interactivity with minimal JS.

1. Create the Counter Component

Create a new file src/components/counter/counter.tsx:

// src/components/counter/counter.tsx
import { component$, useSignal } from '@builder.io/qwik';

export const Counter = component$(() => {
  const count = useSignal(0); // Qwik's signal for reactive state

  return (
    <div style={{ padding: '1rem', border: '1px solid #ccc', borderRadius: '8px', textAlign: 'center' }}>
      <h2>Qwik Counter</h2>
      <p>Current count: {count.value}</p>
      <button onClick$={() => count.value--}>-</button>
      <button onClick$={() => count.value++}>+</button>
    </div>
  );
});
  • component$(): This is a Qwik primitive that defines a component. The $ indicates that this component’s code can be lazy-loaded and serialized.
  • useSignal(): Qwik’s reactive state management. When count.value changes, the component efficiently updates.
  • onClick$(): Event handlers in Qwik also use the $ suffix, marking them as lazy-loadable. This means the JavaScript for onClick is only downloaded when the user actually clicks the button.

2. Use the Counter Component in Your Homepage

Now, open src/routes/index.tsx and import/use your new Counter component:

// src/routes/index.tsx
import { component$ } from '@builder.io/qwik';
import type { DocumentHead } from '@builder.io/qwik-city';
import { Counter } from '~/components/counter/counter'; // Import your Counter component

export default component$(() => {
  return (
    <>
      <h1>Welcome to Qwik + Bun!</h1>
      <p>This is a blazing-fast application.</p>

      <div style={{ margin: '2rem 0' }}>
        <Counter /> {/* Use your Counter component */}
      </div>

      <p>
        Explore the code in <code>src/routes/index.tsx</code> and{' '}
        <code>src/components/counter/counter.tsx</code>.
      </p>
    </>
  );
});

export const head: DocumentHead = {
  title: 'Qwik + Bun App',
  meta: [
    {
      name: 'description',
      content: 'A blazing-fast web application built with Qwik and Bun.',
    },
  ],
};

3. Run and Observe

Start your development server again:

bun run dev

Visit http://localhost:5173. You’ll see your counter.

Crucially, open your browser’s developer tools (Network tab). When you first load the page, you’ll notice minimal JavaScript is downloaded. The counter is rendered on the server. Only when you click the + or - buttons will Qwik download the tiny chunk of JavaScript responsible for that specific onClick$ handler and the useSignal logic. This is the magic of resumability.

4. Building for Production

Now, let’s build this application for production. Bun will be used to execute the Qwik build process and for serving the final optimized output.

bun run build
bun run build

> my-bun-qwik-app@0.0.1 build /path/to/my-bun-qwik-app
> qwik build

[qwik build] 📦 client build
vite v4.4.9 building for production...
... (Vite/Rollup output for client bundle)
✓ 15 modules transformed.
[qwik build] 📦 SSR build
vite v4.4.9 building for production...
... (Vite/Rollup output for SSR bundle)
✓ 1 module transformed.
[qwik build] 🚀 prerendering (1/1)
✓ /
[qwik build] ⚡️ Done!

After the build completes, you’ll have a dist/ directory (for client assets) and a server/ directory (for the SSR bundle).

To preview the production build, Bun will run the Qwik City preview server:

bun run preview
bun run preview

> my-bun-qwik-app@0.0.1 preview /path/to/my-bun-qwik-app
> qwik serve

Qwik production server running at:
> Local: http://localhost:4173/

Listening on: http://localhost:4173/

Access http://localhost:4173/ and observe the incredibly fast load times and minimal JS activity in your network tab. Bun serves these assets and the server-rendered Qwik application with high efficiency.

Beyond the Basics: Where Bun and Qwik Shine Together

  • API Backends: While Qwik focuses on the frontend, you can use Bun’s native HTTP server or a framework like Elysia (built on Bun) to create a separate, incredibly fast API backend for your Qwik application.
  • CLI Tools: Bun is excellent for building standalone CLI tools thanks to its fast startup time and unified bun run command. You could create a CLI tool using Bun that interacts with your Qwik app’s data or deployment pipeline.
  • Testing: Bun’s built-in test runner (bun test) is fast and Jest-compatible, making it ideal for testing both your Bun backend code and Qwik components.
  • Deployment: Deploying Bun + Qwik involves deploying the server-side generated HTML and the Qwik JavaScript chunks. Platforms like Vercel, Netlify, or self-hosted environments can leverage Bun for the build step and for running the production server. Many Qwik City adapters exist for various platforms.

Final Thoughts

Bun and Qwik represent a powerful combination for developers who refuse to compromise on performance. Bun streamlines the entire development workflow, from package installation to bundling and running your code, all with a focus on speed. Qwik revolutionizes frontend performance by fundamentally rethinking how web applications are delivered to the client, achieving near-instant interactivity by default.

By embracing this stack, you’re not just building applications; you’re crafting experiences that are inherently faster, more efficient, and provide a superior user experience from the very first byte. Dive in, experiment, and enjoy the speed!

Last updated on