Universal Digital Signage Containerized Architecture with Schema Definitions and Docker Compose Orchestration Shivam Jadhav This is a multi-container, containerized application managed by Docker Compose: which is referred to interchangeably as a Compose application or project (sometimes 'stack,' though that’s more Swarm terminology). In more architectural terms: a three-tier containerized application stack consisting of: MongoDB (data layer), Node.js backend (API/business-logic layer), React frontend (presentation layer). All three run in their own containers and are wired together by a single docker-compose.yml. Docker Basics Docker Compose puts all services on a private network so they can talk to each other by service name, without exposing internal ports to the outside world. Internal networking When you do docker-compose up, Compose creates a network (universal-signage_default) and attaches mongodb, the backend and the frontend to it. From inside the backend container, MongoDB is reached at host mongodb on port 27017. From outside the app container, such as when you use Mongosh to connect to and manage the database - you would use port 27018 as it is exposed. From inside the frontend container, the frontend reaches the backend at host backend on port 3001 (the API) and serves the appplication itself to the user on port 3002. mongodb: "27018:27017" # so you can mongo-shell into it from your laptop as localhost:27018 app: "3001:3001" # so you can hit your Node API at <your-server>:3001 frontend: "3002:3002" # so you can hit your React dev server at <your-server>:3002 These mappings only affect traffic coming into Docker fthrough the user interaction: they don’t change how the containers talk to one another. An Overview of the Database Schema Collection and Key Fields Collection Primary Fields Notes / FKs departments _id (ObjectId), name (string) — users _id, firstName, lastName, email, password, role departmentId → departments._id groups _id, name, departmentId departmentId → departments._id devices _id, name, ip, location, groupId groupId → groups._id channels _id, name, description — medias _id, url, type, tags — templates _id, name, htmlTemplate — styles _id, name, cssRules — playlists _id, name, templateId, styleId templateId → templates._id; styleId → styles._id slides _id, playlistId, mediaId, order, contentType playlistId → playlists._id; mediaId → medias._id assignplaylists _id, playlistId, deviceId m–n mapping between playlists & devices schedulers _id, deviceId, playlistId, startTime, endTime, rrule deviceId → devices._id; playlistId → playlists._id logrequests _id, deviceId, timestamp, route, statusCode deviceId → devices._id JSON Schema Validators Validators for all collections: // 1. departments db.createCollection('departments', { validator: { $jsonSchema: { bsonType: 'object', required: ['name'], properties: { name: { bsonType: 'string', description: 'Department name is required' }, } } } }); // 2. users db.createCollection('users', { validator: { $jsonSchema: { bsonType: 'object', required: ['firstName', 'lastName', 'email', 'password', 'role', 'departmentName'], properties: { firstName: { bsonType: 'string', description: 'First name is required' }, lastName: { bsonType: 'string', description: 'Last name is required' }, email: { bsonType: 'string', pattern: '^.+@.+$', description: 'Email is required and must be valid' }, password: { bsonType: 'string', description: 'Password is required' }, bio: { bsonType: 'string', description: 'Bio is optional' }, role: { bsonType: 'string', enum: ['standard', 'admin', 'assetManager', 'globalAssetManager'], description: 'Role must be one of the defined values' }, departmentName: { bsonType: 'string', description: 'Department name is required' }, departmentId: { bsonType: 'objectId', description: 'Reference to Departments collection' }, profileImg: { bsonType: 'string', description: 'Profile image URL is optional' } } } } }); // 3. groups db.createCollection('groups', { validator: { $jsonSchema: { bsonType: 'object', required: ['name', 'departmentId'], properties: { name: { bsonType: 'string', description: 'Group name is required' }, departmentId: { bsonType: 'objectId', description: 'Must reference a department _id' }, } } } }); // 4. devices db.createCollection('devices', { validator: { $jsonSchema: { bsonType: 'object', required: ['name', 'ip', 'location', 'groupId'], properties: { name: { bsonType: 'string', description: 'Device name is required' }, ip: { bsonType: 'string', description: 'Device IP address is required' }, location: { bsonType: 'string', description: 'Human‐readable location is required' }, groupId: { bsonType: 'objectId', description: 'Must reference a group _id' }, } } } }); // 5. channels db.createCollection('channels', { validator: { $jsonSchema: { bsonType: 'object', required: ['name', 'description'], properties: { name: { bsonType: 'string', description: 'Channel name is required' }, description: { bsonType: 'string', description: 'Channel description is required' }, } } } }); // 6. medias db.createCollection('medias', { validator: { $jsonSchema: { bsonType: 'object', required: ['url', 'type'], properties: { url: { bsonType: 'string', pattern: '^https?://', description: 'Media URL must be a valid HTTP(S) link' }, type: { bsonType: 'string', enum: ['image', 'video', 'html'], description: 'Type must be one of: image, video, html' }, tags: { bsonType: 'array', items: { bsonType: 'string' }, description: 'Optional array of tag strings' } } } } }); // 7. templates db.createCollection('templates', { validator: { $jsonSchema: { bsonType: 'object', required: ['name', 'htmlTemplate'], properties: { name: { bsonType: 'string', description: 'Template name is required' }, htmlTemplate: { bsonType: 'string', description: 'Raw HTML template is required' } } } } }); // 8. styles db.createCollection('styles', { validator: { $jsonSchema: { bsonType: 'object', required: ['name', 'cssRules'], properties: { name: { bsonType: 'string', description: 'Style name is required' }, cssRules: { bsonType: 'string', description: 'CSS text for this style' } } } } }); // 9. playlists db.createCollection('playlists', { validator: { $jsonSchema: { bsonType: 'object', required: ['name', 'templateId', 'styleId'], properties: { name: { bsonType: 'string', description: 'Playlist name is required' }, templateId: { bsonType: 'objectId', description: 'Must reference a template _id' }, styleId: { bsonType: 'objectId', description: 'Must reference a style _id' } } } } }); // 10. slides db.createCollection('slides', { validator: { $jsonSchema: { bsonType: 'object', required: ['playlistId', 'mediaId', 'order', 'contentType'], properties: { playlistId: { bsonType: 'objectId', description: 'Must reference a playlist _id' }, mediaId: { bsonType: 'objectId', description: 'Must reference a media _id' }, order: { bsonType: 'int', minimum: 0, description: 'Zero‐based position in playlist' }, contentType: { bsonType: 'string', description: 'E.g. "image", "video", etc.' } } } } }); // 11. assignplaylists db.createCollection('assignplaylists', { validator: { $jsonSchema: { bsonType: 'object', required: ['playlistId', 'deviceId'], properties: { playlistId: { bsonType: 'objectId' }, deviceId: { bsonType: 'objectId' } } } } }); // 12. schedulers db.createCollection('schedulers', { validator: { $jsonSchema: { bsonType: 'object', required: ['deviceId', 'playlistId', 'startTime', 'endTime', 'rrule'], properties: { deviceId: { bsonType: 'objectId', description: 'Must reference a device _id' }, playlistId: { bsonType: 'objectId', description: 'Must reference a playlist _id' }, startTime: { bsonType: 'date', description: 'When playback starts' }, endTime: { bsonType: 'date', description: 'When playback ends' }, rrule: { bsonType: 'string', description: 'Recurrence rule in iCal RRULE format' } } } } }); // 13. logrequests db.createCollection('logrequests', { validator: { $jsonSchema: { bsonType: 'object', required: ['deviceId', 'timestamp', 'route', 'statusCode'], properties: { deviceId: { bsonType: 'objectId', description: 'Must reference a device _id' }, timestamp: { bsonType: 'date', description: 'When request occurred' }, route: { bsonType: 'string', description: 'Requested API route' }, statusCode: { bsonType: 'int', description: 'HTTP status code returned' } } } } }); Mongoose Schemas The blueprint used to define the shape, defaults, validation rules, and behavior of the documents in a MongoDB collection — in the Node.js application: const mongoose = require('mongoose'); // 1. Department Schema const departmentSchema = new mongoose.Schema({ name: { type: String, required: true } }); // 2. User Schema const userSchema = new mongoose.Schema({ firstName: { type: String, required: true }, lastName: { type: String, required: true }, email: { type: String, required: true, match: /^.+@.+$/ }, password: { type: String, required: true }, bio: { type: String }, role: { type: String, required: true, enum: ['standard','admin','assetManager','globalAssetManager'] }, departmentName: { type: String, required: true }, departmentId: { type: mongoose.Schema.Types.ObjectId, ref: 'Department' }, profileImg: { type: String } }); // 3. Group Schema const groupSchema = new mongoose.Schema({ name: { type: String, required: true }, departmentId: { type: mongoose.Schema.Types.ObjectId, ref: 'Department', required: true } }); // 4. Device Schema const deviceSchema = new mongoose.Schema({ name: { type: String, required: true }, ip: { type: String, required: true }, location: { type: String, required: true }, groupId: { type: mongoose.Schema.Types.ObjectId, ref: 'Group', required: true } }); // 5. Channel Schema const channelSchema = new mongoose.Schema({ name: { type: String, required: true }, description: { type: String, required: true } }); // 6. Media Schema const mediaSchema = new mongoose.Schema({ url: { type: String, required: true, match: /^https?:\/\// }, type: { type: String, required: true, enum: ['image','video','html'] }, tags: [{ type: String }] }); // 7. Template Schema const templateSchema = new mongoose.Schema({ name: { type: String, required: true }, htmlTemplate: { type: String, required: true } }); // 8. Style Schema const styleSchema = new mongoose.Schema({ name: { type: String, required: true }, cssRules: { type: String, required: true } }); // 9. Playlist Schema const playlistSchema = new mongoose.Schema({ name: { type: String, required: true }, templateId: { type: mongoose.Schema.Types.ObjectId, ref: 'Template', required: true }, styleId: { type: mongoose.Schema.Types.ObjectId, ref: 'Style', required: true } }); // 10. Slide Schema const slideSchema = new mongoose.Schema({ playlistId: { type: mongoose.Schema.Types.ObjectId, ref: 'Playlist', required: true }, mediaId: { type: mongoose.Schema.Types.ObjectId, ref: 'Media', required: true }, order: { type: Number, required: true, min: 0 }, contentType: { type: String, required: true } }); // 11. AssignPlaylist Schema const assignPlaylistSchema = new mongoose.Schema({ playlistId: { type: mongoose.Schema.Types.ObjectId, ref: 'Playlist', required: true }, deviceId: { type: mongoose.Schema.Types.ObjectId, ref: 'Device', required: true } }); // 12. Scheduler Schema const schedulerSchema = new mongoose.Schema({ deviceId: { type: mongoose.Schema.Types.ObjectId, ref: 'Device', required: true }, playlistId: { type: mongoose.Schema.Types.ObjectId, ref: 'Playlist', required: true }, startTime: { type: Date, required: true }, endTime: { type: Date, required: true }, rrule: { type: String, required: true } }); // 13. LogRequest Schema const logRequestSchema = new mongoose.Schema({ deviceId: { type: mongoose.Schema.Types.ObjectId, ref: 'Device', required: true }, timestamp: { type: Date, required: true, default: Date.now }, route: { type: String, required: true }, statusCode: { type: Number, required: true } }); module.exports = { Department: mongoose.model('Department', departmentSchema), User: mongoose.model('User', userSchema), Group: mongoose.model('Group', groupSchema), Device: mongoose.model('Device', deviceSchema), Channel: mongoose.model('Channel', channelSchema), Media: mongoose.model('Media', mediaSchema), Template: mongoose.model('Template', templateSchema), Style: mongoose.model('Style', styleSchema), Playlist: mongoose.model('Playlist', playlistSchema), Slide: mongoose.model('Slide', slideSchema), AssignPlaylist: mongoose.model('AssignPlaylist', assignPlaylistSchema), Scheduler: mongoose.model('Scheduler', schedulerSchema), LogRequest: mongoose.model('LogRequest', logRequestSchema) }; Architecture Diagram Mermaid Flowchart of the Containerized Architecture