AnonMessage stores all application data in MongoDB through two Mongoose models:Documentation Index
Fetch the complete documentation index at: https://mintlify.com/dev0302/nextjs-project-1/llms.txt
Use this file to discover all available pages before exploring further.
User (which embeds Message subdocuments directly inside each user document) and OTP (a short-lived collection with a five-minute TTL index). Both models are fully TypeScript-typed through co-located interfaces.
Message Interface and Schema
Messages are not a standalone collection. They are embedded subdocuments inside eachUser document. The Message interface extends Mongoose’s Document so that each embedded object inherits MongoDB document fields such as _id and save().
User Interface and Schema
TheIUser interface describes the full shape of a user document, including the embedded messages array.
Passwords are never stored in plain text. The
POST /api/sign-up route calls bcrypt.hash(password, 10) before calling User.create(), and auth.ts uses bcrypt.compare() during sign-in.Field Reference
| Field | Type | Default | Notes |
|---|---|---|---|
username | String | — | Required, unique, trimmed |
email | String | — | Required, unique, regex-validated |
password | String | — | Required; stored as bcrypt hash |
isVerified | Boolean | false | Set to true after OTP verification at sign-up |
isAcceptingMessages | Boolean | true | Toggled via POST /api/accept-messages |
messages | Message[] | [] | Embedded array of MessageSchema subdocuments |
Model Export — Next.js Safe Pattern
|| guard checks mongoose.models.User first — if the model was already registered in a previous render cycle it reuses it, otherwise it creates a new one. Without this guard Mongoose would throw Cannot overwrite 'User' model once compiled.
Embedded Documents Pattern
Storing messages inside the user document (rather than in a separatemessages collection with a foreign key) is a deliberate MongoDB design choice.
Atomic push
Adding a new message is a single atomic
$push operation on the user document. There is no separate insert into a second collection and no transaction needed.Atomic pull
Deleting a message uses
$pull — it removes the matching subdocument by _id in one update. The PATCH /api/delete-message route demonstrates this directly.Single query retrieval
The dashboard fetches a user and all their messages in one query via an aggregation pipeline (
$match → $unwind → $sort → $group). No joins or second queries are required.Simple data shape
Because messages belong to exactly one user and are never queried independently, embedding avoids the overhead of a join collection and keeps the schema straightforward.
OTP Model
TheOTP collection is intentionally ephemeral. Each document holds one email-OTP pair and automatically deletes itself after five minutes via a MongoDB TTL index.
Pre-Save Hook — Automatic Email Dispatch
When a newOTP document is saved, the pre("save") hook fires and calls mailSender to deliver the OTP email via Brevo. The hook only fires when this.isNew is true, preventing re-sends on accidental re-saves.
sendVerificationEmail helper calls mailSender with the recipient address, the subject "Verification Email", and the HTML body produced by otpTemplate(otp).
Safe Model Export
mongoose.models.OTP || mongoose.model(...) guard used in User.ts prevents duplicate model registration during hot reloads.
Database Connection — dbConnect()
Check isConnected
The module-level
connection object persists across hot reloads. If connection.isConnected is already set, dbConnect() returns immediately without opening a new connection.Validate environment variable
Throws a clear
Error if DATABASE_URL is missing so misconfiguration is caught at startup.Connect and record state
Calls
mongoose.connect() and stores db.connections[0].readyState (a numeric enum where 1 means connected) into connection.isConnected for subsequent calls.SafeUser Type
SafeUser is the non-sensitive subset of IUser returned in API responses. It deliberately omits the password field.
SafeUser is used as the generic parameter for ApiResponse<SafeUser> in the sign-up and delete-message routes, ensuring the data field of the response always conforms to the safe shape.
ApiResponse<T> Type
success is a boolean flag the frontend checks first. message is a human-readable status string. data is optional and typed by T — routes that carry no payload simply omit it (the default T = null makes data? an optional field).
Usage examples:
Zod Validation Schemas
All five schemas live insrc/app/schemas/ and are used both in client-side forms (via zodResolver) and in server-side route handlers (via safeParse).
signUpSchema — new account registration
signUpSchema — new account registration
usernameSchema is also exported separately so check-username-unique/route.ts can reuse it for query-param validation without importing the full sign-up schema.signInSchema — existing user login
signInSchema — existing user login
bcrypt.compare) happens in auth.ts authorize().messageSchema — anonymous message content
messageSchema — anonymous message content
acceptMessageSchema — message toggle
acceptMessageSchema — message toggle
POST /api/accept-messages to validate that the incoming body contains a strict boolean. Zod’s z.boolean() rejects strings like "true" or numbers like 1, preventing accidental coercion bugs.verifySchema — OTP verification code
verifySchema — OTP verification code
z.string().length(6) requires exactly six characters — matching the six-digit OTPs generated by otp-generator in send-otp/route.ts. Wrapped in z.object() because the data arrives as { code: "123456" } from the form.