better-ts-stack 2.1.0 → 2.1.2
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.
- package/LICENSE +21 -21
- package/dist/args.js +9 -0
- package/dist/builder/configGenerator.js +3 -1
- package/dist/builder/moduleSelector.js +8 -1
- package/dist/builder/readmeGenerator.js +12 -0
- package/dist/builder/templateContext.js +24 -0
- package/dist/output/nextSteps.js +4 -0
- package/dist/prompts/index.js +3 -1
- package/dist/prompts/stack.js +9 -0
- package/dist/types/index.js +3 -0
- package/dist/ui/messages.js +3 -0
- package/package.json +71 -71
- package/templates/backend/express/.prettierignore +52 -52
- package/templates/backend/express/.prettierrc +16 -16
- package/templates/backend/express/config.json +8 -8
- package/templates/backend/express/gitignore +6 -1
- package/templates/backend/express/tsconfig.eslint.json +9 -9
- package/templates/backend/nestjs/.prettierignore +52 -52
- package/templates/backend/nestjs/config.json +10 -10
- package/templates/backend/nestjs/gitignore +6 -1
- package/templates/backend/nestjs/src/app.module.ts.hbs +1 -1
- package/templates/backend/nestjs/src/health/{health.controller.ts → health.controller.ts.hbs} +8 -0
- package/templates/backend/nestjs/tsconfig.eslint.json +9 -9
- package/templates/frontend/nextjs/app/globals.css +89 -89
- package/templates/frontend/nextjs/app/layout.tsx +34 -34
- package/templates/frontend/nextjs/components/ui/button.tsx +51 -51
- package/templates/frontend/nextjs/components/ui/card.tsx +60 -60
- package/templates/frontend/nextjs/components/ui/field.tsx +67 -67
- package/templates/frontend/nextjs/components/ui/input.tsx +18 -18
- package/templates/frontend/nextjs/config.json +35 -37
- package/templates/frontend/nextjs/eslint.config.mjs +11 -11
- package/templates/frontend/nextjs/gitignore +3 -0
- package/templates/frontend/nextjs/next.config.ts.hbs +12 -12
- package/templates/frontend/nextjs/postcss.config.mjs +7 -7
- package/templates/frontend/nextjs/public/file.svg +1 -1
- package/templates/frontend/nextjs/public/globe.svg +1 -1
- package/templates/frontend/nextjs/public/window.svg +1 -1
- package/templates/frontend/tanstack/components.json +18 -18
- package/templates/frontend/tanstack/config.json +15 -15
- package/templates/frontend/tanstack/eslint.config.mjs +23 -23
- package/templates/frontend/tanstack/gitignore +42 -39
- package/templates/frontend/tanstack/public/favicon.svg +7 -7
- package/templates/frontend/tanstack/src/components/ui/button.tsx +51 -51
- package/templates/frontend/tanstack/src/components/ui/card.tsx +60 -60
- package/templates/frontend/tanstack/src/components/ui/field.tsx +67 -67
- package/templates/frontend/tanstack/src/components/ui/input.tsx +18 -18
- package/templates/frontend/tanstack/src/lib/utils.ts +6 -6
- package/templates/frontend/tanstack/src/router.tsx +31 -31
- package/templates/frontend/tanstack/src/routes/__root.tsx +69 -69
- package/templates/frontend/tanstack/tsconfig.json +22 -22
- package/templates/frontend/tanstack/vite.config.ts +5 -4
- package/templates/modules/agents/AGENTS.md.hbs +237 -0
- package/templates/modules/agents/CLAUDE.md +1 -0
- package/templates/modules/agents/config.json +10 -0
- package/templates/modules/auth/express/config.json +1 -1
- package/templates/modules/auth/express/src/lib/jwt.ts.hbs +27 -27
- package/templates/modules/auth/express/src/middleware/requireAuth.ts.hbs +1 -2
- package/templates/modules/auth/nestjs/config.json +1 -1
- package/templates/modules/auth/nextjs/app/api/auth/[...all]/route.ts +4 -4
- package/templates/modules/auth/nextjs/config.json +18 -18
- package/templates/modules/auth/nextjs/lib/auth-client.ts +3 -3
- package/templates/{frontend/nextjs/proxy.ts.hbs → modules/auth/nextjs/proxy.ts} +1 -8
- package/templates/modules/auth/tanstack/config.json +18 -18
- package/templates/modules/auth/tanstack/src/lib/auth-client.ts +2 -2
- package/templates/modules/auth/tanstack/src/lib/auth-functions.ts +8 -8
- package/templates/modules/auth/tanstack/src/lib/auth.ts.hbs +37 -37
- package/templates/modules/auth/tanstack/src/routes/api/auth/$.ts +15 -15
- package/templates/modules/auth/tanstack/src/routes/dashboard.tsx +110 -110
- package/templates/modules/auth/tanstack/src/routes/sign-in.tsx +36 -36
- package/templates/modules/auth/tanstack/src/routes/sign-up.tsx +36 -36
- package/templates/modules/ci/.github/workflows/ci.yml.hbs +43 -0
- package/templates/modules/ci/config.json +10 -0
- package/templates/modules/docker/.dockerignore +3 -0
- package/templates/modules/docker/config.json +15 -15
- package/templates/modules/docker/docker-compose.yml.hbs +1 -1
- package/templates/modules/drizzle/express/config.json +26 -26
- package/templates/modules/drizzle/nestjs/config.json +26 -26
- package/templates/modules/drizzle/nextjs/config.json +26 -26
- package/templates/modules/drizzle/nextjs/drizzle.config.ts +13 -13
- package/templates/modules/drizzle/tanstack/config.json +26 -26
- package/templates/modules/drizzle/tanstack/drizzle.config.ts +12 -12
- package/templates/modules/drizzle/tanstack/src/lib/db.ts +13 -13
- package/templates/modules/mongoose/express/config.json +15 -15
- package/templates/modules/mongoose/express/src/lib/db.ts +43 -43
- package/templates/modules/mongoose/express/src/models/User.ts +48 -48
- package/templates/modules/mongoose/nestjs/config.json +15 -15
- package/templates/modules/mongoose/nextjs/config.json +15 -15
- package/templates/modules/mongoose/nextjs/models/User.ts +48 -48
- package/templates/modules/mongoose/tanstack/config.json +16 -16
- package/templates/modules/mongoose/tanstack/src/models/User.ts +48 -48
- package/templates/modules/prisma/express/config.json +27 -27
- package/templates/modules/prisma/express/prisma/schema.prisma +23 -23
- package/templates/modules/prisma/express/prisma.config.ts +4 -2
- package/templates/modules/prisma/nestjs/config.json +27 -27
- package/templates/modules/prisma/nestjs/prisma.config.ts +4 -2
- package/templates/modules/prisma/nextjs/config.json +28 -28
- package/templates/modules/prisma/nextjs/lib/prisma.ts +18 -18
- package/templates/modules/prisma/nextjs/prisma.config.ts +14 -12
- package/templates/modules/prisma/tanstack/config.json +28 -28
- package/templates/modules/prisma/tanstack/prisma/schema.prisma.hbs +91 -91
- package/templates/modules/prisma/tanstack/prisma.config.ts +14 -12
- package/templates/modules/prisma/tanstack/src/lib/prisma.ts +17 -17
- package/templates/modules/rate-limit/express/config.json +1 -1
- package/templates/modules/rate-limit/express/src/middleware/rateLimit.ts +3 -1
- package/templates/modules/vscode/.vscode/extensions.json.hbs +29 -0
- package/templates/modules/vscode/.vscode/settings.json.hbs +22 -0
- package/templates/modules/vscode/config.json +10 -0
|
@@ -4,7 +4,7 @@ import viteReact from "@vitejs/plugin-react";
|
|
|
4
4
|
import { nitro } from "nitro/vite";
|
|
5
5
|
import { defineConfig } from "vite";
|
|
6
6
|
|
|
7
|
-
export default defineConfig(({ command }) => ({
|
|
7
|
+
export default defineConfig(({ command, isPreview }) => ({
|
|
8
8
|
server: {
|
|
9
9
|
port: 3000,
|
|
10
10
|
},
|
|
@@ -15,8 +15,9 @@ export default defineConfig(({ command }) => ({
|
|
|
15
15
|
tanstackStart({ srcDirectory: "src" }),
|
|
16
16
|
tailwindcss(),
|
|
17
17
|
viteReact(),
|
|
18
|
-
// Nitro builds the production server in .output/ (run with node)
|
|
19
|
-
// left out of `vite dev`, where TanStack Start's
|
|
20
|
-
|
|
18
|
+
// Nitro builds the production server in .output/ (run with node) and serves
|
|
19
|
+
// it for `vite preview`. It is left out of `vite dev`, where TanStack Start's
|
|
20
|
+
// own dev server is steadier
|
|
21
|
+
(command === "build" || isPreview) && nitro(),
|
|
21
22
|
],
|
|
22
23
|
}));
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Instructions for AI coding agents working in {{projectName}}. Read this before making changes.
|
|
4
|
+
|
|
5
|
+
## Stack
|
|
6
|
+
|
|
7
|
+
{{#if (eq framework 'express')}}
|
|
8
|
+
- Express 5 REST API in TypeScript, compiled to CommonJS with `tsc`
|
|
9
|
+
{{/if}}
|
|
10
|
+
{{#if (eq framework 'nestjs')}}
|
|
11
|
+
- NestJS 12 REST API in TypeScript, using ES modules
|
|
12
|
+
{{/if}}
|
|
13
|
+
{{#if (eq framework 'nextjs')}}
|
|
14
|
+
- Next.js 16 with the App Router, React 19, Tailwind CSS 4, and shadcn/ui
|
|
15
|
+
{{/if}}
|
|
16
|
+
{{#if (eq framework 'tanstack')}}
|
|
17
|
+
- TanStack Start with Vite, Nitro, React 19, Tailwind CSS 4, and shadcn/ui
|
|
18
|
+
{{/if}}
|
|
19
|
+
{{#if (eq database 'prisma')}}
|
|
20
|
+
- PostgreSQL through Prisma 7
|
|
21
|
+
{{/if}}
|
|
22
|
+
{{#if (eq database 'drizzle')}}
|
|
23
|
+
- PostgreSQL through Drizzle ORM
|
|
24
|
+
{{/if}}
|
|
25
|
+
{{#if (eq database 'mongoose')}}
|
|
26
|
+
- MongoDB through Mongoose
|
|
27
|
+
{{/if}}
|
|
28
|
+
{{#if useAuth}}
|
|
29
|
+
{{#if (eq framework 'express')}}
|
|
30
|
+
- JWT auth: `POST /auth/register`, `POST /auth/login`, and `GET /auth/me`
|
|
31
|
+
{{/if}}
|
|
32
|
+
{{#if (eq framework 'nestjs')}}
|
|
33
|
+
- JWT auth: `POST /auth/register`, `POST /auth/login`, and `GET /auth/me`
|
|
34
|
+
{{/if}}
|
|
35
|
+
{{#if (eq framework 'nextjs')}}
|
|
36
|
+
- Better Auth with email and password
|
|
37
|
+
{{/if}}
|
|
38
|
+
{{#if (eq framework 'tanstack')}}
|
|
39
|
+
- Better Auth with email and password
|
|
40
|
+
{{/if}}
|
|
41
|
+
{{/if}}
|
|
42
|
+
{{#if useRateLimit}}
|
|
43
|
+
- Rate limiting on every route except `/health`
|
|
44
|
+
{{/if}}
|
|
45
|
+
{{#if useDocker}}
|
|
46
|
+
- Docker: `Dockerfile` and `docker-compose.yml`
|
|
47
|
+
{{/if}}
|
|
48
|
+
{{#if useCi}}
|
|
49
|
+
- GitHub Actions CI in `.github/workflows/ci.yml`
|
|
50
|
+
{{/if}}
|
|
51
|
+
- Package manager: {{packageManager}}. Do not use another one or add its lockfile
|
|
52
|
+
- Node.js 24 or later
|
|
53
|
+
|
|
54
|
+
## Commands
|
|
55
|
+
|
|
56
|
+
- Install: `{{packageManager}} install`
|
|
57
|
+
- Dev server: `{{docker.run}} dev`
|
|
58
|
+
- Build: `{{docker.run}} build`
|
|
59
|
+
- Type-check: `{{docker.run}} type:check`
|
|
60
|
+
- Lint: `{{docker.run}} lint`
|
|
61
|
+
{{#if (eq database 'prisma')}}
|
|
62
|
+
- After changing `prisma/schema.prisma`: `{{docker.run}} prisma:migrate` creates and applies a migration and regenerates the client
|
|
63
|
+
{{/if}}
|
|
64
|
+
{{#if (eq database 'drizzle')}}
|
|
65
|
+
- After changing the schema: `{{docker.run}} db:generate` writes a migration, then `{{docker.run}} db:migrate` applies it
|
|
66
|
+
{{/if}}
|
|
67
|
+
|
|
68
|
+
## Before you finish
|
|
69
|
+
|
|
70
|
+
Run type-check and lint, and fix every error they report. Run the build too when you changed config, dependencies, or anything the build reads. Do not report a task as done while any of them fails.
|
|
71
|
+
|
|
72
|
+
## Layout
|
|
73
|
+
|
|
74
|
+
{{#if (eq framework 'express')}}
|
|
75
|
+
- `src/index.ts`: creates the app, adds middleware, and mounts the routers
|
|
76
|
+
- `src/env.ts`: checks the environment variables with Zod at startup, so a missing one stops the server with a clear message
|
|
77
|
+
- `src/routes/`: one router per resource, mounted in `src/index.ts`
|
|
78
|
+
{{#if useAuth}}
|
|
79
|
+
- `src/controllers/`, `src/services/`: request handlers and data access for auth
|
|
80
|
+
- `src/middleware/requireAuth.ts`: add it to a route to require a valid JWT
|
|
81
|
+
{{/if}}
|
|
82
|
+
{{#if useRateLimit}}
|
|
83
|
+
- `src/middleware/rateLimit.ts`: the rate limiters
|
|
84
|
+
{{/if}}
|
|
85
|
+
{{#if (eq database 'prisma')}}
|
|
86
|
+
- `src/lib/prisma.ts`: the shared Prisma client. Do not create another one
|
|
87
|
+
{{/if}}
|
|
88
|
+
{{#if (eq database 'drizzle')}}
|
|
89
|
+
- `src/lib/db.ts`, `src/lib/schema.ts`: the database client and the tables
|
|
90
|
+
{{/if}}
|
|
91
|
+
{{#if (eq database 'mongoose')}}
|
|
92
|
+
- `src/lib/db.ts`, `src/models/`: the connection and the Mongoose models
|
|
93
|
+
{{/if}}
|
|
94
|
+
{{/if}}
|
|
95
|
+
{{#if (eq framework 'nestjs')}}
|
|
96
|
+
- `src/main.ts`: starts the app
|
|
97
|
+
- `src/app.module.ts`: the root module. Register new modules and controllers here
|
|
98
|
+
- `src/env.ts`: checks the environment variables with Zod at startup, so a missing one stops the server with a clear message
|
|
99
|
+
- `src/health/`: `GET /health`
|
|
100
|
+
{{#if useAuth}}
|
|
101
|
+
- `src/auth/`: the auth module. Put `@UseGuards(AuthGuard)` on a route to require a valid JWT
|
|
102
|
+
{{/if}}
|
|
103
|
+
{{#if (eq database 'prisma')}}
|
|
104
|
+
- `src/prisma/`: `PrismaService`, injected wherever the database is needed
|
|
105
|
+
{{/if}}
|
|
106
|
+
{{#if (eq database 'drizzle')}}
|
|
107
|
+
- `src/database/`: the database module and the tables in `schema.ts`
|
|
108
|
+
{{/if}}
|
|
109
|
+
{{#if (eq database 'mongoose')}}
|
|
110
|
+
- `src/users/user.schema.ts`: the Mongoose schema for users
|
|
111
|
+
{{/if}}
|
|
112
|
+
{{/if}}
|
|
113
|
+
{{#if (eq framework 'nextjs')}}
|
|
114
|
+
- `app/`: routes, layouts, and route handlers. Pages are Server Components unless they need state, effects, or event handlers
|
|
115
|
+
- `components/ui/`: shadcn/ui components. Add new ones with the shadcn CLI instead of writing them by hand
|
|
116
|
+
- `lib/utils.ts`: the `cn()` class name helper
|
|
117
|
+
{{#if useAuth}}
|
|
118
|
+
- `lib/auth.ts`: the Better Auth server. `lib/auth-client.ts`: the client for components
|
|
119
|
+
- `app/api/auth/[...all]/route.ts`: the Better Auth endpoints. Do not rename it
|
|
120
|
+
- `proxy.ts`: sends signed-out visitors from `/dashboard` to `/sign-in`. Add other protected paths to its `matcher`
|
|
121
|
+
{{/if}}
|
|
122
|
+
{{#if (eq database 'prisma')}}
|
|
123
|
+
- `lib/prisma.ts`: the shared Prisma client. Do not create another one
|
|
124
|
+
{{/if}}
|
|
125
|
+
{{#if (eq database 'drizzle')}}
|
|
126
|
+
- `lib/db.ts`, `lib/schema.ts`: the database client and the tables
|
|
127
|
+
{{/if}}
|
|
128
|
+
{{#if (eq database 'mongoose')}}
|
|
129
|
+
- `lib/db.ts`, `models/`: the connection and the Mongoose models
|
|
130
|
+
{{/if}}
|
|
131
|
+
{{/if}}
|
|
132
|
+
{{#if (eq framework 'tanstack')}}
|
|
133
|
+
- `src/routes/`: file-based routes. A new file becomes a route
|
|
134
|
+
- `src/routeTree.gen.ts`: generated by the dev server and the build. Never edit it
|
|
135
|
+
- `src/router.tsx`, `src/routes/__root.tsx`: the router and the root layout
|
|
136
|
+
- `src/components/ui/`: shadcn/ui components. Add new ones with the shadcn CLI instead of writing them by hand
|
|
137
|
+
- `src/lib/utils.ts`: the `cn()` class name helper
|
|
138
|
+
{{#if useAuth}}
|
|
139
|
+
- `src/lib/auth.ts`: the Better Auth server. `src/lib/auth-client.ts`: the client for components
|
|
140
|
+
- `src/routes/api/auth/$.ts`: the Better Auth endpoints. Do not rename it
|
|
141
|
+
- `src/routes/dashboard.tsx`: an example of a protected route that redirects in `beforeLoad`
|
|
142
|
+
{{/if}}
|
|
143
|
+
{{#if (eq database 'prisma')}}
|
|
144
|
+
- `src/lib/prisma.ts`: the shared Prisma client. Do not create another one
|
|
145
|
+
{{/if}}
|
|
146
|
+
{{#if (eq database 'drizzle')}}
|
|
147
|
+
- `src/lib/db.ts`, `src/lib/schema.ts`: the database client and the tables
|
|
148
|
+
{{/if}}
|
|
149
|
+
{{#if (eq database 'mongoose')}}
|
|
150
|
+
- `src/lib/db.ts`, `src/models/`: the connection and the Mongoose models
|
|
151
|
+
{{/if}}
|
|
152
|
+
{{/if}}
|
|
153
|
+
|
|
154
|
+
## Rules
|
|
155
|
+
|
|
156
|
+
- TypeScript is strict. Do not use `any` or `@ts-ignore` to silence an error; fix the type
|
|
157
|
+
{{#if (eq framework 'nestjs')}}
|
|
158
|
+
- Relative imports end in `.js`, as ES modules require: `import { AuthModule } from "./auth/auth.module.js"`
|
|
159
|
+
{{/if}}
|
|
160
|
+
- Keep `.env` out of git. When you add an environment variable, add it to `.env.example` too{{#if (eq framework 'express')}}, and to `src/env.ts`{{/if}}{{#if (eq framework 'nestjs')}}, and to `src/env.ts`{{/if}}
|
|
161
|
+
{{#if (eq database 'prisma')}}
|
|
162
|
+
- The Prisma client is generated into {{#if (eq framework 'express')}}`src/generated/`{{/if}}{{#if (eq framework 'nestjs')}}`src/generated/`{{/if}}{{#if (eq framework 'nextjs')}}`generated/`{{/if}}{{#if (eq framework 'tanstack')}}`generated/`{{/if}}. Never edit or commit it
|
|
163
|
+
{{/if}}
|
|
164
|
+
- Match the existing code style. ESLint settings are in `eslint.config.mjs`{{#if (eq framework 'express')}} and Prettier settings in `.prettierrc`{{/if}}{{#if (eq framework 'nestjs')}} and Prettier settings in `.prettierrc`{{/if}}
|
|
165
|
+
- Add a dependency only when the task needs it, and install it with {{packageManager}}
|
|
166
|
+
|
|
167
|
+
## Production rules
|
|
168
|
+
|
|
169
|
+
- Validate every input from outside the app (request bodies, query strings, form data) with Zod before you use it{{#unless useAuth}}{{#if (eq framework 'nextjs')}} (add `zod` with {{packageManager}} first){{/if}}{{#if (eq framework 'tanstack')}} (add `zod` with {{packageManager}} first){{/if}}{{/unless}}
|
|
170
|
+
- Never log or return secrets, tokens, or passwords. Error responses get a short message and the right status code, never a stack trace
|
|
171
|
+
{{#unless (eq database 'none')}}
|
|
172
|
+
- Query the database through {{#if (eq database 'prisma')}}Prisma{{/if}}{{#if (eq database 'drizzle')}}Drizzle{{/if}}{{#if (eq database 'mongoose')}}Mongoose{{/if}}, never with SQL or queries built from strings
|
|
173
|
+
{{/unless}}
|
|
174
|
+
{{#if (eq framework 'express')}}
|
|
175
|
+
- Keep the security middleware in `src/index.ts` (`helmet`, `cors`{{#if useRateLimit}}, the rate limiter{{/if}}) in place. Set `CORS_ORIGIN` to your real origins in production
|
|
176
|
+
{{/if}}
|
|
177
|
+
{{#if (eq framework 'nestjs')}}
|
|
178
|
+
- Keep the security setup in `src/main.ts` and `src/app.module.ts` in place. Set `CORS_ORIGIN` to your real origins in production
|
|
179
|
+
{{/if}}
|
|
180
|
+
{{#if (eq framework 'nextjs')}}
|
|
181
|
+
- Code that reads secrets or the database stays on the server. Only variables prefixed with `NEXT_PUBLIC_` reach the browser, so never put a secret in one
|
|
182
|
+
{{/if}}
|
|
183
|
+
{{#if (eq framework 'tanstack')}}
|
|
184
|
+
- Code that reads secrets or the database runs in server functions or server routes. Only variables prefixed with `VITE_` reach the browser, so never put a secret in one
|
|
185
|
+
{{/if}}
|
|
186
|
+
{{#if useAuth}}
|
|
187
|
+
{{#if (eq framework 'nextjs')}}
|
|
188
|
+
- Check the session on the server in every protected page, route handler, and server action. Hiding a button in the UI does not protect anything
|
|
189
|
+
{{/if}}
|
|
190
|
+
{{#if (eq framework 'tanstack')}}
|
|
191
|
+
- Check the session on the server in every protected route and server function. Hiding a button in the UI does not protect anything
|
|
192
|
+
{{/if}}
|
|
193
|
+
{{/if}}
|
|
194
|
+
{{#if (eq framework 'express')}}
|
|
195
|
+
- Keep `GET /health` fast and free of auth{{#if useRateLimit}} and rate limiting{{/if}}, so hosts can check that the app is up
|
|
196
|
+
{{/if}}
|
|
197
|
+
{{#if (eq framework 'nestjs')}}
|
|
198
|
+
- Keep `GET /health` fast and free of auth{{#if useRateLimit}} and rate limiting{{/if}}, so hosts can check that the app is up
|
|
199
|
+
{{/if}}
|
|
200
|
+
- Keep changes small and focused on the task. Do not add layers, abstractions, or files the task does not need
|
|
201
|
+
|
|
202
|
+
## When you are unsure
|
|
203
|
+
|
|
204
|
+
The libraries here may be newer than what you know. When you are not sure how an API works, read the official docs for the version in `package.json` instead of guessing:
|
|
205
|
+
|
|
206
|
+
{{#if (eq framework 'express')}}
|
|
207
|
+
- Express 5: https://expressjs.com/en/5x/api.html
|
|
208
|
+
{{/if}}
|
|
209
|
+
{{#if (eq framework 'nestjs')}}
|
|
210
|
+
- NestJS: https://docs.nestjs.com
|
|
211
|
+
{{/if}}
|
|
212
|
+
{{#if (eq framework 'nextjs')}}
|
|
213
|
+
- Next.js: https://nextjs.org/docs
|
|
214
|
+
{{/if}}
|
|
215
|
+
{{#if (eq framework 'tanstack')}}
|
|
216
|
+
- TanStack Start: https://tanstack.com/start/latest/docs
|
|
217
|
+
{{/if}}
|
|
218
|
+
{{#if (eq database 'prisma')}}
|
|
219
|
+
- Prisma: https://www.prisma.io/docs
|
|
220
|
+
{{/if}}
|
|
221
|
+
{{#if (eq database 'drizzle')}}
|
|
222
|
+
- Drizzle: https://orm.drizzle.team/docs/overview
|
|
223
|
+
{{/if}}
|
|
224
|
+
{{#if (eq database 'mongoose')}}
|
|
225
|
+
- Mongoose: https://mongoosejs.com/docs
|
|
226
|
+
{{/if}}
|
|
227
|
+
{{#if useAuth}}
|
|
228
|
+
{{#if (eq framework 'nextjs')}}
|
|
229
|
+
- Better Auth: https://www.better-auth.com/docs
|
|
230
|
+
{{/if}}
|
|
231
|
+
{{#if (eq framework 'tanstack')}}
|
|
232
|
+
- Better Auth: https://www.better-auth.com/docs
|
|
233
|
+
{{/if}}
|
|
234
|
+
{{/if}}
|
|
235
|
+
- Zod: https://zod.dev
|
|
236
|
+
|
|
237
|
+
If the docs and this file disagree, follow the docs and say so.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "agents",
|
|
3
|
+
"name": "AI agent instructions",
|
|
4
|
+
"description": "An AGENTS.md that tells AI coding agents how this project is built, run, and checked, and a CLAUDE.md that points Claude Code to it",
|
|
5
|
+
"type": "feature",
|
|
6
|
+
"dependencies": {},
|
|
7
|
+
"devDependencies": {},
|
|
8
|
+
"scripts": {},
|
|
9
|
+
"envVars": {}
|
|
10
|
+
}
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import jwt, { type SignOptions } from "jsonwebtoken";
|
|
2
|
-
|
|
3
|
-
export interface AuthTokenPayload {
|
|
4
|
-
sub: string;
|
|
5
|
-
email: string;
|
|
6
|
-
name?: string | null;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const { JWT_SECRET, JWT_EXPIRES_IN } = process.env;
|
|
10
|
-
|
|
11
|
-
function ensureSecret(): string {
|
|
12
|
-
if (!JWT_SECRET) {
|
|
13
|
-
throw new Error("JWT_SECRET is not set");
|
|
14
|
-
}
|
|
15
|
-
return JWT_SECRET;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function signToken(payload: AuthTokenPayload): string {
|
|
19
|
-
const secret = ensureSecret();
|
|
20
|
-
const expiresIn = JWT_EXPIRES_IN || "1h";
|
|
21
|
-
return jwt.sign(payload, secret, { expiresIn } as SignOptions);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function verifyToken(token: string): AuthTokenPayload {
|
|
25
|
-
const secret = ensureSecret();
|
|
26
|
-
return jwt.verify(token, secret) as AuthTokenPayload;
|
|
27
|
-
}
|
|
1
|
+
import jwt, { type SignOptions } from "jsonwebtoken";
|
|
2
|
+
|
|
3
|
+
export interface AuthTokenPayload {
|
|
4
|
+
sub: string;
|
|
5
|
+
email: string;
|
|
6
|
+
name?: string | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { JWT_SECRET, JWT_EXPIRES_IN } = process.env;
|
|
10
|
+
|
|
11
|
+
function ensureSecret(): string {
|
|
12
|
+
if (!JWT_SECRET) {
|
|
13
|
+
throw new Error("JWT_SECRET is not set");
|
|
14
|
+
}
|
|
15
|
+
return JWT_SECRET;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function signToken(payload: AuthTokenPayload): string {
|
|
19
|
+
const secret = ensureSecret();
|
|
20
|
+
const expiresIn = JWT_EXPIRES_IN || "1h";
|
|
21
|
+
return jwt.sign(payload, secret, { expiresIn } as SignOptions);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function verifyToken(token: string): AuthTokenPayload {
|
|
25
|
+
const secret = ensureSecret();
|
|
26
|
+
return jwt.verify(token, secret) as AuthTokenPayload;
|
|
27
|
+
}
|
|
@@ -19,8 +19,7 @@ export function requireAuth(req: AuthenticatedRequest, res: Response, next: Next
|
|
|
19
19
|
const payload = verifyToken(token);
|
|
20
20
|
req.user = payload;
|
|
21
21
|
next();
|
|
22
|
-
} catch
|
|
23
|
-
console.error('JWT verification failed:', error);
|
|
22
|
+
} catch {
|
|
24
23
|
res.status(401).json({ error: 'Invalid or expired token' });
|
|
25
24
|
}
|
|
26
25
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { auth } from "@/lib/auth";
|
|
2
|
-
import { toNextJsHandler } from "better-auth/next-js";
|
|
3
|
-
|
|
4
|
-
export const { GET, POST } = toNextJsHandler(auth);
|
|
1
|
+
import { auth } from "@/lib/auth";
|
|
2
|
+
import { toNextJsHandler } from "better-auth/next-js";
|
|
3
|
+
|
|
4
|
+
export const { GET, POST } = toNextJsHandler(auth);
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "auth",
|
|
3
|
-
"name": "Better Auth",
|
|
4
|
-
"description": "Better Auth for Next.js with optional Prisma or Drizzle adapter",
|
|
5
|
-
"type": "feature",
|
|
6
|
-
"dependencies": {
|
|
7
|
-
"@hookform/resolvers": "^5.9.
|
|
8
|
-
"better-auth": "^1.7.
|
|
9
|
-
"react-hook-form": "^7.
|
|
10
|
-
"zod": "^4.6.
|
|
11
|
-
},
|
|
12
|
-
"devDependencies": {},
|
|
13
|
-
"scripts": {},
|
|
14
|
-
"envVars": {
|
|
15
|
-
"BETTER_AUTH_SECRET": "please-change-me-to-a-random-string",
|
|
16
|
-
"BETTER_AUTH_URL": "http://localhost:3000"
|
|
17
|
-
}
|
|
18
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"id": "auth",
|
|
3
|
+
"name": "Better Auth",
|
|
4
|
+
"description": "Better Auth for Next.js with optional Prisma or Drizzle adapter",
|
|
5
|
+
"type": "feature",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@hookform/resolvers": "^5.9.2",
|
|
8
|
+
"better-auth": "^1.7.14",
|
|
9
|
+
"react-hook-form": "^7.91.0",
|
|
10
|
+
"zod": "^4.6.7"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {},
|
|
13
|
+
"scripts": {},
|
|
14
|
+
"envVars": {
|
|
15
|
+
"BETTER_AUTH_SECRET": "please-change-me-to-a-random-string",
|
|
16
|
+
"BETTER_AUTH_URL": "http://localhost:3000"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { createAuthClient } from "better-auth/react";
|
|
2
|
-
|
|
3
|
-
export const authClient = createAuthClient();
|
|
1
|
+
import { createAuthClient } from "better-auth/react";
|
|
2
|
+
|
|
3
|
+
export const authClient = createAuthClient();
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
+
import { getSessionCookie } from "better-auth/cookies";
|
|
1
2
|
import { NextResponse } from "next/server";
|
|
2
|
-
|
|
3
|
-
{{#if useAuth}}
|
|
4
3
|
import type { NextRequest } from "next/server";
|
|
5
|
-
import { getSessionCookie } from "better-auth/cookies";
|
|
6
4
|
|
|
7
5
|
export function proxy(request: NextRequest) {
|
|
8
6
|
const sessionCookie = getSessionCookie(request);
|
|
@@ -17,8 +15,3 @@ export function proxy(request: NextRequest) {
|
|
|
17
15
|
export const config = {
|
|
18
16
|
matcher: ["/dashboard/:path*"],
|
|
19
17
|
};
|
|
20
|
-
{{else}}
|
|
21
|
-
export function proxy() {
|
|
22
|
-
return NextResponse.next();
|
|
23
|
-
}
|
|
24
|
-
{{/if}}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "auth",
|
|
3
|
-
"name": "Better Auth",
|
|
4
|
-
"description": "Better Auth for TanStack Start with optional Prisma, Drizzle, or MongoDB adapter",
|
|
5
|
-
"type": "feature",
|
|
6
|
-
"dependencies": {
|
|
7
|
-
"@hookform/resolvers": "^5.9.
|
|
8
|
-
"better-auth": "^1.7.
|
|
9
|
-
"dotenv": "^17.4.2",
|
|
10
|
-
"react-hook-form": "^7.
|
|
11
|
-
"zod": "^4.6.
|
|
12
|
-
},
|
|
13
|
-
"devDependencies": {},
|
|
14
|
-
"scripts": {},
|
|
15
|
-
"envVars": {
|
|
16
|
-
"BETTER_AUTH_SECRET": "please-change-me-to-a-random-string",
|
|
17
|
-
"BETTER_AUTH_URL": "http://localhost:3000"
|
|
18
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"id": "auth",
|
|
3
|
+
"name": "Better Auth",
|
|
4
|
+
"description": "Better Auth for TanStack Start with optional Prisma, Drizzle, or MongoDB adapter",
|
|
5
|
+
"type": "feature",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@hookform/resolvers": "^5.9.2",
|
|
8
|
+
"better-auth": "^1.7.14",
|
|
9
|
+
"dotenv": "^17.4.2",
|
|
10
|
+
"react-hook-form": "^7.91.0",
|
|
11
|
+
"zod": "^4.6.7"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {},
|
|
14
|
+
"scripts": {},
|
|
15
|
+
"envVars": {
|
|
16
|
+
"BETTER_AUTH_SECRET": "please-change-me-to-a-random-string",
|
|
17
|
+
"BETTER_AUTH_URL": "http://localhost:3000"
|
|
18
|
+
}
|
|
19
19
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { createAuthClient } from "better-auth/react";
|
|
2
|
-
|
|
1
|
+
import { createAuthClient } from "better-auth/react";
|
|
2
|
+
|
|
3
3
|
export const authClient = createAuthClient();
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { createServerFn } from "@tanstack/react-start";
|
|
2
|
-
import { getRequestHeaders } from "@tanstack/react-start/server";
|
|
3
|
-
|
|
4
|
-
import { auth } from "./auth";
|
|
5
|
-
|
|
6
|
-
export const getSession = createServerFn({ method: "GET" }).handler(async () => {
|
|
1
|
+
import { createServerFn } from "@tanstack/react-start";
|
|
2
|
+
import { getRequestHeaders } from "@tanstack/react-start/server";
|
|
3
|
+
|
|
4
|
+
import { auth } from "./auth";
|
|
5
|
+
|
|
6
|
+
export const getSession = createServerFn({ method: "GET" }).handler(async () => {
|
|
7
7
|
const session = await auth.api.getSession({
|
|
8
8
|
headers: getRequestHeaders(),
|
|
9
9
|
});
|
|
10
|
-
|
|
11
|
-
return session;
|
|
10
|
+
|
|
11
|
+
return session;
|
|
12
12
|
});
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import "dotenv/config";
|
|
2
|
-
import { betterAuth } from "better-auth";
|
|
3
|
-
import { tanstackStartCookies } from "better-auth/tanstack-start";
|
|
4
|
-
{{#if (eq database 'prisma')}}
|
|
5
|
-
import { prismaAdapter } from "better-auth/adapters/prisma";
|
|
6
|
-
import client from "./prisma";
|
|
7
|
-
{{/if}}
|
|
8
|
-
{{#if (eq database 'drizzle')}}
|
|
9
|
-
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
10
|
-
import { db } from "./db";
|
|
11
|
-
{{/if}}
|
|
12
|
-
{{#if (eq database 'mongoose')}}
|
|
13
|
-
import { mongodbAdapter } from "better-auth/adapters/mongodb";
|
|
14
|
-
import { MongoClient } from "mongodb";
|
|
15
|
-
|
|
16
|
-
const client = new MongoClient(process.env.MONGODB_URI!);
|
|
17
|
-
const db = client.db();
|
|
18
|
-
{{/if}}
|
|
19
|
-
|
|
20
|
-
export const auth = betterAuth({
|
|
21
|
-
{{#if (eq database 'prisma')}}
|
|
22
|
-
database: prismaAdapter(client, { provider: "postgresql" }),
|
|
23
|
-
{{/if}}
|
|
24
|
-
{{#if (eq database 'drizzle')}}
|
|
25
|
-
database: drizzleAdapter(db, { provider: "pg" }),
|
|
26
|
-
{{/if}}
|
|
27
|
-
{{#if (eq database 'mongoose')}}
|
|
28
|
-
// Transactions require a replica set. Create user/session/account/verification
|
|
29
|
-
// collections before the first sign-up; see the database setup documentation.
|
|
30
|
-
database: mongodbAdapter(db, { client }),
|
|
31
|
-
{{/if}}
|
|
32
|
-
emailAndPassword: {
|
|
33
|
-
enabled: true,
|
|
34
|
-
},
|
|
35
|
-
plugins: [tanstackStartCookies()],
|
|
36
|
-
secret: process.env.BETTER_AUTH_SECRET,
|
|
37
|
-
baseURL: process.env.BETTER_AUTH_URL,
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import { betterAuth } from "better-auth";
|
|
3
|
+
import { tanstackStartCookies } from "better-auth/tanstack-start";
|
|
4
|
+
{{#if (eq database 'prisma')}}
|
|
5
|
+
import { prismaAdapter } from "better-auth/adapters/prisma";
|
|
6
|
+
import client from "./prisma";
|
|
7
|
+
{{/if}}
|
|
8
|
+
{{#if (eq database 'drizzle')}}
|
|
9
|
+
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
10
|
+
import { db } from "./db";
|
|
11
|
+
{{/if}}
|
|
12
|
+
{{#if (eq database 'mongoose')}}
|
|
13
|
+
import { mongodbAdapter } from "better-auth/adapters/mongodb";
|
|
14
|
+
import { MongoClient } from "mongodb";
|
|
15
|
+
|
|
16
|
+
const client = new MongoClient(process.env.MONGODB_URI!);
|
|
17
|
+
const db = client.db();
|
|
18
|
+
{{/if}}
|
|
19
|
+
|
|
20
|
+
export const auth = betterAuth({
|
|
21
|
+
{{#if (eq database 'prisma')}}
|
|
22
|
+
database: prismaAdapter(client, { provider: "postgresql" }),
|
|
23
|
+
{{/if}}
|
|
24
|
+
{{#if (eq database 'drizzle')}}
|
|
25
|
+
database: drizzleAdapter(db, { provider: "pg" }),
|
|
26
|
+
{{/if}}
|
|
27
|
+
{{#if (eq database 'mongoose')}}
|
|
28
|
+
// Transactions require a replica set. Create user/session/account/verification
|
|
29
|
+
// collections before the first sign-up; see the database setup documentation.
|
|
30
|
+
database: mongodbAdapter(db, { client }),
|
|
31
|
+
{{/if}}
|
|
32
|
+
emailAndPassword: {
|
|
33
|
+
enabled: true,
|
|
34
|
+
},
|
|
35
|
+
plugins: [tanstackStartCookies()],
|
|
36
|
+
secret: process.env.BETTER_AUTH_SECRET,
|
|
37
|
+
baseURL: process.env.BETTER_AUTH_URL,
|
|
38
38
|
});
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
-
|
|
3
|
-
import { auth } from "~/lib/auth";
|
|
4
|
-
|
|
5
|
-
export const Route = createFileRoute("/api/auth/$")({
|
|
6
|
-
server: {
|
|
7
|
-
handlers: {
|
|
8
|
-
GET: async ({ request }: { request: Request }) => {
|
|
9
|
-
return await auth.handler(request);
|
|
10
|
-
},
|
|
11
|
-
POST: async ({ request }: { request: Request }) => {
|
|
12
|
-
return await auth.handler(request);
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
},
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
|
|
3
|
+
import { auth } from "~/lib/auth";
|
|
4
|
+
|
|
5
|
+
export const Route = createFileRoute("/api/auth/$")({
|
|
6
|
+
server: {
|
|
7
|
+
handlers: {
|
|
8
|
+
GET: async ({ request }: { request: Request }) => {
|
|
9
|
+
return await auth.handler(request);
|
|
10
|
+
},
|
|
11
|
+
POST: async ({ request }: { request: Request }) => {
|
|
12
|
+
return await auth.handler(request);
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
16
|
});
|