Add D1 Database (#335)

This commit is contained in:
Aman Varshney
2025-06-20 09:17:32 +05:30
committed by GitHub
parent 846d70583e
commit 0c5dd2efee
14 changed files with 214 additions and 15 deletions

View File

@@ -0,0 +1,34 @@
import path from "node:path";
import type { ProjectConfig } from "../../types";
import {
addEnvVariablesToFile,
type EnvVariable,
} from "../project-generation/env-setup";
export async function setupCloudflareD1(config: ProjectConfig): Promise<void> {
const { projectDir } = config;
const envPath = path.join(projectDir, "apps/server", ".env");
const variables: EnvVariable[] = [
{
key: "CLOUDFLARE_ACCOUNT_ID",
value: "",
condition: true,
},
{
key: "CLOUDFLARE_DATABASE_ID",
value: "",
condition: true,
},
{
key: "CLOUDFLARE_D1_TOKEN",
value: "",
condition: true,
},
];
try {
await addEnvVariablesToFile(envPath, variables);
} catch (_err) {}
}

View File

@@ -8,7 +8,10 @@ import { setupAuth } from "../setup/auth-setup";
import { setupBackendDependencies } from "../setup/backend-setup";
import { setupDatabase } from "../setup/db-setup";
import { setupExamples } from "../setup/examples-setup";
import { setupRuntime } from "../setup/runtime-setup";
import {
generateCloudflareWorkerTypes,
setupRuntime,
} from "../setup/runtime-setup";
import { createReadme } from "./create-readme";
import { setupEnvironmentVariables } from "./env-setup";
import { installDependencies } from "./install-dependencies";
@@ -64,6 +67,7 @@ export async function createProject(options: ProjectConfig) {
}
await handleExtras(projectDir, options);
await setupEnvironmentVariables(options);
await updatePackageConfigurations(projectDir, options);
await createReadme(projectDir, options);
@@ -76,6 +80,7 @@ export async function createProject(options: ProjectConfig) {
projectDir,
packageManager: options.packageManager,
});
await generateCloudflareWorkerTypes(options);
}
displayPostInstallInstructions({

View File

@@ -186,7 +186,8 @@ export async function setupEnvironmentVariables(
dbSetup === "prisma-postgres" ||
dbSetup === "mongodb-atlas" ||
dbSetup === "neon" ||
dbSetup === "supabase";
dbSetup === "supabase" ||
dbSetup === "d1";
if (database !== "none" && !specializedSetup) {
switch (database) {

View File

@@ -1,6 +1,12 @@
import { consola } from "consola";
import pc from "picocolors";
import type { Database, ORM, ProjectConfig, Runtime } from "../../types";
import type {
Database,
DatabaseSetup,
ORM,
ProjectConfig,
Runtime,
} from "../../types";
import { getPackageExecutionCommand } from "../../utils/get-package-execution-command";
export function displayPostInstallInstructions(
@@ -16,6 +22,7 @@ export function displayPostInstallInstructions(
runtime,
frontend,
backend,
dbSetup,
} = config;
const isConvex = backend === "convex";
@@ -26,7 +33,7 @@ export function displayPostInstallInstructions(
const databaseInstructions =
!isConvex && database !== "none"
? getDatabaseInstructions(database, orm, runCmd, runtime)
? getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup)
: "";
const tauriInstructions = addons?.includes("tauri")
@@ -96,6 +103,11 @@ export function displayPostInstallInstructions(
}
if (runtime === "workers") {
if (dbSetup === "d1") {
output += `${pc.yellow(
"IMPORTANT:",
)} Complete D1 database setup first (see Database commands below)\n`;
}
output += `${pc.cyan(`${stepCounter++}.`)} bun dev\n`;
output += `${pc.cyan(
`${stepCounter++}.`,
@@ -178,9 +190,46 @@ function getDatabaseInstructions(
orm?: ORM,
runCmd?: string,
runtime?: Runtime,
dbSetup?: DatabaseSetup,
): string {
const instructions = [];
if (runtime === "workers" && dbSetup === "d1") {
const packageManager = runCmd === "npm run" ? "npm" : runCmd || "npm";
instructions.push(
`${pc.cyan("1.")} Login to Cloudflare: ${pc.white(
`${packageManager} wrangler login`,
)}`,
);
instructions.push(
`${pc.cyan("2.")} Create D1 database: ${pc.white(
`${packageManager} wrangler d1 create your-database-name`,
)}`,
);
instructions.push(
`${pc.cyan(
"3.",
)} Update apps/server/wrangler.jsonc with database_id and database_name`,
);
instructions.push(
`${pc.cyan("4.")} Generate migrations: ${pc.white(
"cd apps/server && bun db:generate",
)}`,
);
instructions.push(
`${pc.cyan("5.")} Apply migrations locally: ${pc.white(
`${packageManager} wrangler d1 migrations apply YOUR_DB_NAME --local`,
)}`,
);
instructions.push(
`${pc.cyan("6.")} Apply migrations to production: ${pc.white(
`${packageManager} wrangler d1 migrations apply YOUR_DB_NAME`,
)}`,
);
instructions.push("");
}
if (orm === "prisma") {
if (database === "sqlite") {
instructions.push(
@@ -204,7 +253,7 @@ function getDatabaseInstructions(
} else if (orm === "drizzle") {
instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
if (database === "sqlite") {
if (database === "sqlite" && dbSetup !== "d1") {
instructions.push(
`${pc.cyan(
"•",

View File

@@ -5,6 +5,7 @@ import fs from "fs-extra";
import pc from "picocolors";
import type { ProjectConfig } from "../../types";
import { addPackageDependency } from "../../utils/add-package-deps";
import { setupCloudflareD1 } from "../database-providers/d1-setup";
import { setupMongoDBAtlas } from "../database-providers/mongodb-atlas-setup";
import { setupNeonPostgres } from "../database-providers/neon-setup";
import { setupPrismaPostgres } from "../database-providers/prisma-postgres-setup";
@@ -69,6 +70,8 @@ export async function setupDatabase(config: ProjectConfig): Promise<void> {
if (database === "sqlite" && dbSetup === "turso") {
await setupTurso(config);
} else if (database === "sqlite" && dbSetup === "d1") {
await setupCloudflareD1(config);
} else if (database === "postgres") {
if (orm === "prisma" && dbSetup === "prisma-postgres") {
await setupPrismaPostgres(config);

View File

@@ -1,5 +1,8 @@
import path from "node:path";
import { spinner } from "@clack/prompts";
import { execa } from "execa";
import fs from "fs-extra";
import pc from "picocolors";
import type { Backend, ProjectConfig } from "../../types";
import { addPackageDependency } from "../../utils/add-package-deps";
@@ -25,6 +28,43 @@ export async function setupRuntime(config: ProjectConfig): Promise<void> {
}
}
export async function generateCloudflareWorkerTypes(
config: ProjectConfig,
): Promise<void> {
if (config.runtime !== "workers") {
return;
}
const serverDir = path.join(config.projectDir, "apps/server");
if (!(await fs.pathExists(serverDir))) {
return;
}
const s = spinner();
try {
s.start("Generating Cloudflare Workers types...");
const runCmd =
config.packageManager === "npm" ? "npm" : config.packageManager;
await execa(runCmd, ["run", "cf-typegen"], {
cwd: serverDir,
});
s.stop("Cloudflare Workers types generated successfully!");
} catch {
s.stop(pc.yellow("Failed to generate Cloudflare Workers types"));
const managerCmd =
config.packageManager === "npm"
? "npm run"
: `${config.packageManager} run`;
console.warn(
`Note: You can manually run 'cd apps/server && ${managerCmd} cf-typegen' in the project directory later`,
);
}
}
async function setupBunRuntime(
serverDir: string,
_backend: Backend,

View File

@@ -85,6 +85,7 @@ export async function gatherConfig(
flags.dbSetup,
results.orm,
results.backend,
results.runtime,
),
git: () => getGitChoice(flags.git),
packageManager: () => getPackageManagerChoice(flags.packageManager),

View File

@@ -1,12 +1,13 @@
import { cancel, isCancel, select } from "@clack/prompts";
import pc from "picocolors";
import type { Backend, DatabaseSetup, ORM } from "../types";
import type { Backend, DatabaseSetup, ORM, Runtime } from "../types";
export async function getDBSetupChoice(
databaseType: string,
dbSetup: DatabaseSetup | undefined,
orm?: ORM,
backend?: Backend,
runtime?: Runtime,
): Promise<DatabaseSetup> {
if (backend === "convex") {
return "none";
@@ -32,6 +33,15 @@ export async function getDBSetupChoice(
label: "Turso",
hint: "SQLite for Production. Powered by libSQL",
},
...(runtime === "workers"
? [
{
value: "d1" as const,
label: "Cloudflare D1",
hint: "Cloudflare's managed, serverless database with SQLite's SQL semantics",
},
]
: []),
{ value: "none" as const, label: "None", hint: "Manual setup" },
];
} else if (databaseType === "postgres") {

View File

@@ -60,6 +60,7 @@ export const DatabaseSetupSchema = z
"prisma-postgres",
"mongodb-atlas",
"supabase",
"d1",
"none",
])
.describe("Database hosting setup");

View File

@@ -358,6 +358,22 @@ export function processAndValidateFlags(
process.exit(1);
}
if (config.dbSetup === "d1") {
if (config.database !== "sqlite") {
consola.fatal(
"Cloudflare D1 setup requires SQLite database. Please use '--database sqlite' or choose a different setup.",
);
process.exit(1);
}
if (config.runtime !== "workers") {
consola.fatal(
"Cloudflare D1 setup requires the Cloudflare Workers runtime. Please use '--runtime workers' or choose a different setup.",
);
process.exit(1);
}
}
if (
providedFlags.has("runtime") &&
options.runtime === "workers" &&

View File

@@ -3,6 +3,16 @@ import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./src/db/schema",
out: "./src/db/migrations",
{{#if (eq dbSetup "d1")}}
// DOCS: https://orm.drizzle.team/docs/guides/d1-http-with-drizzle-kit
dialect: "sqlite",
driver: "d1-http",
dbCredentials: {
accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
databaseId: process.env.CLOUDFLARE_DATABASE_ID!,
token: process.env.CLOUDFLARE_D1_TOKEN!,
},
{{else}}
dialect: "turso",
dbCredentials: {
url: process.env.DATABASE_URL || "",
@@ -10,4 +20,5 @@ export default defineConfig({
authToken: process.env.DATABASE_AUTH_TOKEN,
{{/if}}
},
{{/if}}
});

View File

@@ -13,6 +13,12 @@ export const db = drizzle({ client });
{{/if}}
{{#if (eq runtime "workers")}}
{{#if (eq dbSetup "d1")}}
import { drizzle } from "drizzle-orm/d1";
import { env } from "cloudflare:workers";
export const db = drizzle(env.DB);
{{else}}
import { drizzle } from "drizzle-orm/libsql";
import { env } from "cloudflare:workers";
import { createClient } from "@libsql/client";
@@ -26,3 +32,4 @@ const client = createClient({
export const db = drizzle({ client });
{{/if}}
{{/if}}

View File

@@ -5,14 +5,30 @@
"compatibility_flags": ["nodejs_compat"],
"vars": {
"NODE_ENV": "production"
// Non-sensitive environment variables (visible in dashboard)
// "CORS_ORIGIN": "https://your-frontend-domain.com",
// "BETTER_AUTH_URL": "https://your-worker-domain.workers.dev"
// Add public environment variables here
// Example: "CORS_ORIGIN": "https://your-domain.com"
}
// ⚠️ SENSITIVE DATA: Use `wrangler secret put` instead of adding here
// Don't put these in "vars" - they'll be visible in the dashboard!
// - DATABASE_URL
// - DATABASE_AUTH_TOKEN
// - GOOGLE_GENERATIVE_AI_API_KEY
// - BETTER_AUTH_SECRET
// For sensitive data, use:
// wrangler secret put SECRET_NAME
// Don't add secrets to "vars" - they're visible in the dashboard!
{{#if (eq dbSetup "d1")}},
// To set up D1 database:
// 1. Run: wrangler login
// 2. Run: wrangler d1 create your-database-name
// 3. Copy the output and paste below
// Then run migrations:
// bun db:generate
// To apply migrations locally, run:
// wrangler d1 migrations apply YOUR_DB_NAME --local
"d1_databases": [
{
"binding": "DB",
"database_name": "YOUR_DB_NAME",
"database_id": "YOUR_DB_ID",
"preview_database_id": "local-test-db",
"migrations_dir": "./src/db/migrations"
}
]
{{/if}}
}