A sign-my-guestbook form backed by libsql, with every entry safely escaped.
Visitors leave a name and a message, and the page lists everyone who signed. Deploying this example provisions a libsql database and links it into the service as GUESTBOOK_DB, a tokenless connection string; the platform injects the credentials at the network edge, so there is no database secret in the code.
The service creates its table on demand with CREATE TABLE IF NOT EXISTS, stores each entry with a parameterised query (user input can never be treated as SQL), and escapes every name and message before rendering (a message can never inject HTML or script). It redirects after a successful POST, so refreshing the page does not submit the form again.
// A classic guestbook: visitors leave a name and a message, and the page shows // everyone who signed. Data lives in a libsql database that was created and // linked for you at deploy time. Its connection string arrives in GUESTBOOK_DB // as a tokenless URL; the platform injects the credentials at the network edge, // so there is never a database secret in your code. import { createClient } from "npm:@libsql/client@0.14.0/web"; const db = createClient({ url: Deno.env.get("GUESTBOOK_DB")! }); type Entry = { name: string; message: string; created_at: string }; export default async (req: Request): Promise<Response> => { // Create the table on demand. IF NOT EXISTS makes this safe to run every time. await db.execute( `CREATE TABLE IF NOT EXISTS entries ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, message TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime('now')) )`, ); const { pathname } = new URL(req.url); if (req.method === "POST" && pathname === "/") { const form = await req.formData(); const name = String(form.get("name") ?? "").trim().slice(0, 80); const message = String(form.get("message") ?? "").trim().slice(0, 500); if (!name || !message) { // Expected validation miss: 422, not a 5xx, so the run is not marked failed. return render(await recentEntries(), "Please fill in both fields.", 422); } // Parameterised query: values are bound, never spliced into the SQL string. await db.execute({ sql: "INSERT INTO entries (name, message) VALUES (?, ?)", args: [name, message], }); // Redirect after POST so a page refresh does not submit the form again. return new Response(null, { status: 303, headers: { location: "/" } }); } if (req.method === "GET" && pathname === "/") { return render(await recentEntries()); } return new Response("Not found\n", { status: 404 }); }; async function recentEntries(): Promise<Entry[]> { const result = await db.execute( "SELECT name, message, created_at FROM entries ORDER BY id DESC LIMIT 100", ); return result.rows.map((row) => ({ name: String(row.name), message: String(row.message), created_at: String(row.created_at), })); } function render(entries: Entry[], notice = "", status = 200): Response { const list = entries.length === 0 ? `<p class="empty">No messages yet. Be the first to sign.</p>` : entries .map( (entry) => `<li> <p class="msg">${escapeHtml(entry.message)}</p> <p class="meta">${escapeHtml(entry.name)} · ${escapeHtml(entry.created_at)}</p> </li>`, ) .join("\n "); const body = `<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Guestbook</title> <style> body { max-width: 40rem; margin: 3rem auto; padding: 0 1.25rem; font: 16px/1.6 system-ui, sans-serif; color: #16181d; } h1 { letter-spacing: -0.02em; } form { display: grid; gap: 0.6rem; margin: 1.5rem 0 2rem; } input, textarea { font: inherit; padding: 0.55rem 0.7rem; border: 1px solid #d7dae0; border-radius: 6px; } button { justify-self: start; font: inherit; padding: 0.55rem 1rem; border: 0; border-radius: 6px; background: #16181d; color: #fff; cursor: pointer; } .notice { color: #b4232a; } ul { list-style: none; padding: 0; display: grid; gap: 1rem; } li { border: 1px solid #e6e8ec; border-radius: 8px; padding: 0.8rem 1rem; } .msg { margin: 0 0 0.35rem; } .meta { margin: 0; color: #5c636e; font-size: 0.85rem; } .empty { color: #5c636e; } </style> </head> <body> <h1>Guestbook</h1> ${notice ? `<p class="notice">${escapeHtml(notice)}</p>` : ""} <form method="post" action="/"> <input name="name" placeholder="Your name" maxlength="80" required /> <textarea name="message" placeholder="Say hello" rows="3" maxlength="500" required></textarea> <button type="submit">Sign the guestbook</button> </form> <ul> ${list} </ul> </body> </html> `; return new Response(body, { status, headers: { "content-type": "text/html; charset=utf-8" }, }); } // Never write user input into HTML without escaping it first, or a message like // "<script>...</script>" would run in every visitor's browser. function escapeHtml(value: string): string { return value .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); }
Go beyond what seems possible.