Migrate my Node.js project to Bun

Sat, November 2, 2024 - 4 min read

Contents

Remove unnecessary dependencies

  • Delete pnpm-lock.yaml, node_modules and package-lock.json

  • I implemented babel to my node project, but I don’t need it anymore. So removed it.

      bun remove @babel/runtime @babel/cli @babel/core @babel/node @babel/preset-env babel-jest jest bcrypt
      bun add @types/bun
      bun install
    {
      "name": "my-app",
      "version": "1.0.0",
      "author": "",
      "main": "index.js",
      "dependencies": {
        "@babel/runtime": "^7.25.7",
        "bcrypt": "^5.1.1",
        ...
      },
      "devDependencies": {
        "@babel/cli": "^7.25.7",
        "@babel/core": "^7.25.7",
        "@babel/node": "^7.25.7",
        "@babel/plugin-transform-runtime": "^7.25.7",
        "@babel/preset-env": "^7.25.7",
        "babel-jest": "^29.7.0",
        "babel-plugin-module-resolver": "^5.0.2",
        "jest": "^29.7.0",
        "nodemon": "^3.1.7",
        "@types/bun": "latest",
        "@types/express": "^5.0.0"
      },
      "description": "",
      "keywords": [],
      "license": "ISC",
      "scripts": {
        "dev": "nodemon --trace-deprecation --exec babel-node ./src",
        // must have --bun for using bun runtime. If not, it's will run in Node runtime
        // --watch must be before ./src. If not, hot reload WON'T WORK
        "dev": "bun --watch --bun run ./src",
        "test": "jest",
        "build": "babel src -d dist --ignore '**/*.test.js'",
        "build": "bun build --target=bun ./src --outfile ./dist/index.js",
        "binary": "bun build --target=bun ./src --outfile ./dist/app --compile",
        "start": "node ./dist",
        "start": "NODE_ENV=production bun run ./dist",
        "prod": "babel src -d dist && node ./dist",
        "prod": "bun build --target=bun ./src --outdir ./dist && bun --watch --bun run ./dist/src",
        "test": "jest",
        "test": "bun --bun test --watch",
        "clean": "rm -rf dist && echo 'Done.'"
      }
    }
  • Add jsconfig.json. Replace baseUrl and paths following old project paths alias

    {
      "compilerOptions": {
        "target": "ES6",
        "allowSyntheticDefaultImports": true,
        "experimentalDecorators": true,
        "baseUrl": "./",
        "paths": {
          "~/*": ["./src/*"],
          "@/*": ["./src/features/*"]
        }
      },
      "exclude": ["node_modules", "**/node_modules/*", "dist"]
    }

Replace bcrypt by Bun build-in encryption

  • Example:

    ...
    import bcrypt from 'bcrypt'; 
    ...
    
    // compare password
      const passwordMatch = await bcrypt.compare(password, user.password);  
      const passwordMatch = await Bun.password.verify(password, user.password); 
    
    ...
    
    // create hash password
      hashPassword = await bcrypt.hash(user.password, 10); 
      hashPassword = await Bun.password.hash(user.password, { 
          algorithm: 'bcrypt',
          cost: 10, // number between 4-31
        });

VSCode debug

  • Install Bun for Visual Studio Code extension

  • Change .vscode/launch.json

    {
      "version": "0.2.0",
      "configurations": [
        {
          "name": "BE: debug",
          "request": "launch",
          "type": "node-terminal",
          "type": "bun",
          "program": "index.js",
          "cwd": "${workspaceRoot}/voiceotp",
          "runtime": "bun",
          "watchMode": true,
          "cwd": "${workspaceRoot}/project",
          "command": "pnpm run dev",
          "command": "bun run dev"
        }
      ]
    }

Unit test

  • Remove jest.config.js

  • In *.test.js files:

    import { describe, expect, mock, test } from 'bun:test'; 
    ...
    
    jest.mock(path, () => { // [!code --])
      return {}; // [!code --])
    }); // [!code --])
    mock.module('~/config/logger', () => { 
      return {}; 
    }); 
    
    ...
    
    it('should replace placeholders with corresponding values', () => { ... }) // [!code --])
    test('should replace placeholders with corresponding values', () => { ... }) // [!code ++])
    
  • Run test:

    bun run test
  • Dockerize

    FROM oven/bun:canary-alpine AS base
    WORKDIR /app
    
    # install dependencies into temp directory
    # this will cache them and speed up future builds
    FROM base AS install
    # RUN mkdir -p /temp/dev
    # COPY package.json bun.lockb /temp/dev/
    # RUN cd /temp/dev && bun install --frozen-lockfile
    
    # install with --production (exclude devDependencies)
    RUN mkdir -p /temp/prod
    COPY package.json bun.lockb /temp/prod/
    RUN cd /temp/prod && bun install --frozen-lockfile --production
    
    # copy node_modules from temp directory
    # then copy all (non-ignored) project files into the image
    FROM base AS prerelease
    COPY --from=install /temp/prod/node_modules node_modules
    COPY . .
    
    # [optional] tests & build
    ENV NODE_ENV=production
    RUN bun test
    RUN bun run build
    
    # copy production dependencies and source code into final image
    FROM base AS release
    COPY --from=install /temp/prod/node_modules node_modules
    COPY --from=prerelease /app/dist/index.js .
    COPY --from=prerelease /app/package.json .
    
    # run the app
    USER bun
    EXPOSE 3000/tcp
    ENTRYPOINT [ "bun", "run", "--watch", "index.js" ]