Skip to content

Stack Technique

Vue d'ensemble

La plateforme Roomee utilise un stack technique moderne et éprouvé pour garantir performance, scalabilité et maintenabilité.

Backend

Runtime et Framework

TechnologieVersionUsage
Node.js18+Runtime JavaScript serveur
Express.js4.xFramework web léger et flexible
TypeScript5.xServices modernes (auth, news, notification)
JavaScriptES6+Services legacy (hotel, media, staff-member)

Migration en cours : Transition progressive des services JavaScript vers TypeScript avec standardisation via @roomee/shared.

Base de Données

MongoDB + Prisma ORM

typescript
// Prisma schema example
model User {
  id            String   @id @default(auto()) @map("_id") @db.ObjectId
  email         String   @unique
  password      String
  createdAt     DateTime @default(now())
  updatedAt     DateTime @updatedAt
}

Avantages :

  • ✅ Schéma flexible (documents JSON)
  • ✅ Performance horizontale (sharding)
  • ✅ Type-safety avec Prisma
  • ✅ Migrations versionnées

Architecture :

  • Une base MongoDB par service (isolation totale)
  • Base members partagée pour RBAC (api-staff-member)
  • Relations via string IDs (pas de foreign keys)

Gestion de Packages

Monorepo avec Turborepo + PNPM

bash
roomee-services/
├── pnpm-workspace.yaml
├── turbo.json
├── packages/
   └── shared/              # @roomee/shared
└── services/
    ├── api-authentication/
    ├── api-hotel/
    ├── api-staff-member/
    ├── api-media/
    ├── api-news/
    └── api-notification/

Turborepo :

  • Cache de build intelligent
  • Exécution parallèle des tâches
  • Pipeline de dépendances automatique

PNPM :

  • Économie d'espace disque (hard links)
  • Isolation stricte des dépendances
  • Workspaces pour le monorepo

Package Partagé : @roomee/shared

typescript
// Structure du package partagé
@roomee/shared/
├── logger/          // Winston logger configuré
├── amqp/            // Client AMQP/RabbitMQ
├── socket/          // Socket.IO server wrapper
├── auth/            // JWT middleware + encryption
├── encryption/      // AES-256-GCM encryption
├── middlewares/     // Error handlers, validation
└── utils/           // Helpers, constants

Exports :

typescript
import {
  authMiddleware,
  encryptionService,
  logger,
  amqpServer,
  socketServer,
  errorHandler,
  catchAsync,
  ApiError
} from '@roomee/shared'

Communication Inter-Services

1. REST API (Synchrone)

Express.js + Axios

typescript
// Serveur Express
app.use(express.json())
app.use(helmet())
app.use(cors({ origin: allowedOrigins }))

// Appels HTTP entre services
const response = await axios.post(
  `${MEMBER_API_URL}/members/add`,
  memberData,
  {
    headers: {
      Authorization: `Bearer ${token}`,
      'X-API-Key': apiKey
    }
  }
)

Middlewares standards :

  • helmet : Sécurité headers HTTP
  • cors : Cross-Origin Resource Sharing
  • morgan : Logging HTTP
  • express-rate-limit : Rate limiting
  • express-mongo-sanitize : Protection NoSQL injection

2. AMQP / RabbitMQ (Asynchrone)

Event Bus avec amqplib

typescript
// Configuration
amqpServer.initialize({
  gatewayUrl: 'amqp://localhost:5672',
  exchangeName: 'roomee_events',
  queueName: 'notification_queue',
  routingKeyBase: 'roomee.notification'
})

// Publier un événement
await amqpServer.emit('roomee.notification.page.created', {
  type: 'page.created',
  workspaceId: '...',
  payload: pageData
})

// Consommer des événements
await amqpServer.subscribe('roomee.notification.*', async (message) => {
  await handleNotification(message)
})

Features :

  • Topic exchange pour routing flexible
  • Dead Letter Queue pour messages en échec
  • Retry avec backoff exponentiel (3 tentatives)
  • Prefetch pour contrôle de charge
  • Acknowledgment manuel pour fiabilité

3. Socket.IO (Temps Réel)

WebSocket avec JWT Authentication

typescript
// Serveur Socket.IO
socketServer.initialize({
  server: httpServer,
  jwtSecret: config.jwtAccessSecret,
  corsOrigin: config.frontendUrl
})

// Rooms pour isolation multi-tenant
socket.join(`workspace:${workspaceId}`)
socket.join(`member:${memberId}`)

// Émettre un événement
io.to(`workspace:${workspaceId}`).emit('page.created', pageData)

// Client
socket.on('page.created', (data) => {
  console.log('Nouvelle page créée:', data)
})

Rooms utilisées :

  • member:{memberId} - Notifications personnelles
  • workspace:{workspaceId} - Workspace collaboratif
  • ecosystem:{ecosystemId} - Niveau organisation
  • page:{pageId} - Page spécifique

Authentification et Sécurité

JWT avec Encryption AES-256-GCM

typescript
// Génération de token chiffré
const token = authMiddleware.generateToken({
  userId: user.id,
  email: user.email,
  workspaceId: workspace.id,
  roles: ['admin'],
  permissions: ['manage_members']
})

// Format du token : {encryptedText}.{iv}.{tag}
// Algorithme : AES-256-GCM

Middleware d'authentification :

typescript
// Protéger une route
router.use(authMiddleware.middleware)

// Le token décrypté est disponible dans req.user
router.get('/profile', (req, res) => {
  const { userId, workspaceId } = req.user
  // ...
})

Refresh Tokens

typescript
// Rotation des tokens
model RefreshToken {
  id        String   @id @default(auto()) @map("_id") @db.ObjectId
  userId    String   @db.ObjectId
  token     String   @unique
  expiresAt DateTime
  isRevoked Boolean  @default(false)
}

// Flow de refresh
POST /auth/refresh
Body: { refreshToken: "..." }
Response: { accessToken: "...", refreshToken: "..." }

Protection des API

typescript
// Rate Limiting par endpoint
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 20, // 20 requêtes max
  message: 'Trop de tentatives de connexion'
})

app.use('/auth/login', authLimiter)

// NoSQL Injection Protection
app.use(mongoSanitize())

// XSS Protection
app.use(helmet.contentSecurityPolicy())

Validation et Typage

Zod (Services Modernes)

typescript
import { z } from 'zod'

// Définir un schema
const createPageSchema = z.object({
  title: z.string().min(1).max(255),
  content: z.any(), // Editor.js JSON
  workspaceId: z.string().regex(/^[0-9a-fA-F]{24}$/),
  categoryId: z.string().optional(),
  enableNotifications: z.boolean().default(true)
})

// Middleware de validation
const validate = (schema: z.ZodSchema) => {
  return (req, res, next) => {
    try {
      schema.parse(req.body)
      next()
    } catch (error) {
      res.status(400).json({ error: error.errors })
    }
  }
}

// Utilisation
router.post('/', validate(createPageSchema), controller.create)

Validation Legacy (Joi)

typescript
// Services JS utilisent Joi
const schema = Joi.object({
  email: Joi.string().email().required(),
  password: Joi.string().min(8).required()
})

const { error, value } = schema.validate(req.body)

Stockage et Médias

Google Cloud Storage (GCP)

typescript
import { Storage } from '@google-cloud/storage'

// Configuration
const storage = new Storage({
  projectId: config.gcpProjectId,
  keyFilename: config.gcpKeyFile
})

const bucket = storage.bucket(config.gcpBucketName)

// Upload direct avec presigned URL
const [url] = await bucket.file(filename).getSignedUrl({
  version: 'v4',
  action: 'write',
  expires: Date.now() + 15 * 60 * 1000, // 15 minutes
  contentType: mimeType
})

// Client upload directement vers GCP (pas de transit serveur)
await fetch(url, {
  method: 'PUT',
  body: file,
  headers: { 'Content-Type': mimeType }
})

Fonctionnalités :

  • Upload direct via presigned URLs
  • Thumbnails automatiques (Sharp)
  • Conversion de formats (WebP, JPEG)
  • Quota par écosystème (20GB Premium)
  • Chunked upload pour mobile (50MB max)

Notifications Multi-Canal

Push Notifications (OneSignal)

typescript
import OneSignal from 'onesignal-node'

const client = new OneSignal.Client({
  appId: config.oneSignalAppId,
  restApiKey: config.oneSignalApiKey
})

// Envoyer notification push
await client.createNotification({
  include_player_ids: subscriptionIds,
  headings: { en: title },
  contents: { en: message },
  data: { type, entityId, workspaceId }
})

Email (SendGrid + Nodemailer)

typescript
import sgMail from '@sendgrid/mail'

sgMail.setApiKey(config.sendgridApiKey)

// Templates avec variables
await sgMail.send({
  to: email,
  from: 'noreply@roomee.com',
  templateId: 'invitation_template',
  dynamicTemplateData: {
    memberName: 'John Doe',
    workspaceName: 'Mon Workspace',
    invitationLink: 'https://...'
  }
})

Firebase Cloud Messaging (FCM)

typescript
import admin from 'firebase-admin'

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount)
})

// Envoyer notification à un device
await admin.messaging().send({
  token: deviceToken,
  notification: {
    title: 'Nouvelle actualité',
    body: 'Un article a été publié'
  },
  data: {
    type: 'page.created',
    pageId: '...'
  }
})

Logging et Monitoring

Winston Logger

typescript
import winston from 'winston'
import 'winston-daily-rotate-file'

const logger = winston.createLogger({
  level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    // Console (dev)
    new winston.transports.Console({
      format: winston.format.colorize()
    }),

    // Fichiers rotatifs (prod)
    new winston.transports.DailyRotateFile({
      filename: 'logs/error-%DATE%.log',
      level: 'error',
      datePattern: 'YYYY-MM-DD',
      maxSize: '20m',
      maxFiles: '14d'
    }),

    new winston.transports.DailyRotateFile({
      filename: 'logs/combined-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      maxSize: '20m',
      maxFiles: '14d'
    })
  ]
})

// Utilisation
logger.info('User logged in', { userId, email })
logger.error('Database error', { error, context })

Sentry (Error Tracking)

typescript
import * as Sentry from '@sentry/node'

Sentry.init({
  dsn: config.sentryDsn,
  environment: process.env.NODE_ENV,
  tracesSampleRate: 1.0
})

// Capture d'erreur
try {
  // Code
} catch (error) {
  Sentry.captureException(error)
  logger.error('Error', { error })
}

// Middleware Express
app.use(Sentry.Handlers.requestHandler())
app.use(Sentry.Handlers.errorHandler())

Testing

Jest (Tests Unitaires)

typescript
// jest.config.js
export default {
  preset: 'ts-jest',
  testEnvironment: 'node',
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  }
}

// Example test
describe('MemberService', () => {
  it('should create a member', async () => {
    const member = await memberService.create(memberData)
    expect(member).toHaveProperty('id')
    expect(member.email).toBe(memberData.email)
  })
})

Supertest (Tests d'Intégration)

typescript
import request from 'supertest'
import app from '../src/app'

describe('POST /auth/login', () => {
  it('should return JWT token', async () => {
    const response = await request(app)
      .post('/auth/login')
      .send({ email: 'test@example.com', password: 'password' })
      .expect(200)

    expect(response.body).toHaveProperty('accessToken')
  })
})

Documentation API

Swagger / OpenAPI

typescript
import swaggerJsdoc from 'swagger-jsdoc'
import swaggerUi from 'swagger-ui-express'

const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'Roomee API',
      version: '1.0.0',
      description: 'API documentation'
    },
    servers: [
      { url: 'http://localhost:3000', description: 'Development' },
      { url: 'https://api.roomee.io', description: 'Production' }
    ]
  },
  apis: ['./src/routes/*.ts']
}

const swaggerSpec = swaggerJsdoc(options)
app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec))

Annotations JSDoc :

typescript
/**
 * @swagger
 * /api/v1/pages:
 *   post:
 *     summary: Créer une page
 *     tags: [Pages]
 *     security:
 *       - bearerAuth: []
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               title:
 *                 type: string
 *               content:
 *                 type: object
 *     responses:
 *       201:
 *         description: Page créée
 */

Intégrations Externes

Sendbird (Chat)

typescript
import axios from 'axios'

const sendbirdApi = axios.create({
  baseURL: 'https://api-{app_id}.sendbird.com/v3',
  headers: {
    'Api-Token': config.sendbirdApiToken
  }
})

// Créer une application chat
await sendbirdApi.post('/applications', {
  organization_id: orgId,
  name: hotelName
})

Stripe (Paiements)

typescript
import Stripe from 'stripe'

const stripe = new Stripe(config.stripeSecretKey, {
  apiVersion: '2023-10-16'
})

// Créer un abonnement
const subscription = await stripe.subscriptions.create({
  customer: customerId,
  items: [{ price: priceId }]
})

OpenWeather (Météo)

typescript
const weather = await axios.get(
  `https://api.openweathermap.org/data/2.5/weather`,
  {
    params: {
      q: city,
      appid: config.openWeatherApiKey,
      units: 'metric'
    }
  }
)

Déploiement

Docker

dockerfile
# Dockerfile
FROM node:18-alpine

WORKDIR /app

# Install PNPM
RUN npm install -g pnpm

# Copy package files
COPY package.json pnpm-lock.yaml ./
COPY packages/shared packages/shared

# Install dependencies
RUN pnpm install --frozen-lockfile

# Copy service code
COPY services/api-authentication services/api-authentication

# Build
RUN pnpm run build

EXPOSE 3000

CMD ["node", "services/api-authentication/dist/server.js"]

Kubernetes

yaml
# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-authentication
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api-authentication
  template:
    metadata:
      labels:
        app: api-authentication
    spec:
      containers:
      - name: api-authentication
        image: roomee/api-authentication:latest
        ports:
        - containerPort: 3000
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: db-secrets
              key: url

CI/CD (GitHub Actions)

yaml
name: Deploy Service

on:
  push:
    branches: [main]
    paths:
      - 'services/api-authentication/**'

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup PNPM
        uses: pnpm/action-setup@v2
        with:
          version: 8

      - name: Install dependencies
        run: pnpm install

      - name: Run tests
        run: pnpm test

      - name: Build
        run: pnpm build

      - name: Build Docker image
        run: docker build -t roomee/api-authentication:latest .

      - name: Push to registry
        run: docker push roomee/api-authentication:latest

      - name: Deploy to K8s
        run: kubectl apply -f k8s/

Gestion de Configuration

Infisical (Secrets Management)

typescript
import { InfisicalClient } from '@infisical/sdk'

const client = new InfisicalClient({
  siteUrl: config.infisicalUrl
})

// Charger les secrets
const secrets = await client.listSecrets({
  environment: process.env.NODE_ENV,
  projectId: config.infisicalProjectId,
  path: '/COMMON'
})

// Mapper vers process.env
secrets.forEach(({ key, value }) => {
  process.env[key] = value
})

Hiérarchie des secrets :

COMMON/          # Variables partagées (DB, JWT)
AMQP/            # Configuration RabbitMQ
Customer/        # Variables par service
Chat/            # Sendbird

Performance et Optimisation

Indexes MongoDB

typescript
// Prisma indexes
model Page {
  @@index([workspaceId, deleted_at])
  @@index([createdById])
  @@index([categoryId])
}

Caching (à implémenter)

typescript
import Redis from 'ioredis'

const redis = new Redis({
  host: config.redisHost,
  port: 6379
})

// Cache des permissions RBAC
const cacheKey = `permissions:${memberId}:${workspaceId}`
let permissions = await redis.get(cacheKey)

if (!permissions) {
  permissions = await fetchPermissions(memberId, workspaceId)
  await redis.setex(cacheKey, 300, JSON.stringify(permissions)) // TTL 5min
}

Résumé du Stack

CatégorieTechnologies
RuntimeNode.js 18+, TypeScript 5.x
FrameworkExpress.js 4.x
DatabaseMongoDB + Prisma ORM
MonorepoTurborepo + PNPM
CommunicationREST (Axios), AMQP (RabbitMQ), Socket.IO
AuthJWT + AES-256-GCM
ValidationZod, Joi
StorageGoogle Cloud Storage
NotificationsOneSignal, FCM, SendGrid
ChatSendbird
PaymentsStripe
LoggingWinston, Sentry
TestingJest, Supertest
DocsSwagger/OpenAPI
DeploymentDocker, Kubernetes
CI/CDGitHub Actions
SecretsInfisical

Documentation technique Roomee Services