Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 55 additions & 5 deletions betterbase/packages/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export const db = drizzle(client, { schema });
import { drizzle } from 'drizzle-orm/bun-sqlite';
import * as schema from './schema';

const client = new Database('local.db');
const client = new Database('local.db', { create: true });

export const db = drizzle(client, { schema });
`;
Expand Down Expand Up @@ -286,26 +286,76 @@ local.db
export const healthRoute = new Hono();

healthRoute.get('/', (c) => {
return c.json({ status: 'ok', timestamp: new Date().toISOString() });
return c.json({
status: 'healthy',
database: 'connected',
timestamp: new Date().toISOString(),
});
});
`,
);

await writeFile(
path.join(projectPath, 'src/routes/index.ts'),
`import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';
import { HTTPException } from 'hono/http-exception';
import { db } from '../db';
import { users } from '../db/schema';
import { healthRoute } from './health';

const app = new Hono();

app.use('*', cors());
app.use('*', logger());
app.use('*', async (c, next) => {
const start = performance.now();
await next();
const duration = (performance.now() - start).toFixed(2);
console.log(\`⏱ \${c.req.method} \${c.req.path} - \${duration}ms\`);
});

app.onError((err, c) => {
console.error('Error:', err);
return c.json(
{
error: err.message,
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined,
details: err instanceof HTTPException ? (err as { cause?: unknown }).cause ?? null : null,
},
err instanceof HTTPException ? err.status : 500,
);
});

app.route('/health', healthRoute);

export default {
port: Number(process.env.PORT || 3000),
app.get('/api/users', async (c) => {
const allUsers = await db.select().from(users);
return c.json({ users: allUsers });
});

const server = Bun.serve({
fetch: app.fetch,
};
port: Number(process.env.PORT ?? 3000),
development: process.env.NODE_ENV === 'development',
});

console.log('\x1b[32m🚀 BetterBase dev server started\x1b[0m');
console.log(\`\x1b[36m→ URL:\x1b[0m http://localhost:\${server.port}\`);
console.log('\x1b[35m→ Routes:\x1b[0m');
console.log(' GET /health');
console.log(' GET /api/users');

process.on('SIGTERM', () => {
console.log('SIGTERM received, closing server...');
server.stop();
});

process.on('SIGINT', () => {
console.log('SIGINT received, closing server...');
server.stop();
});
`,
);

Expand Down
2 changes: 2 additions & 0 deletions betterbase/templates/base/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ src/
index.ts
schema.ts
routes/
index.ts
health.ts
users.ts
middleware/
validation.ts
lib/
Expand Down
2 changes: 1 addition & 1 deletion betterbase/templates/base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"private": true,
"type": "module",
"scripts": {
"dev": "bun run src/index.ts",
"dev": "bun --hot run src/routes/index.ts",
"db:generate": "drizzle-kit generate",
"db:push": "drizzle-kit push",
"typecheck": "tsc --noEmit"
Expand Down
2 changes: 1 addition & 1 deletion betterbase/templates/base/src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import { drizzle } from 'drizzle-orm/bun-sqlite';
import * as schema from './schema';

const dbPath = process.env.DB_PATH ?? Bun.env.DB_PATH ?? 'local.db';
const sqlite = new Database(dbPath);
const sqlite = new Database(dbPath, { create: true });

export const db = drizzle(sqlite, { schema });
37 changes: 1 addition & 36 deletions betterbase/templates/base/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1 @@
import { Hono } from 'hono';
import { HTTPException } from 'hono/http-exception';
import { env } from './lib/env';
import { healthRoute } from './routes/health';
import { usersRoute } from './routes/users';

const app = new Hono();

app.route('/health', healthRoute);
app.route('/users', usersRoute);

app.get('/', (c) => {
return c.json({
name: 'BetterBase',
message: 'Bun + Hono + Drizzle starter',
});
});

app.onError((error, c) => {
if (error instanceof HTTPException) {
return c.json(
{
error: error.message,
details: (error as { cause?: unknown }).cause ?? null,
},
error.status,
);
}

return c.json({ error: 'Internal Server Error' }, 500);
});

export default {
port: env.PORT,
fetch: app.fetch,
};
import './routes/index';
9 changes: 4 additions & 5 deletions betterbase/templates/base/src/routes/health.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Hono } from 'hono';

const healthRoute = new Hono();
export const healthRoute = new Hono();

healthRoute.get('/', (c) => {
return c.json({
status: 'ok',
service: 'betterbase-template',
status: 'healthy',
database: 'connected',
timestamp: new Date().toISOString(),
});
});
Comment on lines +5 to +11
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hardcoded database: 'connected' is misleading.

The health endpoint always reports "database": "connected" regardless of actual database state. This can mask outages. Either perform a real connectivity check (e.g., a trivial SELECT 1 via the Drizzle client) or remove the field until a real check is implemented.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@betterbase/templates/base/src/routes/health.ts` around lines 5 - 11, The
healthRoute.get handler currently hardcodes database: 'connected'; change it so
the handler performs a real DB connectivity check using the Drizzle client
(e.g., run a trivial query like SELECT 1 or call the project's
drizzleClient/pool health/check method) and set the database field based on
success/failure (or remove the database field entirely if you prefer not to
implement the check yet). Update the logic inside healthRoute.get to await the
drizzle client call, catch errors, and return database: 'connected' on success
or database: 'disconnected' (and an appropriate HTTP status or error info) on
failure; reference the healthRoute.get handler and your drizzle client instance
(e.g., drizzleClient, db, or pool) to locate where to add the check.


export { healthRoute };
62 changes: 62 additions & 0 deletions betterbase/templates/base/src/routes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';
import { HTTPException } from 'hono/http-exception';
import { db } from '../db';
import { users } from '../db/schema';
import { healthRoute } from './health';
import { usersRoute } from './users';

const app = new Hono();

app.use('*', cors());
app.use('*', logger());
app.use('*', async (c, next) => {
const start = performance.now();
await next();
const duration = (performance.now() - start).toFixed(2);
console.log(`⏱ ${c.req.method} ${c.req.path} - ${duration}ms`);
});

app.onError((err, c) => {
console.error('Error:', err);
return c.json(
{
error: err.message,
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined,
details: err instanceof HTTPException ? (err as { cause?: unknown }).cause ?? null : null,
},
err instanceof HTTPException ? err.status : 500,
);
});
Comment on lines +21 to +31
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Error message is exposed in all environments; only stack trace is gated.

err.message is returned unconditionally (line 25), which may leak internal details (e.g., DB errors, file paths) in production. Consider gating error similarly to stack:

Suggested fix
   return c.json(
     {
-      error: err.message,
+      error: process.env.NODE_ENV === 'development' ? err.message : 'Internal Server Error',
       stack: process.env.NODE_ENV === 'development' ? err.stack : undefined,
       details: err instanceof HTTPException ? (err as { cause?: unknown }).cause ?? null : null,
     },

For HTTPExceptions (user-facing errors), you may still want to show the message — gate only non-HTTP errors.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
app.onError((err, c) => {
console.error('Error:', err);
return c.json(
{
error: err.message,
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined,
details: err instanceof HTTPException ? (err as { cause?: unknown }).cause ?? null : null,
},
err instanceof HTTPException ? err.status : 500,
);
});
app.onError((err, c) => {
console.error('Error:', err);
return c.json(
{
error: process.env.NODE_ENV === 'development' ? err.message : 'Internal Server Error',
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined,
details: err instanceof HTTPException ? (err as { cause?: unknown }).cause ?? null : null,
},
err instanceof HTTPException ? err.status : 500,
);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@betterbase/templates/base/src/routes/index.ts` around lines 21 - 31, The
error handler in app.onError currently always returns err.message (potentially
leaking internals); update the response so the "error" field is only the real
err.message when process.env.NODE_ENV === 'development' or when the error is an
instance of HTTPException, otherwise return a generic message (e.g., "Internal
Server Error"); keep the existing gating for "stack" and preserve HTTPException
status handling and the details extraction (err instanceof HTTPException and
c.json return) while changing how error is selected.


app.route('/health', healthRoute);
app.route('/users', usersRoute);

app.get('/api/users', async (c) => {
const allUsers = await db.select().from(users);
return c.json({ users: allUsers });
});
Comment on lines +33 to +39
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Duplicate and inconsistent user routes.

usersRoute is mounted at /users (providing POST /users), while an inline GET /api/users handler is defined separately at lines 36-39. This splits user-related endpoints across two URL prefixes and two locations — the dedicated route module and this file.

Move the GET handler into usersRoute and mount it once at a consistent prefix (e.g., /api/users or /users):

Suggested fix (in routes/index.ts)
 app.route('/health', healthRoute);
-app.route('/users', usersRoute);
-
-app.get('/api/users', async (c) => {
-  const allUsers = await db.select().from(users);
-  return c.json({ users: allUsers });
-});
+app.route('/api/users', usersRoute);

Then add the GET handler inside users.ts alongside the existing POST.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@betterbase/templates/base/src/routes/index.ts` around lines 33 - 39, The
inline GET handler for listing users should be removed from the top-level router
and moved into the usersRoute module so all user endpoints live together; update
the mount so usersRoute is mounted once under a single prefix (choose either
'/users' or '/api/users') by replacing app.route('/users', usersRoute) or
app.route('/api/users', usersRoute) accordingly, then add a GET handler inside
usersRoute (next to the existing POST) that performs const allUsers = await
db.select().from(users); and returns c.json({ users: allUsers }); ensuring route
names (usersRoute, the GET handler) and database call (db.select().from(users))
are used exactly as in the diff.


const server = Bun.serve({
fetch: app.fetch,
port: Number(process.env.PORT ?? 3000),
development: process.env.NODE_ENV === 'development',
});

console.log('\x1b[32m🚀 BetterBase dev server started\x1b[0m');
console.log(`\x1b[36m→ URL:\x1b[0m http://localhost:${server.port}`);
console.log('\x1b[35m→ Routes:\x1b[0m');
console.log(' GET /health');
console.log(' GET /api/users');
console.log(' POST /users');

process.on('SIGTERM', () => {
console.log('SIGTERM received, closing server...');
server.stop();
});

process.on('SIGINT', () => {
console.log('SIGINT received, closing server...');
server.stop();
});