Database Layer
The backend uses a layered database abstraction that decouples business logic from the underlying database technology.
Architecture
DatabaseInterface (interfaces/database.interface.js)
│
└── MongoDBDatabase (database/mongodb/index.js)
│
├── UserDB extends BaseDB
├── DeviceDB extends BaseDB
├── UnitDB extends BaseDB
├── SensorDataDB extends BaseDB
├── DiagnosticDataDB extends BaseDB
├── RawSensorDataDB extends BaseDB
├── NetworkDB extends BaseDB
├── RecipientDB extends BaseDB
├── NotificationDB extends BaseDB
├── UnitAndDeviceDB extends BaseDB
└── TransactionDBAll business classes receive a db instance via constructor injection:
// Example in a controller
const db = getDatabase()
const business = new UserBusiness(db)DatabaseInterface (Abstract Contract)
interfaces/database.interface.js defines the required getters. Attempting to use the base class directly throws 'Method not implemented':
get unit() { throw new Error('Method not implemented') }
get device() { throw new Error('Method not implemented') }
get user() { throw new Error('Method not implemented') }
get transaction() { throw new Error('Method not implemented') }
// ... etc.MongoDBDatabase
database/mongodb/index.js extends DatabaseInterface and exposes composed DB operation instances as getters:
| Getter | DB Class | Collection(s) |
|---|---|---|
.unit | UnitDB | units |
.device | DeviceDB | devices |
.unitAndDevice | UnitAndDeviceDB | unit_and_device_logs, user_and_device_logs |
.user | UserDB | users, user_tokens |
.sensorData | SensorDataDB | sensor_data |
.diagnosticData | DiagnosticDataDB | diagnostic_data |
.rawSensorData | RawSensorDataDB | raw_sensor_data |
.network | NetworkDB | network_data |
.notification | NotificationDB | notification_receivers |
.recipient | RecipientDB | recipients |
.transaction | TransactionDB | (MongoDB sessions) |
BaseDB (Generic CRUD)
database/mongodb/base.db.js provides lean generic operations that all DB classes inherit:
Methods
create(data)
// Mongoose Model.create(data) → returns lean plain object
find(query, options?)
// options: { skip, limit, sort, projection, populate }
// populate: string | string[] | { path, select }[]
// Returns lean array
findOne(query, options?)
// options: { populate, sort }
// Returns lean object or null
findById(id, options?)
// Returns lean object or null
update(id, data, options?)
// findByIdAndUpdate(id, data, { new: true })
// Returns updated lean object
updateOne(filter, update, options?)
// Model.updateOne(filter, update).lean()
updateMany(items)
// bulkWrite with individual updateOne operations
// items: [{ filter, update }]All results are .lean() — plain JavaScript objects, not Mongoose documents. This is intentional for performance since business logic never needs Mongoose document methods.
UserDB (Extended)
UserDB adds methods beyond BaseDB:
| Method | Description |
|---|---|
countDocuments(query) | Count matching documents |
save(user) | Call .save() on a Mongoose document |
createToken(userId, token) | Insert into user_tokens |
deleteToken(token) | Remove from user_tokens by token value |
aggregate(pipeline) | Run an aggregation pipeline on users |
Database Singleton
database/index.js implements factory + singleton:
let instance = null
export const initializeDatabase = (type = 'mongodb') => {
instance = new MongoDBDatabase()
return instance
}
export const getDatabase = () => {
if (!instance) instance = new MongoDBDatabase()
return instance
}Transactions
TransactionDB wraps MongoDB sessions for atomic multi-document operations:
// Usage pattern in business classes
const session = await db.transaction.start()
try {
await db.user.create({ ...userData }, { session })
await db.recipient.create({ ...recipientData }, { session })
await db.transaction.commit(session)
} catch (error) {
await db.transaction.abort(session)
throw error
}Transactions are used in:
UserBusiness.signup()— creates user + default recipient atomicallyUnitBusiness.updateUnits()— multi-document unit/device updatesRecipientBusiness.deleteRecipient()— removes from all units + soft-deletesAdminDeviceBusiness.addDevicesToUser()— creates device logs + updates devices
