D1 Database Setup for Vite + Hono App

Step-by-step guide to implement a D1 database on top of the Hono Vite React Stack https://github.com/yusukebe/hono-vite-react-stack

Please study the existing codebase carefully and implement a D1 database setup as described in "Database Setup Implementation Plan" below. List out all the individual steps as bullets before starting.

Instead of the chat example described, we want to make a classifieds app where users can submit (without authentication) a listing with a title, description, location and phone number.

Complete the entire flow of implementation, migrations and initial deployment.

Database Setup Implementation Plan

Database Setup with Drizzle

1. Project Structure

/
├── src/
│   ├── server/
│   │   ├── index.tsx     # Main worker code with Hono routes
│   │   ├── renderer.tsx  # React SSR shell
│   │   └── db.ts         # Database operations (to be created)
│   ├── client/
│   │   ├── app.tsx       # Client-side React app
│   │   └── index.tsx     # Client entry point
│   ├── lib/
│   │   ├── utils.ts      # Utility functions
│   │   └── schema.ts     # Drizzle schema definitions (to be created)
│   └── style.css         # Global styles
├── migrations/           # SQL migration files
├── drizzle/              # Generated Drizzle artifacts
├── drizzle.config.ts     # Drizzle configuration (to be created)
├── wrangler.jsonc        # Cloudflare configuration
└── vite.config.ts        # Vite configuration

2. Database Schema (src/lib/schema.ts)

import { sql } from 'drizzle-orm';
import { text, integer, sqliteTable } from 'drizzle-orm/sqlite-core';

export const messages = sqliteTable('messages', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  content: text('content').notNull(),
  createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(sql`CURRENT_TIMESTAMP`),
});

3. Drizzle Configuration (drizzle.config.ts)

import type { Config } from 'drizzle-kit';
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  schema: './src/lib/schema.ts',
  out: './drizzle',
  dialect: 'sqlite',
});

4. Wrangler Configuration Update (wrangler.jsonc)

When choosing a database name, append a random 6-character string to make it unique.

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "chat-app",
  "compatibility_date": "2025-03-20",
  "main": "./src/server/index.tsx",
  "assets": {
    "directory": "dist"
  },
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "chat-db",
      "database_id": "<your-database-id>",
      "migrations_dir": "drizzle"
    }
  ]
}

5. Database Operations (src/server/db.ts)

import { drizzle } from 'drizzle-orm/d1';
import { messages } from '@/lib/schema';

export interface Env {
  DB: D1Database;
}

export const getDb = (env: Env) => drizzle(env.DB);

export async function getMessages(env: Env) {
  const db = getDb(env);
  return await db.select().from(messages).all();
}

export async function createMessage(env: Env, content: string) {
  const db = getDb(env);
  return await db.insert(messages).values({ content }).run();
}

6. Implementation Steps

A. Initial Setup

  1. Create D1 database:
wrangler d1 create chat-db
  1. Install dependencies:
npm install drizzle-orm
npm install -D drizzle-kit
  1. Update the worker environment interface in src/server/index.tsx:
import { Hono } from 'hono'
import { renderer } from './renderer'
import { getMessages, createMessage } from './db'

// Define the environment interface
interface Env {
  DB: D1Database;
}

const app = new Hono<{ Bindings: Env }>()

// Add API routes and existing code
// ...

B. Database Schema Management

To manage your database schema using drizzle-kit and wrangler:

  1. Create the schema in src/lib/schema.ts
  2. Generate migration SQL files:
npx drizzle-kit generate

This command will:

  • Read your Drizzle schema file
  • Compare it with the previous schema version
  • Generate SQL migration files in the migrations directory
  1. Apply migrations to local development database:
npx wrangler d1 migrations apply chat-db --local
  1. Apply migrations to production database:
npx wrangler d1 migrations apply chat-db --remote

For development, you can use additional flags with drizzle-kit generate:

npx drizzle-kit generate --verbose  # Show detailed output

C. Worker Implementation

  1. Update the main worker (src/server/index.tsx) with:

    • GET /api/messages endpoint to fetch messages
    • POST /api/messages endpoint to create new messages
    • Update the renderer to display messages
  2. Update the client-side React app (src/client/app.tsx) with:

    • Message display component
    • Text input for new messages
    • Send button
    • Auto-refresh functionality

7. Testing and Deployment

  1. Test locally:
npm run dev
  1. Deploy to production:
npm run deploy

8. Continous Integration

Create a Github action to run migrations, based on this code snippet:

      - name: Apply D1 Migrations
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CF_API_TOKEN }}
          accountId: ${{ secrets.CF_ACCOUNT_ID }}
          command: d1 migrations apply ${{ secrets.CF_DB_NAME }}

9. Documentation

Add a "Database" section to the README file, describing how the schema can be updated, how migrations are generated and applied to the local and remote databases.

Keep the changes in the README focused on the database changes we just did and focus on useful information for further development. Don't replicate the schema.

Include a small section on resetting the local DB, which can be done with this command:

rm .wrangler/state/v3/d1/miniflare-D1DatabaseObject/*

Extend package.json with the most important commands that a user might want to run.

Notes

  • Drizzle provides type-safe database operations
  • The application uses Hono for routing and React for UI
  • Server-side rendering (SSR) is used for the initial page load
  • Client-side hydration makes the app interactive
  • Schema changes are tracked by generating migration files
  • Migration files can be committed to version control
  • Wrangler applies migrations to both local and remote D1 databases
  • Always test schema changes in a development environment first

Add this context to your project via the ctxs command line integration: