···11+import Database from "better-sqlite3";
22+import {resolve, dirname} from "node:path";
33+import { fileURLToPath } from "node:url";
44+55+const __dirname = dirname(fileURLToPath(import.meta.url));
66+77+export interface Entry {
88+ id: number;
99+ user_did: string;
1010+ user_handle: string;
1111+ message: string;
1212+ created_at: string;
1313+}
1414+1515+export interface NewEntry {
1616+ user_did: string;
1717+ user_handle: string;
1818+ message: string;
1919+}
2020+2121+2222+// TODO: allow customization of DB_PATH
2323+// Set db location
2424+const DB_PATH = resolve(__dirname, "..", "guestbook.db");
2525+2626+const db = new Database(DB_PATH);
2727+2828+db.pragma("journal_mode = WAL");
2929+3030+// Setup new db
3131+db.exec(`
3232+ CREATE TABLE IF NOTE EXISTS entries (
3333+ id INTEGER PRIMARY KEY AUTOINCREMENT,
3434+ user_id TEXT NOT NULL
3535+ user_handle TEXT NOT NULL,
3636+ message TEXT NOT NULL,
3737+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
3838+ )
3939+ `);
4040+4141+ console.log("Database ready at", DB_PATH);
4242+4343+ // ===
4444+ // SQL functions
4545+ // ===
4646+4747+ const stmtGetAll = db.prepare(`
4848+ SELECT id, user_did, user_handle, message, created_at
4949+ FROM entries
5050+ ORDER BY created_at DESC
5151+ `);
5252+5353+const stmtInsert = db.prepare(`
5454+ INSERT INTO entries (user_did, user_handle, message)
5555+ VALUES (@user_did, @user_handle, @message)
5656+`);
5757+5858+const stmtDelete = db.prepare(`
5959+ DELETE FROM entries WHERE id = @id AND user_did = @user_did
6060+`);
6161+6262+export function getAllEntries(): Entry[] {
6363+ // IT CAN JUST DO THAT?? `as` is cool!
6464+ return stmtGetAll.all() as Entry[];
6565+}
6666+6767+export function createEntry(entry: NewEntry): Entry {
6868+ const result = stmtInsert.run(entry);
6969+7070+ return {
7171+ id: Number(result.lastInsertRowid),
7272+ created_at: new Date().toISOString(),
7373+ ...entry,
7474+ }
7575+}
7676+7777+/**
7878+ * deletes an entry in the DB if the id and userDID are both accurate.
7979+ * @param {number} id - The ID of the post.
8080+ * @param {string} userDid - The DID provided by the user.
8181+ * @returns {boolean} where changes made to the DB?
8282+ */
8383+export function deleteEntry(id: number, userDid: string): boolean {
8484+ const result = stmtDelete.run({ id, user_did: userDid});
8585+ return result.changes > 0;
8686+}
+66-8
src/index.ts
···33import { readFileSync } from "node:fs";
44import { resolve, dirname } from "node:path";
55import { fileURLToPath } from "node:url";
66+import { getAllEntries, createEntry, deleteEntry } from "./db.js";
6778// In ESM (ES Modules), __dirname doesn't exist like it does in CommonJS.
89// This is a common pattern you'll see everywhere — it reconstructs __dirname
···11121213const app = new Hono();
13141414-// Serve the main page
1515-// Hono uses a similar pattern to Rust frameworks like Axum:
1616-// app.get("/path", handlerFunction)
1717-// The `c` parameter is the "Context" — it holds the request and
1818-// gives you methods to build responses. Similar to how Axum handlers
1919-// receive extractors.
1515+// ===
1616+// User facing routes
1717+// ===
1818+2019app.get("/", (c) => {
2120 const html = readFileSync(resolve(__dirname, "public/index.html"), "utf-8");
2221 return c.html(html);
2322});
24232525-// A simple API route so you can see how JSON responses work.
2626-// We'll replace this with real guestbook routes later.
2424+// ===
2525+// API routes
2626+// ===
2727+2728app.get("/api/health", (c) => {
2829 return c.json({ status: "ok", timestamp: new Date().toISOString() });
2930});
30313232+/** GET /api/entries - return all guest book entries */
3333+app.get("/api/entries", (c) => {
3434+ const entries = getAllEntries();
3535+ return c.json(entries);
3636+});
3737+3838+app.post("/api/entries", async (c) => {
3939+ const body = await c.req.json();
4040+4141+ // basic validation, replace later
4242+ if (!body.message || typeof body.message !== "string") {
4343+ return c.json({ error: "Message is required" }, 400);
4444+ }
4545+4646+ if (!body.user_did || !body.user_handle) {
4747+ return c.json({ error: "User info is required" }, 400);
4848+ }
4949+5050+ const message = body.message.trim().slice(0, 500);
5151+ if (message.length === 0) {
5252+ return c.json({ error: "Message cannot be empty" }, 400);
5353+ }
5454+5555+ const entry = createEntry({
5656+ user_did: body.user_did,
5757+ user_handle: body.user_handle,
5858+ message,
5959+ });
6060+6161+6262+ return c.json(entry, 201);
6363+6464+});
6565+6666+app.delete("/api/entries/:id", async (c) => {
6767+ const id = Number(c.req.param("id"));
6868+ const body = await c.req.json();
6969+7070+ // Right now I am trusting whatever DID the user provides, this will change after auth is added
7171+7272+ if (!body.user_did) {
7373+ return c.json({ error: "User info is required" }, 400);
7474+ }
7575+7676+ const deleted = deleteEntry(id, body.user_did);
7777+7878+ if (!deleted) {
7979+ return c.json({ error: "Entry not found" }, 404);
8080+ }
8181+8282+ return c.body(null, 204);
8383+8484+});
8585+8686+// ===
3187// Start the server
8888+// ===
8989+3290const port = 3000;
3391console.log(`Guestbook server running at http://localhost:${port}`);
3492serve({ fetch: app.fetch, port });