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=falseThe 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 externalGET /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:e2etest/
βββ 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, activePluginsconst status = await migrationService.getMigrationStatus();
// Returns: total, executed, pending, failed- All data is isolated by
tenantIdandorganisationId - 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 lintfeat: 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