Dutch Leraar Technical DOcuments

This blog explain the technical tools used in this tool.

Shobhit Singh
10/17/2025
10 min read
ToolAITechnical

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.tsx is the main component for that route.
  • components/: Contains reusable React components.
    • components/ui/: Houses the shadcn/ui components, 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 the authenticatedFetch function, a wrapper around fetch that automatically handles CSRF tokens and authentication credentials.
    • backend-forwarder.ts: Provides the forwardToBackend function 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.

  1. Login: The user enters credentials on the /login page. The form submission calls a function in lib/auth.ts which makes a POST request to the backend's /auth/login endpoint.
  2. Backend Verification: The backend verifies the credentials against the Supabase auth.users table.
  3. Session Creation: Upon successful login, the backend creates a session and sets secure, HTTP-only cookies (sb-access-token and sb-refresh-token) in the user's browser.
  4. Context Update: The frontend's AuthContext detects the session and updates its state, providing the user object to all child components.
  5. Authenticated Requests: For subsequent requests, the browser automatically sends the session cookies. The authenticatedFetch function ensures that a CSRF token is also fetched and included as a header (X-CSRF-Token) for POST, PUT, and DELETE requests 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.

  1. A frontend component (e.g., in question-answer.tsx) calls authenticatedFetch to make a request to a local Next.js API route (e.g., /api/progress/last-question).
  2. The Next.js API route (e.g., app/api/progress/last-question/route.ts) receives this request.
  3. This route then uses the forwardToBackend utility from lib/backend-forwarder.ts.
  4. forwardToBackend constructs 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.
  5. 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:

  1. Initial State: The component initializes with a loading state.
  2. Question Fetching (generateQuestion):
    • An effect hook triggers generateQuestion when the user's selected level changes.
    • It constructs the API URL, including the userId if the user is authenticated (/api/qa-questions?level=A2&userId=...).
    • It calls authenticatedFetch to request the questions.
    • User-Specific Filtering: The backend uses the userId to filter out questions the user has already answered correctly.
  3. 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/profile endpoint to get the last_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.
  4. Answer Evaluation (evaluateAnswer):
    • When the user submits an answer, evaluateAnswer is called.
    • It makes a POST request to /api/validate/qa with the question, answer, and level.
    • The backend uses an AI model to evaluate the answer and returns feedback and a score.
  5. Progress Tracking:
    • After evaluation, two separate API calls are made to track progress:
      1. recordQuestionAttempt: A POST request to /api/progress/attempt saves the result of the specific attempt (correct/incorrect, feedback, etc.) to the question_attempts table.
      2. recordLastAttemptedQuestion: A POST request to /api/progress/last-question updates the last_attempted_question_id column in the user_profiles table. This is used to resume the session later.

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 is progress-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 like authenticateApiKey.
    • 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 a userId and questionId. It calls the progressService to update the last_attempted_question_id in 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 the userId, and logs it in the question_attempts table.
  • GET /api/progress/profile: Fetches a user's profile data, including their last_attempted_question_id.
  • GET /api/qa-questions: This endpoint is responsible for serving questions for the Q&A mode.
    • It accepts a level and an optional userId as query parameters.
    • If a userId is provided, it first queries the question_attempts table (via progressService.getCorrectlyAnsweredQuestions) to get a list of all questions the user has already answered correctly.
    • It then fetches questions from the qa_questions table, explicitly filtering out the IDs of the already-answered questions.
    • This ensures users are not served questions they have already mastered.
  • 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 the X-API-Key header.
  • verifyCsrf: This middleware implements CSRF protection using a stateless double-submit cookie pattern.
    1. When a user first visits the site, the frontend calls a dedicated endpoint that uses issueCsrfToken to generate a signed token and set it in two places: a csrf-token cookie (readable by the client) and a secure, httpOnly cookie.
    2. For subsequent POST, PUT, etc., requests, the frontend reads the csrf-token cookie and sends its value in an X-CSRF-Token header.
    3. The verifyCsrf middleware 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.

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 to qa_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 to user_profiles.id): The user who made the attempt.
    • question_id (UUID, Foreign Key to qa_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 to user_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.