cda-hackathon-quiz-app-backend

cda-hackathon-quiz-app-backend

Quiz backend API serving questions, storing game history, and computing rankings, built with Express/Prisma/MySQL. The architecture implements full relational modeling with dedicated use cases and a per-mode scoring logic.

🎯 Context and goals

  • Expose an API foundation for a quiz frontend (questions, history, rankings, score updates).
  • Structure a data model covering players, games, questions/answers, and performance aggregates.
  • Make end-of-game business processing reliable: score computation, per-category consolidation, and dynamic ranking.

πŸ› οΈ Deliverables

🧩 Design

{
  "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])
}

πŸ’» Development

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)

Routes clearly split questions, history, rankings, and update use cases, with unified error handling. 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
  })
})

updateData computes per-question score, creates a game record, then updates cumulative player/category aggregates. 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);

Ranking is computed dynamically in total/average mode, sorted post-processing, and capped to top 5 results. 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 })
});

History enrichment backfills incorrect answers with the expected answer (goodAnswerWas) for frontend detail views. 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 : This repository does not implement UI screens, but it exposes API contracts dedicated to frontend rendering (randomized questions, filtered rankings, enriched history).

πŸ—οΈ DevOps & Quality

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 })
}

πŸ“ˆ Results

  • Based on the analyzed Git history, the work spans 2021-12-13 -> 2021-12-16, with 20 commits across 3 contributors. The delivery results in a complete quiz API: category-based question retrieval, game persistence, score aggregation, and ranking/history endpoints. Prisma modeling and seed scripts make the environment reproducible for development and demos. The technical benefit is a coherent backend layer directly consumable by an interactive game frontend.

πŸ”§ Technical environment

  • Backend: Node.js, Express, TypeScript, body-parser, Morgan, CORS.
  • Data: Prisma ORM, MySQL, relational schema and migrations.
  • Architecture: controllers + services/use cases + centralized router.
  • Functional scope: scoring, enriched history, total/average ranking, initial seed dataset.
  • Tooling: Prisma Studio, reset/reseed scripts, Prisma query logs.
🌐 View the project

Tech Stack

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