5.3 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Repository layout
Monorepo with two independent npm packages plus a shared Docker dev stack:
backend/— Node.js + Express 5 (ESM) API, Prisma ORM over PostgreSQL.frontend/— React 19 + Vite + TypeScript admin dashboard, styled with Tailwind 4 + shadcn/ui.docker/— Dev Dockerfiles +entrypoint.sh(used bydocker-compose.dev.ymlat repo root).
There is no root-level package script — run npm commands inside backend/ or frontend/.
Common commands
Docker (full stack)
docker compose -f docker-compose.dev.yml up --build # backend :5008, frontend :3008, postgres :5432 (internal)
docker compose -f docker-compose.dev.yml down
The backend container's entrypoint.sh runs prisma generate then prisma db push (NOT migrate deploy) on every start — schema changes propagate without a migration step in the Docker dev flow.
Backend (cd backend)
npm run dev # nodemon src/app.js
npm start # node src/app.js
npm run migrate # npx prisma migrate dev (use this locally; Docker uses db push)
npm run generate # npx prisma generate
npx prisma studio # GUI for the DB
npm run create-user <username> <password> [role] # role defaults to "admin"
In Docker, create an admin via:
docker exec -it gg-backend-api-backend-1 node src/utils/createUser.js <name> <password> <role>
Frontend (cd frontend)
npm run dev # Vite dev server
npm run build # tsc -b && vite build
npm run lint # eslint .
npm run preview
There is no test runner configured in either package.
Architecture
Backend
Single Express app assembled in backend/src/app.js. Each domain follows the same triplet:
src/routes/<domain>.routes.js— declares the Express router, appliesjwtAuthMiddlewareper-route (mixed public/protected within the same router; seeblog.routes.jsfor the pattern: list/detail public, admin endpoints + writes protected).src/controllers/<domain>.controller.js— handler logic; uses Prisma directly.- Mounted at
/api/<domain>inapp.js.
Domains: departments, auth, blogs, upload, doctors, careers, candidates, appointments, inquiry, academics, email, newsMedia, import. Static uploads/ is served at /uploads.
Cross-cutting pieces:
- Prisma client —
src/prisma/client.jsexports a singleton. Always import from there rather than instantiatingnew PrismaClient()(the import controller currently instantiates its own — match the singleton pattern when adding new code). - Auth —
src/middleware/auth.jsexpectsAuthorization: Bearer <jwt>, attaches the decoded payload toreq.user. Tokens are issued bysrc/utils/jwt.jsusingJWT_SECRET. Passwords hashed viasrc/utils/password.js(bcrypt). - Email —
src/utils/sendEmail.jswraps Postmark withEMAIL_FROMas sender. Recipient lists are looked up by category viagetEmailsByType(type)against theEmailConfigtable — controllers call this to fan out notifications (e.g. on new appointments/inquiries). - CORS —
CORS_ALLOWED_ORIGINSis a space-separated list (not comma-separated). Requests with noOriginheader are allowed. - Body limits — JSON/urlencoded bodies are capped at 50mb to support image-laden Editor.js payloads and bulk Excel imports.
Data model (Prisma — backend/prisma/schema.prisma)
Note the natural-key relations: Appointment.doctorId references Doctor.doctorId (the string business ID), not Doctor.id. Same for Department. The SL_NO / GG_ID columns from imports become these business IDs. Doctor ↔ Department is many-to-many through DoctorDepartment, which has a 1:1 DoctorTiming for weekday schedules. NewsMedia has cascading NewsImage children.
Frontend
- Routing in
src/App.tsx:PublicRoutewraps/(Login); everything else is wrapped byProtectedRoute→DashboardLayout. Unknown paths redirect to/department. src/context/AuthContext.tsxis the auth source of truth;src/services/api.tsis the shared Axios instance that auto-injectsAuthorization: Bearer <token>fromlocalStorage.getItem("token").src/api/<domain>.tsfiles are thin wrappers around that axios client, one per backend domain — keep this pattern when adding endpoints.- Path alias
@/*→src/*(configured in bothvite.config.tsandtsconfig). - File uploads go through Bytescale (
src/components/BytescaleUploader) — server only stores the resulting URL. - The
/importpage parses Excel viaxlsxand POSTs structured payloads to/api/import, whereimportController.jsupserts across multiple tables. When changing import behavior, the frontend's column names (SL_NO,GG_ID,Department,Working Status, etc.) and the controller's destructured keys (departments,doctors,timings, …) must stay in sync.
Environment variables
backend/.env:
DATABASE_URL=postgresql://user:password@db:5432/mydb
PORT=5008
JWT_SECRET=...
CORS_ALLOWED_ORIGINS=http://localhost:5173 # space-separated for multiple
BYTESCALE_SECRET_API_KEY=...
POSTMARK_API_KEY=...
EMAIL_FROM=admin@example.com
frontend/.env:
VITE_API_URL=http://localhost:5008/api