Build an HTTP server in Bun
Sadeedpv🥇
Posted on September 19, 2023
As of last week, Bun has taken over the JavaScript world. In this post, we will see how to create an HTTP server in Bun using the Elysia Framework. Here is the link to the full source code of this project.
So, What is Bun?
Bun is a new JavaScript Runtime built from scratch to serve the JavaScript ecosystem.
Bun has built-in TypeScript support and is blazingly fast ⚡earning it the favor of many JavaScript & TypeScript Developers 💘.
Installing Bun
Installing Bun is very simple. All you have to do is:
curl -fsSL https://bun.sh/install | bash
If you like to know more about the installation process, here is the link
Note that Bun is not yet compatible with Windows, But you can still install if you have WSL installed on your system.
Creating an HTTP server using Elysia Framework
Elysia is a Bun-first performance focused web framework that takes full advantage of Bun's HTTP, file system, and hot reloading APIs.
Let's first initialize our Elysia app:
bun create elysia myapp
cd myapp
bun run dev
Let us Build our server
In this tutorial, I will be building a simple TODO Application. For building the application, I will be using the SQLite Database. The cool thing about Bun is that it has completely free SQLite Database built in the runtime. If you like to read more about the bun:sqlite
, here is the docs
In our src/index.ts
, we will declare our Elysia app:
import { Elysia } from 'elysia'
const app = new Elysia();
app.get('/', () => "Hello World!");
app.listen(8000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);
If you want to use environment variables:
app.listen(Number(Bun.env.PORT));
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);
You can define your Routes with many built-in methods like:
app.get('/', () => {});
app.post('/', () => {});
app.put('/', () => {});
app.patch('/', () => {});
app.delete('/', () => {});
Retrieving the path parameter is also very easy just like any other Backend Framework like Express.js:
// Both does the same thing
app.get("/hello/:name", ({ params: { name } }) => {
return `Hello ${name}!`;
});
app.get("bye/:name", (context) => {
return `Hello ${context.params.name}`;
});
If you have many paths with the same prefix, you can Group them. Grouping allows you to combine multiple prefixes into one. Since we do not require that many routes to build this application, we do not need to Group the paths.
app.group('/user', app => app
.post('/sign-in', signIn)
.post('/sign-up', signUp)
.post('/profile', getProfile)
)
You can also install the cors
plugin which adds support for customizing Cross-Origin Resource Sharing behavior.
Install it with:
bun add @elysiajs/cors
Then use it:
import { cors } from "@elysiajs/cors";
const app = new Elysia();
app.use(cors());
In order for us to build this application, first we need to initialize our Database. Let's take a look at how we can do that:
import { Database } from "bun:sqlite";
// Create DB If not Exists
const DB = new Database("mydb.sqlite", { create: true });
Now, we need to create the Table
DB.query(
`CREATE TABLE IF NOT EXISTS MESSAGES(
id INTEGER PRIMARY KEY AUTOINCREMENT,
message TEXT
);`
).run();
After creating the relation, now we need to implement the GET
and POST
request. Our GET
request would look something like this
app.get("/", (context) => {
const query = DB.query(`SELECT * FROM MESSAGES;`);
const result = query.all();
console.log(result);
context.set.status = 200;
return new Response(JSON.stringify({ messages: result }), {
headers: { "Content-Type": "application/json" },
});
});
We used the db.query()
method on your Database instance to prepare a SQL query and used .all()
to run a query and get back the results as an array of objects.
Our POST
request would look something like this:
app.post(
"/add",
({ body }:any) => {
const message = body?.message;
console.log(message);
const query = DB.query(`INSERT INTO MESSAGES (message) VALUES (?1)`);
query.run(message);
return new Response(JSON.stringify({ message: "Added" }), {
headers: { "Content-Type": "application/json" },
});
});
As you can see here, I am using the any
type. Instead of using the any
type, what we can do is
import { Elysia, t } from "elysia";
app.post(
"/add",
({ body }) => {
const message = body?.message;
console.log(message);
const query = DB.query(`INSERT INTO MESSAGES (message) VALUES (?1)`);
query.run(message);
return new Response(JSON.stringify({ message: "Added" }), {
headers: { "Content-Type": "application/json" },
});
},
{
body: t.Object({
message: t.String(),
}),
}
);
Note that we used context.set.status to set the StatusCode of the response
Lets try running our application:
curl -X POST http://localhost:8000/add -H "Content-Type: application/json" -d '{"message":"This is a Reminder!"}'
curl http://localhost:8000/
And Booom!! 💥💥
{"message":"Added"}
{"messages": [{"id":1, "message": "This is a Reminder!"}]}
Here are some tasks for you:
- Create
PUT
andDELETE
requests to update or DELETE the Database - Implement error handling
CONCLUSION
I hope you gained some new insights from this post. As you can see from the graph, Bun is much faster than Node as of now.
Also, take a look at how fast Elysia is compared to Express:
References
If you like to read more about bun:sqlite
, read more
If you like to read more about Elysia
, read more
If you want the link to the full source code in GitHub, click here
Posted on September 19, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.