diff --git a/betterbase/packages/cli/src/commands/init.ts b/betterbase/packages/cli/src/commands/init.ts index bdcbc68..6a856c8 100644 --- a/betterbase/packages/cli/src/commands/init.ts +++ b/betterbase/packages/cli/src/commands/init.ts @@ -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 }); `; @@ -286,7 +286,11 @@ 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(), + }); }); `, ); @@ -294,18 +298,64 @@ healthRoute.get('/', (c) => { 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(); +}); `, ); diff --git a/betterbase/templates/base/README.md b/betterbase/templates/base/README.md index 3f28464..e6d4cf1 100644 --- a/betterbase/templates/base/README.md +++ b/betterbase/templates/base/README.md @@ -15,7 +15,9 @@ src/ index.ts schema.ts routes/ + index.ts health.ts + users.ts middleware/ validation.ts lib/ diff --git a/betterbase/templates/base/package.json b/betterbase/templates/base/package.json index 30cb61d..3144f6d 100644 --- a/betterbase/templates/base/package.json +++ b/betterbase/templates/base/package.json @@ -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" diff --git a/betterbase/templates/base/src/db/index.ts b/betterbase/templates/base/src/db/index.ts index 8de0521..e2a72b6 100644 --- a/betterbase/templates/base/src/db/index.ts +++ b/betterbase/templates/base/src/db/index.ts @@ -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 }); diff --git a/betterbase/templates/base/src/index.ts b/betterbase/templates/base/src/index.ts index b024a27..80be963 100644 --- a/betterbase/templates/base/src/index.ts +++ b/betterbase/templates/base/src/index.ts @@ -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'; diff --git a/betterbase/templates/base/src/routes/health.ts b/betterbase/templates/base/src/routes/health.ts index 8bce826..c8995ca 100644 --- a/betterbase/templates/base/src/routes/health.ts +++ b/betterbase/templates/base/src/routes/health.ts @@ -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(), }); }); - -export { healthRoute }; diff --git a/betterbase/templates/base/src/routes/index.ts b/betterbase/templates/base/src/routes/index.ts new file mode 100644 index 0000000..d5a0174 --- /dev/null +++ b/betterbase/templates/base/src/routes/index.ts @@ -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, + ); +}); + +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 }); +}); + +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(); +});