This guide explains how to develop the frontend and backend correctly, following the project's architecture, conventions, and shared systems (themes, languages, components, etc.).
# 1. Copy env (if not already done)
copy .env.exemple .env
# 2. Start the FULL stack (required for any integration work)
docker compose up -d --build
# Access via outer nginx (standard):
# https://localhost/- Use https://localhost/ as the primary and standard access point for both development and production testing. This routes through the outer nginx for realistic routing, rate limiting, and same-origin
/apicalls. - Direct access to
http://localhost:3000(frontend) orhttp://localhost:8080(gateway) is possible for targeted debugging but may require the dev proxy fix (see below). - Never start only one service for testing features that cross boundaries.
After changes to frontend source (thanks to volume mounts), the Next dev server usually picks them up. For backend changes, you may need to restart the specific service or use --build.
- Standard: Full
docker compose up -d --build(thefrontendservice runs in dev mode with Turbopack). - The
srcdirectory is mounted in docker, so edits are reflected quickly. - Direct local dev server (
npm run dev) is possible for targeted debugging but not recommended for integration work.
We have a first-class, persisted, system-aware theme system.
- Core:
src/context/ThemeContext.js+ CSS custom properties inglobals.css.:root= light.darkon<html>= dark.high-contraston<html>= high contrast (WCAG/RGAA focused)
- Usage:
- Use CSS variables everywhere:
var(--color-primary),var(--color-bg),var(--color-text),var(--color-on-primary),--color-link, etc. - Never hardcode colors like
#fff,text-white(except inside.high-contrastcareful pairs or when using--color-on-primary). - The
ThemeTogglecomponent (shared) handles cycling + direct selectors.
- Use CSS variables everywhere:
- High-contrast specifics:
- We use a real luminance + contrast ratio calculation (
src/lib/contrast.js) to choose/verify colors. - Black on yellow (or yellow on black) for primary elements. Never white-on-yellow.
- Larger text, thicker borders, strong focus rings are applied via CSS.
- We use a real luminance + contrast ratio calculation (
- In components: Import
useThemeonly if you needisHighContrast/isDarkfor logic. Prefer CSS vars for styling. - Adding tokens: Add to
:root,.dark, and.high-contrastinglobals.css. Update the contrast verification if relevant. - Global toggle: Always available (fixed in layout). Persisted in
localStorage.
Rule: All themeable UI must use the shared token system. Duplicating colors = bug.
Similar client-side persisted system to themes.
- Core:
src/context/LanguageContext.jslanguage('en' | 'fr')t(key)helpersetLanguage/toggleLanguage- Auto-detects from
navigator.languageon first run (prefersfr). - Persisted in
localStorageaslanguage. - Sets
<html lang="...">.
- Usage:
const { t, language, isFrench } = useLanguage(); <h2>{translate('welcomeBack')}</h2> <p>{translate('signInToAccess')}</p>
- Adding translations: Edit the
translationsobject inLanguageContext.js. English is the source of truth. Provide matching French strings. - The toggle:
LanguageToggle(shared), placed next toThemeTogglein the layout. Supports cycle + direct buttons. - Theme labels are also translated when the language changes (via the context).
- Pages/components to update: Use
translate()for user-facing strings (login, footer, privacy, accessibility, loading states, etc.). Brand names ("NovaCampus") and technical terms usually stay in English. - The
LanguageProvidermust wrap the app (it does, inlayout.js).
Current scope: Primarily the login flow, footer, privacy/accessibility pages, and toggle UIs. Expand as new UI is added.
Rule: Do not hardcode English strings in new components. Use translate() + add the key.
- Single source of truth:
src/components/shared/Button.jsx,Input.jsx,Footer.jsx,ThemeToggle.jsx,LanguageToggle.jsx,ProtectedRoute.jsx(if used).
- Always import from shared instead of copying markup or styles.
- Example: There is one
<Footer />— rendered in the root layout. Do not create per-page footers.
- Example: There is one
- Styling rules (professional low-duplication):
- Put general styles, base component styles, and design tokens (CSS vars) in
globals.css. - Use Tailwind utility classes on top of the tokens.
- Component-specific styles only inside the shared component (or via props/variants).
- Page-specific overrides are the exception, not the rule.
- Button ordering (UI convention): For any buttons involving continue/confirm/primary action + cancel/undo/back/secondary, put the cancel/undo on the left and the continue/confirm on the right. Example:
[ Cancel ] [ Confirm ]or[ Undo ] [ Save ]. Follows standard OS/web patterns and prevents accidental destructive actions.
- Put general styles, base component styles, and design tokens (CSS vars) in
- The root layout provides the providers (
LanguageProvider,ThemeProvider,AuthProvider) and the global fixed toggles + single Footer. - Auth: Use
useAuth()fromAuthContext. It handles token storage, automatic/mevalidation on load,login(),logout(), andisAuthenticated. - Forms: Prefer the shared
Input+Button. They already handle labels, errors, focus, disabled states, and theme vars.
Example of correct reuse (login page):
import { useLanguage } from '@/context/LanguageContext';
import Button from '@/components/shared/Button';
import Input from '@/components/shared/Input';
const { translate } = useLanguage();
// ...
<Input label={translate('emailAddress')} ... />
<Button>{translate('signIn')}</Button>- Use relative
/api/...in frontend code (e.g./api/auth/login). - Standard access via
https://localhost/: Outer nginx routes/api/*→ gateway → services. No special config needed in the browser. - When running Next dev server directly (localhost:3000):
next.config.mjsrewrites/api/*→ the gateway.- We set
DEV_API_PROXY_TARGET=http://gateway:80inside docker-compose for the frontend service (so the container can resolve thegatewayservice name).
- If you get 500 on
/api/auth/login(or any API) when hitting :3000 directly:- Make sure the gateway is running.
- Make sure you are using the correct proxy target (check
.env/ docker-compose). - Prefer going through the outer nginx (
https://localhost/).
In AuthContext.js we use const API_BASE = '/api/auth';.
- Loading / redirect states: The root page acts as an auth guard and role-based router. Keep it lightweight.
- Dashboards: Role-specific routes live under
/dashboard/*. Use the shared auth context + role checks. - Accessibility: The high-contrast mode + language support + proper labels (in shared Input) are part of the RGAA/GDPR requirements from the workshops. Test with the toggle.
The identity service is the source of truth for users, roles, JWTs, and campus tenancy.
-
Always start via full
docker compose up -d --build. -
Test credentials (for rapid role testing):
- In
.env:ENABLE_TEST_CREDENTIALS=true - Recreate the iam service (required for env var to take effect):
docker compose up -d --force-recreate iam-service(Add --build if you also changed source code.) - The users are seeded on startup. See logs for
[DEV] Seeded test user.... - Setting
ENABLE_TEST_CREDENTIALS=false(or omitting) + recreate will remove the test accounts (logins will then fail). See[DEV] Removed test user...in logs. - Logins (when enabled):
student@test.com/student123→ studentteacher@test.com/teacher123→ teacher- etc.
- Never leave this enabled in production. Documented in root README and iam-service README.
- In
-
Scheduling service tests (detailed classic + edge + security/injection): Run
node scripts/test-scheduling.js(after enabling test creds + full stack up). It logs in with admin@test.com (using the httpOnly cookies through the gateway), then runs 40+ assertions on rooms/timetables (CRUD, every overlap scenario, filters, bad data, unauthed vs tampered cookies, SQL-ish injection attempts, large payloads, etc.). The script only succeeds when test users exist (dev-only). It always cleans TST* data. -
The gateway strips
/api/authprefix. Routes inside the service are mounted at root (seeauth.route.js).
- Sequelize + Postgres: Models in
src/models/.sync({ alter: true })in dev. - JWT: Claims =
{ id, email, role, campusId }. Use the sharedjwt.util.js. - Passwords: Use the shared
bcrypt.util.js(never store plain text). - Auth flow:
POST /api/auth/login→ returns{ token, user: safeUser }- Protected routes use
authenticatemiddleware (checks Bearer token, attachesreq.user). - Frontend
AuthContextstores the token and calls/meon load.
- Roles:
student | teacher | admin | executive. Some endpoints (user management) are admin/exec only. - Multi-tenancy: Almost everything is scoped by
campusId. - Error responses: Keep them generic for security (no user enumeration). The controller wraps service errors.
- Health:
/api/healthfor docker checks.
- Keep services focused.
- Document new endpoints in the service's
README.md(Method | URL | Action table style). - Use the existing patterns for controllers/services/routes/middleware.
- When the gateway or nginx config changes, remember to test through the full routing path.
See the environment block in docker-compose.yml and the root .env.exemple. Key ones for auth:
JWT_SECRET,JWT_EXPIRES_INENABLE_TEST_CREDENTIALS- DB connection vars (point to the
postgresservice in compose)
- Testing: For anything involving login, auth, themes, languages, or cross-service calls → full
docker compose up -d --build. Do not rely on isolatednpm run devfor backend-dependent features. - Rebuilds: After changing docker env vars (like
ENABLE_TEST_CREDENTIALSorDEV_API_PROXY_TARGET), recreate the affected service(s). - Git hygiene: Run
.\scripts\sync-gitkeep.ps1before every commit. No "finished" in messages. English in code/comments. - Shared nothing duplication: One Footer. One set of theme tokens. One language system. One auth context. Reuse via
components/sharedand contexts. - When in doubt:
- Look at existing shared components and the login page for patterns.
- Check
globals.cssfor tokens before writing custom colors. - Read the service READMEs and the root
README.md.
- Login 500 when using :3000 directly: The Next dev proxy couldn't reach the gateway. Use the standard
https://localhost/or ensureDEV_API_PROXY_TARGETis correct for your environment (see next.config and docker-compose). - Theme not applying: Forgot to wrap in
ThemeProvider, or used hardcoded colors instead of vars, or missingmin-h-0/ height constraints in flex layouts. - Language not switching: Not using
translate()fromuseLanguage(), or component not inside the provider tree, or missing the key in the translations object. - Components not themed: Hardcoded Tailwind colors or
text-whiteoutside of--color-on-primaryusage. - Footer appears twice: You created a local footer instead of using the shared one from layout.
- Scroll on short pages (e.g. login): Use the flex-1 +
min-h-dvh+min-h-0pattern established in the layout and login page (see recent fixes). - Test users not present:
ENABLE_TEST_CREDENTIALSnot set to true in the.envthat docker compose reads for variable substitution, or the iam-service container was not recreated after the change (env vars are injected only when the container is created, usedocker compose up -d --force-recreate iam-serviceto fix).
- Root
README.md(quick start, test credentials table) services/<service-name>/README.md(endpoints + test creds)- Workshop deliverables (for original requirements around accessibility, RGPD, microservices, etc.)
Happy coding! When adding new UI, ask: "Is this using the shared theme tokens, shared components, and the language translate() helper?"