Dutch Leraar Technical DOcuments
This blog explain the technical tools used in this tool.
Technical Documentation: Dutch Leraar Application
This document provides a deep technical overview of the Dutch Leraar application, covering its architecture, frontend and backend implementations, and core functionalities.
1. High-Level Architecture
The application follows a modern, decoupled architecture consisting of a Next.js frontend and a Node.js (Express) backend, with Supabase serving as the database and authentication provider.
- Frontend: A Next.js application responsible for all UI rendering and user interaction. It uses a "Backend for Frontend" (BFF) pattern, where all communication to the main backend is proxied through its own API routes. This encapsulates the backend logic and provides a clean interface for the client-side components.
- Backend: A Node.js/Express server that acts as the primary API. It handles business logic, data processing, user progress tracking, and integration with third-party services (like AI models for content validation).
- Database: A PostgreSQL database managed by Supabase. Supabase is also used for its authentication services, providing user management and JWT-based session control.
- Deployment: The frontend is configured for deployment on Vercel (see
FrontEnd/vercel.json), while the backend is a standard Node.js application.
2. Frontend (Next.js)
The frontend is built with Next.js and React, using TypeScript. It leverages the App Router for routing and server components for efficient data fetching.
2.1. Project Structure
The FrontEnd/src/ directory is organized as follows:
app/: Contains the application's routes, following the Next.js App Router convention.app/api/: This is the core of the BFF pattern. These server-side API routes receive requests from the frontend components and forward them to the backend using a dedicated utility. This prevents direct exposure of the backend API to the client.app/(pages)/: Each folder represents a page (e.g.,dashboard/,login/,practice/).page.tsxis the main component for that route.
components/: Contains reusable React components.components/ui/: Houses theshadcn/uicomponents, providing a consistent and accessible design system.components/modes/: Contains the core logic for the different practice modes (question-answer.tsx,reading.tsx, etc.).
contexts/: Holds React Context providers for managing global state.auth-context.tsx: Manages user authentication state, providing the user object and loading status throughout the application.level-context.tsx: Manages the user's selected difficulty level (e.g., A2, B1).
lib/: Contains library code, utilities, and helper functions.api.ts: Defines theauthenticatedFetchfunction, a wrapper aroundfetchthat automatically handles CSRF tokens and authentication credentials.backend-forwarder.ts: Provides theforwardToBackendfunction used by the Next.js API routes to securely communicate with the backend server.auth.ts: Contains client-side logic for handling login, logout, and session management.
2.2. Authentication Flow
Authentication is managed via Supabase and handled on the frontend by the auth-context.
- Login: The user enters credentials on the
/loginpage. The form submission calls a function inlib/auth.tswhich makes aPOSTrequest to the backend's/auth/loginendpoint. - Backend Verification: The backend verifies the credentials against the Supabase
auth.userstable. - Session Creation: Upon successful login, the backend creates a session and sets secure, HTTP-only cookies (
sb-access-tokenandsb-refresh-token) in the user's browser. - Context Update: The frontend's
AuthContextdetects the session and updates its state, providing theuserobject to all child components. - Authenticated Requests: For subsequent requests, the browser automatically sends the session cookies. The
authenticatedFetchfunction ensures that a CSRF token is also fetched and included as a header (X-CSRF-Token) forPOST,PUT, andDELETErequests to prevent cross-site request forgery.
2.3. API Communication: The BFF Pattern
Direct communication from the browser to the main backend is prohibited. Instead, all requests go through Next.js API routes.
- A frontend component (e.g., in
question-answer.tsx) callsauthenticatedFetchto make a request to a local Next.js API route (e.g.,/api/progress/last-question). - The Next.js API route (e.g.,
app/api/progress/last-question/route.ts) receives this request. - This route then uses the
forwardToBackendutility fromlib/backend-forwarder.ts. forwardToBackendconstructs a new request to the actual backend API endpoint (e.g.,http://127.0.0.1:8787/api/progress/last-question). It securely forwards necessary information:- The request body.
- The user's session cookies.
- The CSRF token header (
x-csrf-token). - The backend-specific API key (
X-API-Key), which is stored as an environment variable and is never exposed to the browser.
- The backend processes the request and sends a response back to the Next.js API route, which in turn sends it back to the original frontend component.
This pattern provides enhanced security by hiding the backend's URL and API key from the client and centralizing communication logic.
2.4. Core Functionality: Q&A Practice Mode
The Q&A practice mode (components/modes/question-answer.tsx) demonstrates several key technical concepts:
- Initial State: The component initializes with a loading state.
- Question Fetching (
generateQuestion):- An effect hook triggers
generateQuestionwhen the user's selectedlevelchanges. - It constructs the API URL, including the
userIdif the user is authenticated (/api/qa-questions?level=A2&userId=...). - It calls
authenticatedFetchto request the questions. - User-Specific Filtering: The backend uses the
userIdto filter out questions the user has already answered correctly.
- An effect hook triggers
- Determining the Starting Question:
- After fetching the list of questions, the component does not render immediately.
- It proceeds to fetch the user's profile from the
/api/progress/profileendpoint to get thelast_attempted_question_id. - It calculates the correct starting index based on this ID. If no ID is found, it defaults to the first question (
index = 0). - Only after the final starting index is determined is the question state updated, which prevents a "flicker" effect on the UI.
- Answer Evaluation (
evaluateAnswer):- When the user submits an answer,
evaluateAnsweris called. - It makes a
POSTrequest to/api/validate/qawith the question, answer, and level. - The backend uses an AI model to evaluate the answer and returns feedback and a score.
- When the user submits an answer,
- Progress Tracking:
- After evaluation, two separate API calls are made to track progress:
recordQuestionAttempt: APOSTrequest to/api/progress/attemptsaves the result of the specific attempt (correct/incorrect, feedback, etc.) to thequestion_attemptstable.recordLastAttemptedQuestion: APOSTrequest to/api/progress/last-questionupdates thelast_attempted_question_idcolumn in theuser_profilestable. This is used to resume the session later.
- After evaluation, two separate API calls are made to track progress:
3. Backend (Node.js/Express)
The backend is a standard Node.js server using the Express framework and TypeScript. It serves as the application's core business logic and data management layer.
3.1. Project Structure
The Backend/src/ directory is organized as follows:
index.ts: The main entry point for the server. It initializes the Express app, sets up all middleware, and registers all route modules.routes/: Contains files that define the application's API endpoints. Each file groups related routes (e.g.,auth.ts,progress.ts) and maps them to service functions.services/: This directory would typically hold business logic, but in this application, the primary service isprogress-service.ts.progress-service.ts: A critical class that encapsulates all database interactions related to user progress. It handles creating user profiles, recording attempts, calculating stats, and fetching progress data.middleware/: Contains Express middleware functions.index.ts: Exports security middleware likeauthenticateApiKey.csrf.ts: Implements the stateless double-submit cookie pattern for CSRF protection.
supabase.ts: A utility file for creating and managing Supabase client instances (public, service role, and user-token-based).
3.2. Key API Endpoints
The backend exposes a RESTful API. All routes are prefixed with /api.
POST /api/progress/last-question: Receives auserIdandquestionId. It calls theprogressServiceto update thelast_attempted_question_idin the user's profile. This is protected by API key and CSRF middleware.POST /api/progress/attempt: Records the result of a single question attempt. It receives detailed attempt data and theuserId, and logs it in thequestion_attemptstable.GET /api/progress/profile: Fetches a user's profile data, including theirlast_attempted_question_id.GET /api/qa-questions: This endpoint is responsible for serving questions for the Q&A mode.- It accepts a
leveland an optionaluserIdas query parameters. - If a
userIdis provided, it first queries thequestion_attemptstable (viaprogressService.getCorrectlyAnsweredQuestions) to get a list of all questions the user has already answered correctly. - It then fetches questions from the
qa_questionstable, explicitly filtering out the IDs of the already-answered questions. - This ensures users are not served questions they have already mastered.
- It accepts a
POST /api/validate/qa: Receives a question prompt and a user's answer. It forwards this information to an AI service to get an evaluation (correct/incorrect) and feedback.
3.3. Security Middleware
Security is handled via a set of middleware functions applied to sensitive routes.
authenticateApiKey: This middleware protects backend endpoints from unauthorized access. It ensures that requests are coming from a trusted source (the Next.js BFF) by checking for a valid API key in theX-API-Keyheader.verifyCsrf: This middleware implements CSRF protection using a stateless double-submit cookie pattern.- When a user first visits the site, the frontend calls a dedicated endpoint that uses
issueCsrfTokento generate a signed token and set it in two places: acsrf-tokencookie (readable by the client) and a secure,httpOnlycookie. - For subsequent
POST,PUT, etc., requests, the frontend reads thecsrf-tokencookie and sends its value in anX-CSRF-Tokenheader. - The
verifyCsrfmiddleware on the backend checks that the header value matches the cookie value. Since an attacker cannot read or set cookies on a different domain, they cannot forge a valid request.
- When a user first visits the site, the frontend calls a dedicated endpoint that uses
4. Database Schema
The database schema is managed in Supabase (PostgreSQL). The key tables are:
-
user_profiles: Stores public-facing user data.id(UUID, Primary Key): The user's internal ID.auth0_user_id(text): The user's ID from the authentication provider (Supabase Auth).display_name(text): The user's display name.last_attempted_question_id(UUID, Foreign Key toqa_questions.id): Stores the ID of the last question the user attempted. This is used to resume practice sessions.
-
qa_questions: Contains the questions for the Q&A practice mode.id(UUID, Primary Key): The unique identifier for the question.prompt(text): The text of the question.level(text): The difficulty level (e.g., 'A2', 'B1').
-
question_attempts: A log of every single question attempt made by any user. This table is crucial for tracking progress and filtering questions.id(bigint, Primary Key): Unique ID for the attempt.user_id(UUID, Foreign Key touser_profiles.id): The user who made the attempt.question_id(UUID, Foreign Key toqa_questions.id): The question that was attempted.is_correct(boolean): Whether the user's answer was correct.attempted_at(timestampz): Timestamp of the attempt.- Other fields include
user_answer,ai_feedback,ai_score, etc.
-
practice_sessions: Stores data about a user's practice session.id(UUID, Primary Key): Unique ID for the session.user_id(UUID, Foreign Key touser_profiles.id): The user for the session.exercise_set_id(text): The ID of the set of exercises being practiced.is_completed(boolean): Whether the session was completed.