DEV Community

Ahmad
Ahmad

Posted on

Running both nodejs and bun apps in turborepo

Image description

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 anymore 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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 pickup.

This is a little bit of downside 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

Enter fullscreen mode Exit fullscreen mode

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 ..
Enter fullscreen mode Exit fullscreen mode

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,
};
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

And you should get a:

Image description

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

Image description

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.

Top comments (1)

Collapse
 
jzubero profile image
Julian Scheuchenzuber

Great tutorial, thanks. I am using this with plain pnpm workspaces (no Turborepo). For this I had to adjust the postinstall command in the bun app to avoid a loop:

  "scripts": {
    "postinstall": "rm -rf node_modules && bun install --ignore-scripts",
    "dev": "bun run --watch src/index.ts"
  }
Enter fullscreen mode Exit fullscreen mode

@0xahmad Are you still using this version/do you still consider this the best practice way to do things?