Skip to main content

Overview

OdontologyApp is built on a modern full-stack architecture using SvelteKit as the foundation. The application follows a monolithic architecture with clear separation between frontend and backend concerns.

Technology Stack

  • Frontend Framework: Svelte 5 with SvelteKit 2
  • Backend Runtime: Node.js (via SvelteKit server)
  • Database: MySQL 2 with connection pooling
  • Authentication: bcryptjs for password hashing
  • Validation: Zod schemas with sveltekit-superforms
  • UI Library: Custom component library
  • Build Tool: Vite 7

Project Structure

src/
├── routes/              # SvelteKit file-based routing
│   ├── +layout.server.js    # Root layout with user session
│   ├── +layout.svelte       # App shell with sidebar/topbar
│   ├── api/                 # API endpoints (+server.js files)
│   ├── dashboard/           # Dashboard pages
│   ├── patients/            # Patient management
│   ├── appointments/        # Appointment scheduling
│   ├── admin/               # Administrative interfaces
│   └── login/               # Authentication
├── lib/
│   ├── components/          # Reusable Svelte components
│   │   ├── ui/              # UI primitives (Button, Modal, etc.)
│   │   ├── inputs/          # Form input components
│   │   ├── Odontology/      # Dental-specific components
│   │   └── patients/        # Patient-specific components
│   ├── server/              # Server-side utilities
│   │   ├── db.js            # Database connection pool
│   │   └── checkPermission.js # Authorization logic
│   ├── permissions.js       # RBAC permission definitions
│   └── schemas.js           # Zod validation schemas
├── hooks.server.js      # Global request handler
├── app.html            # HTML template
└── app.css             # Global styles

SvelteKit Architecture

File-Based Routing

SvelteKit uses a file-based routing system where the directory structure in src/routes/ maps directly to URL paths:
  • +page.svelte: Defines a page component
  • +page.server.js: Server-side load function for the page
  • +layout.svelte: Shared layout wrapper for child routes
  • +layout.server.js: Server-side layout data
  • +server.js: API endpoint handler
Example Route Structure:
src/routes/
├── +layout.server.js        → Loads user session for all pages
├── +layout.svelte           → App shell (sidebar, topbar)
├── dashboard/
│   └── +page.svelte         → /dashboard
├── patients/
│   ├── +page.svelte         → /patients
│   └── [id]/
│       └── +page.svelte     → /patients/:id
└── api/
    └── patients/
        └── +server.js       → /api/patients (GET, POST, etc.)

Configuration Files

svelte.config.js (src/routes/svelte.config.js:1-17):
import adapter from "@sveltejs/adapter-auto";

const config = {
  kit: {
    adapter: adapter(),
    csrf: {
      checkOrigin: false,
    },
  },
};

export default config;
vite.config.js (src/routes/vite.config.js:1-16):
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [sveltekit()],
  server: {
    proxy: {
      "/ollama": {
        target: "http://localhost:11434",
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/ollama/, ""),
      },
    },
  },
});

Frontend Architecture

Component Organization

Components are organized by function and reusability:
  1. UI Components (src/lib/components/ui/):
    • Generic, reusable UI primitives
    • Examples: Button, Modal, DataTable, EmptyState
    • No business logic, pure presentation
  2. Input Components (src/lib/components/inputs/):
    • Form field components with validation
    • Examples: TextInput, DateInput, SelectInput
    • Consistent styling and error handling
  3. Domain Components (src/lib/components/Odontology/):
    • Dental-specific functionality
    • Examples: Odontogram, ToothSVG, PatientFinance
    • Contains business logic

State Management

OdontologyApp uses Svelte 5’s native reactivity system:
  • $state: Reactive state variables
  • $derived: Computed values
  • $effect: Side effects
  • $bindable: Two-way binding for component props
Example from Odontogram component:
let {
  toothStates = $bindable({}),  // Two-way bound state
  readOnly = false,
  onsubmitted = undefined,
} = $props();

let showTreatmentModal = $state(false);
let selectedTooth = $state(0);
let isSaving = $state(false);

Form Handling

Forms use sveltekit-superforms with Zod schemas for validation:
import { validateRequest, createPatientSchema } from "$lib/schemas";

const valid = await validateRequest(request, createPatientSchema);
if (!valid.success) {
  return json({ message: "Error de validación", errors: valid.errors }, { status: 400 });
}

Backend Architecture

Server Hooks

The global request handler in hooks.server.js (src/hooks.server.js:1-68) processes all requests:
export async function handle({ event, resolve }) {
  const session = event.cookies.get("session");
  
  if (session) {
    const user = JSON.parse(session);
    event.locals.user = user;
    
    // Redirect authenticated users from /login
    if (event.url.pathname === "/login") {
      throw redirect(303, "/dashboard");
    }
    
    // Protect admin routes
    const restrictedToAdmin = [
      "/admin/settings",
      "/admin/treatments",
      "/admin/reports",
      "/users",
      "/branches",
      "/logs",
    ];
    
    const isAdminRoute = restrictedToAdmin.some(
      (route) => currentPath === route || currentPath.startsWith(route + "/")
    );
    
    if (isAdminRoute && user.role !== "admin") {
      throw redirect(303, "/dashboard?error=unauthorized");
    }
  } else {
    event.locals.user = null;
    // Redirect unauthenticated users to login
    if (!isLoginPath && !isApiPath && event.url.pathname !== "/") {
      throw redirect(303, "/login");
    }
  }
  
  return resolve(event);
}

API Endpoints

API routes follow RESTful conventions using +server.js files: Example: Patient API (src/routes/api/patients/+server.js:1-80):
import { pool } from "$lib/server/db";
import { checkPermission, forbiddenResponse } from "$lib/server/checkPermission";

export async function GET({ url, locals }) {
  if (!locals.user) {
    return json({ message: "No autorizado" }, { status: 401 });
  }
  
  if (!(await checkPermission(locals, "VIEW_PATIENTS"))) {
    return forbiddenResponse();
  }
  
  const query = url.searchParams.get("query") || "";
  const [rows] = await pool.query("CALL sp_list_patients(?)", [query]);
  
  return json({ success: true, patients: rows[0] });
}

export async function POST({ request, locals }) {
  if (!locals.user) {
    return json({ message: "No autorizado" }, { status: 401 });
  }
  
  if (!(await checkPermission(locals, "CREATE_PATIENTS"))) {
    return forbiddenResponse();
  }
  
  const valid = await validateRequest(request, createPatientSchema);
  if (!valid.success) {
    return json({ message: "Error de validación", errors: valid.errors }, { status: 400 });
  }
  
  const data = valid.data;
  const [rows] = await pool.query(
    "CALL sp_create_patient(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
    [/* parameters */]
  );
  
  return json({ success: true, patient: rows[0][0] });
}

Request Flow

  1. Request arrives → hits hooks.server.js
  2. Authentication check → validates session cookie
  3. Authorization check → verifies route access
  4. Route handler → processes request in +server.js or +page.server.js
  5. Permission check → validates user permissions using RBAC
  6. Database query → executes stored procedure via connection pool
  7. Response → returns JSON or renders page

Data Flow Patterns

Server-Side Rendering (SSR)

Pages load data on the server before rendering:
// +page.server.js
export async function load({ locals }) {
  const [patients] = await pool.query("CALL sp_list_patients('')");
  return {
    patients: patients[0],
    user: locals.user
  };
}
<!-- +page.svelte -->
<script>
  let { data } = $props();
  // data.patients is available immediately
</script>

Client-Side Fetching

For dynamic interactions, components fetch from API endpoints:
async function loadPatients(query) {
  const res = await fetch(`/api/patients?query=${query}`);
  const data = await res.json();
  return data.patients;
}

Security Architecture

See the Security page for detailed information on:
  • Authentication and session management
  • Role-Based Access Control (RBAC)
  • Permission system
  • CSRF protection
  • SQL injection prevention

Database Integration

See the Database page for details on:
  • Schema design
  • Connection pooling
  • Stored procedures
  • Query patterns

Component Library

See the Components page for documentation on:
  • UI component library
  • Input components
  • Dental-specific components
  • Usage examples

Build docs developers (and LLMs) love