Backend
Models
Interface and class model patterns for MongoDB documents.
Models define the shape of MongoDB documents. There is no Mongoose — just TypeScript interfaces and classes.
Pattern: Interface + Class
Each model file exports:
- An interface (
Iprefix) — the full document shape with all optional fields. - A type alias for create inputs (
Tprefix +Createsuffix). - A class implementing the interface — the constructor sets defaults, timestamps, and any derived fields.
// src/models/user.model.ts
import { ObjectId } from 'mongodb'
import bcrypt from 'bcrypt'
import { UserType, UserStatus } from '../enums/user.enum'
import type { TUserType, TUserStatus } from '../types/common.types'
export interface IUser {
_id?: ObjectId
type?: TUserType
organizationId?: ObjectId | string
firstName: string
lastName: string
email: string
phone: string
password?: string
refreshToken?: string | null
status?: TUserStatus
createdAt?: Date
updatedAt?: Date
deletedAt?: Date | null
}
export type TUserCreate = Pick<
IUser,
'type' | 'organizationId' | 'firstName' | 'lastName' | 'email' | 'phone'
> & { password: string }
export class User implements IUser {
_id?: ObjectId
type: TUserType
organizationId?: ObjectId
firstName: string
lastName: string
email: string
phone: string
password?: string
refreshToken: string | null
status: TUserStatus
createdAt: Date
updatedAt: Date
deletedAt: Date | null
constructor(data: TUserCreate) {
this.type = data.type ?? UserType.CUSTOMER
this.organizationId = data.organizationId ? new ObjectId(data.organizationId) : undefined
this.firstName = data.firstName
this.lastName = data.lastName
this.email = data.email.toLowerCase().trim()
this.phone = data.phone
this.password = data.password // service layer hashes before constructing
this.refreshToken = null
this.status = UserStatus.ACTIVE
this.createdAt = new Date()
this.updatedAt = new Date()
this.deletedAt = null
}
}
Rules
- Interfaces own the query/read shape — all fields optional where MongoDB might not include them.
- Classes own the write shape — the constructor enforces defaults so the DB never gets partial documents.
- Never skip
deletedAt: nullin the constructor — it enables the soft-delete filter{ deletedAt: null }on all queries. ObjectIdconversion happens in the constructor (new ObjectId(id)), not in the service.- No methods on model classes other than the constructor. Behaviour lives in services.