REST API for the RHNe (Réseau hospitalier neuchâtelois) hospital network platform, built with Node.js, Express 5, TypeScript, MongoDB, and Redis.
- 7 hospital sites, 93 medical services, 138 doctors, events, jobs, newborns, patient info
- Multi-language content (FR, EN, DE, IT) with
TranslatedFieldpattern - JWT authentication with access + refresh tokens
- Role-Based Access Control — 5 roles, 39 granular permissions
- Full-text search across all resources
- Redis caching on all public endpoints
- Image upload via Multer + Cloudinary
- Email via Nodemailer (Mailpit in dev)
- Zod validation on all endpoints
- Soft delete pattern with
is_activeflag - Standardized API response envelope
| Area | Technology |
|---|---|
| Runtime | Node.js 20+ |
| Language | TypeScript 5 (strict) |
| Framework | Express.js 5 |
| Database | MongoDB 7 (Mongoose 8) |
| Cache | Redis 7 |
| Auth | JWT (access + refresh) |
| Validation | Zod |
| File Uploads | Multer + Cloudinary |
| Nodemailer | |
| Testing | Jest + Supertest |
| Containers | Docker + Docker Compose |
- Node.js 20+ and npm
- Docker and Docker Compose
git clone <repo-url>
cd rhne-node-api
npm installcp .env.example .envEdit .env with your settings. Key variables:
NODE_ENV=development
PORT=5000
MONGODB_URI=mongodb://admin:password@localhost:27017/rhne?authSource=admin
REDIS_HOST=localhost
REDIS_PORT=6379
JWT_ACCESS_SECRET=your-access-secret
JWT_REFRESH_SECRET=your-refresh-secret# Start MongoDB, Redis, Mailpit, Mongo-Express
docker compose up -d| Service | URL |
|---|---|
| MongoDB | localhost:27017 |
| Redis | localhost:6379 |
| Mailpit (email UI) | http://localhost:8025 |
| Mongo-Express | http://localhost:8081 |
npm run seed # Insert seed data (skip duplicates)
npm run seed:fresh # Drop all collections, then seedSeeds 1,493 records across 15 collections.
npm run dev # Development with hot-reloadAPI available at: http://localhost:5000/api/v1
| Script | Description |
|---|---|
npm run dev |
Start dev server (ts-node-dev, hot-reload) |
npm run build |
Compile TypeScript → dist/ |
npm start |
Run compiled JS from dist/ |
npm run seed |
Seed database |
npm run seed:fresh |
Drop & re-seed database |
npm test |
Run Jest test suite |
npm run test:watch |
Run tests in watch mode |
npm run lint |
Lint with ESLint |
npm run lint:fix |
Auto-fix lint issues |
npm run docker:dev |
Start Docker containers |
npm run docker:down |
Stop Docker containers |
npm run docker:logs |
Tail API container logs |
GET /api/v1/sites # List active sites
GET /api/v1/sites/:slug # Get site by slug
GET /api/v1/services # List services (?category=, ?search=)
GET /api/v1/services/:slug # Get service with contacts, links, doctors
GET /api/v1/doctors # List doctors (?service=, ?search=)
GET /api/v1/doctors/:id # Get doctor detail
GET /api/v1/events # List upcoming events
GET /api/v1/events/:slug # Get event detail
GET /api/v1/jobs # List active jobs (?category=, ?site=)
GET /api/v1/jobs/:id # Get job detail
GET /api/v1/newborns # List newborns (paginated)
GET /api/v1/patient-info # List patient info pages (?section=)
GET /api/v1/patient-info/:slug # Get page with sections
GET /api/v1/search?q=... # Full-text search
POST /api/v1/auth/login # Login → access + refresh tokens
POST /api/v1/auth/refresh # Refresh access token
POST /api/v1/auth/logout # Revoke refresh token
POST /api/v1/auth/forgot-password # Request password reset email
POST /api/v1/auth/reset-password # Reset password with token
GET /api/v1/admin/dashboard/stats # Dashboard statistics
GET /api/v1/admin/profile # My profile
PUT /api/v1/admin/profile # Update my profile
PUT /api/v1/admin/profile/password # Change password
# Full CRUD on all resources:
# /admin/sites, /admin/services, /admin/doctors,
# /admin/events, /admin/jobs, /admin/newborns,
# /admin/patient-info, /admin/users
# + /admin/services/:id/contacts, /admin/services/:id/links
POST /api/v1/admin/uploads/images # Upload image
| Param | Type | Description |
|---|---|---|
page |
number | Page number (default: 1) |
limit |
number | Items per page (default: 20, max: 100) |
sort |
string | Sort field (prefix - for desc) |
search |
string | Full-text search |
lang |
string | Language: fr, en, de, it |
is_active |
boolean | Filter by status (admin only) |
- Login → POST
/api/v1/auth/loginwith{ email, password } - Returns
access_token(15min) andrefresh_token(7 days) - Use access token:
Authorization: Bearer <token> - Refresh when expired: POST
/api/v1/auth/refreshwith{ refresh_token }
| Role | Description |
|---|---|
super_admin |
Full platform access |
admin |
Platform administration |
content_editor |
Manage services, events, patient info |
hr_manager |
Manage job postings |
site_manager |
Manage a specific hospital site |
Permissions follow resource.action format (e.g., sites.read, services.create).
| Password | Role | |
|---|---|---|
| superadmin@rhne-clone.ch | SuperAdmin123! | super_admin |
| admin@rhne-clone.ch | Admin123! | admin |
| editor@rhne-clone.ch | Editor123! | content_editor |
| hr@rhne-clone.ch | HrManager123! | hr_manager |
| pourtales.manager@rhne-clone.ch | Manager123! | site_manager |
| chauxdefonds.manager@rhne-clone.ch | Manager123! | site_manager |
npm test # Run all tests (77 integration tests)
npm run test:watch # Watch modeTests use a separate rhne_test database. Requires MongoDB and Redis running.
Test coverage:
- Auth: login, refresh, logout, forgot/reset password (16 tests)
- Public API: sites, services, doctors, events, jobs, newborns, patient info, search, health, language (34 tests)
- Admin API: auth protection, RBAC, dashboard, CRUD, profile (27 tests)
src/
├── config/ # DB, Redis, env, CORS, i18n, Cloudinary, mail
├── api/v1/
│ ├── public/ # Public routes (no auth)
│ │ ├── auth/ # Login, refresh, logout, password reset
│ │ ├── sites/ # Hospital sites
│ │ ├── services/ # Medical services
│ │ ├── doctors/ # Medical professionals
│ │ ├── events/ # Public events
│ │ ├── jobs/ # Job postings
│ │ ├── newborns/ # Birth announcements
│ │ ├── patient-info/ # Patient information pages
│ │ └── search/ # Full-text search
│ └── admin/ # Admin routes (auth + RBAC)
│ ├── dashboard/ # Statistics
│ ├── profile/ # Current user profile
│ ├── sites/ # Sites CRUD
│ ├── services/ # Services CRUD + contacts/links
│ ├── doctors/ # Doctors CRUD
│ ├── events/ # Events CRUD
│ ├── jobs/ # Jobs CRUD
│ ├── newborns/ # Newborns CRUD
│ ├── patient-info/ # Patient info CRUD
│ ├── users/ # Users CRUD + role assignment
│ └── uploads/ # Image upload
├── models/ # 16 Mongoose models
├── middleware/ # Auth, RBAC, validation, error, cache, rate-limit
├── shared/ # Utils, types, constants
├── scripts/ # Database seeder
├── __tests__/ # Integration tests
├── app.ts # Express app
└── server.ts # Server entry point
Each feature module follows the pattern:
feature/
├── feature.routes.ts # Route definitions
├── feature.controller.ts # Request handling (thin)
├── feature.service.ts # Business logic
└── feature.validation.ts # Zod schemas
All user-facing text uses the TranslatedField pattern:
{ fr: "Cardiologie", en: "Cardiology", de: "Kardiologie", it: "Cardiologia" }Language is determined by:
?lang=frquery parameterAccept-Language: frheader- Default:
fr(French)
Import the collection and environment into Postman:
| File | Description |
|---|---|
postman/RHNe-API.postman_collection.json |
Full collection — all public + admin endpoints |
postman/RHNe-API.postman_environment.json |
Development environment variables |
Usage:
- Import both files into Postman
- Select the RHNe — Development environment
- Run Auth → Login (Super Admin) — tokens are saved automatically
- All admin requests inherit the Bearer token from the collection
docker compose up -d # Start all services
docker compose down # Stop all services
docker compose logs -f api # Tail API logsServices: MongoDB, Redis, Mailpit (email testing), Mongo-Express (DB admin).
Private — All rights reserved.