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
- The backend stack is organized around Express, Prisma, TypeScript, and API middleware (CORS, logging, JSON parsing). 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"
}
}
- The Prisma schema models core entities (
Player,Game,Category,Question,Response,Score,History,GameMode) and their relations, formalizing gameplay and aggregation logic. 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])
}
π» Development
- Backend : The server initializes a middleware pipeline with CORS, body parsing, conditional request logging, and versioned routing. 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)
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
- The project includes operational database tooling (
migrate reset,seed,studio) supporting local rebuild and manual validation cycles. 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 })
}
π 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.