theme | title | class | highlighter | drawings | transition | mdc | |
---|---|---|---|---|---|---|---|
bricks |
Hono with database on browser playground |
text-center |
shiki |
|
slide-left |
true |
How to develop a Workers Like execution environment in the browser.
Build a REST API from prompt and screenshots with LLM.
- 🏗️ Prompt to REST - Build a REST API from prompt and screenshots with LLM.
- 🧪 Browser Playground - Test and Develop APIs in the browser.
- 🚀 One Click Deploy - Deploy APIs to Cloudflare Workers with a one click.
- 🎨 Local Build - Clone API in your PC by CLI.
::items::
15yo, open government, mental health, and journalism mitoujr23
18yo, engineer playing at LLM SFC24, seccmp23 inaridiy.eth
17yo, engineer, LLM, seccamp23 @moons_dev(X)
We had all the parts by chance. It looked fun.
- Hono Playground - Hono runs in the browser!
- SQLite Wasm - SQLite in the browser!
- V0 - V0 awesome!
- PoC - It works surprisingly well with ChatGPT4!
V0 is a service created by vercel that allows you to create a UI from a prompt.
graph TD
subgraph "Vercel"
subgraph "Frontend"
A[Next.js App Router]
B[Jotai]
C[shadcn/ui]
end
end
subgraph "Cloudflare"
subgraph "Backend"
subgraph "Main Backend"
E[Hono.js]
G[Cloudflare Workers]
F[Drizzle ORM]
end
subgraph "Generation Durable Object"
J[Durable Object]
O[API Generation Logic]
end
subgraph "Storage"
H[D1 SQLite Database]
I[R2 Object Storage]
end
end
end
subgraph "External Services"
N[Claude Haiku LLM]
K[Clerk]
end
subgraph "Development & Deployment"
L[pnpm workspace]
M[Github Actions]
end
A --> B
A --> C
G -->|Hosts| E
E -->|ORM| F
F -->|Connects to| H
G -->|Calls during API generation| J
J -->|Executes| O
G -->|Stores data| I
G -->|Calls LLM| N
J -->|Calls LLM| N
K --> A
K --> E
L --> A
L --> E
M --> Vercel
M --> Cloudflare
classDef environment fill:#f0f0f0,stroke:#333,stroke-width:4px;
classDef frontend fill:#f9f,stroke:#333,stroke-width:2px;
classDef backend fill:#bbf,stroke:#333,stroke-width:2px;
classDef durable fill:#ffb,stroke:#333,stroke-width:2px;
classDef storage fill:#bff,stroke:#333,stroke-width:2px;
classDef external fill:#fdb,stroke:#333,stroke-width:2px;
classDef devops fill:#fbb,stroke:#333,stroke-width:2px;
class Vercel,Cloudflare environment;
class A,B,C frontend;
class E,F,G backend;
class J,O durable;
class H,I storage;
class N,K external;
class L,M devops;
Genereated by Claude3.5
Claude3.5 made the tech stack diagram shown earlier.
::right::
::right::
How to run Hono with D1 in your browser
Use esbuild to bundle with external packages into a single file
Bundle the npm package type definitions into a single file and insert it into the editor
Execute Hono with D1 in the browser#1
HonoJS APIs are simple functions that take a WebRequest and return a Response. When you pass these functions to modern runtimes like Workers or Deno, they work as web servers.
Execute Hono with D1 in the browser#2
If you write code like this, you can create a runtime that works in the browser!
import { Hono } from "hono";
const app = new Hono();
app.get("/teapot", (c) => c.text("I'm a teapot"), 418)
... // Add more routes
const request = new Request(...);
const response = await app.fetch(request);//Ultra simple
Execute Hono with D1 in the browser#3
The Database is a SQLiteWasm covered with a Wrapper that makes it look like a D1.
import type { Database, SqlValue } from "@sqlite.org/sqlite-wasm";
export class D1Wrapper {
private sqlite: Database;
private stmt: { sql: string; binds?: SqlValue[] } | null = null;
constructor(sqlite: Database) {
this.sqlite = sqlite;
}
prepare(sql: string) { ... }
bind(...args: SqlValue[]) { ... }
run() { ... }
all() { ... }
}
Execute Hono with D1 in the browser#4
Binding by passing the DB as the second argument to the fetch method of Hono
import SQLite from "@sqlite.org/sqlite-wasm";
const db = new SQLite.oo1.DB();
app.get("/db", async (c) => {
c.env.DB.prepare("SELECT * FROM table").all(); // Use the database
});
const request = new Request(...);
const response = await app.fetch(request, {
DB: new D1Wrapper(db), // Pass the database
});
Build with external packages#1
Hanabi uses esbuild to build its code. In doing so, external packages are also bound to a single file by retrieving them from esm.sh and bundling them
import type { Plugin } from "esbuild-wasm";
export const httpPlugin: Plugin = {
name: "http",
setup(build) {
build.onResolve({ filter: /.*/ }, (args) => ({
path: new URL(args.path, "https://esm.sh").toString(),
namespace: "http-url",
}));
build.onLoad({ filter: /.*/, namespace: "http-url" }, async (args) => {
const contents = await fetch(args.path).then((res) => res.text());
return { contents };
});
},
};
import * as esbuild from "esbuild-wasm";
import { httpPlugin } from "./esbuild-http-plugin";
export const compile = async (code: string): Promise<string> => {
await initEsbuild();
const importSource = `import { jsx, Fragment } from 'https://esm.sh/hono/jsx'\n`;
const transformed = await esbuild.build({
...,
plugins: [httpPlugin], // Add the plugin
});
const output = transformed?.outputFiles?.[0].text;
if (!output) throw new Error("Failed to compile", { cause: transformed?.errors });
return output;
};
Typescript Editor#1
The editor of Hanabi's Playground is monaco-editor.
However, like DenoLSP, it has the ability to automatically insert type information from external packages
Typescript Editor#2
When you get a package with esm.sh
, it will be given the header x-typescript-types
.
Typescript Editor#3
Typescript Editor#4
Using Typescript's declare module syntax, dts files are bundled using a simple algorithm
export const getBundledDts = async (module: string) => {
const indexDotDtsUrl = await getIndexeDtsUrl(module);
if (!indexDotDtsUrl) return "";
let queue = [indexDotDtsUrl];
const dtsFiles: Record<string, string> = {};
while (true) {
...
}
// バンドルされた型定義を生成
let formatted = Object.entries(dtsFiles)
.map(([url, dts]) => `declare module "${replacePath(url)}"{${dts}}`)
.join("\n");
formatted += `declare module "${module}" {export * from "${replacePath(indexDotDtsUrl)}"}`;
return formatted;
};