In this tutorial, we'll walk through building a high-performance, edge-ready CRUD application. We'll use SvelteKit for the framework, Drizzle ORM for type-safe database access, and Cloudflare D1 for our SQLite database at the edge. Plus, we'll give it a unique Neubrutalist "Flat Comic" UI style using Tailwind CSS.
pnpm (or npm/yarn).First, let's create a new SvelteKit project and install the necessary dependencies for Drizzle and Cloudflare.
pnpm dlx sv create my-blog
cd my-blog
pnpm add drizzle-orm @libsql/client
pnpm add -D drizzle-kit wrangler @sveltejs/adapter-cloudflare
In Drizzle, we define our database structure using TypeScript. Create a file at src/lib/server/db/schema.ts:
// src/lib/server/db/schema.ts
import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
export const todo = sqliteTable('todo', {
id: text('id')
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
text: text('todo')
});
Drizzle turns this TypeScript definition into SQL migrations and provides full type safety for your queries.
Cloudflare D1 is a serverless SQLite database. We configure it in wrangler.jsonc:
{
"name": "my-blog",
"compatibility_date": "2024-01-01",
"d1_databases": [
{
"binding": "DB",
"database_name": "my-db",
"database_id": "your-id-here"
}
]
}
The wrangler CLI is your gateway to Cloudflare's edge. Here is the step-by-step workflow to get your database live:
Authentication: Link your terminal to your Cloudflare account.
pnpm wrangler login
Creation: Create the physical D1 database on Cloudflare's network.
pnpm wrangler d1 create blog
Note: Copy the database_id from the terminal output and paste it into your wrangler.jsonc!
Local Execution: Run your migrations against your local development database.
pnpm wrangler d1 migrations apply blog --local
Production Sync: When you're ready to go live, apply those same migrations to the remote database.
pnpm wrangler d1 migrations apply blog --remote
Inspection: Want to see your data from the CLI?
pnpm wrangler d1 execute blog --remote --command "SELECT * FROM todo;"
Use these commands to manage your database lifecycle efficiently:
| Action | Command |
|---|---|
| Generate Migration | pnpm drizzle-kit generate (Creates SQL files from schema) |
| Push Schema (Sync) | pnpm drizzle-kit push (Fast sync without migrations) |
| Apply Local Migrations | pnpm wrangler d1 migrations apply blog --local |
| Apply Remote Migrations | pnpm wrangler d1 migrations apply blog --remote |
| Reset Local Database | rm -rf .wrangler/state/v3/d1 (Wipes local D1 data/cache) |
| Drop Remote Table | pnpm wrangler d1 execute blog --remote --command "DROP TABLE todo;" |
| List All Databases | pnpm wrangler d1 list |
| Open DB Studio | pnpm drizzle-kit studio (Visual explorer for your data) |
To make our database accessible everywhere in our app, we'll use SvelteKit's hooks.server.ts. This runs on every request.
// src/hooks.server.ts
import { drizzle } from 'drizzle-orm/d1';
import { handle } from '@sveltejs/kit';
export const handle = async ({ event, resolve }) => {
// Inject the Drizzle client into event.locals
event.locals.db = drizzle(event.platform?.env.DB);
return resolve(event);
};
Don't forget to update src/app.d.ts to include the db type in Locals!
SvelteKit uses +page.server.ts to handle data fetching and form submissions.
export const load = async ({ locals }) => {
const todos = await locals.db.select().from(todo);
return { todos };
};
export const actions = {
create: async ({ request, locals }) => {
const formData = await request.formData();
const text = formData.get('text');
await locals.db.insert(todo).values({ text });
return { success: true };
},
// ... update and delete actions
};
Now for the fun part! We'll use Svelte 5 runes and Tailwind CSS to build a UI that looks like a comic book panel.
In src/routes/+page.svelte:
<script lang="ts">
import { enhance } from '$app/forms';
let { data } = $props();
</script>
<div class="p-8 bg-cyan-50 min-h-screen font-mono">
<!-- Header with Neubrutalist Style -->
<h1 class="text-6xl font-black bg-yellow-400 border-4 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] px-4 py-2 -rotate-2 inline-block">
MY COMIC BLOG
</h1>
<!-- Create Card -->
<section class="mt-12 bg-white border-4 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] p-6 mb-8 uppercase italic font-bold">
<form method="POST" action="?/create" use:enhance>
<input name="text" class="w-full border-4 border-black p-4 text-xl" placeholder="Write something..."/>
<button class="mt-4 bg-rose-400 border-4 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] active:translate-x-1 active:translate-y-1 active:shadow-none transition-all">
POW! ADD IT!
</button>
</form>
</section>
<!-- List Panels -->
<div class="grid gap-6">
{#each data.todos as item}
<article class="bg-white border-4 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] p-6">
<p class="text-2xl font-black">{item.text}</p>
</article>
{/each}
</div>
</div>
Building with SvelteKit and Drizzle on Cloudflare D1 is a developer experience dream. You get full type safety from your database to your UI, incredible performance at the edge, and the freedom to style your app with unique aesthetics like the "Flat Comic" look.
Ready to deploy? Just run pnpm wrangler deploy and your comic blog is live!