Report
Report
I. Introduction
Databases are the backbone of modern applications, storing and managing vast
amounts of data. Choosing the right type of database is critical to meeting project
requirements. RDBMS, characterized by structured tables and relationships, excels
in handling predictable, structured data with complex interconnections. In contrast,
NoSQL databases offer flexibility, scalability, and high performance, catering to
dynamic, unstructured, or large-scale data needs. Understanding their differences,
strengths, and limitations can help businesses make informed decisions for
operational and analytical tasks.
Advantages:
o Easy setup for beginners.
o Bundles all necessary components for a complete web server.
o Local testing and debugging are straightforward with tools like
phpMyAdmin.
o Suitable for small to medium-sized projects.
Disadvantages:
o Scalability is limited for enterprise-level applications.
o Technology stack is not optimized for real-time applications.
o Requires manual configuration for advanced features.
Application in Projects: In your report, XAMPP was used for a Q&A platform. It
offered:
Advantages:
o Full JavaScript stack allows seamless development across frontend
and backend.
o MongoDB provides flexibility for handling unstructured and evolving
datasets.
o Vue.js offers a reactive and component-based framework for dynamic
UI development.
o Suitable for scalable, real-time, and API-driven applications.
Disadvantages:
o Steeper learning curve compared to XAMPP.
o Requires separate deployment of frontend and backend applications.
o Debugging a NoSQL database like MongoDB may be challenging for
those accustomed to relational databases.
Technology Discussion
Concept:
Characteristics:
Advantages:
Node.js:
o High scalability with asynchronous processing.
o Uses JavaScript for both frontend and backend, simplifying
development.
o Rich package ecosystem via npm.
Express.js:
o Simplifies routing and middleware configuration.
o Wide community support and documentation.
Disadvantages:
Node.js:
o Single-threaded, which can be a bottleneck for CPU-intensive tasks.
o Callback-heavy code can lead to issues like "callback hell."
Express.js:
o Limited built-in features; requires many external libraries.
javascript
Copy code
const express = require('express'); const app = express(); app.get('/', (req, res) =>
{ res.send('Hello, world!'); }); app.listen(3000, () => { console.log('Server running
on port 3000'); });
Frontend Technology: Vue.js
Concept:
Characteristics:
Advantages:
Disadvantages:
Code Example:
javascript
Copy code
Concept:
Advantages:
Disadvantages:
javascript
Copy code
Popular Libraries
Cloud Technology
async login() {
try {
await this.$store.dispatch("login", {
email: this.email,
password: this.password,
rememberMe: this.rememberMe
});
if (this.isUserLoggedIn) {
await this.$router.push("/quizzes");
} catch (err) {
if (err.response?.data?.details) {
} else if (err.response) {
switch (err.response.status) {
case 404:
break;
case 401:
break;
case 500:
break;
default:
errorMessage = "An unexpected error occurred.";
this.$toast.error(errorMessage, {
position: "bottom-left",
duration: 2000
});
Error Handling
} catch (err) {
if (err.response?.data?.details) {
} else if (err.response) {
switch (err.response.status) {
case 404:
break;
case 401:
break;
case 500:
break;
default:
this.$toast.error(errorMessage, {
position: "bottom-left",
duration: 2000
});
Code Organization
Well-structured component architecture
Clean separation of concerns (template, script, styles)
Reusable components (AuthCard)
Proper use of Vue.js best practices
User Experience
Clear navigation flow
Informative feedback messages
Smooth transitions and animations
Accessible form elements
Security Considerations
Console logging of errors in production should be removed
Could benefit from rate limiting for login attempts
Password strength requirements not visible
Form Validation
Basic HTML5 validation only (required attribute)
No client-side validation for email format
No password complexity requirements shown to users
Accessibility Issues
No ARIA labels for form elements
Color contrast could be improved for some elements
No keyboard navigation indicators
Performance
No loading states during authentication
No client-side caching implementation
Could benefit from lazy loading components
User Experience Improvements Needed
No password recovery/reset functionality
No password visibility toggle
No form persistence on failed attempts
No progressive enhancement for older browsers
Technical Debt
Hard-coded color values instead of CSS variables
Some duplicate CSS that could be consolidated
Limited responsive design considerations
No TypeScript implementation for better type safety
Conclusion
The report highlights several areas for improvement in terms of performance, user
experience, and technical debt. There are issues with keyboard navigation
indicators, loading states during authentication, and client-side caching. User
experience could be enhanced with password recovery/reset functionality,
password visibility toggle, and form persistence on failed attempts. Additionally,
implementing lazy loading components and progressive enhancement for older
browsers would be beneficial. Addressing technical debt such as hard-coded color
values, duplicate CSS, limited responsive design considerations, and lack of
TypeScript implementation for better type safety would also be advantageous.
Appendices
Challenges:
a. Add a description key to our existing data object with the value
“A pair of warm, fuzzy socks”. Then display description using
an expression in an p element, underneath the h1.
b. Add a link to your data object, and use v-bind to sync it up with
an anchor tag in your HTML. Hint: you’ll be binding to the href
attribute.
c. Add an onSale property to the product’s data that is used to
conditionally render a span that says “On Sale!”
Lab 2
Challenges:
a. Add an array of sizes to the data and use v-for to display them in a
list.
c. When inStock is false, bind a class to the “Out of Stock” p tag that
adds text-decoration: line-through to that element.
Lab 3
Challenges:
a. Create a new component for product-details with a prop of details.
b. Add a button that removes the product from the cart array by emitting
an event with the id of the product to be removed.
c. Add a question to the form: “Would you recommend this product”.
Then take in that response from the user via radio buttons of “yes” or
“no” and add it to the productReview object, with form validation.
Lab 4
NoSQL databases are a class of databases designed to handle unstructured, semi-
structured, or structured data with a high degree of flexibility. Unlike traditional
relational databases, NoSQL databases do not rely on a fixed schema or use SQL
as their primary query language. They are optimized for handling large-scale data,
distributed storage, and high-speed operations. NoSQL databases are categorized
into types such as document stores, key-value stores, column-family stores, and
graph databases, each tailored to specific use cases.
Lab 5
Node.js servers are built on JavaScript's runtime environment, allowing developers to use
JavaScript for both frontend and backend development. The key distinguishing feature of
Node.js is its non-blocking, event-driven architecture.
At its core, Node.js uses an event loop to handle multiple requests concurrently without creating
new threads for each request. When a request comes in, instead of blocking the execution while
waiting for operations like file reading or database queries, Node.js registers callbacks and
continues processing other requests. When the operation completes, the callback is executed.
A basic Node.js server typically uses either the built-in 'http' module or popular frameworks like
Express.js. Express simplifies routing, middleware implementation, and request handling. Here's
a minimal example:
Node.js servers excel at handling many concurrent connections with minimal overhead, making
them ideal for real-time applications like chat systems or streaming services. However, they may
not be the best choice for CPU-intensive tasks due to their single-threaded nature.
The Node Package Manager (npm) ecosystem provides thousands of modules that can be easily
integrated into Node.js servers, making it highly extensible and maintainable.
Lab 6
REST (Representational State Transfer) is an architectural style for designing networked
applications. RESTful APIs use HTTP methods to perform operations on resources, where each
resource is identified by a unique URL.
1. Statelessness: Each request contains all information needed to process it. The server
doesn't store client session data between requests.
2. Resource-Based: Everything is treated as a resource (like users, products, or orders),
identified by unique URLs (endpoints). For example:
{
"status": "success",
"data": {
"id": 123,
"name": "John Doe",
"email": "[email protected]"
},
"message": "User retrieved successfully"
}
RESTful APIs are widely used because they're simple to understand, scalable, and provide a
standardized way for applications to communicate over the internet.
├── frontend/
│ ├── src/
│ │ ├── assets/
│ │ ├── services/
│ │ │ ├── router.js
│ │ │ └── store.js
│ │ └── views/
│ │ ├── HomeView.vue
│ │ ├── LoginView.vue
│ │ ├── RegisterView.vue
│ │ ├── UserView.vue
│ │ ├── UserEditView.vue
│ │ ├── QuizView.vue
│ │ ├── QuizTestView.vue
│ │ ├── QuizCreationView.vue
│ │ ├── QuizEditView.vue
│ │ ├── DashboardView.vue
│ │ └── LeaderboardView.vue
│ ├── package.json
│ └── package-lock.json
│
├── backend/
│ ├── model/
│ │ ├── user.js
│ │ └── quizModel.js
│ ├── middleware/
│ │ ├── authentication.js
│ │ └── roleAuth.js
│ ├── routes/
│ │ └── index.js
│ ├── public/
│ │ └── uploads/
│ │ └── avatars/
│ ├── app.js
│ └── package.json
│
Log
out is handled by clearing the auth cookie:
Auth
orization:
The application uses middleware for role-based access control:
router.post('/quizzes/create',
requireLogin,
requireRole('teacher'),
async (req, res) => {
try {
const { title, category, description, questions } = req.body;
const quiz = new Quiz({
title,
category,
description,
questions,
creator: req.user._id
});
await quiz.save();
res.status(201).json(quiz);
} catch (error) {
res.status(400).json({ message: error.message });
}
});
This shows that certain routes (like quiz creation) require both authentication and specific role
(teacher).
2b. CRUD Operations
The application implements full CRUD operations for quizzes:
Create:
Teachers can create new quizzes with multiple questions:
router.post('/quizzes/create',
requireLogin,
requireRole('teacher'),
async (req, res) => {
try {
const { title, category, description, questions } = req.body;
const quiz = new Quiz({
title,
category,
description,
questions,
creator: req.user._id
});
await quiz.save();
res.status(201).json(quiz);
} catch (error) {
res.status(400).json({ message: error.message });
}
});
Read:
Get all quizzes with statistics:
router.get('/quizzes', async (req, res) => {
try {
const quizzes = await Quiz.find();
// Calculate statistics for each quiz
const quizzesWithStats = await Promise.all(quizzes.map(async (quiz) => {
const stats = await quiz.calculateStats();
const quizObj = quiz.toObject();
return {
...quizObj,
attempts: stats.attempts,
averageScore: stats.averageScore
};
}));
res.json(quizzesWithStats);
} catch (err) {
console.error('Error fetching quizzes:', err);
res.status(500).json({ message: 'Error fetching quizzes' });
}
});
Get single quiz:
router.get('/quizzes/:id', async (req, res) => {
try {
const quiz = await Quiz.findById(req.params.id);
if (!quiz) {
return res.status(404).json({ message: 'Quiz not found' });
}
const stats = await quiz.calculateStats();
const quizObj = quiz.toObject();
res.json({
...quizObj,
attempts: stats.attempts,
averageScore: stats.averageScore
});
} catch (err) {
console.error('Error fetching quiz:', err);
res.status(500).json({ message: 'Error fetching quiz' });
}
});
Update:
Update existing quizzes:
router.put('/quizzes/:id',
requireLogin,
requireRole('teacher'),
async (req, res) => {
try {
const { title, description, category, questions } = req.body;
const quiz = await Quiz.findById(req.params.id);
if (!quiz) {
return res.status(404).json({ message: 'Quiz not found' });
}
// Update quiz fields
quiz.title = title;
quiz.description = description;
quiz.category = category;
quiz.questions = questions;
await quiz.save();
res.json(quiz);
} catch (error) {
console.error('Error updating quiz:', error);
res.status(400).json({ message: 'Error updating quiz' });
}
});
Delete:
While not shown in the provided snippets, the API structure suggests delete functionality exists.
2c. Additional Functions
Quiz Statistics:
The system tracks quiz attempts and calculates statistics:
quizSchema.methods.calculateStats = async function() {
const users = await User.find({
'attempts.quizId': this._id
});
const attempts = users.reduce((acc, user) => {
return acc.concat(user.attempts.filter(attempt =>
attempt.quizId.toString() === this._id.toString()
));
}, []);
const totalAttempts = attempts.length;
const totalScore = attempts.reduce((sum, attempt) => sum + attempt.score, 0);
return {
attempts: totalAttempts,
averageScore: totalAttempts ? +(totalScore / totalAttempts).toFixed(1) : 0
};
};
Leaderboard System:
Implements a leaderboard showing top performers:
router.get('/leaderboard', async (req, res) => {
try {
const quizzes = await Quiz.find().lean();
const leaderboardData = await Promise.all(quizzes.map(async (quiz) => {
const users = await User.find({
'attempts.quizId': quiz._id
}).select('username attempts');
const attempts = users.reduce((acc, user) => {
return acc.concat(
user.attempts
.filter(attempt => attempt.quizId.toString() === quiz._id.toSt
ring())
.map(attempt => ({
username: user.username,
score: attempt.score,
timestamp: attempt.timestamp
}))
);
}, []);
// Sort attempts by score (descending) and take top 5
const topAttempts = attempts
.sort((a, b) => b.score - a.score)
.slice(0, 5);
return {
_id: quiz._id,
title: quiz.title,
category: quiz.category,
attempts: topAttempts,
totalAttempts: attempts.length,
averageScore: attempts.length
? +(attempts.reduce((sum, a) => sum + a.score, 0) / attempt
s.length).toFixed(1)
: 0
};
}));
res.json(leaderboardData);
} catch (err) {
File Upload:
Supports avatar uploads using multer:
const multer = require('multer');
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'public/uploads/avatars')
},
filename: function (req, file, cb) {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9)
cb(null, uniqueSuffix + '-' + file.originalname)
}
});
const upload = multer({ storage: storage });
Progress Tracking:
Tracks user progress and scores:
router.post('/users/attempts', requireLogin, async (req, res) => {
try {
const userId = req.user._id;
const { quizId, score } = req.body;
// Add attempt to user's attempts array
req.user.attempts.push({ quizId, score });
await req.user.save();
res.status(200).json({ message: 'Attempt saved successfully' });
} catch (error) {
console.error('Error saving attempt:', error);
res.status(500).json({ message: 'Error saving attempt' });
}
});
Multiple Question Types:
The system supports different types of questions:
questions: [{
type: {
type: String,
enum: ['multiple_choice', 'text_answer'],
default: 'multiple_choice'
},
question: String,
options: [String],
correctAnswer: {
type: mongoose.Schema.Types.Mixed,
required: true
}
}],
Multiple choice
Text answer
These implementations follow RESTful practices and include proper error
handling, authentication checks, and data validation throughout the application.
3. Testing
4. Challenges and Solution