14 KiB
14 KiB
Sequelize Quick Reference Guide
Common Operations for Interview Quiz App
1. Database Connection
// config/database.js
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASSWORD,
{
host: process.env.DB_HOST,
dialect: 'mysql',
pool: { max: 10, min: 0, acquire: 30000, idle: 10000 }
}
);
// Test connection
sequelize.authenticate()
.then(() => console.log('MySQL connected'))
.catch(err => console.error('Unable to connect:', err));
module.exports = sequelize;
2. User Operations
Register New User
const bcrypt = require('bcrypt');
const { User } = require('../models');
const registerUser = async (userData) => {
const hashedPassword = await bcrypt.hash(userData.password, 10);
const user = await User.create({
username: userData.username,
email: userData.email,
password: hashedPassword,
role: 'user'
});
return user;
};
Login User
const loginUser = async (email, password) => {
const user = await User.findOne({
where: { email },
attributes: ['id', 'username', 'email', 'password', 'role']
});
if (!user) {
throw new Error('User not found');
}
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
throw new Error('Invalid credentials');
}
return user;
};
Get User Dashboard
const getUserDashboard = async (userId) => {
const user = await User.findByPk(userId, {
attributes: [
'id', 'username', 'email',
'totalQuizzes', 'totalQuestions', 'correctAnswers', 'streak'
],
include: [
{
model: QuizSession,
where: { status: 'completed' },
required: false,
limit: 10,
order: [['completedAt', 'DESC']],
include: [{ model: Category, attributes: ['name'] }]
}
]
});
return user;
};
3. Category Operations
Get All Categories
const getCategories = async () => {
const categories = await Category.findAll({
where: { isActive: true },
attributes: {
include: [
[
sequelize.literal(`(
SELECT COUNT(*)
FROM questions
WHERE questions.category_id = categories.id
AND questions.is_active = true
)`),
'questionCount'
]
]
}
});
return categories;
};
Get Guest-Accessible Categories
const getGuestCategories = async () => {
const categories = await Category.findAll({
where: {
isActive: true,
guestAccessible: true
},
attributes: ['id', 'name', 'description', 'icon', 'publicQuestionCount']
});
return categories;
};
4. Question Operations
Create Question (Admin)
const createQuestion = async (questionData) => {
const question = await Question.create({
question: questionData.question,
type: questionData.type,
categoryId: questionData.categoryId,
difficulty: questionData.difficulty,
options: JSON.stringify(questionData.options), // Store as JSON
correctAnswer: questionData.correctAnswer,
explanation: questionData.explanation,
keywords: JSON.stringify(questionData.keywords),
tags: JSON.stringify(questionData.tags),
visibility: questionData.visibility || 'registered',
isGuestAccessible: questionData.isGuestAccessible || false,
createdBy: questionData.userId
});
// Update category question count
await Category.increment('questionCount', {
where: { id: questionData.categoryId }
});
return question;
};
Get Questions by Category
const getQuestionsByCategory = async (categoryId, options = {}) => {
const { difficulty, limit = 10, visibility = 'registered' } = options;
const where = {
categoryId,
isActive: true,
visibility: {
[Op.in]: visibility === 'guest' ? ['public'] : ['public', 'registered', 'premium']
}
};
if (difficulty) {
where.difficulty = difficulty;
}
const questions = await Question.findAll({
where,
order: sequelize.random(),
limit,
include: [{ model: Category, attributes: ['name'] }]
});
return questions;
};
Search Questions (Full-Text)
const searchQuestions = async (searchTerm) => {
const questions = await Question.findAll({
where: {
[Op.or]: [
sequelize.literal(`MATCH(question, explanation) AGAINST('${searchTerm}' IN NATURAL LANGUAGE MODE)`)
],
isActive: true
},
limit: 50
});
return questions;
};
5. Quiz Session Operations
Start Quiz Session
const startQuizSession = async (sessionData) => {
const { userId, guestSessionId, categoryId, questionCount, difficulty } = sessionData;
// Create quiz session
const quizSession = await QuizSession.create({
userId: userId || null,
guestSessionId: guestSessionId || null,
isGuestSession: !userId,
categoryId,
totalQuestions: questionCount,
status: 'in-progress',
startTime: new Date()
});
// Get random questions
const questions = await Question.findAll({
where: {
categoryId,
difficulty: difficulty || { [Op.in]: ['easy', 'medium', 'hard'] },
isActive: true
},
order: sequelize.random(),
limit: questionCount
});
// Create junction entries
const sessionQuestions = questions.map((q, index) => ({
quizSessionId: quizSession.id,
questionId: q.id,
questionOrder: index + 1
}));
await sequelize.models.QuizSessionQuestion.bulkCreate(sessionQuestions);
return { quizSession, questions };
};
Submit Answer
const submitAnswer = async (answerData) => {
const { quizSessionId, questionId, userAnswer } = answerData;
// Get question
const question = await Question.findByPk(questionId);
// Check if correct
const isCorrect = question.correctAnswer === userAnswer;
// Save answer
const answer = await QuizAnswer.create({
quizSessionId,
questionId,
userAnswer,
isCorrect,
timeSpent: answerData.timeSpent || 0
});
// Update quiz session score
if (isCorrect) {
await QuizSession.increment('score', { where: { id: quizSessionId } });
}
// Update question statistics
await Question.increment('timesAttempted', { where: { id: questionId } });
return {
isCorrect,
correctAnswer: question.correctAnswer,
explanation: question.explanation
};
};
Complete Quiz Session
const completeQuizSession = async (sessionId) => {
const session = await QuizSession.findByPk(sessionId, {
include: [
{
model: QuizAnswer,
include: [{ model: Question, attributes: ['id', 'question'] }]
}
]
});
// Calculate results
const correctAnswers = session.QuizAnswers.filter(a => a.isCorrect).length;
const percentage = (correctAnswers / session.totalQuestions) * 100;
const timeTaken = Math.floor((new Date() - session.startTime) / 1000); // seconds
// Update session
await session.update({
status: 'completed',
endTime: new Date(),
completedAt: new Date()
});
// Update user stats if not guest
if (session.userId) {
await User.increment({
totalQuizzes: 1,
totalQuestions: session.totalQuestions,
correctAnswers: correctAnswers
}, { where: { id: session.userId } });
}
return {
score: session.score,
totalQuestions: session.totalQuestions,
percentage,
timeTaken,
correctAnswers,
incorrectAnswers: session.totalQuestions - correctAnswers,
results: session.QuizAnswers
};
};
6. Guest Session Operations
Create Guest Session
const createGuestSession = async (deviceId, ipAddress, userAgent) => {
const guestId = `guest_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const sessionToken = jwt.sign({ guestId }, process.env.JWT_SECRET, { expiresIn: '24h' });
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
const guestSession = await GuestSession.create({
guestId,
deviceId,
sessionToken,
quizzesAttempted: 0,
maxQuizzes: 3,
expiresAt,
ipAddress,
userAgent
});
return guestSession;
};
Check Guest Quiz Limit
const checkGuestQuizLimit = async (guestSessionId) => {
const session = await GuestSession.findByPk(guestSessionId);
if (!session) {
throw new Error('Guest session not found');
}
if (new Date() > session.expiresAt) {
throw new Error('Guest session expired');
}
const remainingQuizzes = session.maxQuizzes - session.quizzesAttempted;
if (remainingQuizzes <= 0) {
throw new Error('Guest quiz limit reached');
}
return {
remainingQuizzes,
resetTime: session.expiresAt
};
};
7. Bookmark Operations
Add Bookmark
const addBookmark = async (userId, questionId) => {
const bookmark = await sequelize.models.UserBookmark.create({
userId,
questionId
});
return bookmark;
};
Get User Bookmarks
const getUserBookmarks = async (userId) => {
const user = await User.findByPk(userId, {
include: [{
model: Question,
as: 'bookmarks',
through: { attributes: ['bookmarkedAt'] },
include: [{ model: Category, attributes: ['name'] }]
}]
});
return user.bookmarks;
};
Remove Bookmark
const removeBookmark = async (userId, questionId) => {
await sequelize.models.UserBookmark.destroy({
where: { userId, questionId }
});
};
8. Admin Operations
Get System Statistics
const getSystemStats = async () => {
const [totalUsers, activeUsers, totalQuizzes] = await Promise.all([
User.count(),
User.count({
where: {
lastLogin: { [Op.gte]: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) }
}
}),
QuizSession.count({ where: { status: 'completed' } })
]);
const popularCategories = await Category.findAll({
attributes: [
'id', 'name',
[sequelize.fn('COUNT', sequelize.col('QuizSessions.id')), 'quizCount']
],
include: [{
model: QuizSession,
attributes: [],
required: false
}],
group: ['Category.id'],
order: [[sequelize.literal('quizCount'), 'DESC']],
limit: 5
});
const avgScore = await QuizSession.findOne({
attributes: [[sequelize.fn('AVG', sequelize.col('score')), 'avgScore']],
where: { status: 'completed' }
});
return {
totalUsers,
activeUsers,
totalQuizzes,
popularCategories,
averageScore: avgScore.dataValues.avgScore || 0
};
};
Update Guest Settings
const updateGuestSettings = async (settings, adminUserId) => {
const guestSettings = await GuestSettings.findOne();
if (guestSettings) {
await guestSettings.update({
...settings,
updatedBy: adminUserId
});
} else {
await GuestSettings.create({
...settings,
updatedBy: adminUserId
});
}
return guestSettings;
};
9. Transaction Example
const { Op } = require('sequelize');
const convertGuestToUser = async (guestSessionId, userData) => {
const t = await sequelize.transaction();
try {
// Create user
const user = await User.create(userData, { transaction: t });
// Get guest session
const guestSession = await GuestSession.findByPk(guestSessionId, { transaction: t });
// Migrate quiz sessions
await QuizSession.update(
{ userId: user.id, isGuestSession: false },
{ where: { guestSessionId }, transaction: t }
);
// Calculate stats
const stats = await QuizSession.findAll({
where: { userId: user.id },
attributes: [
[sequelize.fn('COUNT', sequelize.col('id')), 'totalQuizzes'],
[sequelize.fn('SUM', sequelize.col('score')), 'totalScore']
],
transaction: t
});
// Update user stats
await user.update({
totalQuizzes: stats[0].dataValues.totalQuizzes || 0
}, { transaction: t });
// Delete guest session
await guestSession.destroy({ transaction: t });
await t.commit();
return user;
} catch (error) {
await t.rollback();
throw error;
}
};
10. Common Query Operators
const { Op } = require('sequelize');
// Examples of common operators
const examples = {
// Equals
{ status: 'completed' },
// Not equals
{ status: { [Op.ne]: 'abandoned' } },
// Greater than / Less than
{ score: { [Op.gte]: 80 } },
{ createdAt: { [Op.lte]: new Date() } },
// Between
{ score: { [Op.between]: [50, 100] } },
// In array
{ difficulty: { [Op.in]: ['easy', 'medium'] } },
// Like (case-sensitive)
{ question: { [Op.like]: '%javascript%' } },
// Not null
{ userId: { [Op.not]: null } },
// OR condition
{
[Op.or]: [
{ visibility: 'public' },
{ isGuestAccessible: true }
]
},
// AND condition
{
[Op.and]: [
{ isActive: true },
{ difficulty: 'hard' }
]
}
};
Error Handling
const handleSequelizeError = (error) => {
if (error.name === 'SequelizeUniqueConstraintError') {
return { status: 400, message: 'Record already exists' };
}
if (error.name === 'SequelizeValidationError') {
return { status: 400, message: error.errors.map(e => e.message).join(', ') };
}
if (error.name === 'SequelizeForeignKeyConstraintError') {
return { status: 400, message: 'Invalid foreign key reference' };
}
return { status: 500, message: 'Database error' };
};
Best Practices
- Always use parameterized queries (Sequelize does this by default)
- Use transactions for multi-step operations
- Index frequently queried columns
- Use eager loading to avoid N+1 queries
- Limit result sets with pagination
- Use connection pooling
- Handle errors gracefully
- Log slow queries in development
- Use prepared statements
- Validate input before database operations
Happy coding! 🚀