better-ts-stack 1.1.1 → 1.1.3
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/README.md +16 -23
- package/dist/builder/configGenerator.d.ts.map +1 -1
- package/dist/builder/configGenerator.js +5 -1
- package/dist/builder/configGenerator.js.map +1 -1
- package/dist/builder/moduleSelector.d.ts.map +1 -1
- package/dist/builder/moduleSelector.js +2 -5
- package/dist/builder/moduleSelector.js.map +1 -1
- package/dist/output/nextSteps.d.ts.map +1 -1
- package/dist/output/nextSteps.js +33 -11
- package/dist/output/nextSteps.js.map +1 -1
- package/package.json +1 -1
- package/templates/backend/express/gitignore +0 -3
- package/templates/backend/express/src/index.ts +19 -4
- package/templates/frontend/nextjs/config.json +1 -2
- package/templates/frontend/nextjs/gitignore +1 -0
- package/templates/frontend/nextjs/next.config.ts.hbs +12 -0
- package/templates/frontend/tanstack/config.json +48 -49
- package/templates/frontend/tanstack/gitignore +1 -0
- package/templates/modules/auth/express/src/controllers/authController.ts.hbs +62 -13
- package/templates/modules/auth/express/src/middleware/requireAuth.ts.hbs +26 -26
- package/templates/modules/auth/express/src/services/userStore.ts.hbs +22 -0
- package/templates/modules/docker/Dockerfile.hbs +130 -125
- package/templates/modules/docker/config.json +1 -1
- package/templates/modules/mongoose/{config.json → express/config.json} +2 -2
- package/templates/modules/mongoose/{src → express/src}/models/User.ts +48 -48
- package/templates/modules/mongoose/nextjs/config.json +16 -0
- package/templates/modules/mongoose/nextjs/lib/db.ts +36 -0
- package/templates/modules/mongoose/nextjs/models/User.ts +48 -0
- package/templates/modules/mongoose/tanstack/config.json +17 -0
- package/templates/modules/mongoose/tanstack/src/lib/db.ts +37 -0
- package/templates/modules/mongoose/tanstack/src/models/User.ts +48 -0
- package/templates/frontend/nextjs/next.config.ts +0 -8
- package/templates/modules/mongoose/src/lib/db.ts.hbs +0 -43
- /package/templates/modules/mongoose/{src → express/src}/lib/db.ts +0 -0
|
@@ -77,6 +77,14 @@ export {{#unless (eq database 'none')}}async {{/unless}}function createUser(data
|
|
|
77
77
|
{{/if}}
|
|
78
78
|
|
|
79
79
|
{{#if (eq database 'none')}}
|
|
80
|
+
// Enforce unique emails like a database constraint would, so a sign-up that
|
|
81
|
+
// raced past the findUserByEmail check is rejected instead of duplicated
|
|
82
|
+
if (users.some((candidate) => candidate.email === data.email)) {
|
|
83
|
+
return Promise.reject(
|
|
84
|
+
Object.assign(new Error('User already exists'), { code: 'DUPLICATE_EMAIL' }),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
80
88
|
const newUser: AuthUser = {
|
|
81
89
|
id: crypto.randomUUID(),
|
|
82
90
|
email: data.email,
|
|
@@ -89,6 +97,20 @@ export {{#unless (eq database 'none')}}async {{/unless}}function createUser(data
|
|
|
89
97
|
{{/if}}
|
|
90
98
|
}
|
|
91
99
|
|
|
100
|
+
// Unique-constraint error codes: Prisma, PostgreSQL (Drizzle wraps it in `cause`),
|
|
101
|
+
// MongoDB, and the in-memory store
|
|
102
|
+
const DUPLICATE_KEY_CODES = new Set<unknown>(['P2002', '23505', 11000, 'DUPLICATE_EMAIL']);
|
|
103
|
+
|
|
104
|
+
// True when an insert failed because the email is already taken (e.g. concurrent sign-ups)
|
|
105
|
+
export function isDuplicateEmailError(error: unknown): boolean {
|
|
106
|
+
let current = error;
|
|
107
|
+
for (let depth = 0; depth < 5 && current && typeof current === 'object'; depth++) {
|
|
108
|
+
if (DUPLICATE_KEY_CODES.has((current as { code?: unknown }).code)) return true;
|
|
109
|
+
current = (current as { cause?: unknown }).cause;
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
92
114
|
export function toPublicUser(user: AuthUser): Omit<AuthUser, 'password'> {
|
|
93
115
|
const { id, email, name } = user;
|
|
94
116
|
return { id, email, name };
|
|
@@ -1,126 +1,131 @@
|
|
|
1
|
-
{{#if (eq framework 'nextjs')}}
|
|
2
|
-
# Base stage
|
|
3
|
-
FROM node:24-alpine AS base
|
|
4
|
-
WORKDIR /app
|
|
5
|
-
RUN apk add --no-cache libc6-compat
|
|
6
|
-
|
|
7
|
-
# Dependencies stage
|
|
8
|
-
FROM base AS deps
|
|
9
|
-
COPY package*.json ./
|
|
10
|
-
RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi
|
|
11
|
-
|
|
12
|
-
# Build stage
|
|
13
|
-
FROM base AS builder
|
|
14
|
-
COPY --from=deps /app/node_modules ./node_modules
|
|
15
|
-
COPY . .
|
|
16
|
-
{{#if
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
{{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
{{
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
{{
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
#
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
1
|
+
{{#if (eq framework 'nextjs')}}
|
|
2
|
+
# Base stage
|
|
3
|
+
FROM node:24-alpine AS base
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
RUN apk add --no-cache libc6-compat
|
|
6
|
+
|
|
7
|
+
# Dependencies stage
|
|
8
|
+
FROM base AS deps
|
|
9
|
+
COPY package*.json ./
|
|
10
|
+
RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi
|
|
11
|
+
|
|
12
|
+
# Build stage
|
|
13
|
+
FROM base AS builder
|
|
14
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
15
|
+
COPY . .
|
|
16
|
+
{{#if useAuth}}
|
|
17
|
+
# next build loads the auth route, which initializes Better Auth. Use a
|
|
18
|
+
# build-only secret here; the real BETTER_AUTH_SECRET is supplied at runtime.
|
|
19
|
+
ENV BETTER_AUTH_SECRET="build-only-placeholder-secret-not-used-at-runtime"
|
|
20
|
+
{{/if}}
|
|
21
|
+
{{#if (eq database 'prisma')}}
|
|
22
|
+
RUN DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" npm run build
|
|
23
|
+
{{else if (eq database 'mongoose')}}
|
|
24
|
+
RUN MONGODB_URI="mongodb://localhost:27017/build" npm run build
|
|
25
|
+
{{else}}
|
|
26
|
+
RUN npm run build
|
|
27
|
+
{{/if}}
|
|
28
|
+
|
|
29
|
+
# Production stage
|
|
30
|
+
FROM node:24-alpine AS runner
|
|
31
|
+
WORKDIR /app
|
|
32
|
+
ENV NODE_ENV=production
|
|
33
|
+
|
|
34
|
+
RUN addgroup --system --gid 1001 nodejs && \
|
|
35
|
+
adduser --system --uid 1001 nextjs
|
|
36
|
+
|
|
37
|
+
COPY --from=builder /app/public ./public
|
|
38
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
|
39
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|
40
|
+
|
|
41
|
+
USER nextjs
|
|
42
|
+
EXPOSE {{port}}
|
|
43
|
+
ENV PORT={{port}} HOSTNAME=0.0.0.0
|
|
44
|
+
|
|
45
|
+
CMD ["node", "server.js"]
|
|
46
|
+
|
|
47
|
+
{{else if (eq framework 'tanstack')}}
|
|
48
|
+
# Build stage
|
|
49
|
+
FROM node:24-alpine AS builder
|
|
50
|
+
WORKDIR /app
|
|
51
|
+
|
|
52
|
+
COPY package*.json ./
|
|
53
|
+
RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi
|
|
54
|
+
|
|
55
|
+
COPY . .
|
|
56
|
+
|
|
57
|
+
# Only build if package.json has a build script
|
|
58
|
+
{{#if (eq database 'prisma')}}
|
|
59
|
+
RUN DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" npm run build
|
|
60
|
+
{{else if (eq database 'mongoose')}}
|
|
61
|
+
RUN MONGODB_URI="mongodb://localhost:27017/build" npm run build
|
|
62
|
+
{{else}}
|
|
63
|
+
RUN npm run build
|
|
64
|
+
{{/if}}
|
|
65
|
+
|
|
66
|
+
# Production stage
|
|
67
|
+
FROM node:24-alpine
|
|
68
|
+
WORKDIR /app
|
|
69
|
+
ENV NODE_ENV=production
|
|
70
|
+
|
|
71
|
+
# Create non-root user for security
|
|
72
|
+
RUN addgroup --system --gid 1001 nodejs && \
|
|
73
|
+
adduser --system --uid 1001 tanstackuser
|
|
74
|
+
|
|
75
|
+
COPY package*.json ./
|
|
76
|
+
RUN if [ -f package-lock.json ]; then npm ci --omit=dev; else npm install --omit=dev; fi
|
|
77
|
+
|
|
78
|
+
# Copy built output (dist/server/server.js + dist/client)
|
|
79
|
+
COPY --from=builder --chown=tanstackuser:nodejs /app/dist ./dist
|
|
80
|
+
|
|
81
|
+
# Include the Prisma client generated during the build stage
|
|
82
|
+
{{#if (eq database 'prisma')}}
|
|
83
|
+
COPY --from=builder --chown=tanstackuser:nodejs /app/generated ./generated
|
|
84
|
+
{{/if}}
|
|
85
|
+
|
|
86
|
+
USER tanstackuser
|
|
87
|
+
EXPOSE {{port}}
|
|
88
|
+
ENV PORT={{port}}
|
|
89
|
+
|
|
90
|
+
CMD ["node_modules/.bin/srvx", "--prod", "-s", "../client", "dist/server/server.js"]
|
|
91
|
+
|
|
92
|
+
{{else}}
|
|
93
|
+
# Build stage
|
|
94
|
+
FROM node:24-alpine AS builder
|
|
95
|
+
WORKDIR /app
|
|
96
|
+
|
|
97
|
+
COPY package*.json ./
|
|
98
|
+
RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi
|
|
99
|
+
|
|
100
|
+
COPY . .
|
|
101
|
+
|
|
102
|
+
# Only build if package.json has a build script
|
|
103
|
+
{{#if (eq database 'prisma')}}
|
|
104
|
+
RUN DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" npm run build
|
|
105
|
+
{{else if (eq database 'mongoose')}}
|
|
106
|
+
RUN MONGODB_URI="mongodb://localhost:27017/build" npm run build
|
|
107
|
+
{{else}}
|
|
108
|
+
RUN npm run build
|
|
109
|
+
{{/if}}
|
|
110
|
+
|
|
111
|
+
# Production stage
|
|
112
|
+
FROM node:24-alpine
|
|
113
|
+
WORKDIR /app
|
|
114
|
+
ENV NODE_ENV=production
|
|
115
|
+
|
|
116
|
+
# Create non-root user for security
|
|
117
|
+
RUN addgroup --system --gid 1001 nodejs && \
|
|
118
|
+
adduser --system --uid 1001 expressuser
|
|
119
|
+
|
|
120
|
+
COPY package*.json ./
|
|
121
|
+
RUN if [ -f package-lock.json ]; then npm ci --omit=dev; else npm install --omit=dev; fi
|
|
122
|
+
|
|
123
|
+
# Copy built files (if TypeScript) or source files (if JavaScript)
|
|
124
|
+
COPY --from=builder --chown=expressuser:nodejs /app/dist ./dist
|
|
125
|
+
|
|
126
|
+
USER expressuser
|
|
127
|
+
EXPOSE {{port}}
|
|
128
|
+
ENV PORT={{port}}
|
|
129
|
+
|
|
130
|
+
CMD ["node", "dist/index.js"]
|
|
126
131
|
{{/if}}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"devDependencies": {},
|
|
8
8
|
"scripts": {
|
|
9
9
|
"docker:build": "docker build -t {{projectName}} .",
|
|
10
|
-
"docker:up": "docker compose up -d",
|
|
10
|
+
"docker:up": "docker compose up -d --build",
|
|
11
11
|
"docker:down": "docker compose down",
|
|
12
12
|
"docker:logs": "docker compose logs -f"
|
|
13
13
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "mongoose",
|
|
3
3
|
"name": "Mongoose (MongoDB)",
|
|
4
|
-
"description": "MongoDB database integration using Mongoose ODM",
|
|
4
|
+
"description": "MongoDB database integration for Express using Mongoose ODM",
|
|
5
5
|
"type": "database",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"mongoose": "^9.9.5",
|
|
@@ -12,5 +12,5 @@
|
|
|
12
12
|
"envVars": {
|
|
13
13
|
"MONGODB_URI": "mongodb://localhost:27017/myapp"
|
|
14
14
|
},
|
|
15
|
-
"templateFiles": [
|
|
15
|
+
"templateFiles": []
|
|
16
16
|
}
|
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
import mongoose, { type Model, Schema } from "mongoose";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* User document interface
|
|
5
|
-
*/
|
|
6
|
-
export interface IUser {
|
|
7
|
-
email: string;
|
|
8
|
-
name?: string | null;
|
|
9
|
-
password: string;
|
|
10
|
-
createdAt: Date;
|
|
11
|
-
updatedAt: Date;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* User schema definition
|
|
16
|
-
*/
|
|
17
|
-
const userSchema = new Schema<IUser>(
|
|
18
|
-
{
|
|
19
|
-
email: {
|
|
20
|
-
type: String,
|
|
21
|
-
required: true,
|
|
22
|
-
unique: true,
|
|
23
|
-
lowercase: true,
|
|
24
|
-
trim: true,
|
|
25
|
-
},
|
|
26
|
-
name: {
|
|
27
|
-
type: String,
|
|
28
|
-
trim: true,
|
|
29
|
-
},
|
|
30
|
-
password: {
|
|
31
|
-
type: String,
|
|
32
|
-
required: true,
|
|
33
|
-
minlength: 6,
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
timestamps: true,
|
|
38
|
-
}
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* User model
|
|
43
|
-
*/
|
|
44
|
-
const User =
|
|
45
|
-
(mongoose.models.User as Model<IUser> | undefined) ??
|
|
46
|
-
mongoose.model<IUser>("User", userSchema);
|
|
47
|
-
|
|
48
|
-
export default User;
|
|
1
|
+
import mongoose, { type Model, Schema } from "mongoose";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* User document interface
|
|
5
|
+
*/
|
|
6
|
+
export interface IUser {
|
|
7
|
+
email: string;
|
|
8
|
+
name?: string | null;
|
|
9
|
+
password: string;
|
|
10
|
+
createdAt: Date;
|
|
11
|
+
updatedAt: Date;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* User schema definition
|
|
16
|
+
*/
|
|
17
|
+
const userSchema = new Schema<IUser>(
|
|
18
|
+
{
|
|
19
|
+
email: {
|
|
20
|
+
type: String,
|
|
21
|
+
required: true,
|
|
22
|
+
unique: true,
|
|
23
|
+
lowercase: true,
|
|
24
|
+
trim: true,
|
|
25
|
+
},
|
|
26
|
+
name: {
|
|
27
|
+
type: String,
|
|
28
|
+
trim: true,
|
|
29
|
+
},
|
|
30
|
+
password: {
|
|
31
|
+
type: String,
|
|
32
|
+
required: true,
|
|
33
|
+
minlength: 6,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
timestamps: true,
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* User model
|
|
43
|
+
*/
|
|
44
|
+
const User =
|
|
45
|
+
(mongoose.models.User as Model<IUser> | undefined) ??
|
|
46
|
+
mongoose.model<IUser>("User", userSchema);
|
|
47
|
+
|
|
48
|
+
export default User;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "mongoose",
|
|
3
|
+
"name": "Mongoose (MongoDB)",
|
|
4
|
+
"description": "MongoDB database integration for Next.js using Mongoose ODM",
|
|
5
|
+
"type": "database",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"mongoose": "^9.9.5",
|
|
8
|
+
"mongodb": "^7.6.0"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {},
|
|
11
|
+
"scripts": {},
|
|
12
|
+
"envVars": {
|
|
13
|
+
"MONGODB_URI": "mongodb://localhost:27017/myapp"
|
|
14
|
+
},
|
|
15
|
+
"templateFiles": []
|
|
16
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import mongoose from "mongoose";
|
|
2
|
+
|
|
3
|
+
// Reuse one connection across hot reloads and concurrent requests
|
|
4
|
+
const globalForMongoose = globalThis as unknown as {
|
|
5
|
+
mongooseConnection?: Promise<typeof mongoose>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Connects Mongoose once and returns the shared connection.
|
|
10
|
+
* Call this in server code before querying Mongoose models.
|
|
11
|
+
*/
|
|
12
|
+
export async function connectDB(): Promise<typeof mongoose> {
|
|
13
|
+
const mongoUri = process.env.MONGODB_URI;
|
|
14
|
+
|
|
15
|
+
if (!mongoUri) {
|
|
16
|
+
throw new Error("MONGODB_URI environment variable is not defined");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
globalForMongoose.mongooseConnection ??= mongoose
|
|
20
|
+
.connect(mongoUri)
|
|
21
|
+
.catch((error: unknown) => {
|
|
22
|
+
// Let the next call retry instead of caching the failure
|
|
23
|
+
globalForMongoose.mongooseConnection = undefined;
|
|
24
|
+
throw error;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return globalForMongoose.mongooseConnection;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Gracefully close the MongoDB connection
|
|
32
|
+
*/
|
|
33
|
+
export async function disconnectDB(): Promise<void> {
|
|
34
|
+
await mongoose.connection.close();
|
|
35
|
+
globalForMongoose.mongooseConnection = undefined;
|
|
36
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import mongoose, { type Model, Schema } from "mongoose";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* User document interface
|
|
5
|
+
*/
|
|
6
|
+
export interface IUser {
|
|
7
|
+
email: string;
|
|
8
|
+
name?: string | null;
|
|
9
|
+
password: string;
|
|
10
|
+
createdAt: Date;
|
|
11
|
+
updatedAt: Date;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* User schema definition
|
|
16
|
+
*/
|
|
17
|
+
const userSchema = new Schema<IUser>(
|
|
18
|
+
{
|
|
19
|
+
email: {
|
|
20
|
+
type: String,
|
|
21
|
+
required: true,
|
|
22
|
+
unique: true,
|
|
23
|
+
lowercase: true,
|
|
24
|
+
trim: true,
|
|
25
|
+
},
|
|
26
|
+
name: {
|
|
27
|
+
type: String,
|
|
28
|
+
trim: true,
|
|
29
|
+
},
|
|
30
|
+
password: {
|
|
31
|
+
type: String,
|
|
32
|
+
required: true,
|
|
33
|
+
minlength: 6,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
timestamps: true,
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* User model
|
|
43
|
+
*/
|
|
44
|
+
const User =
|
|
45
|
+
(mongoose.models.User as Model<IUser> | undefined) ??
|
|
46
|
+
mongoose.model<IUser>("User", userSchema);
|
|
47
|
+
|
|
48
|
+
export default User;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "mongoose",
|
|
3
|
+
"name": "Mongoose (MongoDB)",
|
|
4
|
+
"description": "MongoDB database integration for TanStack Start using Mongoose ODM",
|
|
5
|
+
"type": "database",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"mongoose": "^9.9.5",
|
|
8
|
+
"mongodb": "^7.6.0",
|
|
9
|
+
"dotenv": "^17.4.2"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {},
|
|
12
|
+
"scripts": {},
|
|
13
|
+
"envVars": {
|
|
14
|
+
"MONGODB_URI": "mongodb://localhost:27017/myapp"
|
|
15
|
+
},
|
|
16
|
+
"templateFiles": []
|
|
17
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import mongoose from "mongoose";
|
|
3
|
+
|
|
4
|
+
// Reuse one connection across hot reloads and concurrent requests
|
|
5
|
+
const globalForMongoose = globalThis as unknown as {
|
|
6
|
+
mongooseConnection?: Promise<typeof mongoose>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Connects Mongoose once and returns the shared connection.
|
|
11
|
+
* Call this in server code before querying Mongoose models.
|
|
12
|
+
*/
|
|
13
|
+
export async function connectDB(): Promise<typeof mongoose> {
|
|
14
|
+
const mongoUri = process.env.MONGODB_URI;
|
|
15
|
+
|
|
16
|
+
if (!mongoUri) {
|
|
17
|
+
throw new Error("MONGODB_URI environment variable is not defined");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
globalForMongoose.mongooseConnection ??= mongoose
|
|
21
|
+
.connect(mongoUri)
|
|
22
|
+
.catch((error: unknown) => {
|
|
23
|
+
// Let the next call retry instead of caching the failure
|
|
24
|
+
globalForMongoose.mongooseConnection = undefined;
|
|
25
|
+
throw error;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return globalForMongoose.mongooseConnection;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Gracefully close the MongoDB connection
|
|
33
|
+
*/
|
|
34
|
+
export async function disconnectDB(): Promise<void> {
|
|
35
|
+
await mongoose.connection.close();
|
|
36
|
+
globalForMongoose.mongooseConnection = undefined;
|
|
37
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import mongoose, { type Model, Schema } from "mongoose";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* User document interface
|
|
5
|
+
*/
|
|
6
|
+
export interface IUser {
|
|
7
|
+
email: string;
|
|
8
|
+
name?: string | null;
|
|
9
|
+
password: string;
|
|
10
|
+
createdAt: Date;
|
|
11
|
+
updatedAt: Date;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* User schema definition
|
|
16
|
+
*/
|
|
17
|
+
const userSchema = new Schema<IUser>(
|
|
18
|
+
{
|
|
19
|
+
email: {
|
|
20
|
+
type: String,
|
|
21
|
+
required: true,
|
|
22
|
+
unique: true,
|
|
23
|
+
lowercase: true,
|
|
24
|
+
trim: true,
|
|
25
|
+
},
|
|
26
|
+
name: {
|
|
27
|
+
type: String,
|
|
28
|
+
trim: true,
|
|
29
|
+
},
|
|
30
|
+
password: {
|
|
31
|
+
type: String,
|
|
32
|
+
required: true,
|
|
33
|
+
minlength: 6,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
timestamps: true,
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* User model
|
|
43
|
+
*/
|
|
44
|
+
const User =
|
|
45
|
+
(mongoose.models.User as Model<IUser> | undefined) ??
|
|
46
|
+
mongoose.model<IUser>("User", userSchema);
|
|
47
|
+
|
|
48
|
+
export default User;
|