Harnessing Cloudflare D1 and Drizzle ORM for Scalable Serverless Apps in Next.js

nks102000

NITISH KUMAR SONTHALIA

Posted on November 25, 2024

Harnessing Cloudflare D1 and Drizzle ORM for Scalable Serverless Apps in Next.js

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"
Enter fullscreen mode Exit fullscreen mode

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 {};
Enter fullscreen mode Exit fullscreen mode

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()),
})
Enter fullscreen mode Exit fullscreen mode

/db.ts

import {drizzle} from "drizzle-orm/d1";
import * as schema from "./schema"
export const db = drizzle(process.env.DB, {schema})
Enter fullscreen mode Exit fullscreen mode

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',
})
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
        }
    })
})
Enter fullscreen mode Exit fullscreen mode

Create a .dev.vars file for secrets

CLOUDFLARE_ACCOUNT_ID=""
CLOUDFLARE_DATABASE_ID=""
CLOUDFLARE_D1_API_TOKEN=""
Enter fullscreen mode Exit fullscreen mode

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.

CLOUDFLARE_D1_API_TOKEN

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

CLOUDFLARE_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.

💖 💪 🙅 🚩
nks102000
NITISH KUMAR SONTHALIA

Posted on November 25, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related