In today's web development landscape, leveraging the right tools and services is pivotal in creating efficient, scalable, and secure applications. This blog post aims to guide you through the process of setting up a Next.js application, enhanced with Cloudflare Workers for serverless functionality and Drizzle ORM for streamlined database management. Whether you're building a complex application or a simple website, this setup ensures performance, security, and ease of maintenance.
Setting up Cloudflare
1 - init project
We're going to use the cloudflare template for NextJS given from their docs here.
This will setup some basic things for us to start customizing, but not too much that it feels overwhelming for development compared to the standard NextJS inits.
npm create cloudflare@latest my-next-app -- --framework=next
2 - create D1 instance
We want to create a cloudflare d1 instance, this uses a sqlite
database that is edge-compatible.
npx wrangler d1 create <db-name>
Then, copy the output into wrangler.toml
in the project root. We will also need to configure the DB
setting in the Cloudflare page settings to <db-name>
3 - update env.d.ts types
We will need to update the CloudflareEnv type so we can access our D1 Database within the code.
npx wrangler types --env-interface CloudflareEnv env.d.ts
This command should update the env.d.ts
file automatically.
Installing Drizzle
4 - install drizzle dependencies
npm i drizzle-orm
npm i -D drizzle-kit
5 - drizzle config
Create a drizzle.config.ts
file in root
import { defineConfig } from "drizzle-kit";
// using cloudflare wrangler
export default defineConfig({
schema: "./src/db/schema.ts",
dbCredentials: {
dbName: "chamber-db",
wranglerConfigPath: "."
},
driver: "d1"
})
6 - create schema file
Create schema.ts
in src/db
directory
// src/db/schema.ts
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
export const roadmap_tasks = sqliteTable("roadmap_tasks", {
id: integer("id").primaryKey({ autoIncrement: true }),
title: text("title"),
description: text("description"),
});
7 - wrapping d1 with drizzle adapter
Add d1 driver for drizzle. This allows us to use db
as the standard drizzle variable. This code is a little hacky in order to get the project to build without errors, as getRequestContext
cannot be called outside of a request, but will be called when importing, which is outside of a request function.
// src/db/db.ts
import { drizzle } from "drizzle-orm/d1";
import * as schema from "@/db/schema";
import { getRequestContext } from "@cloudflare/next-on-pages";
function getDB() {
if (process.env.NODE_ENV === "development") {
const { env } = getRequestContext();
return drizzle(env.DB, { schema });
}
// Production
return drizzle(process.env.DB, { schema });
}
export const db = getDB();
In order to get proper types, we need to add this to env.d.ts
namespace NodeJS {
interface ProcessEnv {
DB: D1Database
}
}
Testing & Deployment
8 - add a test route
Here we're overwriting the app/api/hello/route.ts
. This will fetch whatever we put in roadmap_tasks
. You can add these to your local DB manually, or using the cloudflare d1 interface for production.
import { getRequestContext } from '@cloudflare/next-on-pages'
import { db } from '@/db/db'
import { roadmap_tasks } from '@/db/schema'
export const runtime = 'edge'
export async function GET() {
const result = await db.select().from(roadmap_tasks);
return new Response(JSON.stringify(result));
}
9 - setting up migration & development scripts
Modify your package.json
with these scripts
"scripts": {
"dev": "npx wrangler pages dev --local --d1=DB -- next dev",
"schema": "npx drizzle-kit generate:sqlite --schema=./src/db/schema.ts --out=./src/db/migrations",
"migrate": "npx wrangler d1 migrations apply chamber-db --local",
"migrate-prod": "npx wrangler d1 migrations apply chamber-db --remote"
...
}
and then in wrangler.toml
under [[d1_databases]]
migrations_dir = "src/db/migrations"
10 - deploying
Run these commands
npm run schema
npm run migrate-prod
npm run deploy