All docs / Build
Recipes
Complete, working apps to copy: a CRUD API, short links with click stats, uploads, background jobs, crons, chat, push notifications, WHOIS and more.
Open in the consoleEach recipe lists the bindings it needs, then the code. Paste the code into an app (or a project deployed with rr deploy), add the bindings and run the SQL where there is some.
JSON API on D1 (posts CRUD)
Bindings: D1 as DB. Schema:
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
body TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL
);const json = (data, status = 200) => Response.json(data, { status });
export default {
async fetch(request, env) {
const url = new URL(request.url);
const m = url.pathname.match(/^\/api\/posts(?:\/(\d+))?$/);
if (!m) return json({ error: "not found" }, 404);
const id = m[1] ? Number(m[1]) : null;
if (request.method === "GET" && id === null) {
const { results } = await env.DB.prepare(
"SELECT id, title, created_at FROM posts ORDER BY id DESC LIMIT 50",
).all();
return json(results);
}
if (request.method === "GET") {
const post = await env.DB.prepare("SELECT * FROM posts WHERE id = ?").bind(id).first();
return post ? json(post) : json({ error: "no such post" }, 404);
}
if (request.method === "POST" && id === null) {
let input;
try { input = await request.json(); } catch { return json({ error: "body must be JSON" }, 400); }
if (typeof input.title !== "string" || !input.title.trim()) return json({ error: "title is required" }, 400);
const res = await env.DB.prepare("INSERT INTO posts (title, body, created_at) VALUES (?, ?, ?)")
.bind(input.title.trim(), String(input.body ?? ""), Date.now()).run();
return json({ id: res.meta.last_row_id }, 201);
}
if (request.method === "DELETE" && id !== null) {
const res = await env.DB.prepare("DELETE FROM posts WHERE id = ?").bind(id).run();
return res.meta.changes ? new Response(null, { status: 204 }) : json({ error: "no such post" }, 404);
}
return json({ error: "method not allowed" }, 405);
},
};