@yoms/create-monorepo 2.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +132 -4
  2. package/dist/index.js +113 -4
  3. package/package.json +1 -1
  4. package/templates/backend-hono/base/{AGENT.md → CLAUDE.md} +42 -3
  5. package/templates/backend-hono/features/better-auth/env-additions.txt +4 -0
  6. package/templates/backend-hono/features/better-auth/package-additions.json +5 -0
  7. package/templates/backend-hono/features/better-auth/prisma/schema.prisma +69 -0
  8. package/templates/backend-hono/features/better-auth/src/config/env-auth.ts +8 -0
  9. package/templates/backend-hono/features/better-auth/src/index.ts +53 -0
  10. package/templates/backend-hono/features/better-auth/src/lib/auth.ts +21 -0
  11. package/templates/backend-hono/features/better-auth/src/middleware/auth.middleware.ts +38 -0
  12. package/templates/frontend-nextjs/base/CLAUDE.md +183 -0
  13. package/templates/frontend-nextjs/base/app/layout.tsx +4 -1
  14. package/templates/frontend-nextjs/base/components/examples/users-list-example.tsx +127 -0
  15. package/templates/frontend-nextjs/base/lib/auth-client.ts +18 -0
  16. package/templates/frontend-nextjs/base/package.json +4 -1
  17. package/templates/frontend-nextjs/base/providers/query-provider.tsx +28 -0
  18. package/templates/frontend-nextjs/base/services/README.md +184 -0
  19. package/templates/frontend-nextjs/base/{lib/api-client.ts → services/api/client.ts} +1 -0
  20. package/templates/frontend-nextjs/base/services/api/endpoints.ts +26 -0
  21. package/templates/frontend-nextjs/base/services/api/index.ts +6 -0
  22. package/templates/frontend-nextjs/base/services/auth/auth.hook.ts +61 -0
  23. package/templates/frontend-nextjs/base/services/auth/auth.types.ts +38 -0
  24. package/templates/frontend-nextjs/base/services/auth/index.ts +12 -0
  25. package/templates/frontend-nextjs/base/services/users/index.ts +8 -0
  26. package/templates/frontend-nextjs/base/services/users/users.hook.ts +119 -0
  27. package/templates/frontend-nextjs/base/services/users/users.queries.ts +14 -0
  28. package/templates/frontend-nextjs/base/services/users/users.service.ts +65 -0
  29. package/templates/frontend-nextjs/base/services/users/users.types.ts +37 -0
  30. package/templates/shared/base/CLAUDE.md +95 -0
  31. package/templates/backend-hono/features/jwt-auth/env-additions.txt +0 -5
  32. package/templates/backend-hono/features/jwt-auth/package-additions.json +0 -10
  33. package/templates/backend-hono/features/jwt-auth/src/config/env-additions.ts +0 -16
  34. package/templates/backend-hono/features/jwt-auth/src/lib/jwt.ts +0 -75
  35. package/templates/backend-hono/features/jwt-auth/src/middleware/auth.middleware.ts +0 -50
  36. package/templates/backend-hono/features/jwt-auth/src/routes/auth.route.ts +0 -157
package/README.md CHANGED
@@ -33,8 +33,10 @@ npm create @yoms/monorepo my-project
33
33
 
34
34
  ## Quick Start
35
35
 
36
+ ### Interactive Mode (Default)
37
+
36
38
  ```bash
37
- # Create a new project
39
+ # Create a new project with interactive prompts
38
40
  npx @yoms/create-monorepo my-project
39
41
 
40
42
  # Navigate to project
@@ -51,6 +53,49 @@ pnpm dev
51
53
  # - Frontend: http://localhost:3000
52
54
  ```
53
55
 
56
+ ### Non-Interactive Mode with CLI Flags
57
+
58
+ ```bash
59
+ # Create a project with all features using CLI flags
60
+ npx @yoms/create-monorepo my-project \
61
+ --backend hono \
62
+ --database postgres \
63
+ --redis \
64
+ --smtp \
65
+ --swagger \
66
+ --auth \
67
+ --frontend \
68
+ --shadcn \
69
+ --docker \
70
+ --pm pnpm
71
+
72
+ # Quick start with defaults (skips all prompts)
73
+ npx @yoms/create-monorepo my-project --yes
74
+ ```
75
+
76
+ ## CLI Options
77
+
78
+ ```bash
79
+ npx @yoms/create-monorepo [dir] [options]
80
+
81
+ Options:
82
+ --backend <framework> Backend framework (hono)
83
+ --database <db> Database (postgres, mongodb, none)
84
+ --redis Include Redis cache
85
+ --smtp Include SMTP email
86
+ --swagger Include Swagger docs
87
+ --auth Include JWT authentication
88
+ --frontend Include Next.js frontend
89
+ --shadcn Include shadcn/ui components
90
+ --pm <manager> Package manager (pnpm, npm, yarn, bun)
91
+ --docker Include Docker Compose setup
92
+ --skip-install Skip dependency installation
93
+ --skip-git Skip git initialization
94
+ -y, --yes Skip all prompts and use defaults
95
+ -h, --help Display help
96
+ -v, --version Display version
97
+ ```
98
+
54
99
  ## What You Get
55
100
 
56
101
  ### Backend Options
@@ -60,8 +105,12 @@ pnpm dev
60
105
  - **Caching**: Redis with ready-to-use cache service
61
106
  - **Email**: SMTP with Nodemailer
62
107
  - **Docs**: Swagger/OpenAPI with Scalar UI
108
+ - **Authentication**: JWT with refresh tokens (optional)
63
109
  - **Logging**: Winston with structured logging
64
110
  - **Validation**: Zod schemas
111
+ - **Error Handling**: Custom error classes and response helpers
112
+ - **Rate Limiting**: Memory-based rate limiter with presets
113
+ - **Testing**: Vitest with example tests
65
114
  - **Type Safety**: Full TypeScript with strict mode
66
115
 
67
116
  ### Frontend
@@ -85,12 +134,14 @@ my-project/
85
134
  │ ├── api/ # Backend API
86
135
  │ │ ├── src/
87
136
  │ │ │ ├── routes/ # API routes
88
- │ │ │ ├── middleware/ # Express/Hono middleware
137
+ │ │ │ ├── middleware/ # Hono middleware
89
138
  │ │ │ ├── services/ # Business logic
90
139
  │ │ │ ├── config/ # Configuration (env, logger, db)
140
+ │ │ │ ├── lib/ # Utilities (jwt, errors, response)
91
141
  │ │ │ └── types/ # TypeScript types
92
- │ │ ├── prisma/ # Database schema
93
- │ │ └── Dockerfile # Production container
142
+ │ │ ├── prisma/ # Database schema (if database selected)
143
+ │ │ ├── Dockerfile # Production container
144
+ │ │ └── vitest.config.ts # Test configuration
94
145
  │ │
95
146
  │ ├── web/ # Next.js frontend
96
147
  │ │ ├── app/ # App router pages
@@ -102,6 +153,7 @@ my-project/
102
153
  │ ├── schemas/ # Zod schemas
103
154
  │ └── types.ts # Common types
104
155
 
156
+ ├── docker-compose.yml # Docker services (if --docker)
105
157
  ├── pnpm-workspace.yaml # Workspace configuration
106
158
  └── tsconfig.base.json # Shared TypeScript config
107
159
  ```
@@ -157,6 +209,35 @@ SMTP_HOST=smtp.gmail.com
157
209
  SMTP_PORT=587
158
210
  SMTP_USER=...
159
211
  SMTP_PASS=...
212
+
213
+ # JWT (if --auth selected)
214
+ JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
215
+ JWT_EXPIRES_IN=15m
216
+ JWT_REFRESH_SECRET=your-super-secret-refresh-key-change-this-in-production
217
+ JWT_REFRESH_EXPIRES_IN=7d
218
+ ```
219
+
220
+ ## Docker Compose
221
+
222
+ When using the `--docker` flag, a `docker-compose.yml` file is generated with the following services based on your configuration:
223
+
224
+ - **PostgreSQL**: If `--database postgres` is selected
225
+ - **MongoDB**: If `--database mongodb` is selected
226
+ - **Redis**: If `--redis` is selected
227
+ - **MailHog**: If `--smtp` is selected (SMTP testing server with web UI)
228
+
229
+ ```bash
230
+ # Start all services
231
+ docker-compose up -d
232
+
233
+ # View logs
234
+ docker-compose logs -f
235
+
236
+ # Stop services
237
+ docker-compose down
238
+
239
+ # View MailHog web UI (if SMTP selected)
240
+ open http://localhost:8025
160
241
  ```
161
242
 
162
243
  ## Available Scripts
@@ -165,11 +246,16 @@ SMTP_PASS=...
165
246
  # Development
166
247
  pnpm dev # Start all packages in dev mode
167
248
  pnpm --filter api dev # Start only backend
249
+ pnpm --filter web dev # Start only frontend
168
250
 
169
251
  # Building
170
252
  pnpm build # Build all packages
171
253
  pnpm typecheck # Type-check all packages
172
254
 
255
+ # Testing
256
+ pnpm --filter api test # Run backend tests
257
+ pnpm --filter api test:watch # Run tests in watch mode
258
+
173
259
  # Database (if Prisma is selected)
174
260
  pnpm --filter api prisma:generate # Generate Prisma client
175
261
  pnpm --filter api prisma:migrate # Run migrations
@@ -180,6 +266,48 @@ pnpm lint # Lint all packages
180
266
  pnpm format # Format with Prettier
181
267
  ```
182
268
 
269
+ ## Authentication (JWT)
270
+
271
+ When using the `--auth` flag, JWT authentication is set up with the following features:
272
+
273
+ - **Access tokens**: Short-lived (15 minutes default)
274
+ - **Refresh tokens**: Long-lived (7 days default)
275
+ - **Password hashing**: bcryptjs with salt rounds
276
+ - **Protected routes**: Auth middleware for route protection
277
+
278
+ ### Example Usage
279
+
280
+ ```typescript
281
+ // Register a new user
282
+ POST /auth/register
283
+ {
284
+ "email": "user@example.com",
285
+ "password": "securepassword",
286
+ "name": "John Doe"
287
+ }
288
+
289
+ // Login
290
+ POST /auth/login
291
+ {
292
+ "email": "user@example.com",
293
+ "password": "securepassword"
294
+ }
295
+
296
+ // Refresh access token
297
+ POST /auth/refresh
298
+ {
299
+ "refreshToken": "..."
300
+ }
301
+
302
+ // Protected route example
303
+ import { authMiddleware } from './middleware/auth.middleware';
304
+
305
+ app.get('/protected', authMiddleware, (c) => {
306
+ const { userId, email } = c.get('jwtPayload');
307
+ return c.json({ userId, email });
308
+ });
309
+ ```
310
+
183
311
  ## Requirements
184
312
 
185
313
  - Node.js >= 18
package/dist/index.js CHANGED
@@ -199,11 +199,11 @@ async function promptBackendConfig() {
199
199
  includeSwagger = swagger;
200
200
  }
201
201
  let includeAuth = false;
202
- if (backendType === "web") {
202
+ if (backendType === "web" && database !== "none") {
203
203
  const { auth } = await enquirer2.prompt({
204
204
  type: "confirm",
205
205
  name: "auth",
206
- message: "Include JWT authentication?",
206
+ message: "Include authentication? (better-auth \u2014 email/password + sessions)",
207
207
  initial: false
208
208
  });
209
209
  includeAuth = auth;
@@ -265,6 +265,7 @@ var BaseGenerator = class {
265
265
  __PROJECT_NAME__: this.options.projectName,
266
266
  __PACKAGE_SCOPE__: `@${this.options.projectName}`,
267
267
  __DATABASE_PROVIDER__: "none",
268
+ __PRISMA_PROVIDER__: "postgresql",
268
269
  __HAS_REDIS__: "false",
269
270
  __HAS_SMTP__: "false",
270
271
  __API_PORT__: "3001",
@@ -357,6 +358,113 @@ tmp/
357
358
  `;
358
359
  await writeFile(path2.join(this.options.projectDir, ".gitignore"), gitignoreContent);
359
360
  const tokens = this.getTokens();
361
+ const pm = this.options.packageManager;
362
+ const hasBackend = config.includeBackend;
363
+ const hasFrontend = config.includeFrontend;
364
+ const hasShared = hasBackend && hasFrontend;
365
+ const db = config.backend?.database;
366
+ const hasAuth = config.backend?.includeAuth;
367
+ const hasRedis = config.backend?.includeRedis;
368
+ const claudeMdContent = `# CLAUDE.md \u2014 ${tokens.__PROJECT_NAME__}
369
+
370
+ This file provides guidance to Claude Code when working in this monorepo.
371
+
372
+ ## Project Overview
373
+
374
+ **${tokens.__PROJECT_NAME__}** is a TypeScript monorepo generated with create-monorepo.
375
+
376
+ Packages:
377
+ ${hasBackend ? `- \`packages/api/\` \u2014 Hono backend API (port ${tokens.__API_PORT__})
378
+ ` : ""}${hasFrontend ? `- \`packages/web/\` \u2014 Next.js frontend (port ${tokens.__WEB_PORT__})
379
+ ` : ""}${hasShared ? `- \`packages/shared/\` \u2014 Shared Zod schemas and TypeScript types
380
+ ` : ""}
381
+ ## Stack
382
+
383
+ ${hasBackend ? `- **Backend**: Hono, TypeScript, Zod${db ? `, Prisma (${db})` : ""}${hasAuth ? ", better-auth" : ""}${hasRedis ? ", Redis" : ""}
384
+ ` : ""}${hasFrontend ? `- **Frontend**: Next.js 15 App Router, TanStack Query, Tailwind CSS, shadcn/ui${hasAuth ? ", better-auth client" : ""}
385
+ ` : ""}${hasShared ? `- **Shared**: Zod schemas, inferred TypeScript types
386
+ ` : ""}- **Package Manager**: ${pm}
387
+ - **Monorepo**: pnpm workspaces
388
+
389
+ ## Development Commands
390
+
391
+ \`\`\`bash
392
+ # Run everything
393
+ ${pm} dev # Start all packages in parallel
394
+
395
+ # Individual packages
396
+ ${pm} --filter api dev # Backend only
397
+ ${pm} --filter web dev # Frontend only
398
+
399
+ # Build
400
+ ${pm} build # Build all packages
401
+ ${pm} --filter shared build # Build shared first if types changed
402
+
403
+ # Quality
404
+ ${pm} typecheck # Type-check all packages
405
+ ${pm} lint # Lint all packages
406
+ ${pm} format # Format all packages
407
+ \`\`\`
408
+
409
+ ## Package Dependency
410
+
411
+ \`\`\`
412
+ ${hasShared ? `packages/shared \u2190 packages/api
413
+ \u2190 packages/web` : hasBackend ? "packages/api" : "packages/web"}
414
+ \`\`\`
415
+
416
+ ${hasShared ? `> Always build \`packages/shared\` first after schema changes: \`${pm} --filter shared build\`
417
+ ` : ""}${db ? `
418
+ ## Database
419
+
420
+ - **Provider**: ${db === "postgres" ? "PostgreSQL" : "MongoDB"} via Prisma
421
+ - Schema: \`packages/api/prisma/schema.prisma\`
422
+
423
+ \`\`\`bash
424
+ ${pm} --filter api prisma:generate # Regenerate Prisma client
425
+ ${pm} --filter api prisma:migrate # Run migrations
426
+ ${pm} --filter api prisma:studio # Open Prisma Studio
427
+ \`\`\`
428
+ ` : ""}${hasAuth ? `
429
+ ## Authentication
430
+
431
+ Powered by [better-auth](https://better-auth.com).
432
+
433
+ - Sessions stored in database, sent as HTTP-only cookies
434
+ - Auth routes handled at \`/api/auth/**\` on the backend
435
+ - Frontend uses \`lib/auth-client.ts\` \u2014 no manual token management
436
+ - Required env vars: \`BETTER_AUTH_SECRET\`, \`BETTER_AUTH_URL\`
437
+
438
+ See \`packages/api/CLAUDE.md\` for protecting routes.
439
+ See \`packages/web/CLAUDE.md\` for frontend usage.
440
+ ` : ""}
441
+ ## Environment Setup
442
+
443
+ \`\`\`bash
444
+ cp packages/api/.env.example packages/api/.env
445
+ ${hasFrontend ? `cp packages/web/.env.example packages/web/.env
446
+ ` : ""}\`\`\`
447
+
448
+ Edit the \`.env\` files before running \`${pm} dev\`.
449
+
450
+ ## Key Files
451
+
452
+ ${hasBackend ? `- \`packages/api/src/index.ts\` \u2014 Hono app entry point
453
+ - \`packages/api/src/lib/errors.ts\` \u2014 Custom error classes
454
+ - \`packages/api/src/lib/response.ts\` \u2014 Response helpers
455
+ ` : ""}${hasFrontend ? `- \`packages/web/services/api/client.ts\` \u2014 HTTP client
456
+ - \`packages/web/services/api/endpoints.ts\` \u2014 API endpoint constants
457
+ - \`packages/web/lib/auth-client.ts\` \u2014 better-auth client
458
+ ` : ""}${hasShared ? `- \`packages/shared/src/index.ts\` \u2014 All shared type exports
459
+ ` : ""}
460
+ ## Per-Package Guidance
461
+
462
+ Each package has its own \`CLAUDE.md\` with detailed patterns:
463
+ ${hasBackend ? `- \`packages/api/CLAUDE.md\` \u2014 routes, services, middleware, error handling
464
+ ` : ""}${hasFrontend ? `- \`packages/web/CLAUDE.md\` \u2014 services, hooks, auth, components
465
+ ` : ""}${hasShared ? `- \`packages/shared/CLAUDE.md\` \u2014 adding schemas and types
466
+ ` : ""}`;
467
+ await writeFile(path2.join(this.options.projectDir, "CLAUDE.md"), claudeMdContent);
360
468
  const readmeContent = `# ${tokens.__PROJECT_NAME__}
361
469
 
362
470
  Generated with create-monorepo.
@@ -530,6 +638,7 @@ var BackendGenerator = class extends BaseGenerator {
530
638
  const tokens = this.getTokens({
531
639
  __BACKEND_FRAMEWORK__: this.config.framework,
532
640
  __DATABASE_PROVIDER__: this.config.database || "none",
641
+ __PRISMA_PROVIDER__: this.config.database === "postgres" ? "postgresql" : this.config.database === "mongodb" ? "mongodb" : "postgresql",
533
642
  __HAS_REDIS__: String(this.config.includeRedis),
534
643
  __HAS_SMTP__: String(this.config.includeSmtp)
535
644
  });
@@ -561,8 +670,8 @@ var BackendGenerator = class extends BaseGenerator {
561
670
  const swaggerFeaturePath = path4.join(featuresPath, "swagger");
562
671
  await mergeFeature(backendDir, swaggerFeaturePath, tokens);
563
672
  }
564
- if (this.config.includeAuth) {
565
- const authFeaturePath = path4.join(featuresPath, "jwt-auth");
673
+ if (this.config.includeAuth && this.config.database) {
674
+ const authFeaturePath = path4.join(featuresPath, "better-auth");
566
675
  await mergeFeature(backendDir, authFeaturePath, tokens);
567
676
  }
568
677
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yoms/create-monorepo",
3
- "version": "2.0.0",
3
+ "version": "4.0.0",
4
4
  "description": "CLI tool to scaffold monorepo projects from templates",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
- # AGENT.md - Backend API
1
+ # CLAUDE.md Backend API (`__PACKAGE_SCOPE__/api`)
2
2
 
3
- This file provides guidance to AI agents (Claude, Cursor, etc.) when working with this backend API.
3
+ This file provides guidance to Claude Code when working in this package.
4
4
 
5
5
  ## Project Overview
6
6
 
@@ -315,10 +315,49 @@ logger.error('Database error', { error, query });
315
315
  - Use `prisma.$transaction()` for multiple related operations
316
316
  - Profile with `pnpm test:coverage` to find slow tests
317
317
 
318
+ ## Authentication (better-auth)
319
+
320
+ If auth is enabled, the project uses [better-auth](https://better-auth.com) with cookie-based sessions.
321
+
322
+ ### How it works
323
+
324
+ - All auth routes are mounted at `/api/auth/**` — handled automatically by better-auth
325
+ - Sessions are stored in the database (Prisma adapter)
326
+ - Cookies are HTTP-only — no manual token management
327
+
328
+ ### Protecting routes
329
+
330
+ ```typescript
331
+ import { requireAuth, optionalAuth } from '../middleware/auth.middleware.js';
332
+
333
+ // Require a valid session
334
+ app.get('/profile', requireAuth, (c) => {
335
+ const session = c.get('session'); // { user: User, session: Session }
336
+ return success(c, session.user);
337
+ });
338
+
339
+ // Attach session if present, but don't block
340
+ app.get('/feed', optionalAuth, (c) => {
341
+ const session = c.get('session'); // null if not logged in
342
+ ...
343
+ });
344
+ ```
345
+
346
+ ### Prisma schema
347
+
348
+ The schema includes `user`, `session`, `account`, and `verification` tables managed by better-auth. Do not modify them manually — run `npx better-auth generate` if you change `src/lib/auth.ts`.
349
+
350
+ ### Auth environment variables
351
+
352
+ ```
353
+ BETTER_AUTH_SECRET=... # min 32 chars
354
+ BETTER_AUTH_URL=... # your API URL, e.g. http://localhost:3001
355
+ ```
356
+
318
357
  ## Security Checklist
319
358
 
320
359
  - [ ] All inputs are validated with Zod
321
- - [ ] Rate limiting is applied to auth endpoints
360
+ - [ ] Rate limiting is applied to sensitive endpoints
322
361
  - [ ] Sensitive data is not logged
323
362
  - [ ] Database queries use parameterized queries (Prisma does this)
324
363
  - [ ] CORS is configured correctly
@@ -0,0 +1,4 @@
1
+
2
+ # Better Auth Configuration
3
+ BETTER_AUTH_SECRET=your-super-secret-key-change-in-production-must-be-32-chars-min
4
+ BETTER_AUTH_URL=http://localhost:__API_PORT__
@@ -0,0 +1,5 @@
1
+ {
2
+ "dependencies": {
3
+ "better-auth": "^1.2.7"
4
+ }
5
+ }
@@ -0,0 +1,69 @@
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ datasource db {
6
+ provider = "__PRISMA_PROVIDER__"
7
+ url = env("DATABASE_URL")
8
+ }
9
+
10
+ // Better Auth models — managed by better-auth, do not modify manually.
11
+ // Run `npx better-auth generate` to regenerate if you change auth config.
12
+
13
+ model user {
14
+ id String @id
15
+ name String
16
+ email String @unique
17
+ emailVerified Boolean
18
+ image String?
19
+ createdAt DateTime
20
+ updatedAt DateTime
21
+ sessions session[]
22
+ accounts account[]
23
+
24
+ @@map("user")
25
+ }
26
+
27
+ model session {
28
+ id String @id
29
+ expiresAt DateTime
30
+ token String @unique
31
+ createdAt DateTime
32
+ updatedAt DateTime
33
+ ipAddress String?
34
+ userAgent String?
35
+ userId String
36
+ user user @relation(fields: [userId], references: [id], onDelete: Cascade)
37
+
38
+ @@map("session")
39
+ }
40
+
41
+ model account {
42
+ id String @id
43
+ accountId String
44
+ providerId String
45
+ userId String
46
+ user user @relation(fields: [userId], references: [id], onDelete: Cascade)
47
+ accessToken String?
48
+ refreshToken String?
49
+ idToken String?
50
+ accessTokenExpiresAt DateTime?
51
+ refreshTokenExpiresAt DateTime?
52
+ scope String?
53
+ password String?
54
+ createdAt DateTime
55
+ updatedAt DateTime
56
+
57
+ @@map("account")
58
+ }
59
+
60
+ model verification {
61
+ id String @id
62
+ identifier String
63
+ value String
64
+ expiresAt DateTime
65
+ createdAt DateTime?
66
+ updatedAt DateTime?
67
+
68
+ @@map("verification")
69
+ }
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod';
2
+
3
+ const authEnvSchema = z.object({
4
+ BETTER_AUTH_SECRET: z.string().min(32, 'BETTER_AUTH_SECRET must be at least 32 characters'),
5
+ BETTER_AUTH_URL: z.string().url('BETTER_AUTH_URL must be a valid URL'),
6
+ });
7
+
8
+ export const authEnv = authEnvSchema.parse(process.env);
@@ -0,0 +1,53 @@
1
+ import { Hono } from 'hono';
2
+ import { env } from './config/env.js';
3
+ import { logger } from './config/logger.js';
4
+ import { corsMiddleware } from './middleware/cors.middleware.js';
5
+ import { loggerMiddleware } from './middleware/logger.middleware.js';
6
+ import { errorHandler } from './middleware/error.middleware.js';
7
+ import { health } from './routes/health.route.js';
8
+ import { auth } from './lib/auth.js';
9
+
10
+ const app = new Hono();
11
+
12
+ // Global middleware
13
+ app.use('*', corsMiddleware);
14
+ app.use('*', loggerMiddleware);
15
+
16
+ // Better Auth — handles all /api/auth/* routes (sign-in, sign-up, sign-out, session, etc.)
17
+ app.on(['POST', 'GET'], '/api/auth/**', (c) => auth.handler(c.req.raw));
18
+
19
+ // Routes
20
+ app.route('/health', health);
21
+
22
+ // Root endpoint
23
+ app.get('/', (c) => {
24
+ return c.json({
25
+ success: true,
26
+ message: 'Welcome to __PROJECT_NAME__ API',
27
+ version: '0.1.0',
28
+ });
29
+ });
30
+
31
+ // Error handling
32
+ app.onError(errorHandler);
33
+
34
+ // 404 handler
35
+ app.notFound((c) => {
36
+ return c.json({
37
+ success: false,
38
+ error: 'Not found',
39
+ message: `Route ${c.req.method} ${c.req.url} not found`,
40
+ }, 404);
41
+ });
42
+
43
+ // Start server
44
+ const port = env.PORT;
45
+
46
+ logger.info(`Starting server in ${env.NODE_ENV} mode...`);
47
+
48
+ export default {
49
+ port,
50
+ fetch: app.fetch,
51
+ };
52
+
53
+ console.log(`Server running on http://localhost:${port}`);
@@ -0,0 +1,21 @@
1
+ import { betterAuth } from 'better-auth';
2
+ import { prismaAdapter } from 'better-auth/adapters/prisma';
3
+ import { PrismaClient } from '@prisma/client';
4
+ import { authEnv } from '../config/env-auth.js';
5
+
6
+ const prisma = new PrismaClient();
7
+
8
+ export const auth = betterAuth({
9
+ baseURL: authEnv.BETTER_AUTH_URL,
10
+ secret: authEnv.BETTER_AUTH_SECRET,
11
+ database: prismaAdapter(prisma, {
12
+ provider: '__PRISMA_PROVIDER__',
13
+ }),
14
+ emailAndPassword: {
15
+ enabled: true,
16
+ },
17
+ trustedOrigins: [process.env.WEB_URL || 'http://localhost:__WEB_PORT__'],
18
+ });
19
+
20
+ export type Session = typeof auth.$Infer.Session;
21
+ export type User = typeof auth.$Infer.Session.user;
@@ -0,0 +1,38 @@
1
+ import type { Context, Next } from 'hono';
2
+ import { auth } from '../lib/auth.js';
3
+ import { UnauthorizedError } from '../lib/errors.js';
4
+
5
+ /**
6
+ * Middleware to require an authenticated session.
7
+ * Attaches the session to c.get('session') on success.
8
+ *
9
+ * Usage:
10
+ * app.get('/protected', requireAuth, (c) => {
11
+ * const session = c.get('session');
12
+ * return c.json({ user: session.user });
13
+ * });
14
+ */
15
+ export async function requireAuth(c: Context, next: Next): Promise<void> {
16
+ const session = await auth.api.getSession({ headers: c.req.raw.headers });
17
+
18
+ if (!session) {
19
+ throw new UnauthorizedError('Authentication required');
20
+ }
21
+
22
+ c.set('session', session);
23
+ await next();
24
+ }
25
+
26
+ /**
27
+ * Optional auth middleware — attaches session if present, continues either way.
28
+ * Use for routes that behave differently for authenticated vs. anonymous users.
29
+ */
30
+ export async function optionalAuth(c: Context, next: Next): Promise<void> {
31
+ const session = await auth.api.getSession({ headers: c.req.raw.headers });
32
+
33
+ if (session) {
34
+ c.set('session', session);
35
+ }
36
+
37
+ await next();
38
+ }