This project is a full‑stack face‑recognition attendance system inspired by tools like ubiAttendance, but built to be fully self‑hosted and customizable.
The system enables:
- Student & subject management per teacher
- Face registration (embeddings only, no raw images stored in the database)
- Fast attendance capture from a live webcam feed
- Duplicate prevention (one attendance per student per day/session)
- Multi‑tenant isolation so each teacher sees only their own data
- Dashboards, history, and export for attendance analytics
It is composed of a FastAPI backend and a Next.js 14 frontend, communicating via a documented REST API.
- Language: Python 3.12
- Framework: FastAPI
- Server: Uvicorn (ASGI)
- Database:
- PostgreSQL in production
- SQLite (
sqlite:///./attendance.db) for local development
- ORM: SQLAlchemy 2.x
- Validation & settings:
- Pydantic v2 (request/response models)
pydantic-settings(environment‑based config)
- Authentication & security:
- JWT tokens via
python-jose - Password hashing via
bcrypt adminandteacherroles with role‑based access using FastAPI dependencies
- JWT tokens via
- Face recognition:
face_recognition(dlib‑based 128‑dimension embeddings)face_recognition_modelsopencv-python-headlessfor image decodingnumpyfor numeric computations
- Other:
- CORS middleware for frontend↔backend communication
- Optional seeding script for demo data
- Language: TypeScript
- Framework: Next.js 14 (App Router)
- UI & styling:
- React + Tailwind CSS
- Global dark theme tailored to a “smart classroom dashboard” look
- Lucide icons for consistent iconography
- HTTP client: Axios with centralized interceptors
- State / Storage:
- LocalStorage for storing access tokens and user info
- Charts & visualization:
- Recharts for attendance analytics on the dashboard
- Media:
- Native
navigator.mediaDevices.getUserMediafor webcam video - A reusable
WebcamCapturecomponent for capture flows
- Native
Backend root: backend/app
-
main.py- Creates the FastAPI app instance.
- Configures CORS using origins from
Settings.cors_origins. - Includes all routers:
auth_routersubjects_routerstudents_routerface_routerattendance_routeranalytics_router
- On startup, calls
Base.metadata.create_all(bind=engine)to ensure tables exist. - Exposes a
/healthendpoint.
-
config.py- Uses
BaseSettingsto load environment variables. - Important settings:
DATABASE_URLSECRET_KEYALGORITHMACCESS_TOKEN_EXPIRE_MINUTESFACE_MATCH_THRESHOLDFACE_EMBEDDING_MODELCORS_ORIGINS
- Uses
-
database.py- Creates the SQLAlchemy engine for the configured
DATABASE_URL. - Provides
SessionLocaland aget_dbdependency for per‑request sessions.
- Creates the SQLAlchemy engine for the configured
-
models.py-
SQLAlchemy ORM models:
-
User- Fields:
id,email,hashed_password,full_name,role,created_at - Enum
UserRolewith valuesadminandteacher - Relationships:
subjects,students
- Fields:
-
Subject- Fields:
id,name,code,teacher_id,created_at - Each subject belongs to exactly one teacher.
- Relationship:
teacher,students
- Fields:
-
Student- Fields:
id,student_id,full_name,email,created_at teacher_id– the owning teachersubject_id– optional, linking toSubject- Uniqueness:
(teacher_id, student_id)unique, so different teachers can reuse IDs without conflict.
- Relationships:
teacher,subject,face_embeddings,attendances
- Fields:
-
FaceEmbedding- Fields:
id,student_id,embedding_data,created_at embedding_datais a comma‑separated list of 128 floats (128‑dim embedding).
- Fields:
-
Attendance- Fields:
id,student_id,marked_at,attendance_date,session_id,status - Unique constraint on
(student_id, attendance_date, session_id)to prevent duplicate marks in the same session/day.
- Fields:
-
-
-
schemas.py- Pydantic models for request/response validation:
- Auth:
LoginRequest,RegisterRequest,TokenResponse,UserResponse - Subjects:
SubjectCreate,SubjectUpdate,SubjectResponse,SubjectListResponse - Students:
StudentCreate,StudentUpdate,StudentResponse,StudentListResponse - Face:
RecognizeResponse - Attendance:
MarkAttendanceRequestMarkAttendanceResponse(includesalready_marked: bool)AttendanceRecord,AttendanceHistoryResponseDailyStat,StatsResponse
- Auth:
- Pydantic models for request/response validation:
-
auth.pyhash_password/verify_passwordusingbcrypt.- JWT helpers:
create_access_token(data, expires_delta)decode_token(token)
- Security dependencies:
get_current_user– loads theUserfrom the JWT.require_role(*roles)– ensures the current user has one of the allowed roles; used throughout routers.
-
POST /auth/login- Accepts email + password.
- Validates credentials.
- Returns JWT access token + basic user info.
-
POST /auth/register- Accepts email, password,
full_name, and role. - Prevents duplicate emails.
- Creates a new
Userwith roleteacheroradmin.
- Accepts email, password,
All endpoints require admin or teacher role; data is scoped to the current teacher.
-
GET /subjects- Query:
skip,limit,search. - Returns subjects belonging to the logged‑in teacher.
- Query:
-
POST /subjects- Body:
SubjectCreate. - Creates a subject for the teacher, preventing duplicates by
(name, code)per teacher.
- Body:
-
PUT /subjects/{id}- Updates name and/or code for a subject owned by the teacher.
-
DELETE /subjects/{id}- Deletes a subject; linked students simply lose the
subject_id.
- Deletes a subject; linked students simply lose the
All endpoints require admin or teacher. Teachers are restricted to their own students.
-
GET /students- Query:
skip,limit,search,subject_id. - For teachers:
- Filters by
Student.teacher_id == user.id. - Optionally further filters by
Student.subject_id == subject_id.
- Filters by
- Search covers ID, name, and email.
- Query:
-
GET /students/{id}- Fetches a single student, scoped to teacher if applicable.
-
POST /students- Body:
StudentCreatewith optionalsubject_id. - Ensures:
- No duplicate
student_idfor this teacher. - If
subject_idis provided, that subject belongs to the current teacher.
- No duplicate
- Body:
-
PUT /students/{id}- Allows updating
student_id,full_name,email, andsubject_id. - Maintains uniqueness of
student_idper teacher.
- Allows updating
-
DELETE /students/{id}- Deletes a student and cascades to face embeddings and attendance via relationships.
-
POST /face/register-face- Multipart form:
student_id,image: File. - Steps:
- Reads image bytes, decodes to RGB.
- Extracts a single face embedding (errors on none/multiple/low‑quality).
- Looks up the student within the current teacher’s students.
- Stores a
FaceEmbeddingrow (embedding only).
- Multipart form:
-
POST /face/recognize-face- Multipart form:
image: File, optionalexpected_student_id. - Steps:
- Extracts probe embedding.
- Loads all embeddings for students owned by this teacher.
- Finds closest embedding within a configurable threshold.
- If
expected_student_idis supplied and mismatch occurs → 400 with a clear message. - Resolves the matched student again within teacher scope; returns
{ student_id, full_name, confidence }.
- Multipart form:
POST /attendance/mark- Body:
MarkAttendanceRequest { student_id, session_id? }. - Validates that the student belongs to the teacher.
- Uses UTC
nowto set:marked_at = nowattendance_date = now.date()
- If an existing attendance record exists for
(student_id, attendance_date, session_id):- Returns it with
already_marked = true.
- Returns it with
- Else:
- Inserts a new record with status
"present"and returnsalready_marked = false.
- Inserts a new record with status
- Body:
All endpoints are scoped: teachers see only their own students’ records; admins see global data.
-
GET /attendance/stats- Query:
from,to. - Aggregates daily present counts and returns:
daily: [{ date, present, total }]summarywithtotal_present,total_sessions,total_students.
- Query:
-
GET /attendance/history- Query:
from,to,student_id,skip,limit. - Returns paginated
AttendanceRecordlist with student IDs and names.
- Query:
-
GET /attendance/export- Exports attendance data as CSV or Excel (
xlsx) for the chosen range.
- Exports attendance data as CSV or Excel (
Frontend root: frontend/
- Uses Next.js App Router structure.
- Public routes:
/login– Sign in/register– Create an account
- Protected routes are under the
(protected)segment:/dashboard– summary analytics/students– student list & CRUD/students/[id]– per‑student face registration/subjects– subject management/attendance– live webcam attendance capture/history– attendance history and export
(protected)/layout.tsx:
- Checks authentication using
isAuthenticated()and redirects to/loginif necessary. - Provides a shared dark gradient + grid background plus the
Navbar.
Centralized Axios client configured with:
baseURLfromNEXT_PUBLIC_API_URL.- Request interceptor: attaches
Authorization: Bearer <token>when present. - Response interceptor:
- For non‑auth endpoints:
- On 401: clears auth and redirects to
/login.
- On 401: clears auth and redirects to
- For
/auth/loginand/auth/register:- Lets the calling pages handle 401 to display inline error messages.
- For non‑auth endpoints:
Typed functions map directly to backend endpoints (login, register, subjects, students, face, attendance, analytics).
setStoredAuth(accessToken, user)– saves token + user tolocalStorage.getStoredUser()– fetches the stored user.clearStoredAuth()– removes both.isAuthenticated()– simple boolean based on presence of a token.
-
Navbar- Shows app name and nav links (Dashboard, Students, Subjects, Capture, History).
- Indicates active page with primary color and subtle background.
- Displays logged‑in user’s email and logout button.
-
WebcamCapture- Props:
onCapture,disabled,className,autoStart. - Handles:
- Starting and stopping camera via
getUserMedia. - Drawing current video frame to a canvas and returning a JPEG
BlobtoonCapture. - Optional
autoStartto turn the camera on automatically (used for the Capture page).
- Starting and stopping camera via
- Props:
- User navigates to
/login. - Enters email + password.
- Frontend calls
POST /auth/login. - On success:
- Stores token + user and redirects to
/dashboard.
- Stores token + user and redirects to
- On 401:
- Shows “Incorrect email or password.” in the login card.
Registration (/register) follows a similar pattern but first calls POST /auth/register, then logs in.
- Teacher opens
/subjects. - Creates subjects like “Mathematics” or “Physics” with optional codes.
- Subjects are only visible to that teacher.
- Teacher opens
/students. - Adds a new student:
- Inputs
student_id,full_name, optionalemail. - Optionally assigns a subject from a dropdown (populated from
/subjects).
- Inputs
- Student appears in the table, filtered by search or subject as needed.
- On
/students, click Register face for a specific student. - The dedicated page:
- Shows which student is being registered.
- Prompts to start the camera and capture a single face.
- On capture:
- Sends the image +
student_idtoPOST /face/register-face. - Backend stores the embedding linked strictly to that student and this teacher.
- Sends the image +
- Teacher opens
/attendance:- Either directly (auto‑detect any known student).
- Or via Take attendance from a specific student (strict match mode).
- Camera starts automatically (using
autoStart). - Students come in a line:
- Each stands in front of camera and clicks Capture.
- For each capture:
- Frontend sends the frame to
POST /face/recognize-face. - If strict mode is active and the face does not match the selected student:
- UI displays “Face does not match the selected student.”
- If recognized:
- Calls
POST /attendance/mark. - If first mark today:
- Shows “Attendance marked successfully.”
- If already marked:
- Shows “You have already been marked present for today's lecture.”
- Displays Recognized: and Marked at: .
- Calls
- Frontend sends the frame to
The camera remains on the whole time; only the Capture button is pressed for each student.
- Teacher opens
/history. - Selects a date range (
from,to). - Page lists all attendance records in that range for this teacher.
- Buttons allow exporting to:
- CSV
- Excel (XLSX)
- On
/dashboard, teacher sees:- Total students.
- Total attendance marks in the last 30 days.
- Number of days with recorded data.
- A bar chart of daily attendance counts.
-
Auth failures
- 401 from non‑auth endpoints triggers logout + redirect to
/login. - 401 from
/auth/loginor/auth/registersimply surfaces the message in the UI.
- 401 from non‑auth endpoints triggers logout + redirect to
-
Face recognition failures
- No face / multiple faces / low quality result in 400 with clear messages.
- Frontend shows those messages in the Result panel.
-
Attendance duplicates
- Backend treats duplicate marks as idempotent, returning the original record and
already_marked = true. - Frontend reflects this with a different message and the original timestamp.
- Backend treats duplicate marks as idempotent, returning the original record and
-
Cross‑teacher conflicts
- Every query for students, subjects, embeddings, or attendance is scoped by
teacher_idfor teachers. - Even when student IDs coincide across teachers, recognition and attendance always resolve the student within the owning teacher’s scope.
- Every query for students, subjects, embeddings, or attendance is scoped by
See README.md for exact commands, but at a high level:
- Install system dependencies for Python + dlib.
- Create Python virtual environment in
backendand install requirements. - Configure
.envfor backend and.env.localfor frontend. - Run:
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000npm run devinfrontend.
- Seed demo data with
python -m seed(admin + teacher + demo students & subjects).
Some ideas for extension:
- Class timetable integration (scheduled sessions with automatic
session_id). - Additional roles (e.g. principal, operator).
- Push/email notifications for absentees.
- Better embedding management (e.g. multiple embeddings per student with weighting).
- Face anti‑spoofing checks to detect photos/videos.
- PWA or kiosk mode for dedicated attendance stations.
This report is intended as a comprehensive, GitHub‑ready overview of the system’s architecture, technologies, and workflows. It can be linked from the main README.md or kept as a separate REPORT.md in the repository root.