Skip to main content
The platform follows a decoupled, three-tier architecture: a React single-page application communicates with a Django REST Framework API over HTTP, and the API persists data to a relational database while delegating media storage to Supabase.
Architecture overview
┌─────────────────────────────────────────────────────────────┐
│                        Browser (SPA)                        │
│              React 19 + Vite + Tailwind CSS 4               │
│   PublicLayout │ ClientLayout │ AdminLayout                  │
└───────────────────────────┬─────────────────────────────────┘
                            │ HTTP/JSON (Axios)
                            │ Authorization: Bearer <JWT>
┌───────────────────────────▼─────────────────────────────────┐
│                  Django REST Framework                       │
│               /api/*  (16 resource modules)                  │
│      djangorestframework-simplejwt  │  django-cors-headers   │
└──────────┬──────────────────────────────────────┬───────────┘
           │                                      │
  ┌────────▼────────┐                   ┌─────────▼──────────┐
  │  SQLite (dev)   │                   │  Supabase Storage  │
  │  MySQL (prod)   │                   │  (media assets)    │
  └─────────────────┘                   └────────────────────┘

Frontend

The frontend is a React 19 single-page application built with Vite 6. All routing is client-side, using React Router v7. Components are loaded lazily with React.lazy and Suspense to minimize initial bundle size. The application is organized into three layout regions, each with its own layout component and route guard:

Route structure

Public routes are rendered inside PublicLayout and require no authentication. They are accessible to all visitors.
PathPage
/Home
/loginLogin
/registerRegister
/forgot-passwordForgot password
/quienes-somos/informacion-institucionalInstitutional information
/quienes-somos/historiaPark history
/quienes-somos/equipoTeam
/quienes-somos/transparencia-institucionalInstitutional transparency
/exhibiciones-y-servicios/exhibicionesExhibits
/exhibiciones-y-servicios/servicios-educativosEducational services
/exhibiciones-y-servicios/visitas-guiadasGuided visits
/investigacion-y-conservacion/acuicultura-y-biotecnologia-marinaAquaculture & marine biotechnology
/investigacion-y-conservacion/centro-de-rescate-y-rehabilitacionRescue & rehabilitation center
/investigacion-y-conservacion/investigacionResearch
/investigacion-y-conservacion/proyectosProjects
/apoyo/voluntariadoVolunteering
/apoyo/donacionesDonations
/purchase-form/ticketeraTicket purchase
/terminos-y-condiciones/terminosTerms and conditions
/privacidad/politica-de-privicidadPrivacy policy
All page components are code-split using React.lazy. A shared <Loading /> fallback component is shown during lazy load via <Suspense>.

Backend

The backend is a Django 5.2 application exposing a REST API at /api/. Each functional domain is its own Django app under the api/ package, with its own models.py, serializers.py, views.py, and urls.py.

API modules

Animals

/api/animals/ — Individual animal records: name, species, habitat assignment, photos, and section placement within the park.

Species

/api/species/ — Scientific classification of marine species, linked to animals and conservation status records.

Habitats

/api/habitats/ — Habitat definitions (e.g., reef, open ocean, mangrove) assigned to species and animal records.

Conservation status

/api/conservation_status/ — IUCN-style conservation categories (e.g., Endangered, Vulnerable) linked to species.

Exhibits

/api/exhibiciones/ — Park exhibit definitions with images, descriptions, and section assignments.

Educational services

/api/servicios_educativos/ — Educational service offerings available to the public and registered users.

Educational programs

/api/programas_educativos/ — Structured programs (workshops, tours) with scheduling and registration.

Tickets

/api/tickets/ — Ticket types and pricing configuration for park entry.

Visits

/api/visits/ — Individual visit records linked to purchase orders, including date, visitor count, and QR codes.

Purchase orders

/api/purchase_orders/ — Purchase order records tracking buyer information, total, and payment status.

Ticket purchases

/api/tickets_purchase_orders/ — Junction table linking individual ticket line items to their parent purchase orders.

Payments

/api/payments/ — Payment records linked to purchase orders, capturing PayPal transaction IDs and amounts.

Sections

/api/sections/ — Physical sections of the park, used to organize exhibits and animal placements.

Provinces

/api/provinces/ — Costa Rican province data used in visitor registration and reporting.

Documents

/api/documents/ — Institutional documents (reports, transparency files) accessible through the public portal.

Audit log

/api/audit_log/ — Admin-only log of all significant API actions for compliance and debugging.

Module structure

Every backend module follows a consistent file layout:
Module layout
api/
└── <module_name>/
    ├── __init__.py
    ├── models.py       # Database model definitions
    ├── serializers.py  # DRF serializers (model → JSON)
    ├── views.py        # ViewSets and API logic
    └── urls.py         # URL patterns for this module

Authentication

The platform uses JWT (JSON Web Tokens) via djangorestframework-simplejwt. Token lifetimes are:
  • Access token: 5 minutes
  • Refresh token: 1 day, with automatic rotation enabled
1

Client sends credentials

The frontend posts email and password to /api/auth/login/.
POST /api/auth/login/
Content-Type: application/json

{
  "email": "[email protected]",
  "password": "password"
}
2

Server returns token pair

On success, the API returns an access token and a refresh token.
{
  "access": "<short-lived JWT>",
  "refresh": "<long-lived JWT>"
}
3

Frontend stores tokens

The React app stores tokens in cookies via js-cookie and attaches the access token to every subsequent Axios request as an Authorization: Bearer header.
4

Token refresh

When the access token expires, the frontend posts the refresh token to /api/auth/token/refresh/ to obtain a new access token without re-authentication.
5

Route guards enforce roles

PrivateRoute checks for a valid token before rendering client pages. AdminRoute additionally verifies the user’s role claim before rendering admin pages.

Role-based access

RoleAccess
PublicAll public routes, anonymous ticket purchase, donations, volunteering
ClientAll public routes + /Client_Dashboard + /profile
AdminAll routes + /admin/dashboard, audit logs, full CRUD on all resources

Database

The backend supports two database engines controlled by environment variables:
SQLite is the default configuration for local development. No external service is required.
DATABASE_ENGINE=django.db.backends.sqlite3
DATABASE_NAME=parque_marino_db.sqlite3
Never commit the .env file to source control. The DATABASE_PASSWORD and DJANGO_SECRET_KEY values must be kept secret in all environments.

Media storage

User-uploaded and system-generated files are stored in Supabase Storage, replacing Django’s default local media/ directory in production. Supabase provides a global CDN, Row Level Security policies, and an S3-compatible REST API. The following buckets are used:
Bucket pathContents
media/species/Species and animal images
media/exhibitions/Exhibit photos
media/profile_pics/User profile pictures
media/programas/Educational program files
media/servicios-educativos/Educational service files
media/documentos/Institutional documents
media/qr_codes/Generated QR code images
During local development, Django falls back to the local media/ directory. Supabase integration is activated by setting the SUPABASE_URL and SUPABASE_KEY environment variables.

CORS configuration

The backend uses django-cors-headers to allow cross-origin requests from the frontend development server. The following origins are allowed by default:
CORS_ALLOWED_ORIGINS = [
    "http://localhost:5173",   # Vite dev server
    "http://127.0.0.1:5173",
    "http://localhost:3000",
    "http://127.0.0.1:3000",
]
In production, update this list to include only your deployed frontend domain.

Build docs developers (and LLMs) love