Case Study
CertiHub
A production training institute management platform handling student registration, fee payment tracking, batch management, and automated PDF certificate generation using Puppeteer-core with Mustache HTML templates — built for SLOG Solutions Private Limited.
Real Production Use
CertiHub was built and deployed for SLOG Solutions Private Limited, a training institute. It manages hundreds of real student registrations, fee tracking, batch completions, and generates official PDF certificates delivered to students via email.
Core Feature Overview
🎓
Student Lifecycle
Full student registration system with invitation-code onboarding, course enrollment, college-branch mapping, photo upload, and active/inactive status management.
Complete Project Structure
root
CertiHub-main/
backend/
models/
Student.js— Full student profile + course enrollment
Certificate.js— Issued certificate with PDF URL + QR URL
CertificateTemplate.js— HTML template metadata
CertificateBatch.js— Batch generation job tracking
CourseDetail.js— Technology + duration combination
FeePayment.js— Fee installment records
FeeDiscount.js— Discount rules per student/course
Technology.js— Course categories (e.g. Web Dev, Python)
Duration.js— Course duration options
College.js— Affiliated institute details + logo/signature flags
Branch.js— Branch under college
User.js— Admin and staff accounts
Notification.js— In-app notification feed
Enquiry.js— Pre-enrollment enquiry records
GeneratedCourseLink.js— Shareable course registration links
routes/
auth.js— Login, register, refresh token
registration.js— Student CRUD, enrollment
certificates.js— Generate, download, revoke
fee.js— Payment recording, outstanding, export
masters.js— Technology, Duration, College, Branch CRUD
reports.js— Dashboard KPI, batch reports
backup.js— Scheduled MongoDB + MySQL backup runner
notifications.js— Notification feed management
utils/
pdfGenerator.js— Puppeteer + Mustache PDF engine
backupScheduler.js— Cron-based auto-backup
templates/
default.html— Mustache certificate HTML template
certificate_bg.jpg— A4 landscape background art
middleware/
auth.js— JWT verification middleware
server.js
frontend/
src/
pages/— Dashboard, Students, Certificates, Fees
components/— DataTable, CertPreview, FeeCard
Student Enrollment & Certificate Flow
1
Student Registration
Staff registers a student with full profile data: name, father's name, DOB, gender, phone, college, branch, course (technology), and duration. The `registrationNo` is auto-assigned as a unique identifier used throughout the system.
2
Fee Payment Recording
Multiple `FeePayment` installments are recorded against the student, tracking payment date, amount paid, and outstanding balance. Discounts are applied via `FeeDiscount` rules before computing net payable.
3
Course Batch Completion
Once a student's course period ends, a `CertificateBatch` job is triggered. It collects all eligible students in that batch, checks fee clearance status, and queues certificate generation.
4
Certificate Generation
`generateCertificatePDF()` is called for each student. Puppeteer-core locates the system Chrome/Edge executable, launches headless, renders the Mustache-compiled HTML at 2480×1754px viewport (A4 landscape at 1.5x scale), then exports as a full-bleed PDF.
5
QR Code Embedding
A QR code URL pointing to the certificate's public verification page is generated and embedded in the PDF template. The `Certificate` document stores both `pdfUrl` and `qrCodeUrl` in MongoDB.
6
Email Delivery
The generated PDF is emailed to the student's registered email address as an attachment. A push notification is sent via OneSignal/Firebase to any registered mobile app session.
MongoDB Data Models
Student Model — Full Training Profile
javascript
// backend/models/Student.js
const studentSchema = new mongoose.Schema({
registrationNo: { type: String, required: true, unique: true },
name: { type: String, required: true, trim: true },
fatherName: { type: String, required: true, trim: true },
dob: { type: Date, required: true },
gender: { type: String, required: true },
email: { type: String, required: true, trim: true },
phone: {
type: String, required: true,
validate: {
validator: (v) => /^\d{10}$/.test(v),
message: (props) => `${props.value} is not a valid 10-digit phone number!`
}
},
// Institution mapping
collegeId: { type: mongoose.Schema.Types.ObjectId, ref: 'College', required: true },
branchId: { type: mongoose.Schema.Types.ObjectId, ref: 'Branch', required: true },
// Training course
courseDetailId: { type: mongoose.Schema.Types.ObjectId, ref: 'CourseDetail', required: true },
durationId: { type: mongoose.Schema.Types.ObjectId, ref: 'Duration', required: true },
startDate: { type: Date, required: true },
// Fee summary
totalFee: { type: Number, required: true },
isActive: { type: Boolean, default: true },
}, { timestamps: true });Certificate Model — Issued Credential
javascript
// backend/models/Certificate.js
const certificateSchema = new mongoose.Schema({
certificateId: { type: String, required: true, unique: true },
studentId: { type: mongoose.Schema.Types.ObjectId, ref: 'Student', required: true },
courseDetailId: { type: mongoose.Schema.Types.ObjectId, ref: 'CourseDetail', required: true },
templateId: { type: mongoose.Schema.Types.ObjectId, ref: 'CertificateTemplate', required: true },
issueDate: { type: Date, default: Date.now },
pdfUrl: { type: String, required: true }, // Local file path or CDN URL
qrCodeUrl: { type: String, default: '' }, // Verification deep link
status: {
type: String,
enum: ['Active', 'Revoked'],
default: 'Active'
}
}, { timestamps: true });PDF Generation Engine
The certificate PDF engine uses puppeteer-core with the system's own Chrome or Edge installation — no bundled Chromium dependency:
javascript
// backend/utils/pdfGenerator.js
const generateCertificatePDF = async (student, courseDetail, certificateId, qrCodeBase64, outputPath, completionDate) => {
// 1. Load Mustache HTML template from disk
const templateHtml = fs.readFileSync(path.join(__dirname, '..', 'templates', 'default.html'), 'utf8');
// 2. Convert all assets to base64 data URIs (avoids CORS / file:// issues in headless)
const bgUrl = toDataUri(path.join(__dirname, '..', 'templates', 'certificate_bg.jpg'));
const leftSigUrl = toDataUri(path.join(__dirname, '..', 'uploads', 'signatures', 'left_signature.png'));
const rightSigUrl = toDataUri(path.join(__dirname, '..', 'uploads', 'signatures', 'right_signature.png'));
// 3. Build template data — keys match {{ placeholders }} in default.html
const templateData = {
student_name: (student.name || '').toUpperCase(),
course_name: courseDetail.technologyId.name.toUpperCase(),
duration_label: `${courseDetail.durationId.value} ${courseDetail.durationId.unit}(s)`,
issue_date: new Date(completionDate).toLocaleDateString('en-IN', { day: '2-digit', month: '2-digit', year: 'numeric' }),
company_name: process.env.COMPANY_NAME || 'SLOG Solutions Private Limited',
registration_id: student.registrationNo || certificateId,
bgUrl,
include_signatures: student.collegeId.showSignature !== false ? [{}] : [],
left_signature_url: leftSigUrl,
right_signature_url: rightSigUrl,
left_sign_name: process.env.LEFT_SIGN_NAME || 'Suraj Prasad Singh',
right_sign_name: process.env.RIGHT_SIGN_NAME || 'Kiran Bisht',
};
const renderedHtml = Mustache.render(templateHtml, templateData);
// 4. Launch system Chrome/Edge (no bundled Chromium)
const browser = await puppeteer.launch({
executablePath: findBrowserExecutable(), // Scans known Windows Chrome/Edge paths
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'],
});
const page = await browser.newPage();
await page.setViewport({ width: 2480, height: 1754, deviceScaleFactor: 1.5 });
await page.setContent(renderedHtml, { waitUntil: ['domcontentloaded', 'load'] });
// 5. Wait for background image + signatures to fully render
await page.evaluate(async () => {
const imgs = Array.from(document.querySelectorAll('img'));
await Promise.race([
Promise.all(imgs.map(img => img.complete ? Promise.resolve() : new Promise(r => { img.onload = r; img.onerror = r; }))),
new Promise(r => setTimeout(r, 5000)),
]);
});
// 6. Export A4 landscape — full bleed, zero margins
const pdfBuffer = await page.pdf({
width: '297mm', height: '210mm', landscape: true,
printBackground: true, margin: { top: 0, right: 0, bottom: 0, left: 0 },
});
await browser.close();
fs.writeFileSync(outputPath, pdfBuffer);
return outputPath;
};Browser Auto-Detection
The engine finds Chrome or Edge automatically on Windows without requiring any environment configuration:
javascript
function findBrowserExecutable() {
if (process.env.CHROME_EXECUTABLE && fs.existsSync(process.env.CHROME_EXECUTABLE))
return process.env.CHROME_EXECUTABLE;
const candidates = [
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
path.join(process.env.LOCALAPPDATA, 'Google', 'Chrome', 'Application', 'chrome.exe'),
'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
path.join(process.env.LOCALAPPDATA, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
];
for (const p of candidates) {
if (p && fs.existsSync(p)) return p;
}
throw new Error('No Chrome/Edge found. Install Google Chrome or set CHROME_EXECUTABLE in .env');
}Dual Database Architecture
CertiHub uses MongoDB as the primary application database and MySQL for legacy data compatibility (the institute had existing SQL records). Data migration scripts (`diff_students.js`, `inspect_duplicates.js`, `inspect_payments.js`) were written to reconcile and migrate records between the two databases during the initial deployment.