Junius L
September 16, 2023
4 min read
Junius L
September 16, 2023
4 min read
In this blog post, we will explore the process of building a RESTful API using Bun and ElysiaJS. Bun is an all-in-one toolkit for JavaScript and TypeScript apps. It ships as a single executable called bun, and ElysiaJS is a TypeScript framework supercharged by Bun with End-to-End Type Safety. By combining these two tools, we can create efficient and scalable RESTful APIs with ease.
Before we dive into the details, make sure you have the following prerequisites installed:
curl -fsSL https://bun.sh/install | bashFirst, let's create a new Bun project:
bun create elysia booksNow, navigate to the books directory and open the index.ts file. Paste the following code into it:
import { Elysia, t } from "elysia";
const app = new Elysia()
app.get('/books', () => 'books');
app.post('/books', () => 'books')
app.put('/books', () => 'books')
app.get('/books/:id', () => 'books')
app.delete('/books/:id', () => 'books')
app.listen(8081)
console.log(
`🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}`
);Execute the following command to run your application:
bun devYou should see the following output in your terminal:
🦊 Elysia is running at localhost:8081Now let's add SQLite database to our application, inside src create a new file and name it db.ts and paste the following code
import { Database } from 'bun:sqlite';
export interface Book {
id?: number;
name: string;
author: string;
}
export class BooksDatabase {
private db: Database;
constructor() {
this.db = new Database('books.db');
// Initialize the database
this.init()
.then(() => console.log('Database initialized'))
.catch(console.error);
}
// Get all books
async getBooks() {
return this.db.query('SELECT * FROM books').all();
}
// Add a book
async addBook(book: Book) {
// q: Get id type safely
return this.db.query(`INSERT INTO books (name, author) VALUES (?, ?) RETURNING id`).get(book.name, book.author) as Book;
}
// Update a book
async updateBook(id: number, book: Book) {
return this.db.run(`UPDATE books SET name = '${book.name}', author = '${book.author}' WHERE id = ${id}`)
}
// Delete a book
async deleteBook(id: number) {
return this.db.run(`DELETE FROM books WHERE id = ${id}`)
}
async getBook(id: number) {
return this.db.query(`SELECT * FROM books WHERE id=${id}`).get() as Book;
}
// Initialize the database
async init() {
return this.db.run('CREATE TABLE IF NOT EXISTS books (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, author TEXT)');
}
}We can utilized the database in our handlers by passing it to the Elysia context using decorate
let's modify the index file and add the db to Elysia context
import { Elysia, t } from "elysia";
import { BooksDatabase } from './db/db';
const app = new Elysia().decorate('db', new BooksDatabase)
app.get('/books', ({ db }) => db.getBooks());
app.post('/books', () => 'books')
app.put('/books', () => 'books')
app.get('/books/:id', () => 'books')
app.delete('/books/:id', () => 'books')
app.listen(8081)
console.log(
`🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}`
);Now our first endpoint uses the SQLite db from the context
To define strict types for Elysia handlers, we've introduced schema usage. The schema ensures type safety for parameters like the request body
Let's modify our code to use the schema for our post and put endpoint
import { Elysia, t } from "elysia";
import { BooksDatabase } from './db/db';
const app = new Elysia().decorate('db', new BooksDatabase)
app.get('/books', ({ db }) => db.getBooks());
app.post('/books', ({db, body }) => db.addBook(body), {
body: t.Object({
name: t.String(),
author: t.String()
})
})
app.put('/books', ({ db, body }) => db.updateBook(body.id, {name: body.name, author: body.author }), {
body: t.Object({
id: t.Number(),
name: t.String(),
author: t.String()
})
})
app.get('/books/:id', () => 'books')
app.delete('/books/:id', () => 'books')
app.listen(8081)
console.log(
`🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}`
);Elysia enables us to access path parameters using the params object from the Elysia context. We've used path parameters in the remaining endpoints for enhanced flexibility.
Let's modify the code and use the path parameters for the remaining endpoints.
import { Elysia, t } from "elysia";
import { BooksDatabase } from './db/db';
const app = new Elysia().decorate('db', new BooksDatabase)
app.get('/books', ({ db }) => db.getBooks());
app.post('/books', ({db, body }) => db.addBook(body), {
body: t.Object({
name: t.String(),
author: t.String()
})
})
app.put('/books', ({ db, body }) => db.updateBook(body.id, {name: body.name, author: body.author }), {
body: t.Object({
id: t.Number(),
name: t.String(),
author: t.String()
})
})
app.get('/books/:id', ({db, params }) => db.getBook(parseInt(params.id)))
app.delete('/books/:id', ({db, params }) => db.deleteBook(parseInt(params.id)))
app.listen(8081)
console.log(
`🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}`
);https://github.com/julekgwa/Elysia-with-Bun-runtime
In this blog post, we've embarked on a journey to build a robust RESTful API using Bun and ElysiaJS. These tools, when combined, offer a seamless development experience with features like type safety, flexible routing, and easy database integration.