Bun

This comprehensive guide shows how to integrate Bun's fast tooling with Strapi v5 using a hybrid architecture. Learn to use Bun for package management, frontend development, and build processes alongside a Node.js-powered Strapi backend end to end

Bun logo

What Is Bun?

Bun is a JavaScript runtime built on Apple's JavaScriptCore engine. It ships as an all-in-one toolkit: runtime, package manager, bundler, and test runner in a single binary. Where Node.js requires you to assemble a toolchain from npm, webpack or Vite, ts-node, and Jest, Bun handles all of those roles natively.

The package manager is where most developers feel the difference immediately. bun install resolves and installs dependencies significantly faster than npm or yarn, thanks to a global module cache and optimized resolution algorithm. Bun also executes TypeScript directly without a separate compilation step, and its built-in bundler can handle production builds without additional configuration.

As of Bun v1.3.10, the runtime provides broad Node.js API compatibility, though gaps remain for applications that depend heavily on native C/C++ addons and specific middleware frameworks.

Why Integrate Bun with Strapi

Even with the runtime limitation, Bun adds tangible value to a Strapi development workflow. Here's what you gain from the hybrid approach:

  • Faster dependency installs. bun install works with Strapi's package.json and resolves the full dependency tree in a fraction of the time npm takes. This adds up fast across CI pipelines and fresh environment setups.
  • Native TypeScript for scripts and tooling. Migration scripts, seed files, and build orchestration scripts run directly with bun run script.ts, no ts-node or tsc step required.
  • Built-in bundling for frontends. Bun's bundler can handle your React frontend without webpack or Vite configuration overhead, while Astro and SvelteKit currently rely on their existing Vite-based build tooling when run with Bun.
  • Monorepo workspace support. Bun workspaces manage dependencies across your Strapi backend and frontend apps from a single lockfile.
  • Faster test execution. The Bun test runner provides built-in watch mode and coverage reporting for frontend and utility code.
  • Simplified development orchestration. Bun scripts can spawn and manage both your Node.js Strapi process and Bun-powered frontend server from a single command.

How to Integrate Bun with Strapi

This section walks through setting up a hybrid workspace where Bun manages tooling and frontend development while Node.js powers the Strapi backend.

Prerequisites

Before starting, verify you have the following installed:

ToolVersionPurpose
Node.jsv20, v22, or v24 (LTS)Strapi v5 runtime
Bunv1.3+Package management, frontend runtime
PostgreSQL14.0+Database (recommended for production)
GitLatestVersion control

Check your installations:

node --version    # Should output v20.x, v22.x, or v24.x
bun --version     # Should output 1.3.x
psql --version    # Should output 14.0+

You should also be comfortable with Strapi's Content-Type Builder and REST API concepts. Familiarity with TypeScript is helpful but not required.

Step 1: Create the Workspace Structure

Start by scaffolding a monorepo that separates the Strapi backend from your frontend application. This structure lets each project use the runtime it needs.

mkdir strapi-bun-project && cd strapi-bun-project

Create the root package.json with Bun workspace configuration:

{
  "name": "fullstack-strapi-project",
  "workspaces": ["backend", "frontend"],
  "scripts": {
    "install": "bun install",
    "dev": "bun run scripts/dev.ts",
    "build": "bun run scripts/build.ts"
  }
}

The workspaces field tells Bun to manage dependencies across both directories from a single bun.lockb lockfile.

Step 2: Install Strapi v5

Scaffold a new Strapi v5 project inside the backend directory:

npx create-strapi@latest backend

During the interactive setup, select your preferred database. For production projects, PostgreSQL is the recommended choice according to a Strapi blog article on cloud database providers, not the core database documentation.

Once the scaffolding completes, install dependencies using Bun's package manager from the project root:

bun install

This installs workspace dependencies via Bun, but some Strapi dependencies (such as aliased packages like codemirror5) may still require manual workarounds when using bun install. The Bun CLI handles package.json resolution the same way npm does, just faster.

Important: While bun install works for dependency installation, always use Node.js to run Strapi commands. Attempting bun run strapi develop triggers errors in Koa.js middleware and database drivers documented in GitHub Issue #24892.

Step 3: Configure the Strapi Backend

Set up your Strapi environment variables in backend/.env:

HOST=0.0.0.0
PORT=1337
APP_KEYS=yourAppKey1,yourAppKey2
API_TOKEN_SALT=yourApiTokenSalt
ADMIN_JWT_SECRET=yourAdminJwtSecret
TRANSFER_TOKEN_SALT=yourTransferTokenSalt
JWT_SECRET=yourJwtSecret
DATABASE_CLIENT=postgres
DATABASE_HOST=127.0.0.1
DATABASE_PORT=5432
DATABASE_NAME=strapi
DATABASE_USERNAME=strapi
DATABASE_PASSWORD=strapi

Verify the database configuration in backend/config/database.js matches your environment:

// backend/config/database.js
module.exports = ({ env }) => ({
  connection: {
    client: env("DATABASE_CLIENT", "postgres"),
    connection: {
      host: env("DATABASE_HOST", "127.0.0.1"),
      port: env.int("DATABASE_PORT", 5432),
      database: env("DATABASE_NAME", "strapi"),
      user: env("DATABASE_USERNAME", "strapi"),
      password: env("DATABASE_PASSWORD", "strapi"),
    },
  },
});

Start Strapi to confirm everything works:

cd backend && node node_modules/.bin/strapi develop

The Admin Panel should be accessible at http://localhost:1337/admin. Create your admin account, then stop the server.

Step 4: Set Up the Frontend with Bun

For this guide, we'll use Astro as the frontend framework. Astro officially documents Strapi integration, and while it can run on Bun's runtime, the Astro team notes there are still rough edges and Bun is not yet officially supported.

From the project root:

cd frontend
bun create astro@latest .

Follow the Astro setup prompts. Once complete, add environment variables for the Strapi connection in frontend/.env:

STRAPI_URL=http://localhost:1337
PUBLIC_STRAPI_URL=http://localhost:1337

Step 5: Create a Development Orchestration Script

This is where Bun's native TypeScript execution shines. Create a script that starts both services simultaneously:

mkdir scripts
// scripts/dev.ts
import { spawn } from "bun";

console.log("Starting development environment...");

const strapiProcess = Bun.spawn(
  ["node", "node_modules/.bin/strapi", "develop"],
  {
    cwd: "./backend",
    stdout: "inherit",
    stderr: "inherit",
    env: { ...process.env, NODE_ENV: "development" },
  }
);

const clientProcess = Bun.spawn(["bun", "run", "dev"], {
  cwd: "./frontend",
  stdout: "inherit",
  stderr: "inherit",
});

process.on("SIGINT", () => {
  strapiProcess.kill();
  clientProcess.kill();
  process.exit(0);
});

await Promise.all([strapiProcess.exited, clientProcess.exited]);

Now bun run dev from the root starts both Strapi (on Node.js) and Astro (on Bun) in a single terminal session. No separate tabs, no third-party process managers for development.

Step 6: Create a Production Build Script

Add a build script that handles both projects:

// scripts/build.ts
import { spawn } from "bun";

console.log("Building for production...");

// Build Strapi admin panel with Node.js
const strapiBuild = Bun.spawn(
  ["node", "node_modules/.bin/strapi", "build"],
  {
    cwd: "./backend",
    stdout: "inherit",
    stderr: "inherit",
    env: { ...process.env, NODE_ENV: "production" },
  }
);

await strapiBuild.exited;
console.log("Strapi admin panel built.");

// Build frontend with Bun
const frontendBuild = Bun.spawn(["bun", "run", "build"], {
  cwd: "./frontend",
  stdout: "inherit",
  stderr: "inherit",
});

await frontendBuild.exited;
console.log("Frontend built. Ready for deployment.");

Run the full build with:

bun run build

Project Example: Blog with Astro Frontend and Strapi v5

Let's build a blog that demonstrates the hybrid architecture in practice. Strapi handles content management, and an Astro frontend powered by Bun fetches and renders articles.

Define the Content Model in Strapi

Start the backend and open the Content-Type Builder at http://localhost:1337/admin:

cd backend && node node_modules/.bin/strapi develop

Create a new Collection Type called Article with the following fields:

Field NameTypeDetails
titleTextRequired
slugUIDAttached to title
contentRich Text (Blocks)Main article body
excerptText (Long)Short summary
publishedDateDatePublication date
coverMedia (Single)Featured image

After creating the Collection Type, add a few sample articles through the Content Manager. Make sure to publish them.

Next, configure API permissions. Navigate to Settings → Users & Permissions → Roles → Public, and enable find and findOne for the Article content type. This exposes the REST API endpoints without authentication for public content.

Build the Astro Frontend

Create a utility function for fetching data from Strapi's REST API:

// frontend/src/lib/strapi.ts
const STRAPI_URL = import.meta.env.STRAPI_URL || "http://localhost:1337";

interface StrapiResponse<T> {
  data: T;
  meta: {
    pagination?: {
      page: number;
      pageSize: number;
      pageCount: number;
      total: number;
    };
  };
}

export async function fetchFromStrapi<T>(
  endpoint: string,
  params: Record<string, string> = {}
): Promise<StrapiResponse<T>> {
  const url = new URL(`/api/${endpoint}`, STRAPI_URL);

  Object.entries(params).forEach(([key, value]) => {
    url.searchParams.set(key, value);
  });

  const response = await fetch(url.toString());

  if (!response.ok) {
    throw new Error(`Strapi API error: ${response.status} ${response.statusText}`);
  }

  return response.json();
}

Create the articles listing page. According to Astro's data fetching documentation, you can call APIs directly in the frontmatter:

---
// frontend/src/pages/articles.astro
import { fetchFromStrapi } from '../lib/strapi';
import Layout from '../layouts/Layout.astro';

interface Article {
  id: number;
  documentId: string;
  title: string;
  slug: string;
  excerpt: string;
  publishedDate: string;
}

const { data: articles } = await fetchFromStrapi<Article[]>('articles', {
  'fields[0]': 'title',
  'fields[1]': 'slug',
  'fields[2]': 'excerpt',
  'fields[3]': 'publishedDate',
  'sort[0]': 'publishedDate:desc',
  'pagination[pageSize]': '10',
});
---

<Layout title="Blog Articles">
  <main>
    <h1>Articles</h1>
    <div class="article-grid">
      {articles.map((article) => (
        <article>
          <h2>
            <a href={`/articles/${article.slug}`}>
              {article.title}
            </a>
          </h2>
          <time datetime={article.publishedDate}>
            {new Date(article.publishedDate).toLocaleDateString()}
          </time>
          <p>{article.excerpt}</p>
        </article>
      ))}
    </div>
  </main>
</Layout>

Containerize with Docker

For production deployment, Docker provides environment consistency. Here's a docker-compose.yml following Strapi's Docker documentation:

version: '3'

services:
  strapi:
    build:
      context: ./backend
      dockerfile: Dockerfile
    ports:
      - "1337:1337"
    environment:
      - DATABASE_CLIENT=postgres
      - DATABASE_HOST=postgres
      - DATABASE_PORT=5432
      - DATABASE_NAME=strapi
      - DATABASE_USERNAME=strapi
      - DATABASE_PASSWORD=strapi
      - APP_KEYS=yourAppKey1,yourAppKey2
      - API_TOKEN_SALT=yourApiTokenSalt
      - ADMIN_JWT_SECRET=yourAdminJwtSecret
      - TRANSFER_TOKEN_SALT=yourTransferTokenSalt
      - JWT_SECRET=yourJwtSecret
      - NODE_ENV=production
    depends_on:
      - postgres
    volumes:
      - strapi-uploads:/srv/app/public/uploads
    networks:
      - app-network

  postgres:
    image: postgres:17-alpine
    environment:
      - POSTGRES_DB=strapi
      - POSTGRES_USER=strapi
      - POSTGRES_PASSWORD=strapi
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - app-network

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - STRAPI_URL=http://strapi:1337
      - PUBLIC_STRAPI_URL=http://localhost:1337
    depends_on:
      - strapi
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

volumes:
  postgres-data:
  strapi-uploads:

Notice the two different Strapi URLs. Server-side API calls within the Docker network use http://strapi:1337 (service name resolution), while client-side browser requests use http://localhost:1337 (exposed port mapping).

The Strapi backend container uses a Node.js base image as required, while the frontend container can use Bun's official image for faster builds and script execution.

Strapi Open Office Hours

If you have any questions about Strapi 5 or just would like to stop by and say hi, you can join us at Strapi's Discord Open Office Hours, Monday through Friday, from 12:30 pm to 1:30 pm CST: Strapi Discord Open Office Hours.

For more details, visit the Strapi documentation and Bun documentation.

Frequently Answered Questions

No. Strapi v5 requires Node.js v20, v22, or v24 as its runtime. Some developers have reported practical issues using Koa.js with Bun's runtime environment, but there is no documented evidence in the provided sources that Knex.js or Sharp have incompatibilities with Bun. Attempting bun run strapi develop results in middleware TypeError failures and database connection errors. These failures occur because Bun's runtime handles native C/C++ addons and middleware differently than Node.js. There is currently no officially announced timeline for Bun runtime support in Strapi, but GitHub Issue #24892 does not contain a Strapi team confirmation of any Bun-specific limitation.

Yes, with a caveat. bun install can resolve and install Strapi's dependency tree, including native modules, and it's the most practical day-to-day Bun win in a Strapi workflow. However, Bun GitHub Issue #5868 notes bun install may not respect package aliases in subdependencies. If you hit resolution errors, add explicit aliases to your root package.json and reinstall. In CI, it's usually stable once the lockfile is committed, and you still run Strapi with Node.js as usual.

Astro and SvelteKit both support Bun as a frontend runtime and integrate with Strapi v5's REST API — though Strapi itself must run on Node.js, not Bun. Astro officially documents Strapi as a CMS integration, and SvelteKit's server-side loading fetches Strapi data cleanly. React and Next.js frontends also work, though Next.js has its own runtime considerations. Use Bun for the frontend build and development process while keeping Strapi on Node.js.

There is no confirmed timeline. The Strapi Feedback Platform tracks community interest with active votes requesting this feature, and GitHub Issue #24892 documents a Strapi Admin Content Manager/CTB rendering failure after cloning the repo, not technical blockers for Bun runtime support. As of the last verified team statement in November 2025, incompatibilities with Koa.js and Knex.js remain fundamental. Bun's ongoing Node.js compatibility improvements may eventually resolve these, but no release has specifically addressed them yet. In the meantime, the hybrid architecture described in this guide provides a practical workaround.

Node.js optimization strategies deliver strong results. PM2 cluster mode improves traffic handling by running multiple processes across available CPU cores. Redis caching reduces response times for frequently accessed content. CDN integration through services like CloudFront offloads static asset delivery. Proper database connection pooling in Strapi's configuration cuts per-query overhead. For the stuff that bites later in production, watch slow queries and add basic request logging or tracing at the edge. Combined, these are production-ready, supported optimizations.