Skip to content

Commit 0c5dd2e

Browse files
Add D1 Database (#335)
1 parent 846d705 commit 0c5dd2e

File tree

14 files changed

+214
-15
lines changed

14 files changed

+214
-15
lines changed

.changeset/eager-zebras-jog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"create-better-t-stack": minor
3+
---
4+
5+
add d1 database setup
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import path from "node:path";
2+
import type { ProjectConfig } from "../../types";
3+
import {
4+
addEnvVariablesToFile,
5+
type EnvVariable,
6+
} from "../project-generation/env-setup";
7+
8+
export async function setupCloudflareD1(config: ProjectConfig): Promise<void> {
9+
const { projectDir } = config;
10+
11+
const envPath = path.join(projectDir, "apps/server", ".env");
12+
13+
const variables: EnvVariable[] = [
14+
{
15+
key: "CLOUDFLARE_ACCOUNT_ID",
16+
value: "",
17+
condition: true,
18+
},
19+
{
20+
key: "CLOUDFLARE_DATABASE_ID",
21+
value: "",
22+
condition: true,
23+
},
24+
{
25+
key: "CLOUDFLARE_D1_TOKEN",
26+
value: "",
27+
condition: true,
28+
},
29+
];
30+
31+
try {
32+
await addEnvVariablesToFile(envPath, variables);
33+
} catch (_err) {}
34+
}

apps/cli/src/helpers/project-generation/create-project.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import { setupAuth } from "../setup/auth-setup";
88
import { setupBackendDependencies } from "../setup/backend-setup";
99
import { setupDatabase } from "../setup/db-setup";
1010
import { setupExamples } from "../setup/examples-setup";
11-
import { setupRuntime } from "../setup/runtime-setup";
11+
import {
12+
generateCloudflareWorkerTypes,
13+
setupRuntime,
14+
} from "../setup/runtime-setup";
1215
import { createReadme } from "./create-readme";
1316
import { setupEnvironmentVariables } from "./env-setup";
1417
import { installDependencies } from "./install-dependencies";
@@ -64,6 +67,7 @@ export async function createProject(options: ProjectConfig) {
6467
}
6568

6669
await handleExtras(projectDir, options);
70+
6771
await setupEnvironmentVariables(options);
6872
await updatePackageConfigurations(projectDir, options);
6973
await createReadme(projectDir, options);
@@ -76,6 +80,7 @@ export async function createProject(options: ProjectConfig) {
7680
projectDir,
7781
packageManager: options.packageManager,
7882
});
83+
await generateCloudflareWorkerTypes(options);
7984
}
8085

8186
displayPostInstallInstructions({

apps/cli/src/helpers/project-generation/env-setup.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,8 @@ export async function setupEnvironmentVariables(
186186
dbSetup === "prisma-postgres" ||
187187
dbSetup === "mongodb-atlas" ||
188188
dbSetup === "neon" ||
189-
dbSetup === "supabase";
189+
dbSetup === "supabase" ||
190+
dbSetup === "d1";
190191

191192
if (database !== "none" && !specializedSetup) {
192193
switch (database) {

apps/cli/src/helpers/project-generation/post-installation.ts

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { consola } from "consola";
22
import pc from "picocolors";
3-
import type { Database, ORM, ProjectConfig, Runtime } from "../../types";
3+
import type {
4+
Database,
5+
DatabaseSetup,
6+
ORM,
7+
ProjectConfig,
8+
Runtime,
9+
} from "../../types";
410
import { getPackageExecutionCommand } from "../../utils/get-package-execution-command";
511

612
export function displayPostInstallInstructions(
@@ -16,6 +22,7 @@ export function displayPostInstallInstructions(
1622
runtime,
1723
frontend,
1824
backend,
25+
dbSetup,
1926
} = config;
2027

2128
const isConvex = backend === "convex";
@@ -26,7 +33,7 @@ export function displayPostInstallInstructions(
2633

2734
const databaseInstructions =
2835
!isConvex && database !== "none"
29-
? getDatabaseInstructions(database, orm, runCmd, runtime)
36+
? getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup)
3037
: "";
3138

3239
const tauriInstructions = addons?.includes("tauri")
@@ -96,6 +103,11 @@ export function displayPostInstallInstructions(
96103
}
97104

98105
if (runtime === "workers") {
106+
if (dbSetup === "d1") {
107+
output += `${pc.yellow(
108+
"IMPORTANT:",
109+
)} Complete D1 database setup first (see Database commands below)\n`;
110+
}
99111
output += `${pc.cyan(`${stepCounter++}.`)} bun dev\n`;
100112
output += `${pc.cyan(
101113
`${stepCounter++}.`,
@@ -178,9 +190,46 @@ function getDatabaseInstructions(
178190
orm?: ORM,
179191
runCmd?: string,
180192
runtime?: Runtime,
193+
dbSetup?: DatabaseSetup,
181194
): string {
182195
const instructions = [];
183196

197+
if (runtime === "workers" && dbSetup === "d1") {
198+
const packageManager = runCmd === "npm run" ? "npm" : runCmd || "npm";
199+
200+
instructions.push(
201+
`${pc.cyan("1.")} Login to Cloudflare: ${pc.white(
202+
`${packageManager} wrangler login`,
203+
)}`,
204+
);
205+
instructions.push(
206+
`${pc.cyan("2.")} Create D1 database: ${pc.white(
207+
`${packageManager} wrangler d1 create your-database-name`,
208+
)}`,
209+
);
210+
instructions.push(
211+
`${pc.cyan(
212+
"3.",
213+
)} Update apps/server/wrangler.jsonc with database_id and database_name`,
214+
);
215+
instructions.push(
216+
`${pc.cyan("4.")} Generate migrations: ${pc.white(
217+
"cd apps/server && bun db:generate",
218+
)}`,
219+
);
220+
instructions.push(
221+
`${pc.cyan("5.")} Apply migrations locally: ${pc.white(
222+
`${packageManager} wrangler d1 migrations apply YOUR_DB_NAME --local`,
223+
)}`,
224+
);
225+
instructions.push(
226+
`${pc.cyan("6.")} Apply migrations to production: ${pc.white(
227+
`${packageManager} wrangler d1 migrations apply YOUR_DB_NAME`,
228+
)}`,
229+
);
230+
instructions.push("");
231+
}
232+
184233
if (orm === "prisma") {
185234
if (database === "sqlite") {
186235
instructions.push(
@@ -204,7 +253,7 @@ function getDatabaseInstructions(
204253
} else if (orm === "drizzle") {
205254
instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
206255
instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
207-
if (database === "sqlite") {
256+
if (database === "sqlite" && dbSetup !== "d1") {
208257
instructions.push(
209258
`${pc.cyan(
210259
"•",

apps/cli/src/helpers/setup/db-setup.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import fs from "fs-extra";
55
import pc from "picocolors";
66
import type { ProjectConfig } from "../../types";
77
import { addPackageDependency } from "../../utils/add-package-deps";
8+
import { setupCloudflareD1 } from "../database-providers/d1-setup";
89
import { setupMongoDBAtlas } from "../database-providers/mongodb-atlas-setup";
910
import { setupNeonPostgres } from "../database-providers/neon-setup";
1011
import { setupPrismaPostgres } from "../database-providers/prisma-postgres-setup";
@@ -69,6 +70,8 @@ export async function setupDatabase(config: ProjectConfig): Promise<void> {
6970

7071
if (database === "sqlite" && dbSetup === "turso") {
7172
await setupTurso(config);
73+
} else if (database === "sqlite" && dbSetup === "d1") {
74+
await setupCloudflareD1(config);
7275
} else if (database === "postgres") {
7376
if (orm === "prisma" && dbSetup === "prisma-postgres") {
7477
await setupPrismaPostgres(config);

apps/cli/src/helpers/setup/runtime-setup.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import path from "node:path";
2+
import { spinner } from "@clack/prompts";
3+
import { execa } from "execa";
24
import fs from "fs-extra";
5+
import pc from "picocolors";
36
import type { Backend, ProjectConfig } from "../../types";
47
import { addPackageDependency } from "../../utils/add-package-deps";
58

@@ -25,6 +28,43 @@ export async function setupRuntime(config: ProjectConfig): Promise<void> {
2528
}
2629
}
2730

31+
export async function generateCloudflareWorkerTypes(
32+
config: ProjectConfig,
33+
): Promise<void> {
34+
if (config.runtime !== "workers") {
35+
return;
36+
}
37+
38+
const serverDir = path.join(config.projectDir, "apps/server");
39+
40+
if (!(await fs.pathExists(serverDir))) {
41+
return;
42+
}
43+
44+
const s = spinner();
45+
46+
try {
47+
s.start("Generating Cloudflare Workers types...");
48+
49+
const runCmd =
50+
config.packageManager === "npm" ? "npm" : config.packageManager;
51+
await execa(runCmd, ["run", "cf-typegen"], {
52+
cwd: serverDir,
53+
});
54+
55+
s.stop("Cloudflare Workers types generated successfully!");
56+
} catch {
57+
s.stop(pc.yellow("Failed to generate Cloudflare Workers types"));
58+
const managerCmd =
59+
config.packageManager === "npm"
60+
? "npm run"
61+
: `${config.packageManager} run`;
62+
console.warn(
63+
`Note: You can manually run 'cd apps/server && ${managerCmd} cf-typegen' in the project directory later`,
64+
);
65+
}
66+
}
67+
2868
async function setupBunRuntime(
2969
serverDir: string,
3070
_backend: Backend,

apps/cli/src/prompts/config-prompts.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export async function gatherConfig(
8585
flags.dbSetup,
8686
results.orm,
8787
results.backend,
88+
results.runtime,
8889
),
8990
git: () => getGitChoice(flags.git),
9091
packageManager: () => getPackageManagerChoice(flags.packageManager),

apps/cli/src/prompts/database-setup.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { cancel, isCancel, select } from "@clack/prompts";
22
import pc from "picocolors";
3-
import type { Backend, DatabaseSetup, ORM } from "../types";
3+
import type { Backend, DatabaseSetup, ORM, Runtime } from "../types";
44

55
export async function getDBSetupChoice(
66
databaseType: string,
77
dbSetup: DatabaseSetup | undefined,
88
orm?: ORM,
99
backend?: Backend,
10+
runtime?: Runtime,
1011
): Promise<DatabaseSetup> {
1112
if (backend === "convex") {
1213
return "none";
@@ -32,6 +33,15 @@ export async function getDBSetupChoice(
3233
label: "Turso",
3334
hint: "SQLite for Production. Powered by libSQL",
3435
},
36+
...(runtime === "workers"
37+
? [
38+
{
39+
value: "d1" as const,
40+
label: "Cloudflare D1",
41+
hint: "Cloudflare's managed, serverless database with SQLite's SQL semantics",
42+
},
43+
]
44+
: []),
3545
{ value: "none" as const, label: "None", hint: "Manual setup" },
3646
];
3747
} else if (databaseType === "postgres") {

apps/cli/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export const DatabaseSetupSchema = z
6060
"prisma-postgres",
6161
"mongodb-atlas",
6262
"supabase",
63+
"d1",
6364
"none",
6465
])
6566
.describe("Database hosting setup");

0 commit comments

Comments
 (0)