How to Deploy a 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.
Contents
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.
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.
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.