Category: Bun JS

  • MASTER bun Elysia by Creating a Server – Guide

    Elysia Framework

    This article will show you how to create a server using the bun Elysia Framework. By the end you will know about the following features:

    • Understand the bun Javascript Runtime to create an api
    • Basic setup and server scaffolding
    • Get requests with URL parameters
    • Post requests with body data
    • Architecture for organising your routes
    • Sending responses to the end user

    I won’t cover anything generic such as database connections so that we can concentrate on the topic at hand. That said, it’s important to remember that bun is a drop in replacement for node js, so any business logic you need can simply be copy pasted from a node implementation with zero compatibility issues!

    bun Elysia course for node developers
    If you want even more bun goodness then check out my bun course

    Setup bun Elysia App

    The first step is to setup bun and create the Elysia app.

    1. Open up Visual Studio Code
    2. Open your working folder with “File => Open Folder”
    3. Open a terminal with “Terminal => New Terminal”
    4. In terminal type: “bun create Elysia myApp” (myApp is the name of your app)
    5. Follow the onscreen instructions, simply accepting the defaults

    Run Server

    Now that your Elysia server has been created you should test it out. By default an npm script to run the server should be in package.json under the scripts section:

    “scripts”: {
    “test”: “echo \”Error: no test specified\” && exit 1″,
    “dev”: “bun run –watch src/index.ts”
    },

    If this is not there then go ahead and add it. Then in your terminal, build and create the Elysia instance:

    npm run dev

    You should see “Elysia is running at localhost:3000” in the terminal. Use your browser to go to localhost:3000 and you should see a welcome message corresponding to what’s in your index.ts server declaration code. At this point you may also want to check out my guide on hot reloading natively.

    Create a Get Route

    Before we start, a word on architecture … one option to create your get route is just to place it straight into the index.ts file but after adding a couple of routes that would really clutter up the index file!

    Let’s use good architecture practice instead:

    1. Create a “routes” folder in the “src” folder.
    2. Create a “shop.ts” file in that routes folder.

    This shop.ts file will hold all the routes corresponding to our pseudo online shopping server. Open that file and paste in the following code:

    import { Elysia } from "elysia";
    
    const shopRoutes = new Elysia({ prefix: '/shop' })
    .get('/', () => 'HOME PAGE for shop, welcome')
    
    export default shopRoutes;

    A couple of notes on the above code:

    We first import the Elysia class as it contains everything we need to define all things “server”. Then we define a constant called “shopRoutes” which will hold all of our routes.

    This takes an optional object in which we can define things such as the URL prefix. In this case we have defined “/shop” so this route group will only be called if the URL entered has this form:

    Eg: localhost:3000/shop

    You should have noticed that the server auto reloads when you save any code. The –watch option in the dev script watches all our code, automatically reloading when required!

    Although if we tried this route now it wouldn’t work as we need to specify that these routes exist in the index.ts file.

    Add Route Groups

    Open up the index.ts file and clear out all the boilerplate code. Paste in the following lines:

    import { Elysia } from "elysia";
    import shopRoutes from "./routes/shop"
    
    const app = new Elysia()
    app
      .group('', (app) => app.use(shopRoutes)) //ADDITION OF ROUTE GROUPS
      .listen(process.env.PORT || 3000);
    
    console.log(
      `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
    );
    

    Our “shopRoutes” are imported at the top of file which can then be used the .group function. This registered those routes with the main Elysia object.

    Now open up any browser and navigate to localhost:3000/shop to see your bun GET request in action!

    Add URL Parameters to a GET Route

    On a bun server you will need a way to retrieve data based on some variable passed in from the client.

    For example, in our shop we may want to retrieve a product based on a code or product name. Usually this is specified in a GET route using a URL parameter, ie a subsection of the URL. The example below will take the last part of the URL as a variable which is used to find product information and return it:

    myshop.com/shop/butter => Should return a page for butter
    myshop.com/shop/ribeye => Should return a page for ribeye steak

    This is pretty easy to implement in bun. Let’s open up the routes/shop.ts file and add a second GET route as marked below:

    import { Elysia } from "elysia";
    
    const shopRoutes = new Elysia({ prefix: '/shop' })
      .get('/', () => 'HOME PAGE for shop, welcome')
      .get('/:item', ({ params: params }) => "Returned data for: " + params.item) //ADD THIS LINE
    
    export default shopRoutes;

    Test it out by going to localhost:3000/shop/shampoo. You should get “Returned data for shampoo” in your browser!

    Let’s now step through this GET request so you understand it fully. The URL is defined as having a variable with the colon character ( : ).

    Any name after that will be the variable name but you have to explicitly pass the params to the response function! This is done with the { params: params } argument, then the anonymous function will have access to your variables.

    Note that the above is a shortened version of reality. You would normally be doing a database lookup somewhere based on the variable (usually an id that is passed over).

    Create a POST Route with Body Data

    As you probably know POST routes differ from GET routes as the former sends structured data along with the request. Usually this data would be too complex to include in a bun GET parameter so instead is bundled into the BODY of the POST request.

    Using the routes/shop.ts we already have in place we will add a POST route that allows us to send over some data about a users cart. Start by adding a route as below:

    import { Elysia } from "elysia";
    
    const shopRoutes = new Elysia({ prefix: '/shop' })
      .get('/', () => 'HOME PAGE for shop, welcome')
      .get('/:item', ({ params: params }) => "Returned data for: " + params.item)
      .post('/cart', ({ body }) => JSON.stringify(body)) //ADD THIS LINE
    
    export default shopRoutes;

    Now there are tools in Chrome that you can use to send a POST request but for sake of speed we’ll issue a curl command (on Linux / Mac). Simply copy and paste the following into your terminal:

    curl localhost:3000/shop/cart -X POST -H “Content-Type: application/json” -d ‘{“id”: “Bread”, “count”: 9}’

    When we send over our CURL request when also send some body data with the type “application/json” (defined in the header -H). The body data is defined after the -d flag in the curl command.

    Our bun application picks up the body, converts it to a string using JSON.stringify and sends it back to us. In reality you’d actually use this data to calculate the cart price or whatever your application needs.

    Finally

    If you liked this then you’ll love my full bun and Elysia course.

    Appendix: Full Code

    Here’s the code from index.ts

    import { Elysia } from "elysia";
    import shopRoutes from "./routes/shop"
    
    const app = new Elysia()
    app
      .group('', (app) => app.use(shopRoutes))
      .listen(process.env.PORT || 3000);
    
    console.log(
      `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
    );

    And here’s the code for routes/shop.ts

    import { Elysia } from "elysia";
    
    const shopRoutes = new Elysia({ prefix: '/shop' })
      .get('/', () => 'HOME PAGE for shop, welcome')
      .get('/:item', ({ params: params }) => "Returned data for: " + params.item)
      .post('/cart', ({ body }) => JSON.stringify(body))
    
    export default shopRoutes;

    Don’t forget to see the full course!

  • Bun Hot Reload Not Working – 3 Fixes

    Is hot reloading not working in your bun.js project? This article gives you 3 potential fixes (from a real developer).

    First of all there are 2 ways you can hot reload code in your project:

    1. bun –hot index.ts
    2. bun –watch index.ts

    The first one, –hot, watches your files for changes and only reloads the content. It DOES NOT restart the bun process. Therefore if your new code has some dependancy on the process restarting then it (probably) won’t be picked up. This bug may appear if you’re trying to read bun env files through a hot reload!

    –hot reloading is great if you’re only working on content and want to keep state in your server across reloads. If you don’t need that then it’s much better to do a cold reload as below:

    Fix 1: Use the –watch command instead of –hot

    You’re much better off using the –watch command to “hot” reload the app on changes. This behaves more more like the traditional way of hot reloading in node.js, for example when using pm2, nodemon or similar process managers.

    Note that each detected change causes the bun process to restart and server state will be lost.

    Fix 2: WSL (Linux on Windows) doesn’t hot reload

    This is a know bug when using bun.js on Windows Subsystem for Linux. The process somehow doesn’t pick up the changes in files on the Windows file system. See this issue on github: https://github.com/oven-sh/bun/issues/5155

    Fix a) The best option is to run bun on native Linux or Mac of course but for some devs that’s not an option due to work place restrictions.

    Fix b) Personally I use a Virtual Machine in Virtualbox or VMware and bun runs as expected. Again this may not be an option due to IT restrictions.

    Also note: bun is not ready for prime time on Windows just yet (start of 2024) so you may run into other issues that aren’t yet documented.

    Fix 3: Use a traditional code watcher

    You don’t have to use the bun.js watcher tools. Traditional code watchers and reloaders work well too. In fact, if you’re migrating from node then you probably already have these services setup and don’t particularly want to change them out for the bun alternative.

    a) You can use pm2 (my go to) to reload your project: https://www.npmjs.com/package/pm2

    b) You can also use nodemon to do the same: https://www.npmjs.com/package/nodemon

    Have you had any other issues with bun.js? If so then please leave them in the comments below!

    Finally

    If you want the easy route to bun mastery then check out my course on bun and Elysia Framework. It has everything you need to get you started.

  • How to Use Files in Bun JS

    Bun comes with its own modules for handling file read and writes. Let’s whip up a simple server that allows us to see this in action.

    Read a File with Bun

    Initialise a new server and paste in the following code to your index.ts file:

    const server = Bun.serve({
      port: 3000,
      async fetch(req) {
        const url = new URL(req.url);
    
        if (url.pathname === "/")
          return new Response(Bun.file("index.html"), {
            headers: {
              "Content-Type": "text/html",
            },
          });
    
        return new Response("404 Not Found", { status: 404 });
      },
    });
    
    console.log(`Server running at http://localhost:${server.port}`);

    This will read an index.html file off disk and return a bun response to the caller. Let’s create a simple html file in the project root:

    <!DOCTYPE <em>html</em>>
    <html>
      <head>
        <meta <em>charset</em>="utf-8" />
        <title>Hello World of bun</title>
      </head>
      <body>
        Bun is sooper fast!
      </body>
    </html>

    Go ahead and run your server with

    bun index.ts

    If you navigate to localhost:3000 you should see the index.html response returned and rendered.

    Bun JS and Json Files

    Bun includes support for reading JSON files natively. Create a server.json file in your project root folder and paste in some JSON:

    {
      "production": false,
      "version": "0.1alpha"
    }

    Now in your index.ts file you can log that file anywhere you please:

    const serverDeets = Bun.file("server.json")
    console.log(await serverDeets.json())

    Note that the file API is asynchronous and that you need to call the file type as a function, in this case .json(). From there you can select properties from the file in the usual manner, eg:

    console.log((await serverDeets.json()).version)

    Be aware that if your file doesn’t exist you will get an error that stops your server. It also outputs that error to the endpoint being requested (including the code surrounding it!).

    I’m not sure if that only happens in the development phase but I sure hope it doesn’t happen in production – otherwise that’s a security hole! Imagine if a private key is stored in code and gets output in that error log! (Don’t judge me, we all do it from time to time thinking we can ‘refactor’ later… 😉

  • Environment Variables in Bun JS (bun env)

    Bun JS, like all other server side tech, comes with the ability to read environment variables from files.

    Environment files in your project root directory are read in this particular order:

    1. .env
    2. .env.production, .env.development, .env.test
    3. .env.local

    Note that for item 2 bun will first read from variable NODE_ENV, to decide which one to select.

    You can also manually override which file bun env should read when you start the server:

    bun --env-file=.env.v1 index.ts
    
    //you can even have multiple env files!
    bun --env-file=.env.v1 --env-file=.env.test src/index.ts

    Expanding (Concatenating) Environment Variables in bun

    Bun will automatically concat any variables in env files, for example:

    SITE=iamdev
    SERVER=$SITE.net/login
    process.env.SERVER; // outputs "iamdev.net/login"

    How to Access bun env Environment Variables in Code

    You can use the standard syntax:

    process.env.VARNAME

    Alternatively you can use the bun version

    Bun.env.VARNAME

    That’s all for the most commonly requested points on environment variables. There are a couple of other more advanced points that bun themselves document, which you can find here.

  • How to Add NPM Packages in Bun JS

    Bun JS has an insanely fast package installer which is one great reason to upgrade to it over Node!

    To install a package in bun simply use:

    bun add [PACKAGE_NAME]

    For example, install figlet (ASCII art) and its typings:

    bun add figlet
    bun add -d @types/figlet 

  • Bun js Hot Reload – Full Guide

    Bun incudes 2 very handy flags that will watch your project for file changes. I also cover this in my bun Elysia course.

    1. Reload with the –watch flag

    This is used in the command to run your server, eg:

    bun --watch index.ts

    If a files contents change within your project folder then bun will cold reload your project – equivalent to restarting the server. All states and connections will be destroyed.

    2. Reload with the –hot flag

    bun --hot index.ts

    Again, if files change then bun will reload your project but only the code directly affected. This keeps state and connections open. Obviously if the changed code affects state or connection then you may have very strange debug issues!

    Personally I like to just use –watch. I have very very few instances where it would save my time to keep state or connections. However, I could see the use for it on a production server so there was a minimum of user disruption.

    If you want to know more then see my full hot reload guide here.

    Alternatively you can also check out my course on bun and Elysia Framework, which has everything you need to get you started.

  • How to Create a Server in native Bun JS

    Note that these instructions are for installing on Mac. Linux or WSL on Windows. Native Windows is experimental at this point (2023) so best to avoid it for now.

    Open up a folder in VS Code where your project will live.

    Open up a terminal within VS Code and enter:

    npm install -g bun

    This will install bun for global use on your system.

    Spin up a new bun server:

    bun init

    This will create an index.ts file, package.json and a typescript config file with the bun standard settings.

    Open up the index.ts file and paste in:

    const server = Bun.serve({
      port: 3000,
      fetch(request) {
        return new Response("Welcome to Bun!");
      },
    });
    
    console.log(`Listening on localhost: ${server.port}`);

    Now, in your terminal run the following command:

    bun index.ts

    Your server should now be running – as the console log message will tell you.

    Open up a browser and go to: localhost:3000. You should see a ‘Welcome to Bun!’ message.

    Finally, if you change some code in the index.ts file then you will need to quit the bun run time and restart it. However there is a handy watcher feature in bun. Instead of ‘bun index.ts’ now you can add in –watch:

    bun --watch index.ts

    This will reload the server (cold start) every time the file receives a change. Alternatively you can use the –hot flag to keep the server running and simply slot in the code you’ve changed.

    bun --hot index.ts

    The latter –hot version keeps the state of your server and any current connections so could come in quite handy!

    Any question or clarifications? Please leave them in the comments!