Harnessing Cloudflare D1 and Drizzle ORM for Scalable Serverless Apps in Next.js
NITISH KUMAR SONTHALIA
Posted on November 25, 2024
Building serverless APIs has become a game-changer for modern application development. With tools like Cloudflare D1, a lightweight serverless database, and Drizzle ORM, a type-safe and developer-friendly ORM, managing data for APIs can be simple, efficient, and scalable. These technologies work seamlessly together to eliminate the complexity of traditional database hosting and simplify data interactions.
In this blog, we'll show you how to use Cloudflare D1 as the serverless database backend for your API application. While the API itself can be hosted on Cloudflare workers, we'll focus on deploying the database to Cloudflare's edge network, ensuring global availability, low latency, and effortless scaling. Drizzle ORM will handle the database operations, making queries and schema management straightforward and type-safe.
By the end of this guide, you'll have a serverless database deployed with Cloudflare D1 and a clear understanding of how to integrate it with your API application using Drizzle ORM. Let's dive in!
Prerequisites
- Node JS.
- Cloudflare account.
- Wrangler[CLI tool for Cloudflare] setup on local.
Setting up Project
Run the following command in the Terminal to create a next.js application to be deployed to Cloudflare.
npm create cloudflare@latest -- drizle-d1-demo --framework=next
While app creation select the option as shown in the images:
- What would you like to start with? Framework Starter
- Which development framework do you want to use? Next
- TypeScript? Yes
- ESLint? Yes,
- Tailwind CSS? Yes,
- src/ directory? No,
- App Router? Yes,
- Import alias? Yes,
- Import alias configured? @/*
- Do you want to deploy the application? No
Creating D1 Database
npx wrangler d1 create drizzle-demo-db
Once the creation is complete copy the database details from your terminal and paste in ./wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "drizzle-demo-db"
database_id = "9443a6ee-a77d-44d8-8eb5-977d51c06918"
Installing and setting up Drizzle
Run the following command:
npm add drizzle-orm drizzle-kit
Post creation of db add the following to ./wrangler.toml
migrations_dir = 'drizzle'
We need to generate a database type to make the DB initialization type safe. Run:
wrangler types --env-interface CloudflareEnv env.d.ts
Extending process.env to have database bindings ass the following code to globals.d.ts
declare global {
namespace NodeJS {
interface ProcessEnv extends CloudflareEnv {
}
}
}//extends process.env to have new types given by cloudflare
export type {};
Creating Schema & initializing DB
./schema.ts
import {integer, sqliteTable, text} from "drizzle-orm/sqlite-core";
import dayjs from "dayjs";
export const todosTable = sqliteTable('todos', {
id: integer("id").primaryKey({autoIncrement: true}),
title: text("title").notNull(),
completed: integer("completed", {
mode: 'boolean'
}),
createdAt: integer("createdAt", {
mode: 'timestamp'
}).$defaultFn(() => dayjs().toDate()),
updatedAt: integer("createdAt", {
mode: 'timestamp'
}).$defaultFn(() => dayjs().toDate()).$onUpdateFn(() => dayjs().toDate()),
})
/db.ts
import {drizzle} from "drizzle-orm/d1";
import * as schema from "./schema"
export const db = drizzle(process.env.DB, {schema})
Setting up Drizzle config
D1 database does not allow us to query remote db for local development intend to create a local db in the system to use thus first we need to create that and then go for remote db.
Add the following in drizzle.config.ts
import {defineConfig} from "drizzle-kit";
export default defineConfig({
dialect: 'sqlite',
schema: './app/api/server/schema.ts',
out: './drizzle',
})
Add scripts in package.json
"db:generate": "drizzle-kit generate",
"db:midrate:local": "wrangler d1 migrations apply drizzle-demo-db --local",
"db:midrate:prod": "wrangler d1 migrations apply drizzle-demo-db --remote"`
`npm db:generate
npm db:midrate:local
This should create a .wrangler folder that has the local db and the./drizzle folder will have the migration files
Let us add the code for the remote DB. The final code in drizzle.config.ts
import path from "node:path";
import * as fs from "node:fs";
import {defineConfig} from "drizzle-kit";
function getLocalD1DB() {
try {
const basePath = path.resolve(".wrangler")
const dbFile = fs.readdirSync(basePath, {encoding: "utf-8", recursive: true}).find((f) => f.endsWith('.sqlite'))
if (!dbFile) {
throw new Error('.sqlite file not found')
}
const url = path.resolve(basePath, dbFile)
return url;
} catch (e) {
console.error(`Error ${e}`)
}
}
export default defineConfig({
dialect: 'sqlite',
schema: './app/api/server/schema.ts',
out: './drizzle',
...(process.env.NODE_ENV === 'production' ? {
driver: 'd1-http', dbCredentials: {
accountId: process.env.CLOUDFLARE_ACCOUNT_ID,
databaseId: process.env.CLOUDFLARE_DATABASE_ID,
token: process.env.CLOUDFLARE_D1_API_TOKEN
}
} : {
dbCredentials: {
url: getLocalD1DB()
}
})
})
Create a .dev.vars file for secrets
CLOUDFLARE_ACCOUNT_ID=""
CLOUDFLARE_DATABASE_ID=""
CLOUDFLARE_D1_API_TOKEN=""
Add the following data from Cloudflare where CLOUDFLARE_DATABASE_ID is present in wrangler.toml, CLOUDFLARE_D1_API_TOKEN can be created from the Cloudflare dashboard My Profile> API TOKENS> CREATE NEW and give the API a name and following access.
For CLOUDFLARE_ACCOUNT_ID for to Workers & Pages and deploy a Hello world worker if there are no workers already deployed copy the account id
Finally, run Migration to prod.
npm db:midrate:prod
Check your CloudFlare D1 for the DB with the schema defined.
Conclusion
By combining Cloudflare D1 and Drizzle ORM, you can create a powerful, streamlined setup for building and managing APIs with a serverless database backend. This approach offers several key benefits:
Global Scalability: With Cloudflare's edge network, your database is globally distributed, ensuring low-latency access for users around the world.
Simplified Deployment: Deploying the database to Cloudflare D1 removes the need to manage infrastructure, allowing you to focus entirely on application logic.
Effortless Maintenance: Drizzle ORM provides type-safe models, intuitive migrations, and a clean interface for working with your data, making it easy to build and maintain robust APIs.
Cost-Effectiveness: The serverless nature of D1 ensures you only pay for what you use, making it a budget-friendly option for applications of any size.
Future-Ready: Cloudflare's integrated ecosystem means your database can seamlessly integrate with Workers, Pages, and other services as your application grows.
This setup is perfect for developers who value performance, scalability, and simplicity. Whether you're building a small project or scaling a larger application, Cloudflare D1 and Drizzle ORM offer a modern, efficient way to handle your data needs.
With this foundation in place, you're ready to create APIs that are not only robust and reliable but also optimized for the demands of a global audience.
Thanks for Reading.
Posted on November 25, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.