add express, automated mongodb atlas setup, fix stack architech

This commit is contained in:
Aman Varshney
2025-04-07 21:32:22 +05:30
parent c6c73fce76
commit 2cf01d155b
38 changed files with 902 additions and 393 deletions

View File

@@ -27,6 +27,13 @@ export async function setupBackendDependencies(
dependencies.push("@elysiajs/node");
devDependencies.push("tsx", "@types/node");
}
} else if (framework === "express") {
dependencies.push("express", "cors");
devDependencies.push("@types/express", "@types/cors");
if (runtime === "node") {
devDependencies.push("tsx", "@types/node");
}
}
if (runtime === "bun") {

View File

@@ -54,8 +54,9 @@ export async function createProject(options: ProjectConfig): Promise<string> {
options.database,
options.orm,
options.packageManager,
options.turso ?? options.database === "sqlite",
options.prismaPostgres,
options.dbSetup === "turso",
options.dbSetup === "prisma-postgres",
options.dbSetup === "mongodb-atlas",
);
await setupAuthTemplate(

View File

@@ -8,6 +8,7 @@ import type {
ProjectPackageManager,
} from "../types";
import { addPackageDependency } from "../utils/add-package-deps";
import { setupMongoDBAtlas } from "./mongodb-atlas-setup";
import { setupPrismaPostgres } from "./prisma-postgres-setup";
import { setupTurso } from "./turso-setup";
@@ -18,6 +19,7 @@ export async function setupDatabase(
packageManager: ProjectPackageManager,
setupTursoDb: boolean,
setupPrismaPostgresDb: boolean,
setupMongoDBAtlasDb: boolean,
): Promise<void> {
const s = spinner();
const serverDir = path.join(projectDir, "apps/server");
@@ -68,6 +70,18 @@ export async function setupDatabase(
await setupPrismaPostgres(projectDir, packageManager);
}
}
} else if (databaseType === "mongodb") {
if (orm === "prisma") {
addPackageDependency({
dependencies: ["@prisma/client"],
devDependencies: ["prisma"],
projectDir: serverDir,
});
}
if (setupMongoDBAtlasDb) {
await setupMongoDBAtlas(projectDir);
}
}
} catch (error) {
s.stop(pc.red("Failed to set up database"));

View File

@@ -44,14 +44,18 @@ export async function setupEnvironmentVariables(
if (options.database !== "none") {
if (options.orm === "prisma" && !envContent.includes("DATABASE_URL")) {
const databaseUrlLine =
options.database === "sqlite"
? ""
: `\nDATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"`;
let databaseUrlLine = "";
if (options.database === "sqlite") {
databaseUrlLine = "";
} else if (options.database === "postgres") {
databaseUrlLine = `\nDATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"`;
} else if (options.database === "mongodb") {
databaseUrlLine = `\nDATABASE_URL="mongodb://localhost:27017/mydatabase"`;
}
envContent += databaseUrlLine;
}
if (options.database === "sqlite" && !options.turso) {
if (options.database === "sqlite" && options.dbSetup !== "turso") {
if (!envContent.includes("TURSO_CONNECTION_URL")) {
envContent += "\nTURSO_CONNECTION_URL=http://127.0.0.1:8080";
}

View File

@@ -37,7 +37,7 @@ export async function setupExamples(
if (
examples.includes("ai") &&
backend === "hono" &&
(backend === "hono" || backend === "express") &&
hasWebFrontend &&
webAppExists
) {
@@ -89,6 +89,7 @@ async function updateServerIndexWithAIRoute(projectDir: string): Promise<void> {
if (await fs.pathExists(serverIndexPath)) {
let indexContent = await fs.readFile(serverIndexPath, "utf8");
const isHono = indexContent.includes("hono");
const isExpress = indexContent.includes("express");
if (isHono) {
const importSection = `import { streamText } from "ai";\nimport { google } from "@ai-sdk/google";\nimport { stream } from "hono/streaming";`;
@@ -110,6 +111,7 @@ app.post("/ai", async (c) => {
return stream(c, (stream) => stream.pipe(result.toDataStream()));
});`;
// Add imports and route handler for Hono
if (indexContent.includes("import {")) {
const lastImportIndex = indexContent.lastIndexOf("import");
const endOfLastImport = indexContent.indexOf("\n", lastImportIndex);
@@ -141,9 +143,66 @@ ${indexContent.substring(exportIndex)}`;
${aiRouteHandler}`;
}
}
} else if (isExpress) {
// Express implementation
const importSection = `import { streamText } from "ai";\nimport { google } from "@ai-sdk/google";`;
await fs.writeFile(serverIndexPath, indexContent);
const aiRouteHandler = `
// AI chat endpoint
app.post("/ai", async (req, res) => {
const { messages = [] } = req.body;
const result = streamText({
model: google("gemini-1.5-flash"),
messages,
});
result.pipeDataStreamToResponse(res);
});`;
// Add imports for Express
if (
indexContent.includes("import {") ||
indexContent.includes("import ")
) {
const lastImportIndex = indexContent.lastIndexOf("import");
const endOfLastImport = indexContent.indexOf("\n", lastImportIndex);
indexContent = `${indexContent.substring(0, endOfLastImport + 1)}
${importSection}
${indexContent.substring(endOfLastImport + 1)}`;
} else {
indexContent = `${importSection}
${indexContent}`;
}
// Add route handler for Express
const trpcHandlerIndex = indexContent.indexOf('app.use("/trpc"');
if (trpcHandlerIndex !== -1) {
indexContent = `${indexContent.substring(0, trpcHandlerIndex)}${aiRouteHandler}
${indexContent.substring(trpcHandlerIndex)}`;
} else {
const appListenIndex = indexContent.indexOf("app.listen(");
if (appListenIndex !== -1) {
// Find the line before app.listen
const prevNewlineIndex = indexContent.lastIndexOf(
"\n",
appListenIndex,
);
indexContent = `${indexContent.substring(0, prevNewlineIndex)}${aiRouteHandler}
${indexContent.substring(prevNewlineIndex)}`;
} else {
// Fallback: append to the end
indexContent = `${indexContent}
${aiRouteHandler}`;
}
}
}
await fs.writeFile(serverIndexPath, indexContent);
}
}

View File

@@ -0,0 +1,121 @@
import path from "node:path";
import { cancel, isCancel, log, text } from "@clack/prompts";
import { execa } from "execa";
import fs from "fs-extra";
import pc from "picocolors";
import { commandExists } from "../utils/command-exists";
type MongoDBConfig = {
connectionString: string;
};
async function checkAtlasCLI(): Promise<boolean> {
return commandExists("atlas");
}
async function initMongoDBAtlas(
serverDir: string,
): Promise<MongoDBConfig | null> {
try {
const hasAtlas = await checkAtlasCLI();
if (!hasAtlas) {
log.error(pc.red("MongoDB Atlas CLI not found."));
log.info(
pc.yellow(
"Please install it from: https://www.mongodb.com/docs/atlas/cli/current/install-atlas-cli/",
),
);
return null;
}
log.info(pc.yellow("Setting up MongoDB Atlas..."));
await execa("atlas", ["deployments", "setup"], {
cwd: serverDir,
stdio: "inherit",
});
log.info(pc.yellow("Please enter your connection string"));
const connectionString = await text({
message: "Paste your complete MongoDB connection string:",
validate(value) {
if (!value) return "Please enter a connection string";
if (!value.startsWith("mongodb")) {
return "URL should start with mongodb";
}
},
});
if (isCancel(connectionString)) {
cancel("MongoDB setup cancelled");
return null;
}
return {
connectionString: connectionString as string,
};
} catch (error) {
if (error instanceof Error) {
log.error(pc.red(error.message));
}
return null;
}
}
async function writeEnvFile(projectDir: string, config?: MongoDBConfig) {
const envPath = path.join(projectDir, "apps/server", ".env");
let envContent = "";
if (await fs.pathExists(envPath)) {
envContent = await fs.readFile(envPath, "utf8");
}
const mongoUrlLine = config
? `DATABASE_URL="${config.connectionString}"`
: `DATABASE_URL="mongodb://localhost:27017/mydb"`;
if (!envContent.includes("DATABASE_URL=")) {
envContent += `\n${mongoUrlLine}`;
} else {
envContent = envContent.replace(
/DATABASE_URL=.*(\r?\n|$)/,
`${mongoUrlLine}$1`,
);
}
await fs.writeFile(envPath, envContent.trim());
}
function displayManualSetupInstructions() {
log.info(`MongoDB Atlas Setup:
1. Install Atlas CLI: https://www.mongodb.com/docs/atlas/cli/stable/install-atlas-cli/
2. Run 'atlas deployments setup' and follow prompts
3. Get your connection string from the output
4. Format: mongodb+srv://USERNAME:PASSWORD@CLUSTER.mongodb.net/DATABASE_NAME
5. Add to .env as DATABASE_URL="your_connection_string"`);
}
export async function setupMongoDBAtlas(projectDir: string) {
const serverDir = path.join(projectDir, "apps/server");
try {
const config = await initMongoDBAtlas(serverDir);
if (config) {
await writeEnvFile(projectDir, config);
log.success(
pc.green("MongoDB Atlas connection string saved to .env file!"),
);
} else {
await writeEnvFile(projectDir);
displayManualSetupInstructions();
}
} catch (error) {
log.error(pc.red(`Error during MongoDB Atlas setup: ${error}`));
await writeEnvFile(projectDir);
displayManualSetupInstructions();
}
}

View File

@@ -2,6 +2,7 @@ import { note } from "@clack/prompts";
import pc from "picocolors";
import type {
ProjectAddons,
ProjectDBSetup,
ProjectDatabase,
ProjectFrontend,
ProjectOrm,
@@ -18,6 +19,7 @@ export function displayPostInstallInstructions(
addons: ProjectAddons[],
runtime: ProjectRuntime,
frontends: ProjectFrontend[],
dbSetup?: ProjectDBSetup,
) {
const runCmd = packageManager === "npm" ? "npm run" : packageManager;
const cdCmd = `cd ${projectName}`;

View File

@@ -372,9 +372,9 @@ function getOrmTemplateDir(orm: ProjectOrm, database: ProjectDatabase): string {
}
if (orm === "prisma") {
return database === "sqlite"
? "template/with-prisma-sqlite"
: "template/with-prisma-postgres";
if (database === "sqlite") return "template/with-prisma-sqlite";
if (database === "postgres") return "template/with-prisma-postgres";
if (database === "mongodb") return "template/with-prisma-mongodb";
}
return "template/base";
@@ -388,9 +388,9 @@ function getAuthLibDir(orm: ProjectOrm, database: ProjectDatabase): string {
}
if (orm === "prisma") {
return database === "sqlite"
? "with-prisma-sqlite-lib"
: "with-prisma-postgres-lib";
if (database === "sqlite") return "with-prisma-sqlite-lib";
if (database === "postgres") return "with-prisma-postgres-lib";
if (database === "mongodb") return "with-prisma-mongodb-lib";
}
throw new Error("Invalid ORM or database configuration for auth setup");

View File

@@ -12,6 +12,7 @@ import {
import { $ } from "execa";
import fs from "fs-extra";
import pc from "picocolors";
import { commandExists } from "../utils/command-exists";
type TursoConfig = {
dbUrl: string;
@@ -26,12 +27,7 @@ type TursoGroup = {
};
async function isTursoInstalled() {
try {
const result = await $`turso --version`;
return result.exitCode === 0;
} catch (error) {
return false;
}
return commandExists("turso");
}
async function isTursoLoggedIn() {