Introduction a Bun.sh
Tags: french, bunjs
Published: 2022-09-27 13:47:49

Bun est un nouveau runtime JavaScript, comme Node ou Deno, il a commencé à faire parler de lui en Juillet de cette année, notamment via un tweet de son créateur.

{% embed https://twitter.com/jarredsumner/status/1544460933753229312?ref_src=twsrc%5Etfw %}

Bun est construit autour de 3 principaux piliers:

- Démarrage rapide
- Gros niveau de performance
- Outil complet (package manager, bundler, transpiler, et beaucoup d'api disponibles)

Pour tenir ces promesses, Bun utilise le moteur [JavaScriptCore](https://github.com/WebKit/WebKit/tree/main/Source/JavaScriptCore) (qui d'après la page du site démarre et fonctionne plus vite que V8).
Bun est en grande partie écrit en [ZIG](https://ziglang.org/) notamment le transpiler JSX/TypeScript, le client npm ou encore le client http ... .
La grande majorité des API disponibles a été réécrite de zéro.

Dans cet article, j'aimerais utiliser Bun pour écrire une petite API permettant de gérer des inscriptions à une newsletter, rien de fou niveau métier, il s'agit juste d'un prétexte pour écrire une petite application et utiliser l'API SQLite nativement supportée par Bun.

## Installation

Bun fournit un script pour l'installation, pour le moment il est possible de l'installer sur macOs x64 et Apple Sillicon, Linux x64, et le Windows Subsystem for Linux.

Pour ma part, étant repassé sur PC depuis peu, je vais utiliser le WSL avec Ubuntu 20.04. 

Pour exécuter le script d'installation:

```
curl https://bun.sh/install | bash
```
 
Pour vérifier que l'installation fonctionne et avoir une première idée des commandes disponibles:

```
bun help
```
Vous devriez avoir cet affichage:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nnwpaiag718i3hnowdfh.png)

Je ne vais pas rentrer dans le détail de toutes les commandes dans cet article, mais davantage me concentrer sur celles utiles pour construire mon exemple. La documentation est disponible [ici](https://github.com/oven-sh/bun#reference).

## Premier script

Pour valider le fonctionnement de  mon installation, j'ai utilisé le script d'exemple présent sur la homepage du site.

```javascript
export default {  
    port: 3000,  
    fetch(request) {  
        return new Response("Welcome to Bun!");  
    },
};
```
_le code est disponible dans le fichier premier-script.js_

Pour lancer le script:

```
bun run premier-script.js
```

Une visite sur http://localhost:3000 doit vous répondre "Welcome to Bun!".

Parfait, ça fonctionne ! 
Bun supporte aussi TypeScript, pour ajouter les types à mon projet, je vais utiliser la commande suivante:

```
bun add -d bun-types
```

et ensuite ajouter la propriété `type` dans le fichier tsconfig.json:

```javascript
{
  "compilerOptions": {
    "types": ["bun-types"]
  }
}
```
on peut donc réécrire notre premier script en TypeScript, ce qui donne quelque chose comme :

```typescript
Bun.serve({  
    fetch(req: Request) {  
        return new Response("Welcome to Bun with TS!");  
    },    
    port: 3000,  
});
```
_le code est disponible dans le fichier premier-script-ts.ts_ 

Un truc intéressant, c'est le support natif des fichiers `.env` par la commande `bun run`, ce qui nous permet de configurer facilement nos applis à l'exécution. Les variables d'environnement sont disponibles dans process.env.XXXX.

La lecture des fichiers est faite selon l'ordre suivant:

1. `.env.local`
2. if ($NODE_ENV === "production") `.env.production` else `.env.development`
3. `.env`

Un exemple de l'utilisation des variables d'environnement est disponible dans le fichier `using-env.ts`


## Développer une API simple avec Bun

Comme je l'ai dit dans l'intro, mon objectif est d'écrire une API très simple pour gérer les inscriptions à une newsletter, un `POST` avec un email, une petite vérification si l'email est déjà présent en base (pour simuler une logique métier) et la sauvegarde dans une table SQLite. Une seconde route permettra de récupérer les inscriptions.

En Node, j'ai l'habitude d'utiliser Express pour développer mes API, malheureusement ce n'est pas (encore ?) possible avec Bun (certaines API utilisées par Express sont propres à V8 et nécessitent le développement d'un polyfill).

Lorsque j'ai commencé à suivre le projet, courant juillet, je suis un peu resté sur ma faim à ce niveau, impossible pour moi de continuer mes tests, et il était difficile d'envisager Bun pour autre chose que des applications React.

En creusant un peu le sujet, cette semaine, j'ai découvert le framework [Hono](https://honojs.dev/) qui est supporté par Bun ainsi que par d'autres runtimes comme les Workers Cloudflare et deno entre autres.

Pour ajouter hono dans le projet:

```
bun add hono
```

Et une première api simple:

```typescript
import { Hono } from "hono";

const app = new Hono();

app.get("/", (c) => c.text("Hello! Hono!"));

Bun.serve({
  port: parseInt(process.env.PORT),
  fetch: app.fetch,
});
```
_le code est disponible dans le fichier hono.ts_
 
Pour la base de données, Bun supporte nativement SQLite, via le module `bun:sqlite`, ce module n'a pas besoin d'être installé il est directement intégré dans Bun. 
La documentation du module est disponible [ici](https://github.com/oven-sh/bun#bunsqlite-sqlite3-module). 

En l'état le module semble assez complet, mais pour cet exemple je ne vais pas avoir besoin de grand chose: j'ai besoin de récupérer un ensemble d'enregistrements, un enregistrement unitaire et d'en insérer un nouveau. J'ai aussi besoin de créer la table.


Bun propose de créer un objet de type `Database` qui nous servira pour les différentes opérations. 

```typescript
import { Database } from "bun:sqlite";

const db = new Database("mydb.sqlite");
```

Pour créer la table et insérer un enregistrement, nous utilisons la méthode `run`.

```typescript
db.run(
  "CREATE TABLE IF NOT EXISTS foo (id INTEGER PRIMARY KEY AUTOINCREMENT, greeting TEXT)"
);
db.run("INSERT INTO foo (greeting) VALUES (?)", "Welcome to bun!");
db.run("INSERT INTO foo (greeting) VALUES (?)", "Hello World!");
```

Pour les requêtes la méthode `query` est suivie de `all` ou `get`

```typescript
// get the first row
db.query("SELECT * FROM foo").get();
// { id: 1, greeting: "Welcome to bun!" }

// get all rows
db.query("SELECT * FROM foo").all();
// [
//   { id: 1, greeting: "Welcome to bun!" },
//   { id: 2, greeting: "Hello World!" },
// ]

// get all rows matching a condition
db.query("SELECT * FROM foo WHERE greeting = ?").all("Welcome to bun!");
// [
//   { id: 1, greeting: "Welcome to bun!" },
// ]

// get first row matching a named condition
db.query("SELECT * FROM foo WHERE greeting = $greeting").get({
  $greeting: "Welcome to bun!",
});
// [
//   { id: 1, greeting: "Welcome to bun!" },
// ]
```
Dans la doc de Bun, il y a un benchmark réalisé à partir de la base Northwind Traders (souvenir de Access :) ), qui semble donner des résultats prometteurs.

Voici donc le code correspondant à la petite API de gestion des inscriptions, je passe volontairement sur les détails concernant l'utilisation de Hono.

```typescript
import { Hono } from "hono";
import { logger } from "hono/logger";
import { Database } from "bun:sqlite";

interface Subscription {
  id: number;
  email: string;
}

  

const db = new Database();
// Pour stocker la base dans un fichier
// const db = new Database("newsletter.sqlite");

db.run(
  "CREATE TABLE IF NOT EXISTS subscription (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT)"
);

const app = new Hono();

app.use("*", logger());

app.get("/", (c) => {
  const subs = db.query("SELECT * FROM subscription").all();
  return c.json(subs);
});

app.post("/", async (c) => {
  const body = await c.req.json<Subscription>();
  const sub = db
    .query("SELECT * From subscription WHERE email = ?")
    .get<Subscription>(body.email);
  if (!sub) {
    db.run("INSERT INTO subscription (email) VALUES (?)", body.email);
    return c.json(body, 201);
  }
  return c.json(body, 409);
});

Bun.serve({
  port: parseInt(process.env.PORT),
  fetch: app.fetch,
});

```
_le code est disponible dans le fichier newsletter.ts_

## Pour conclure

Je trouve le projet vraiment intéressant, Jarred Sumner a fait un boulot titanesque pour en arriver là. Au delà de l'aspect performant des choses, j'aime particulièrement le tout en un, d'autant plus que je pense utiliser Bun pour prototyper rapidement des idées. 

Apres ma petite déception de début juillet, je suis content d'avoir découvert Hono qui me permet d'envisager véritablement de continuer avec Bun.

Bref, je vais expérimenter sur quelques idées et je garde un œil sur le projet, d'autant plus qu'il y a maintenant une structure pour le soutenir, [Oven](https://oven.sh/).

Les sources des exemples sont disponibles [ici](https://github.com/fclaeys/bun-intro).


Crédits: Photo by <a href="https://unsplash.com/@gilndjouwou?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Gil Ndjouwou</a> on <a href="https://unsplash.com/s/photos/bun?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>