Acadify-Backend: The School System That Treats Attendance Like a Security Protocol

A clean-architecture Express stack built around rolling QR codes, Redis-backed token revocation, and role-aware academic workflows.

8 min read • View on GitHub • More from aakashpatle10

A classroom doorway scene where a teacher holds up a phone with a QR code that dissolves into a clock-like shield. Students wait at the threshold while a thin thread connects the QR to a timetable grid and a teacher badge. The image explains that attendance is enforced by time, identity, and context, not by trust alone.
The core idea is not just check-in. It is check-in with a short fuse, a named authority, and a schedule lock.
Key Takeaways

Most school software starts with records. This one starts with a threat model. If a student can photograph a QR code and reuse it later, attendance becomes theater, so the backend turns the check-in into a short-lived, teacher-bound, schedule-validated security event.

The QR Code That Refuses to Be Shared

That is the project’s sharpest idea: attendance should fail if the wrong person, the wrong class, or the wrong minute is involved. The QR session is not a static artifact. It is a live token with an expiry window, a timetable check, and a teacher identity check baked into the flow.

The important part is not the QR itself. It is the chain of checks that decides whether the scan is still allowed to matter.

A close-up postal-system metaphor where a request envelope passes through three gates labeled by their function through visual cues only: validation, role check, and token blacklist check. On the far side, the envelope splits toward a database stack and a memory cache stack. The image explains how requests are filtered before they reach persistence.
The backend behaves like a controlled mailroom. Every envelope gets inspected before it reaches the archive.

Why Attendance Is a Security Problem

A QR code by itself solves nothing. It is easy to copy, easy to forward, and easy to abuse if it stays valid too long. The repository’s design acknowledges that weakness by tying the code to context, not just content.

Simple QR attendanceAcadify-style attendance
Static codeShort-lived token
Anyone can reuse itOnly the right teacher and lecture context can mint it
Validation happens after the factValidation happens before the scan becomes real
Proxy attendance is easyProxy attendance is constrained by time and role

That is why the attendance flow feels closer to authorization than to form submission. It is not collecting a presence mark. It is deciding whether a particular presence claim deserves to become a record.

The Clean Architecture Skeleton

The codebase is organized to keep that trust model intact. Models define the shape of data, repositories hide the database details, services hold the business rules, controllers speak HTTP, and middleware enforces boundaries before the system does any real work.

LayerJobWhy it matters
ModelsDefine Mongoose schemasKeep data shape explicit
RepositoriesEncapsulate data accessDecouple logic from MongoDB
ServicesRun business rulesKeep workflows testable
ControllersHandle HTTP input and outputStay thin
MiddlewareCheck auth, roles, and validationStop bad requests early

How the Request Actually Moves

A representative request starts at the controller. Validation middleware checks the payload, auth middleware checks the token and role, the service executes the business rule, and the repository layer talks to Mongoose. The point is not ceremony. The point is that each layer owns one kind of decision.

router.post(
  '/attendance/mark',
  authMiddleware,
  requireRole(['student']),
  validate(markAttendanceSchema),
  asyncHandler(async (req, res) => {
    const result = await attendanceService.mark(req.user.id, req.body.token);
    res.status(200).json(result);
  })
);

That shape matters because it prevents one big function from becoming a landfill. A request either passes each gate or it does not. By the time the service layer runs, the input has already been narrowed to something meaningful.

JWT, But Not Naively Stateless

The Redis blacklist pattern is the project’s quietest strong decision. Most small Express apps treat JWTs as fire-and-forget tokens. This codebase adds a revocation check, which means logout can actually invalidate the token instead of just pretending to.

Basic JWT setupJWT plus Redis blacklist
Token remains valid until expiryToken can be revoked immediately
Logout is mostly client-sideLogout has server-side enforcement
State is impossible to inspectState is tiny and targeted
Good for demosBetter for systems that care about control

That is a good example of stateful statelessness. The token still behaves like a JWT, but the system refuses to treat it as untouchable.

Validation as a Boundary, Not a Cleanup Step

Joi validators sit at the edge, which is exactly where they belong. They keep malformed input from drifting deeper into the system, so the service layer can focus on real rules instead of defensive parsing.

Without boundary validationWith boundary validation
Services must guard every fieldServices receive shaped input
Errors appear lateErrors fail fast
Business logic gets noisyBusiness logic stays readable
Bad requests consume more of the stackBad requests die near the door
const markAttendanceSchema = Joi.object({
  token: Joi.string().required()
});

export const validate = (schema) => (req, res, next) => {
  const { error } = schema.validate(req.body);
  if (error) return res.status(400).json({ message: error.message });
  next();
};

That pattern keeps the project honest. A backend that expects discipline at the edge can keep its middle layers simpler, and simpler middle layers are easier to test, reason about, and evolve.

Where the AI Assistant Fits

The chatbot and document parsing pieces widen the system from record-keeping into assistance. That matters, but it is still a secondary story. The backend’s identity is defined first by its trust boundaries, then by its AI feature set.

In practice, that makes the AI layer feel like an add-on to a disciplined core rather than the product’s entire reason to exist. The architecture can absorb it because the core already separates concerns cleanly.

What This Project Is, and What It Is Not

Typical tutorial Express appAcadify-Backend
Controllers often reach straight into modelsRepositories and services stay in between
JWT is treated as fully statelessJWT is paired with Redis revocation
Validation is inconsistentValidation is a first-class boundary
Attendance is just CRUDAttendance is a time-limited authorization event

This is not a finished enterprise AMS with every edge case solved. It is something more interesting for a technical reader: a backend that already thinks in systems, not just endpoints. That is why the QR flow stands out. It is the smallest feature that reveals the largest design intent.