One of the most exciting parts of building this site has been working with Cloudflare’s developer tools. They’re truly cloud-native, designed for performance, and offer immense value to developers looking to build scalable applications.
Why Cloudflare D1?
I’ve had my eye on Cloudflare D1 for a while. NoSQL databases are everywhere, but relational databases still shine when structured data is key. D1 delivers the power of SQL without the usual headaches of latency, replication, or server management.
What Makes D1 Stand Out?
- SQLite-based database that seamlessly integrates with Cloudflare Workers.
- No latency concerns, no server maintenance, no replication issues—Cloudflare handles it all.
- Separate local and production databases, easily maintained with SQL files.
- workers-qb — a lightweight TypeScript library that simplifies SQL queries.
For a full breakdown, check out Cloudflare’s D1 announcement.
Building a Sample Project with D1
My blog primarily uses Markdown as a datastore, so I needed to create an app that would benefit from structured data.
I decided to create a shirt catalog using D1 as the backend. It’s a fun way to experiment, document my designs, and build a more interactive content system.
Project Goals
Setting Up the Cloudflare D1
Thanks to Wrangler, setting up D1 is quick and painless.
1️⃣ Create the Database
Run:
wrangler d1 create <db_name>
Your database will now appear in Cloudflare’s Dashboard under Workers & Pages → D1.
2️⃣ Configure Wrangler
Add the reference to wrangler.toml
:
[[d1_databases]]
binding = "DB" # customizable
database_name = "[DB_NAME]"
database_id = "[DB_ID]"
3️⃣ Generate TypeScript Types
npm run cf-typegen
Now, TypeScript can reference the database using the binding name.
4️⃣ Populate the Database with SQL
wrangler d1 execute <db_name> --local --schema.sql
DROP TABLE IF EXISTS Shirts;
CREATE TABLE
IF NOT EXISTS Shirts (
id INTEGER PRIMARY KEY,
name TEXT,
createdDate TEXT
);
INSERT INTO
Shirts (
id,
name,
createdDate
)
VALUES
(
1,
'Rainbow Deciduous Tee',
"2024-03-02 21:16:35.000"
);
Local vs. Remote Databases
Key Concept:
- --remote updates the Cloudflare-hosted database.
- --local updates the database inside your project’s .wrangler/ folder. 💡 You can’t call the remote database from your local environment—this is by design, preventing accidental production data modifications.
For more details, check out Cloudflare’s discussion on this design choice.
Using D1 with Remix
1️⃣ Fetching Data with a Remix Loader
I’m using worker-qb to simplify queries.
import type { LoaderFunctionArgs } from "@remix-run/cloudflare";
import { D1QB, OrderTypes } from 'workers-qb'
export const loader = async ({ context }: LoaderFunctionArgs) => {
const { env } = context.cloudflare;
const qb = new D1QB(env.DB)
const query = await qb
.fetchAll<Shirt>({
tableName: 'shirts',
orderBy: { 'createdDate': OrderTypes.DESC },
})
.execute();
const shirts = query.results;
return json(shirts);
};
Key Concept: Use LoaderFunctionArgs
to ensure type inference when passing data to other functions.
2️⃣ Using the Data in the Page Component
export default function ShirtsIndex() {
const shirts = useLoaderData<typeof loader>();
return (
<>
<h1>Shirts</h1>
{shirts.map((shirt) =>
<div key={shirt.id}>
{shirt.name}
</div >
)}
</>
)
}
Final Thoughts & Next Steps
Once I got past the two key concepts—understanding how D1 separates local and production databases, and optimizing data queries in Remix—everything else fell into place.
Next, I’ll be:
- Exploring advanced data retrieval and storage in Cloudflare D1.
- Adapting my SQL Server background to this serverless model.
- Experimenting with performance optimizations for production-ready apps.
Epilogue: Features I Need to Build
Writing this post made me realize I’m missing key blog features. Here’s what I've added to my to-do list:
- New layout components: Aside, Callout, Image Gallery, Modal, and Table of Contents.
- Improved Markdown handling: Better whitespace control and inline elements.
- Syntax highlighting for code blocks—especially in Remix.
- Custom endmarks: Dynamic designs using css-doodle.
Plenty more to build—stay tuned! 🚀