Running both nodejs and bun apps in turborepo

Running both nodejs and bun apps in turborepo

·

5 min read

So finally today, after a little bit of tinkering about, I figured out how to use a bun-powered app, simultaneously with my other nodejs apps in a turborepo.

And so here I am, writing this article so that someone else who is seeking a solution on the web for this exact kind of problem can benefit from this article. Or at least learn something from it.

So without falling into any more tangents, let's get down to how I put it together.

Quick overview of the tech involved:

  • Turborepo

  • Pnpm as the package manager

  • Bun

    • HonoJS
  • NodeJS

    • Fastify

    • NextJS

  • other...

I'll setup a fresh turborepo with the following command

npx create-turbo@latest
# where : dual-runtimes-turborepo
# package manager : pnpm

At the root, we get the current setup

ROOT_DIR:

apps
 - apps/docs
 - apps/web
packages
 - packages/eslint-config-custom
 - packages/tsconfig
 - packages/ui
package.json
pnpm-lock.json
pnpm-workspaces.yaml
turbo.json
... other files

Run it with pnpm run dev to ensure it works properly.

Some cleanup

  1. I prefixed all the local package names with "@local101/", to make sure my package names never conflict with any external npm packages

  2. I removed the eslint-config-custom, to simplify the example, and because I don't use it anyways.

Core config required to make it all work

Open up the root package.json and add the following to it

{
  "name": "dual-runtimes-monorepo",
  "private": true,
  "workspaces": [
    // example app/package, for the example setup,
    // And don't copy/paste comments into JSON
    "apps/aserver",
    "packages/logic"
  ],
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "lint": "turbo run lint",
    "format": "prettier --write \"**/*.{ts,tsx,md}\""
  },
  "devDependencies": {
    "@turbo/gen": "^1.9.7",
    "eslint": "^7.32.0",
    "prettier": "^2.5.1",
    "turbo": "latest"
  },
  "packageManager": "pnpm@7.15.0",
  "volta": {
    "node": "20.3.1"
  }
}

Here I added a couple of entries. Ignoring the volta config(which is a Toolchain manager), the workspaces part is the key component required for bun to function. Here you specify the path to the packages and apps which you require for bun to pick up.

This is a little bit of down side for yarm and pnpm users like me, as bun follows the npm spec, and I'm using pnpm workspaces to manage for example. I just had to do their respective config, which is just a handful of more lines of code.

Important Note: This may not be required BUT if you run into a bun linking error, I have a helper script (link-packages-to-bun.sh) in the root of the monorepo to make it easier to register your packages to bun's repository.

#!/bin/bash

# iterate over "packages" directory, cd into each package and run "bun link" for each package and then cd out of the path
for package in packages/*; do
 echo "[...] Linking $package to bun"
 # $package is "packages/pkgdir"
 cd $package
 # get the "pkgdir" part from the string
 package_name=$(echo $package | cut -d'/' -f2)
 # ensure that this matches the "name" in each package.json in your local packages.
 # this only works if your package name matches the dir name ex. "ui" for dir "ui" etc.
 bun link "@local101/$package_name"
 cd ../..
 echo "[+] Done linking $package to bun"
done

So before doing pnpm i(don't run until the next section is done), run ./link-packages-to-bun.sh to link packages

For now, let's move onto setting up our aserver app

Bun app setup - Hono

Lately, I've been checking out different bun frameworks, the latest one being hono. So I just chose it for this example.

Now to setup the app, I did the following:

rm -rf apps/docs packages/eslint-config-custom
# ^ optional command, I did it to reduce the focus scope

cd apps

npm create hono@latest aserver
# select bun and wait for it to complete

cd ..

Then I replaced the index.ts of our new app with the following

// apps/aserver/src/index.ts

import { uselessAddFunctionForTheSakeOfExample } from "@local101/logic";
import { Hono } from "hono";

const hono = new Hono({});

hono.get("/", async (c) => {
  const ans = uselessAddFunctionForTheSakeOfExample(2, 2);
  return c.json({
    message: `Hey, did you know that 2 + 2 = ${ans}. I know right?`,
  });
});
hono.get("/ping", async (c) => c.json({ message: "pong" }));

export default {
  port: 9001,
  fetch: hono.fetch,
};

And now finally to ensure our bun dependencies are properly installed, modify the package.json of the newly setup app as follows:

{
  "name": "@local101/aserver",
  "version": "0.0.0",
  "private": true,
  "description": "Well, it's a... server",
  "scripts": {
    "postinstall": "bun install",
    "dev": "bun run --hot src/index.ts",
    "start": "bun run src/index.ts"
  },
  "dependencies": {
    "@local101/logic": "workspace:*",
    "hono": "^3.2.6"
  },
  "devDependencies": {
    "bun-types": "^0.6.2"
  }
}

The postinstall and start scripts are the only main requirements, but I put in a @local101/logic package for demonstration purposes.

And with that the core setup is complete!

Test running it all

Now from the root of the monorepo, run:

pnpm i
# you should see your bun's post install script running here

pnpm run dev

And you should get a:

Now testing out our bun application, if you followed this tutorial, you should get:

Now try running pnpm build and then pnpm start(if setup in the root turbo.json+package.json), it should all should work as expected. And so that concludes it all!

I hope you got something out of this article... if not then... go do something meaningful... like touch some grass.

Anyways, Click Here for the link to view the source code of the monorepo I used as the example in this article.

If you face any issues, then let me know and I'll try to help out.

I'll leave with a final note: If you're going to use bleeding edge technology, then bleed responsibly.