CBT app database
A CBT app database is the part of a Computer-Based Testing system that stores and organizes the information the app needs: questions, users, answers, attempts, scores, subjects, exam sessions and progress.
One common mistake is treating the question bank as the entire database.
A real CBT application usually needs two different types of data:
QUESTION DATA
Questions
Options
Correct answers
Solutions
Subjects
Years
Images
USER DATA
Students
Attempts
Selected answers
Scores
Bookmarks
Progress
Subscriptions
Keeping those responsibilities separate makes a CBT platform much easier to build and scale.
What should a CBT app database contain?
For a simple practice app, you might start with these collections or tables:
users
exam_attempts
answers
bookmarks
results
A simplified attempt record could look like:
{
"user_id": "usr_291",
"exam": "utme",
"subject": "physics",
"year": 2024,
"question_ids": [8931, 8932, 8933],
"answers": {
"8931": "b",
"8932": "d",
"8933": "a"
},
"score": 2,
"completed": true
}
Notice that you don't necessarily need to duplicate the full question text inside every attempt.
You can reference questions using their IDs.
Should you store the question bank yourself?
There are three reasonable approaches.
The first is storing everything locally:
CBT App
↓
Your Backend
↓
Your Database
├── Users
├── Questions
├── Attempts
└── Results
This gives you maximum control, but you're responsible for sourcing, correcting and maintaining the question bank.
The second is using an external question API:
CBT App
↓
Your Backend
├── Your Database → users/results/progress
│
└── Question API → questions/answers/solutions
For example, SdashAPI provides structured examination data for UTME, WASSCE, NECO and Post-UTME. Its current public database reports 368,868+ questions across 26 subjects. SdashAPI
The third option is a hybrid: use the API as your source while caching question sets that your users request frequently.
A practical CBT database architecture
For most online exam-practice applications, something like this works well:
Mobile App / Website
↓
Backend
↙ ↘
Your Database SdashAPI
↓ ↓
Users Questions
Attempts Options
Scores Answers
Progress Solutions
Bookmarks Metadata
Your database remains responsible for your product and users.
The question API remains responsible for examination content.
SdashAPI itself recommends pairing its educational-data API with your own backend/database for user authentication and persistent user history. Its documentation mentions options such as Firebase, Supabase, PostgreSQL-backed services and custom PHP/MySQL systems. SdashAPI
Fetching questions for the CBT
The current SdashAPI V1 question endpoint is:
GET https://sdashapi.com/api/v1/q
Authentication uses:
AccessToken: YOUR_ACCESS_TOKEN
A documented UTME request is:
GET https://sdashapi.com/api/v1/q?type=utme&subject=mathematics
``` :chatgpt-content-reference{index="2"}
If you need a complete 40-question Physics session, you can use the documented filters:
```http
GET https://sdashapi.com/api/v1/q?type=utme&subject=physics&year=2019&limit=40
SdashAPI currently allows up to 50 questions in one request, so you should fetch questions in batches rather than making one API call per question. SdashAPI
Store answers by question ID
Imagine the student receives 40 questions.
Don't rely only on:
answers[0] = "b";
answers[1] = "c";
because your questions might later be shuffled.
A better structure is:
answers = {
8931: "b",
7402: "c",
9912: "a"
};
SdashAPI gives questions unique integer IDs and allows a specific question to be retrieved using its ID. SdashAPI
That makes it easier to build:
Questions I Got Wrong
Bookmarks
Review Answers
Attempt History
Performance Analytics
without duplicating complete question records everywhere.
Don't expose your question API key
This matters especially for React, React Native and Flutter applications.
A structure like this is risky:
Mobile App
↓
SdashAPI directly with production key
because credentials embedded in client applications can potentially be extracted.
A safer production architecture is:
Mobile App
↓
Your Backend
↓
SdashAPI
Keep the API key in your backend environment variables.
SdashAPI's current authentication documentation specifically recommends this approach and warns against committing keys to public repositories or embedding production credentials in frontend code. SdashAPI
What database should you use?
There isn't one database technology that every CBT app needs.
PostgreSQL works well when you want relational data and advanced querying.
MySQL is perfectly suitable for traditional PHP/server applications.
Firebase/Firestore can be convenient for mobile apps, especially when you want authentication and real-time synchronization.
Supabase is useful when you want PostgreSQL with a managed backend experience.
SQLite is valuable for fully offline applications.
The important question isn't "Which database is best?"
It's:
What data does my CBT app need to store, and does it need to work online, offline or both?
For example, a school running exams on a local network has different requirements from a public JAMB practice app with 50,000 users.
Online CBT and offline CBT need different designs
For an online platform:
Student
↓
Internet
↓
Backend
↓
Database + Question API
works well.
For an offline school CBT system, you may prefer:
School Computers
↓
Local Network
↓
Local Server
↓
Local Question Database
SdashAPI currently offers bulk dataset licensing in formats including JSON, CSV and SQL for teams that need the full examination dataset offline. SdashAPI
That can be more appropriate than depending on live API requests during an examination.
A hybrid CBT database can handle poor internet better
You don't necessarily need a fully offline dataset to make your app resilient.
Historical questions can be cached.
For example:
Student requests Physics 2019
↓
Check Cache
↙ ↘
Found Missing
↓ ↓
Return SdashAPI
↓
Cache
↓
Return
SdashAPI's current caching guide recommends caching historical question responses and demonstrates both browser storage and server-side Redis. SdashAPI
Its CBT tutorial also recommends fetching the full exam session before the test begins so a temporary internet interruption doesn't necessarily stop the active examination. SdashAPI
That's particularly useful for mobile CBT apps where connectivity may fluctuate.
Your database should record more than the final score
Suppose two students both score:
28 / 40
That doesn't mean they have the same weaknesses.
Store enough attempt data to determine things like:
Student A
Mechanics: 90%
Electricity: 42%
Student B
Mechanics: 48%
Electricity: 88%
Then your application can recommend what each student should revise next.
SdashAPI's richer API versions provide additional educational information for applications that need more than basic questions. Its current documentation describes V1 as core question data, with higher versions adding educational metadata, explanations and intelligence features. SdashAPI
You don't need all of that for your first CBT version, though.
Start with:
Question
Options
Answer
Timer
Navigation
Submit
Score
Review
Then add analytics when the basic exam experience works properly.
Don't make your database unnecessarily huge
If you're using an API, there's little benefit in saving the same complete question record every time a student attempts it.
Instead of this:
Attempt 1 → Full question copied
Attempt 2 → Full question copied again
Attempt 3 → Full question copied again
store references:
Attempt 1 → Question ID 8931 → B → Wrong
Attempt 2 → Question ID 8931 → C → Correct
Attempt 3 → Question ID 7402 → A → Correct
Then retrieve the question itself when needed.
If your product needs very fast search or offline access, you can deliberately cache or synchronize question data into your own database instead.
SdashAPI's current search guide even recommends a local PostgreSQL or MongoDB database with native indexing when developers need extensive full-text searching over cached question data. SdashAPI
One database can power several exam types
Your CBT architecture shouldn't necessarily be tied to JAMB alone.
SdashAPI's current coverage includes:
- UTME: 22 subjects, 1988–2026 overall
- WASSCE: 23 subjects, 1988–2026 overall
- NECO: 25 subjects, 1988–2026 overall
- Post-UTME: 19 subjects, 2000–2026 overall SdashAPI
So your database can store something as simple as:
{
"exam_type": "neco",
"subject": "biology",
"year": 2024
}
and the same CBT engine can be reused across different examinations.
That's much cleaner than creating an entirely different application architecture for every exam.
Keep the database simple at the beginning
A first version doesn't need 30 tables.
You can start with something like:
users
attempts
attempt_answers
bookmarks
Use an examination-data API for the question bank, then expand your own database as you discover what your users actually need.
If you're building something that must own all its question data locally, use a properly licensed bulk dataset and import it into PostgreSQL, MySQL, MongoDB, SQLite or whatever database fits your architecture.
The SdashAPI documentation provides the current question endpoints and JSON structure, while SdashAPI Coverage shows the examinations and data currently available.
A good CBT app database isn't simply where you dump thousands of questions. It should connect question data with attempts, answers and student progress in a way that makes the CBT fast, reliable and useful.