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
- La stack backend est organisée autour d’Express, Prisma, TypeScript et middleware API (CORS, logs, parsing JSON). Source: cda-hackathon-quiz-app-backend/package.json
{
"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"
}
}
- Le schéma Prisma couvre les entités métier (
Player,Game,Category,Question,Response,Score,History,GameMode) et leurs relations, ce qui formalise la logique de jeu et d’agrégation. Source: cda-hackathon-quiz-app-backend/prisma/schema.prisma
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
- Backend : Le serveur initialise un pipeline middleware avec CORS, body parsing, logs conditionnels et routage versionné. Source: cda-hackathon-quiz-app-backend/app.ts
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é
- Le projet inclut un outillage base de données opérationnel (
migrate reset,seed,studio) qui supporte les cycles de test manuel et de reconstruction locale. Source: cda-hackathon-quiz-app-backend/prisma/seed.ts
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.