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.