add postgres support

This commit is contained in:
Aman Varshney
2025-03-17 16:36:41 +05:30
parent 30e16b5d09
commit 98b1262b41
21 changed files with 261 additions and 29 deletions

View File

@@ -0,0 +1,5 @@
---
"create-better-t-stack": patch
---
Add postgres support

View File

@@ -52,9 +52,11 @@ export async function configureAuth(
const envPath = path.join(serverDir, ".env"); const envPath = path.join(serverDir, ".env");
const templateEnvPath = path.join( const templateEnvPath = path.join(
PKG_ROOT, PKG_ROOT,
options.orm === "drizzle" getOrmTemplatePath(
? "template/with-drizzle/packages/server/_env" options.orm,
: "template/base/packages/server/_env", options.database,
"packages/server/_env",
),
); );
if (!(await fs.pathExists(envPath))) { if (!(await fs.pathExists(envPath))) {
@@ -108,7 +110,11 @@ ${options.orm === "prisma" ? 'DATABASE_URL="file:./dev.db"' : ""}
const prismaAuthPath = path.join(serverDir, "src/lib/auth.ts"); const prismaAuthPath = path.join(serverDir, "src/lib/auth.ts");
const defaultPrismaAuthPath = path.join( const defaultPrismaAuthPath = path.join(
PKG_ROOT, PKG_ROOT,
"template/with-prisma/packages/server/src/lib/auth.ts", getOrmTemplatePath(
options.orm,
options.database,
"packages/server/src/lib/auth.ts",
),
); );
if ( if (
@@ -147,7 +153,11 @@ ${options.orm === "prisma" ? 'DATABASE_URL="file:./dev.db"' : ""}
const drizzleAuthPath = path.join(serverDir, "src/lib/auth.ts"); const drizzleAuthPath = path.join(serverDir, "src/lib/auth.ts");
const defaultDrizzleAuthPath = path.join( const defaultDrizzleAuthPath = path.join(
PKG_ROOT, PKG_ROOT,
"template/with-drizzle/packages/server/src/lib/auth.ts", getOrmTemplatePath(
options.orm,
options.database,
"packages/server/src/lib/auth.ts",
),
); );
if ( if (
@@ -180,6 +190,24 @@ ${options.orm === "prisma" ? 'DATABASE_URL="file:./dev.db"' : ""}
} }
} }
function getOrmTemplatePath(
orm: string,
database: string,
relativePath: string,
): string {
if (orm === "drizzle") {
return database === "sqlite"
? `template/with-drizzle-sqlite/${relativePath}`
: `template/with-drizzle-postgres/${relativePath}`;
}
if (orm === "prisma") {
return database === "sqlite"
? `template/with-prisma-sqlite/${relativePath}`
: `template/with-prisma-postgres/${relativePath}`;
}
return `template/base/${relativePath}`;
}
function generateAuthSecret(length = 32): string { function generateAuthSecret(length = 32): string {
const characters = const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

View File

@@ -27,9 +27,7 @@ export async function createProject(options: ProjectConfig): Promise<string> {
if (options.orm !== "none" && options.database !== "none") { if (options.orm !== "none" && options.database !== "none") {
const ormTemplateDir = path.join( const ormTemplateDir = path.join(
PKG_ROOT, PKG_ROOT,
options.orm === "drizzle" getOrmTemplateDir(options.orm, options.database),
? "template/with-drizzle"
: "template/with-prisma",
); );
if (await fs.pathExists(ormTemplateDir)) { if (await fs.pathExists(ormTemplateDir)) {
@@ -143,3 +141,19 @@ export async function createProject(options: ProjectConfig): Promise<string> {
throw error; throw error;
} }
} }
function getOrmTemplateDir(orm: string, database: string): string {
if (orm === "drizzle") {
return database === "sqlite"
? "template/with-drizzle-sqlite"
: "template/with-drizzle-postgres";
}
if (orm === "prisma") {
return database === "sqlite"
? "template/with-prisma-sqlite"
: "template/with-prisma-postgres";
}
return "template/base";
}

View File

@@ -21,24 +21,21 @@ export async function setupDatabase(
try { try {
if (databaseType === "sqlite") { if (databaseType === "sqlite") {
if (orm === "drizzle") { if (orm === "drizzle") {
await setupDrizzleDependencies(projectDir); await setupDrizzleDependencies(projectDir, "sqlite");
await setupTurso(projectDir, setupTursoDb); if (setupTursoDb) {
await setupTurso(projectDir, true);
}
} else if (orm === "prisma") { } else if (orm === "prisma") {
await setupPrismaDependencies(projectDir); await setupPrismaDependencies(projectDir, "sqlite");
await setupTurso(projectDir, setupTursoDb); if (setupTursoDb) {
await setupTurso(projectDir, true);
}
} }
} else if (databaseType === "postgres") { } else if (databaseType === "postgres") {
log.info(
pc.blue(
"PostgreSQL setup is coming in a future update. Using SQLite configuration for now.",
),
);
if (orm === "drizzle") { if (orm === "drizzle") {
await setupDrizzleDependencies(projectDir); await setupDrizzleDependencies(projectDir, "postgres");
await setupTurso(projectDir, setupTursoDb);
} else if (orm === "prisma") { } else if (orm === "prisma") {
await setupPrismaDependencies(projectDir); await setupPrismaDependencies(projectDir, "postgres");
await setupTurso(projectDir, setupTursoDb);
} }
} }
} catch (error) { } catch (error) {
@@ -50,7 +47,10 @@ export async function setupDatabase(
} }
} }
async function setupDrizzleDependencies(projectDir: string): Promise<void> { async function setupDrizzleDependencies(
projectDir: string,
dbType: string,
): Promise<void> {
const serverDir = path.join(projectDir, "packages/server"); const serverDir = path.join(projectDir, "packages/server");
const packageJsonPath = path.join(serverDir, "package.json"); const packageJsonPath = path.join(serverDir, "package.json");
@@ -60,9 +60,14 @@ async function setupDrizzleDependencies(projectDir: string): Promise<void> {
packageJson.dependencies = { packageJson.dependencies = {
...packageJson.dependencies, ...packageJson.dependencies,
"drizzle-orm": "^0.38.4", "drizzle-orm": "^0.38.4",
"@libsql/client": "^0.14.0",
}; };
if (dbType === "sqlite") {
packageJson.dependencies["@libsql/client"] = "^0.14.0";
} else if (dbType === "postgres") {
packageJson.dependencies.postgres = "^3.4.5";
}
packageJson.devDependencies = { packageJson.devDependencies = {
...packageJson.devDependencies, ...packageJson.devDependencies,
"drizzle-kit": "^0.30.4", "drizzle-kit": "^0.30.4",
@@ -79,7 +84,10 @@ async function setupDrizzleDependencies(projectDir: string): Promise<void> {
} }
} }
async function setupPrismaDependencies(projectDir: string): Promise<void> { async function setupPrismaDependencies(
projectDir: string,
dbType: string,
): Promise<void> {
const serverDir = path.join(projectDir, "packages/server"); const serverDir = path.join(projectDir, "packages/server");
const packageJsonPath = path.join(serverDir, "package.json"); const packageJsonPath = path.join(serverDir, "package.json");
@@ -89,10 +97,15 @@ async function setupPrismaDependencies(projectDir: string): Promise<void> {
packageJson.dependencies = { packageJson.dependencies = {
...packageJson.dependencies, ...packageJson.dependencies,
"@prisma/client": "^5.7.1", "@prisma/client": "^5.7.1",
"@prisma/adapter-libsql": "^5.7.1",
"@libsql/client": "^0.14.0",
}; };
if (dbType === "sqlite") {
packageJson.dependencies["@prisma/adapter-libsql"] = "^5.7.1";
packageJson.dependencies["@libsql/client"] = "^0.14.0";
} else if (dbType === "postgres") {
// PostgreSQL specific dependencies if needed
}
packageJson.devDependencies = { packageJson.devDependencies = {
...packageJson.devDependencies, ...packageJson.devDependencies,
prisma: "^5.7.1", prisma: "^5.7.1",
@@ -112,7 +125,10 @@ async function setupPrismaDependencies(projectDir: string): Promise<void> {
if (await fs.pathExists(envPath)) { if (await fs.pathExists(envPath)) {
const envContent = await fs.readFile(envPath, "utf8"); const envContent = await fs.readFile(envPath, "utf8");
if (!envContent.includes("DATABASE_URL")) { if (!envContent.includes("DATABASE_URL")) {
const databaseUrlLine = `\nDATABASE_URL="file:./dev.db"`; const databaseUrlLine =
dbType === "sqlite"
? `\nDATABASE_URL="file:./dev.db"`
: `\nDATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"`;
await fs.appendFile(envPath, databaseUrlLine); await fs.appendFile(envPath, databaseUrlLine);
} }
} }

View File

@@ -0,0 +1,10 @@
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./src/db/schema.ts",
out: "./migrations",
dialect: "postgresql",
dbCredentials: {
url: process.env.POSTGRES_URL!,
},
});

View File

@@ -0,0 +1,5 @@
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
const queryClient = postgres(process.env.DATABASE_URL);
const db = drizzle({ client: queryClient });

View File

@@ -0,0 +1,47 @@
import { pgTable, text, integer, timestamp, boolean } from "drizzle-orm/pg-core";
export const user = pgTable("user", {
id: text("id").primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
emailVerified: boolean('email_verified').notNull(),
image: text('image'),
createdAt: timestamp('created_at').notNull(),
updatedAt: timestamp('updated_at').notNull()
});
export const session = pgTable("session", {
id: text("id").primaryKey(),
expiresAt: timestamp('expires_at').notNull(),
token: text('token').notNull().unique(),
createdAt: timestamp('created_at').notNull(),
updatedAt: timestamp('updated_at').notNull(),
ipAddress: text('ip_address'),
userAgent: text('user_agent'),
userId: text('user_id').notNull().references(()=> user.id, { onDelete: 'cascade' })
});
export const account = pgTable("account", {
id: text("id").primaryKey(),
accountId: text('account_id').notNull(),
providerId: text('provider_id').notNull(),
userId: text('user_id').notNull().references(()=> user.id, { onDelete: 'cascade' }),
accessToken: text('access_token'),
refreshToken: text('refresh_token'),
idToken: text('id_token'),
accessTokenExpiresAt: timestamp('access_token_expires_at'),
refreshTokenExpiresAt: timestamp('refresh_token_expires_at'),
scope: text('scope'),
password: text('password'),
createdAt: timestamp('created_at').notNull(),
updatedAt: timestamp('updated_at').notNull()
});
export const verification = pgTable("verification", {
id: text("id").primaryKey(),
identifier: text('identifier').notNull(),
value: text('value').notNull(),
expiresAt: timestamp('expires_at').notNull(),
createdAt: timestamp('created_at'),
updatedAt: timestamp('updated_at')
});

View File

@@ -0,0 +1,4 @@
BETTER_AUTH_SECRET=jdUstNuiIZLVh897KOMMS8EmTP0QkD32
BETTER_AUTH_URL=http://localhost:3000
TURSO_CONNECTION_URL=http://127.0.0.1:8080
CORS_ORIGIN=http://localhost:3001

View File

@@ -0,0 +1,15 @@
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "../db";
import * as schema from "../db/schema";
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "sqlite",
schema: schema,
}),
trustedOrigins: [process.env.CORS_ORIGIN!],
emailAndPassword: {
enabled: true,
},
});

View File

@@ -0,0 +1,5 @@
import { PrismaClient } from "@prisma/client";
let prisma = new PrismaClient();
export default prisma;

View File

@@ -0,0 +1,74 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgres"
url = env("DATABASE_URL")
}
model User {
id String @id @map("_id")
name String
email String
emailVerified Boolean
image String?
createdAt DateTime
updatedAt DateTime
sessions Session[]
accounts Account[]
@@unique([email])
@@map("user")
}
model Session {
id String @id @map("_id")
expiresAt DateTime
token String
createdAt DateTime
updatedAt DateTime
ipAddress String?
userAgent String?
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([token])
@@map("session")
}
model Account {
id String @id @map("_id")
accountId String
providerId String
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
accessToken String?
refreshToken String?
idToken String?
accessTokenExpiresAt DateTime?
refreshTokenExpiresAt DateTime?
scope String?
password String?
createdAt DateTime
updatedAt DateTime
@@map("account")
}
model Verification {
id String @id @map("_id")
identifier String
value String
expiresAt DateTime
createdAt DateTime?
updatedAt DateTime?
@@map("verification")
}

View File

@@ -0,0 +1,9 @@
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import prisma from "../db";
export const auth = betterAuth({
database: prismaAdapter(prisma, {
provider: "pg",
}),
});

View File

@@ -7,14 +7,14 @@
"@biomejs/biome": "1.9.4", "@biomejs/biome": "1.9.4",
"@changesets/cli": "^2.28.1", "@changesets/cli": "^2.28.1",
"husky": "^9.1.7", "husky": "^9.1.7",
"lint-staged": "^15.4.3", "lint-staged": "^15.5.0",
"turbo": "^2.4.4", "turbo": "^2.4.4",
"typescript": "5.7.3", "typescript": "5.7.3",
}, },
}, },
"apps/cli": { "apps/cli": {
"name": "create-better-t-stack", "name": "create-better-t-stack",
"version": "0.11.0", "version": "0.11.1",
"bin": { "bin": {
"create-better-t-stack": "dist/index.js", "create-better-t-stack": "dist/index.js",
}, },