A robust, scalable NestJS microservice for managing the entire lifecycle of tests and assessments with support for rule-based tests, subjective questions, and enterprise-grade plugin architecture.
- ✅ Test Management - Create, update, and manage tests with various types
- ✅ Question Types - MCQ, Multiple Answer, True/False, Fill-in-Blank, Matching, Subjective, Essay
- ✅ Rule-Based Tests - Dynamic question selection based on rules and criteria
- ✅ Test Sections - Organize tests into logical sections
- ✅ Attempt Management - Track user attempts with detailed analytics
- ✅ Review System - Manual review for subjective questions with rubric support
- ✅ Scoring & Grading - Automatic and manual scoring with flexible grading strategies
- ✅ Multi-tenancy - Complete tenant and organization isolation
- ✅ Plugin System - Joomla-like triggers for extensible functionality
- ✅ Database Migrations - Version-controlled schema management
- ✅ Caching - Redis-based caching for performance
- ✅ API Documentation - Swagger/OpenAPI documentation
- ✅ Health Checks - Comprehensive health monitoring
- ✅ Rate Limiting - Built-in throttling and protection
- ✅ Internal Plugins - Fast, in-process event handling
- ✅ External Services - Webhook-based external integrations
- ✅ Hybrid Approach - Best of both worlds for scalability
- ✅ Event-Driven - Loose coupling through standardized events
- Framework: NestJS (Node.js)
- Language: TypeScript
- Database: PostgreSQL with TypeORM
- Cache: Redis
- Documentation: Swagger/OpenAPI
- Testing: Jest
- Migration: Custom migration system
tests (1) ←→ (N) testSections
tests (1) ←→ (N) testQuestions
testSections (1) ←→ (N) testQuestions
testRules (1) ←→ (N) testQuestions (via ruleId)
testAttempts (1) ←→ (1) tests (generated) (via resolvedTestId)
testUserAnswers (N) ←→ (1) testAttempts
questions (1) ←→ (N) questionOptions
- Node.js (v16 or higher)
- PostgreSQL (v12 or higher)
- Redis (v6 or higher)
# Clone the repository
git clone https://github.com/tekdi/shiksha-assessment-service.git
cd shiksha-assessment-service
# Install dependencies
npm install
# Copy environment file
cp env.example .env
# Configure environment variables
# Edit .env file with your database and Redis settings
# Run database migrations
npm run migration:run
# Start the application
npm run start:dev
# Database
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_USERNAME=postgres
DATABASE_PASSWORD=password
DATABASE_NAME=assessment_db
# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
# Application
PORT=3000
NODE_ENV=development
# Plugin Configuration
USE_INTERNAL_PLUGINS=true
USE_EXTERNAL_SERVICES=false
USE_HYBRID_PLUGINS=false
The assessment service includes a powerful plugin system that supports both internal plugins (for development) and external services (for production scalability).
export class NotificationPlugin implements Plugin {
id = 'notification-plugin';
name = 'Notification Plugin';
type: 'internal' = 'internal';
isActive = true;
hooks: PluginHook[] = [
{
name: 'attempt.submitted',
priority: 100,
handler: async (event) => {
await this.sendEmail(event.data);
}
}
];
}
export class WebhookPlugin implements Plugin {
id = 'webhook-plugin';
name = 'Webhook Plugin';
type: 'external' = 'external';
isActive = true;
hooks = [];
externalService = {
type: 'webhook',
webhook: {
url: 'https://api.external.com/webhooks',
method: 'POST',
headers: { 'Authorization': 'Bearer API_KEY' },
timeout: 10000,
retries: 3,
events: ['attempt.submitted']
}
};
}
- Test Events:
test.created
,test.updated
,test.deleted
,test.published
- Question Events:
question.created
,question.updated
,question.deleted
- Attempt Events:
attempt.started
,attempt.submitted
,attempt.reviewed
- Answer Events:
answer.submitted
- Rule Events:
rule.created
,rule.updated
,rule.deleted
- User Events:
user.registered
,user.login
,user.logout
- System Events:
system.startup
,system.shutdown
,error.occurred
// Register plugins based on environment
await PluginConfiguration.registerPlugins(pluginManager);
// Environment variables control the approach:
// NODE_ENV=development → Internal plugins
// NODE_ENV=production → External services
// USE_HYBRID_PLUGINS=true → Both internal and external
GET /tests # List tests with filtering
POST /tests # Create new test
GET /tests/:id # Get test details
PUT /tests/:id # Update test
DELETE /tests/:id # Delete test
POST /tests/:id/publish # Publish test
POST /tests/:id/unpublish # Unpublish test
GET /questions # List questions with filtering
POST /questions # Create new question
GET /questions/:id # Get question details
PUT /questions/:id # Update question
DELETE /questions/:id # Delete question
POST /attempts/:testId/start # Start test attempt
GET /attempts/:id/questions # Get attempt questions
POST /attempts/:id/answers # Submit answer
POST /attempts/:id/submit # Submit attempt
GET /attempts/:id/result # Get attempt result
GET /rules # List rules
POST /rules # Create new rule
GET /rules/:id # Get rule details
PUT /rules/:id # Update rule
DELETE /rules/:id # Delete rule
GET /sections # List sections
POST /sections # Create new section
GET /sections/:id # Get section details
PUT /sections/:id # Update section
DELETE /sections/:id # Delete section
GET /reviews/pending # Get pending reviews
POST /reviews/:attemptId # Review attempt
- MCQ - Single choice with auto-scoring
- Multiple Answer - Multiple choice with partial scoring
- True/False - Auto-scored
- Fill-in-Blank - Auto-scored with case sensitivity
- Matching - Auto-scored
- Subjective - Manual review with rubric-based scoring
- Essay - Manual review with comprehensive rubric
{
"selectedOptionIds": ["opt-1"], // MCQ/True-False
"selectedOptionIds": ["opt-1", "opt-3"], // Multiple Answer
"text": "Answer text...", // Subjective/Essay
"blanks": ["answer1", "answer2"], // Fill-in-Blank
"matches": ["A-1", "B-3"] // Matching
}
- Create Test with type 'rule_based'
- Create Rules with criteria and selection strategies
- Add Questions to testQuestions table with ruleId
- User Attempt triggers question generation
- System Creates generated test with selected questions
- Attempt Links to generated test via resolvedTestId
- Category-based - Select questions by category
- Difficulty-based - Select questions by difficulty level
- Type-based - Select questions by question type
- Marks-based - Select questions by marks range
- Random - Random selection from available questions
- Sequential - First N questions in order
- Weighted - Selection based on question weights/difficulty
// Run pending migrations
const result = await migrationService.runMigrations(migrations);
// Rollback migrations
const rollbackResult = await migrationService.rollbackMigrations(2);
// Get migration status
const status = await migrationService.getMigrationStatus();
- ✅ Version Tracking - Records executed migrations in database
- ✅ Dependency Resolution - Handles migration dependencies
- ✅ Rollback Support - Can undo migrations in reverse order
- ✅ Error Recovery - Continues from failed migrations
- ✅ Performance Monitoring - Tracks execution time
# Unit tests
npm run test
# Watch mode
npm run test:watch
# Coverage
npm run test:cov
# E2E tests
npm run test:e2e
test/
├── unit/ # Unit tests
├── integration/ # Integration tests
└── e2e/ # End-to-end tests
- Swagger UI:
http://localhost:3000/api-docs
- OpenAPI Spec:
http://localhost:3000/api-json
- Schema Documentation:
docs/db-design.md
- Service PRD:
docs/service-prd.md
- Learner Flow:
docs/learner_flow.md
- Plugin Documentation:
PLUGIN_SYSTEM_README.md
- Configuration Examples:
src/modules/plugins/plugin-config.example.ts
npm run start:dev
# Build the application
npm run build
# Start production server
npm run start:prod
# Build Docker image
docker build -t assessment-service .
# Run container
docker run -p 3000:3000 assessment-service
# Development
NODE_ENV=development
USE_INTERNAL_PLUGINS=true
# Production
NODE_ENV=production
USE_EXTERNAL_SERVICES=true
# Hybrid
USE_HYBRID_PLUGINS=true
// src/config/database.config.ts
export class DatabaseConfig implements TypeOrmOptionsFactory {
createTypeOrmOptions(): TypeOrmModuleOptions {
return {
type: 'postgres',
host: process.env.DATABASE_HOST,
port: parseInt(process.env.DATABASE_PORT),
username: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
synchronize: false, // Use migrations in production
logging: process.env.NODE_ENV === 'development',
};
}
}
// src/config/redis.config.ts
export class RedisConfig implements CacheOptionsFactory {
createCacheOptions(): CacheModuleOptions {
return {
store: redisStore,
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT),
password: process.env.REDIS_PASSWORD,
ttl: 60 * 60 * 24, // 24 hours
};
}
}
GET /health # Basic health check
GET /health/detailed # Detailed health information
const stats = PluginConfiguration.getPluginStats(pluginManager);
// Returns: totalPlugins, totalHooks, externalServices, registeredEvents, activePlugins
const status = await migrationService.getMigrationStatus();
// Returns: total, executed, pending, failed
- All data is isolated by
tenantId
andorganisationId
- No cross-tenant data access
- Tenant-specific caching
- Built-in throttling (100 requests per minute)
- Configurable limits per endpoint
- IP-based rate limiting
- DTO-based validation with class-validator
- Type-safe request handling
- SQL injection protection via TypeORM
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
# Format code
npm run format
# Lint code
npm run lint
feat: add new feature
fix: bug fix
docs: documentation changes
style: code style changes
refactor: code refactoring
test: add tests
chore: maintenance tasks
This project is licensed under the ISC License.
- Report bugs: GitHub Issues
- Feature requests: GitHub Discussions
- Join our Discord Server
- Follow us on Twitter
Built with ❤️ by the Shiksha Team
Empowering education through technology