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
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
- I prefixed all the local package names with "@local101/", to make sure my package names never conflict with any external npm packages
- 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 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
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.
Top comments (1)
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:@0xahmad Are you still using this version/do you still consider this the best practice way to do things?