cda-hackathon-quiz-app-backend

cda-hackathon-quiz-app-backend

API backend d'un système de quiz thématique servant les questions, historisant les parties et calculant les classements, développée en Express/Prisma/MySQL. L'architecture met en place une modélisation relationnelle complète avec des use cases dédiés et une logique de scoring par mode de jeu.

🎯 Contexte et objectifs

  • Exposer un socle API pour un frontend de quiz (questions, historique, classement, mise à jour des scores).
  • Structurer une base de données permettant de stocker joueurs, parties, questions/réponses et agrégats de performance.
  • Fiabiliser les traitements métiers de fin de partie: calcul score, consolidation par catégorie et ranking dynamique.

🛠️ Réalisations

🧩 Conception

{
  "scripts": {
    "start": "npx nodemon app.ts",
    "studio": "npx prisma studio",
    "seed": "npx ts-node prisma/seed.ts",
    "reset": "prisma migrate reset --force && npm run seed"
  },
  "dependencies": {
    "@prisma/client": "^3.6.0",
    "cors": "^2.8.5",
    "dotenv": "^10.0.0",
    "express": "^4.17.1",
    "morgan": "^1.10.0"
  },
  "devDependencies": {
    "prisma": "^3.6.0",
    "ts-node": "^10.4.0",
    "typescript": "^4.5.3"
  }
}
model Game {
  id         Int      @id @default(autoincrement())
  score      Int
  playerId   Int
  categoryId Int

  category   Category @relation(fields: [categoryId], references: [id])
  player     Player   @relation(fields: [playerId], references: [id])
  histories  History[]
}

model Score {
  id          Int  @id @default(autoincrement())
  totalScore  Int
  backScore   Int
  frontScore  Int
  devOpsScore Int
  backCount   Int
  frontCount  Int
  devOpsCount Int
  totalCount  Int
  playerId    Int  @unique

  player      Player @relation(fields: [playerId], references: [id])
}

💻 Développement

app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json())
app.use(cors({
  origin: ["http://localhost:3000", "<REDACTED_URL>"]
}))

if (process.env.NODE_ENV === 'development') {
  app.use(morgan('dev'));
}

app.use(process.env.APP_BASE_URL, router)

Les routes séparent clairement les use cases questions, history, rankings et update, avec un handler d’erreurs unifié. Source: cda-hackathon-quiz-app-backend/routes/router.ts

router.get('/questions/:category', GetQuestionController.getQuestions)
router.get('/history/:player', GetHistoryController.getHistory)
router.get('/rankings/:category/:avg', GetRankingsController.getRankings)
router.post('/update', UpdateDataController.updateData)

router.use(async (req, res, next) => {
  next(createError.NotFound('Route not found !'))
})

router.use((err, req, res, next) => {
  res.status(err.status || 500).json({
      status: false,
      message: err.message
  })
})

Le service updateData calcule le score par question, crée la partie et met à jour les agrégats cumulés par joueur et catégorie. Source: cda-hackathon-quiz-app-backend/useCases/updateData/updateData.service.ts

for (let question of data.game.history) {
  question.score = question.rightAnswer ? 10 * question.gameModeId : 0;
  question.position = data.game.history.indexOf(question) + 1;
};

let game = await this.prisma.game.create({
  data: {
    score: data.game.history.map((a: any) => a.score).reduce((a: any, b: any) => a + b),
    playerId: player.id,
    categoryId: data.game.categoryId,
    histories: {
      create : data.game.history
    }
  }
})

if (game) await this.updateScore(player.id);

Le ranking est calculé dynamiquement avec mode total/moyenne et tri final, puis borné aux 5 meilleurs résultats. Source: cda-hackathon-quiz-app-backend/useCases/getRankings/getRankings.service.ts

let rankings = await prisma.score.findMany({
  orderBy: {
    [`${cat}Score`]: 'desc',
  },
  take: 5,
  include: {
    player: true,
  }
})

rankings.forEach(score => {
  let catScore;
  switch (cat) {
    case 'total':
      catScore = avg ? score.totalScore / score.totalCount : score.totalScore;
      break;
    case 'front':
      catScore = avg ? score.frontScore / score.frontCount : score.frontScore;
      break;
  }

  data.push({ nickname: score.player.nickname, score: catScore })
});

L’historique enrichit les réponses incorrectes avec la bonne réponse (goodAnswerWas) pour alimenter l’écran détail côté frontend. Source: cda-hackathon-quiz-app-backend/useCases/getHistory/getHistory.service.ts

for (let game of history) {
  for (let question of game.histories) {
    if (!question.rightAnswer) {
      let answer = await prisma.question.findUnique({
        where: { id: question.questionId },
        include: {
          responses: {
            where: { trueOrFalse: true },
            take: 1
          }
        }
      })

      if (answer) {
        question.goodAnswerWas = answer.responses[0].content;
      }
    }
  }
}
  • Frontend : Ce dépôt n’implémente pas d’UI, mais expose des contrats API dédiés à l’affichage frontend (questions aléatoires, classements filtrés, historique détaillé).

🏗️ DevOps & Qualité

async function main() {
  console.log('Seeding categories...')
  for (const c of categories) await prisma.category.create({ data: c })

  console.log('Seeding game modes...')
  for (const gm of gameModes) await prisma.gameMode.create({ data: gm })

  console.log('Seeding players...')
  for (const p of players) await prisma.player.create({ data: p })
}

📈 Résultats

  • Sur la base de l’historique Git du dépôt analysé, le travail couvre la période 2021-12-13 -> 2021-12-16, avec 20 commits et 3 contributeurs. La livraison aboutit à une API quiz complète: sélection de questions par thème, persistance des parties, agrégations de score et endpoints de classement/historique. La modélisation Prisma et les seeds rendent l’environnement reproductible pour développement et démonstration. Le bénéfice technique est une couche backend cohérente, directement exploitable par un frontend de jeu en temps réel différé.

🔧 Environnement technique

  • Backend: Node.js, Express, TypeScript, body-parser, Morgan, CORS.
  • Data: Prisma ORM, MySQL, schémas relationnels et migrations.
  • Architecture: controllers + services/use cases + routeur central.
  • Fonctionnel: scoring, historique enrichi, ranking total/moyenne, seed initial.
  • Qualité/outillage: Prisma Studio, scripts de reset/reseed, logs Prisma.
🌐 Voir le projet

Technologies utilisées

Backend
Express
Node.js
Bases de donnees (SGBD & SQL)
MySQL
Design Patterns & Architecture
Prisma
Frontend
TypeScript