How to Deploy a Bun Application?

How to Deploy a Bun Application?
How to Deploy an Bun Application_

Bun is a JavaScript runtime that is designed to be fast, lightweight, and easy to use. It is written in Zig and powered by JavaScriptCore, the JavaScript engine that powers Safari.

Bun has a built-in Node.js-compatible package manager, test runner, and bundler. It also provides a minimal set of highly optimized APIs for performing common tasks, like starting an HTTP server and writing files.

In this article, you will build a simple web API with Bun and deploy it on Back4app using Back4app containers. Keep reading to learn more how to host a Bun application.

Advantages of Bun

Since Bun’s initial announcement, even before the V1 release in September 2023, Bun has become increasingly popular in the JavaScript community. Here are some of the reasons.

Speed

Bun is written in Zig, a low-level programming language designed for systems programming, focusing on performance, safety, and readability. Intended to be a modern alternative to C & C++.

Additionally, unlike Node.js and Deno, which uses Chrome’s V8 JavaScript engine, it uses the JavaScriptCore engine, which powers Safari.

In addition to Zig and JavaScript Core, Bun also uses a number of other techniques, such as a custom memory allocator that is optimized for JavaScript and a just-in-time (JIT) compiler to optimize code as it is executed.

Overall, the combination of Zig, JavaScript Core, and other optimizations make Bun a very fast JavaScript runtime when compared to other runtimes.

Node.js Compatibility

Bun is designed to be a drop-in replacement for Node.js, and as such, it is compatible with all Node.js APIs.

It also has all the built-in Node.js modules, such as crypto, fs, path, etc. You can check for the available and un-available Node.js modules on the Bun.js documentation.

Additionally, Bun is an npm-compatible package manager. This means that you can use Bun to install and manage Node.js packages using Bun.

TypeScript Support Out-of-Box

Bun has native and seamless support for TypeScript, making it an excellent choice if you prefer or require TypeScript in your projects.

TypeScript, an extended and statically typed version of JavaScript, introduces advanced language features and static typing to enhance JavaScript development.

With Bun, there is no need for additional configuration, and there’s no requirement for extra setup or build procedures to enable TypeScript functionality.

Limitations of Bun

Despite its advantages, Bun has certain limitations you need to consider before using it in your project.

Limited Resources

Bun is still relatively new, meaning that the community is currently small. There are not a lot of resources covering Bun-js-specific development, meaning you might have a hard time figuring out how to use the runtime.

However, the Bun documentation is comprehensive and serves as a valuable reference point. Should you encounter difficulties, there is also the option to seek assistance via their Discord channel.

Support for Windows

Bun currently provides limited support for the Windows operating system. At the time of writing, only the runtime is supported on Windows.

The test runner, package manager, and bundler are still under development and, as such, do not work on Windows. Keep reading to learn more how to host a Bun app.

Building a Bun App

Before you can use Bun, you have to install it.

To install Bun on macOS, WSL, and Linux, run the command below:

curl -fsSL https://bun.sh/install | bash

Setting up your Development Environment

To create a new Bun project, run the command below:

bun init

Running the command above initializes an empty Bun project in your project directory.

For the tutorial, you will build a simple API with Elysia, one of the fastest Bun HTTP server frameworks (According to their benchmarks).

Run the command below to install the Elysia and other dependencies required for this project:

bun add elysia knex dotenv pg

The other dependencies installed in the command include:

  • Knex, a query builder. You will use this dependency to simplify interacting with your database.
  • dotenv, this package helps you manage environmental variables in your project.
  • pg (Postgres database driver) for interacting with your postgres database.

Next, initialize knex in your project by running:

knex init

The command above creates a knexfile.js file. This file contains configuration options for your database.

Replace the code in the file with the code block below:

require("dotenv").config();

export const development = {
  client: "pg",
  connection: process.env.DATABASE_URL,
  migrations: {
    directory: "./db/migrations",
  }
};

Next, create a db.js file in your project root directory and add the code block below to it.

const knex = require("knex");
const knexFile = require("./knexfile.js");

const environment = process.env.NODE_ENV || "development";

export default knex(knexFile[environment]);

Next, create an .env file and add your database connection details or URI to the file.

For example:

DATABASE_URL = <YOUR_DATABASE_URI>

Replace “YOUR_DATABASE_URI” with your database URL.

Note: Ensure you add your .env file to your .gitignore file to ensure you do not commit private information to version control.

Creating your Database Model

To create your database model using Knex, you will create a migration file and write a create SQL command using Knex.

Run the command below to create your first migration:

knex migrate:make blog

Next, replace the code in the generated migration file with the code block below:

/**
 * @param { import("knex").Knex } knex
 * @returns { Promise<void> }
 */
export function up (knex) {
  return knex.schema.createTable("blog", (table) => {
    table.increments("id").primary();
    table.string("title").notNullable();
    table.string("content").notNullable();
    table.string("author").notNullable();
    table.timestamp("created_at").defaultTo(knex.fn.now());
  });
}

/**
 * @param { import("knex").Knex } knex
 * @returns { Promise<void> }
 */
export function down (knex) {
  return knex.schema.dropTable("blog");
}

Finally, run the code block below to execute your migration file:

knex migrate:latest

The command above executes the migration file you generated earlier, thus creating a Blog table in your database.

Creating a Bun-Elysia Server

In this step, you will create a simple API server.

Open your index.ts file and add the code block below:

//index.ts
const { Elysia } = require("elysia");
let db = require("./db");

db = db.default;

const app = new Elysia();

In the code block below, you imported Elysia and created an instance of the Elysia framework (app).

Creating Route Handlers

Next, you will create route handlers for your application. The handler you create will be for the following routes:

  • POST /posts/create-new-post
  • GET /posts
  • GET /posts/:id
  • PATCH /posts/:id/update-post
  • DELETE /posts/:id/delete-post

Add the code block below to your index.ts file to create the handler for “/posts/create-new-post”:

app.post("/posts/create-new-post", async (context) => {
  try {
    //Extract the title, content and author from the request body
    const { title, content, author } = context.body;

    //Insert the post into the database
    const post = await db("blog").insert({
      title,
      content,
      author,
    });

    //Send response to the client
    return new Response(JSON.stringify(post));
  } catch (error: any) {
    //Send error to the client
    return new Response(error.message, { status: 500 });
  }
});

The code block above is a route handler for your endpoint that adds new posts to your database.

Add the code block below to your index.ts file to create the handler that fetches all your posts “/posts”:

app.get("/posts", async (context) => {
  try {
    //Get all posts from the database
    const posts = await db.select("*").from("blog");

    //Send response to the client
    return new Response(JSON.stringify(posts));
  } catch (error: any) {
    //Send error to the client
    return new Response(error.message, { status: 500 });
  }
});

Add the code block below to your index.ts file to create the handler that fetches a single post by id “/posts/:id”:

app.get("/posts/:id", async (context) => {
  //Extract the id from the request params
  const { id } = context.params;

  //Get the post from the database
  const post = await db("blog").where({ id });

  //If the post is not found, send a 404 response
  if (post.length === 0) {
    return new Response("Post not found", { status: 404 });
  }

  //Send response to the client
  return new Response(JSON.stringify(post));
});

Add the code block below to your index.ts file to create the handler that updates a single post with the data in the payload by id “/posts/:id/update-post”:

app.patch("/posts/:id/update-post", async (context) => {
  //Extract the id from the request params
  const { id } = context.params;

  //Extract the title and content from the request body
  const { title, content } = context.body;

  //Update the post in the database
  const post = await db("blog").where({ id }).update(
    {
      title,
      content,
    },
    ["id", "title", "content"]
  );

  //Send response to the client
  return new Response(JSON.stringify(post));
});

Add the code block below to your index.ts file to create the handler that deletes a single post by id “/posts/:id/delete-post”:

app.delete("/posts/:id/delete-post", async (context) => {
  //Extract the id from the request params
  const { id } = context.params;

  //Delete the post from the database
  const post = await db("blog").where({ id }).del();

  //Send response to the client
  return new Response(JSON.stringify(post));
});

Finally, add the code block below to set the PORT for your application.

app.listen(3000, () => {
  console.log("Server running on port 3000");
});

Run the command below to start up your app:

bun --watch index.ts

Deploying a Bun App on Back4app Containers

Deploying your Bun app requires a few steps.

Step 1: Write a Dockerfile

To create a Dockerfile, run the command below on your terminal.

touch Dockerfile

Running the command above creates a Dockerfile in your project’s root directory.

Next, open your Dockerfile and add the code block below to it:

FROM oven/bun

WORKDIR /app

COPY package.json .
COPY bun.lockb .

RUN bun install

COPY . .

EXPOSE 3000

CMD ["bun", "index.ts"]

In the Dockerfile above, the first line, FROM oven/bun, specifies the base image to use. This image is a pre-built image that contains the Bun runtime and all of its dependencies.

The next line, WORKDIR /app, sets the working directory for the image. This is the directory where the application code will be copied and run.

The following two lines, COPY package.json . and COPY bun.lockb ., copy the package.json and bun.lockb files from the current directory into the image. These files are necessary for the Bun runtime to install the application’s dependencies.

The next line, RUN bun install, installs the application’s dependencies using the Bun runtime.

The next line, COPY . ., copies the entire current directory into the image. This includes the application code and any other necessary files.

The next line, EXPOSE 3000, exposes port 3000 from the container to the outside world. This is the port that the application will listen on.

The last line, CMD ["bun", "index.ts"], specifies the command that will be run when the container is started. This command will start the Bun runtime and run the application’s index.ts file.

Finally, push your code to GitHub.

Step 2: Create a Back4app Application

The next step to host a Bun application is to create a new application on Back4App. First, sign in to your Back4App account or sign up if you don’t have one yet. Once you’re logged in, you’ll find yourself on the Back4App dashboard.

Click on the “NEW APP” button and select the “Containers as a Service” option.

Back4app BaaS vs CaaS

As the next step to host a Bun app, connect your GitHub account to your Back4app account. Connecting your account allows Back4app to access repositories on your account.

You can decide to grant access to all the repositories in your account or specific repositories. Choose the application you want to deploy, in this case, the application you built in this tutorial, and click Select.

New app back4app

After clicking the Select button, you will be directed to a configuration page, where you will fill out details about your app, such as the PORT and the environment variables.

After filling out the details, click the Create App button. This starts the deployment process. Your deployment should succeed, and you’ll get a URL to access your app, but if it fails, you can take advantage of Back4app ChatGPT integration to solve the issues you have with your Dockerfile.

Alternatively, you can manually troubleshoot your deployment errors using Back4app’s detailed logs and troubleshooting guide.

Conclusion

In this article, you explored the Bun JavaScript runtime, its advantages, and apparent limitations. You also explored how to build a Bun app using Elysia, Knex, and PostgreSQL.

Finally, you explored Back4app containers and how to deploy your Bun applications on the platform.

When using Bun, it is important to note that it is still in its early stages and might introduce some major changes later in the future.

FAQ

What is Bun?

Bun is a JavaScript runtime that is designed to be fast and efficient. It is built on top of the JavaScriptCore engine and uses a number of optimizations (advantages of using the low-level language, Zig) to make it faster.

How to Deploy a Bun App?

– Create a Bun app.
– Write a Dockerfile.
– Push your Bun application to GitHub.
– Open a Back4app account or sign in to your existing account.
– Create a new “CaaS” app on Back4app.
– Grant Back4app access to the application you want to deploy.
– Select the app and fill out the configuration details.
– Click Deploy.


Leave a reply

Your email address will not be published.