This assessment contains 4 exercises designed to evaluate your ability to work with our technology stack. Our application is a microservices-based platform for audio processing with AI capabilities.
- Backend Framework: NestJS (TypeScript)
- Database: MongoDB with Mongoose ODM
- Testing: Jest
- Architecture: Microservices
- External Services: Azure Speech/Translator, SendGrid Email
- Build Tool: Nx Monorepo
Objective: Demonstrate NestJS service patterns, error handling, and MongoDB integration.
You need to create a service that retrieves user statistics from a MongoDB database. The service should handle errors appropriately and follow NestJS best practices.
Assume you have the following User schema:
// user.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
@Schema()
export class User extends Document {
@Prop({ required: true })
email: string;
@Prop({ required: true })
name: string;
@Prop({ default: Date.now })
createdAt: Date;
@Prop()
lastLoginAt: Date;
@Prop({ default: 0 })
apiCallsCount: number;
@Prop({ type: [String], default: [] })
customModelIds: string[];
}
export const UserSchema = SchemaFactory.createForClass(User);-
Create a NestJS service (
users.service.ts) with the following:- Method:
getUserStatistics(userId: string) - Return an object with:
- Total API calls made by the user
- Number of custom models assigned
- Last login date
- Account creation date
- Use proper dependency injection for the User model
- Method:
-
Implement proper error handling:
- Throw
NotFoundExceptionwhen user doesn't exist - Handle MongoDB errors gracefully
- Add logging using NestJS Logger
- Throw
- ✅ Proper use of NestJS dependency injection
- ✅ Appropriate exception handling with HTTP status codes
- ✅ Correct TypeScript typing and interfaces
- ✅ Use of async/await patterns
- ✅ Clean code structure
Objective: Demonstrate understanding of async operations, retry mechanisms, and error handling.
You need to implement a utility function that calls an external API (Azure Speech Service) with retry logic for transient failures.
interface AzureApiError {
statusCode: number;
message: string;
retryAfter?: number; // seconds
}
interface SpeechRecognitionResult {
transcription: string;
confidence: number;
duration: number;
}-
Create a retry utility function:
async function callWithRetry<T>(apiCall: () => Promise<T>, options?: RetryOptions): Promise<T>;
- Implement exponential backoff (start with 1s, double each retry)
- Maximum 3 retry attempts
- Only retry on specific HTTP status codes: 429, 500, 503
- If error has
retryAfterproperty, respect that delay - Log each retry attempt with context
-
Add proper error handling:
- After max retries, throw a descriptive error with all attempt details
- Handle non-retryable errors immediately
- Type the function generically to work with any Promise
- ✅ Correct retry logic with exponential backoff
- ✅ Proper async/await and Promise handling
- ✅ Selective retry based on error codes
- ✅ Clean, reusable function design
- ✅ Good TypeScript generics usage
Objective: Demonstrate MongoDB/Mongoose proficiency and query optimization skills.
You need to create an aggregated report of model requests with filtering, pagination, and user information.
// model-request.schema.ts
@Schema()
export class ModelRequest extends Document {
@Prop({ required: true, type: mongoose.Schema.Types.ObjectId, ref: 'User' })
userId: mongoose.Types.ObjectId;
@Prop({ required: true, enum: ['STT', 'TTS', 'Translator'] })
modelType: string;
@Prop({ required: true, enum: ['pending', 'approved', 'rejected'] })
status: string;
@Prop({ required: true })
createdAt: Date;
@Prop()
processedAt: Date;
@Prop({ type: Object })
metadata: Record<string, any>;
}
// user.schema.ts (same as Exercise 1)-
Create an aggregation method in a service:
async getModelRequestsReport(filters: { startDate?: Date; endDate?: Date; status?: string; modelType?: string; page?: number; limit?: number; }): Promise<{ data: ModelRequestReport[]; total: number; page: number; totalPages: number; }>
-
Report should include:
- Model request details (id, type, status, dates)
- User information (name, email) via populate/lookup
- Grouped count by status for the date range
- Sorted by creation date (most recent first)
- Paginated results (default: 20 per page)
-
Optimize the query:
- Use MongoDB aggregation pipeline (not multiple queries)
- Suggest appropriate indexes
- Handle empty results gracefully
- ✅ Efficient use of MongoDB aggregation pipeline
- ✅ Proper pagination implementation
- ✅ Clean query structure (avoiding N+1 queries)
- ✅ Correct TypeScript interfaces
- ✅ Index recommendations with reasoning
Objective: Demonstrate code quality, refactoring skills, and debugging ability.
The following code sends notification emails but has code duplication and a subtle bug. Refactor it and fix the issues.
// email.service.ts
import { Injectable, Logger } from '@nestjs/common';
import * as sgMail from '@sendgrid/mail';
@Injectable()
export class EmailService {
private readonly logger = new Logger(EmailService.name);
constructor() {
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
}
async sendModelApprovedEmail(userEmail: string, modelType: string, modelName: string) {
const subject = 'Your Custom Model Has Been Approved';
const htmlContent = `
<html>
<body style="font-family: Arial, sans-serif;">
<h2 style="color: #4CAF50;">Model Approved!</h2>
<p>Hello,</p>
<p>Your ${modelType} model "${modelName}" has been approved and is now ready to use.</p>
<p><strong>Model Type:</strong> ${modelType}</p>
<p><strong>Model Name:</strong> ${modelName}</p>
<p>Best regards,<br>The Team</p>
</body>
</html>
`;
const textContent = `Model Approved!\n\nYour ${modelType} model "${modelName}" has been approved.\n\nModel Type: ${modelType}\nModel Name: ${modelName}\n\nBest regards,\nThe Team`;
await sgMail.send({
to: userEmail,
from: process.env.FROM_EMAIL,
subject: subject,
text: textContent,
html: htmlContent,
});
this.logger.log(`Approval email sent to ${userEmail}`);
}
async sendModelRejectedEmail(userEmail: string, modelType: string, modelName: string, reason: string) {
const subject = 'Your Custom Model Request Has Been Rejected';
const htmlContent = `
<html>
<body style="font-family: Arial, sans-serif;">
<h2 style="color: #F44336;">Model Rejected</h2>
<p>Hello,</p>
<p>Unfortunately, your ${modelType} model "${modelName}" has been rejected.</p>
<p><strong>Model Type:</strong> ${modelType}</p>
<p><strong>Model Name:</strong> ${modelName}</p>
<p><strong>Reason:</strong> ${reason}</p>
<p>Best regards,<br>The Team</p>
</body>
</html>
`;
const textContent = `Model Rejected\n\nYour ${modelType} model "${modelName}" has been rejected.\n\nModel Type: ${modelType}\nModel Name: ${modelName}\nReason: ${reason}\n\nBest regards,\nThe Team`;
await sgMail.send({
to: userEmail,
from: process.env.FROM_EMAIL,
subject: subject,
text: textContent,
html: htmlContent,
});
this.logger.log(`Rejection email sent to ${userEmail}`);
}
async sendModelProcessingEmail(userEmail: string, modelType: string, modelName: string, estimatedTime: string) {
const subject = 'Your Custom Model Is Being Processed';
const htmlContent = `
<html>
<body style="font-family: Arial, sans-serif;">
<h2 style="color: #2196F3;">Model Processing</h2>
<p>Hello,</p>
<p>Your ${modelType} model "${modelName}" is currently being processed.</p>
<p><strong>Model Type:</strong> ${modelType}</p>
<p><strong>Model Name:</strong> ${modelName}</p>
<p><strong>Estimated Time:</strong> ${estimatedTime}</p>
<p>Best regards,<br>The Team</p>
</body>
</html>
`;
const textContent = `Model Processing\n\nYour ${modelType} model "${modelName}" is being processed.\n\nModel Type: ${modelType}\nModel Name: ${modelName}\nEstimated Time: ${estimatedTime}\n\nBest regards,\nThe Team`;
await sgMail.send({
to: userEmail,
from: process.env.FROM_EMAIL,
subject: subject,
text: textContent,
html: htmlContent,
});
this.logger.log(`Processing email sent to ${userEmail}`);
}
}-
Identify the bugs:
- What happens if
SENDGRID_API_KEYorFROM_EMAILare not set? - What happens if SendGrid API call fails?
- Are there any potential XSS vulnerabilities?
- What happens if
-
Refactor the code:
- Eliminate code duplication
- Create reusable helper functions for email templates
- Create a generic
sendEmailmethod - Improve error handling and validation
-
Add proper configuration:
- Use NestJS ConfigService for environment variables
- Validate configuration on service initialization
- Add proper error types
- ✅ Identification of all bugs and issues
- ✅ Significant reduction in code duplication
- ✅ Proper error handling and validation
- ✅ Use of NestJS best practices (ConfigService)
- ✅ Security considerations (XSS, input validation)
- ✅ Clean, maintainable code structure
- Follow NestJS and TypeScript best practices
- Use strict TypeScript (avoid
anytypes) - Add comments for complex logic
- Use meaningful variable and function names
- Follow SOLID principles
-
Code files for each exercise:
- Service implementations
- Any additional utility files
-
A
SOLUTION.mdfile explaining:- Your approach to each exercise
- Design decisions and trade-offs
- Any assumptions made
- Time spent on each exercise
- How you would improve given more time
- Exercise 1: 45 minutes
- Exercise 2: 60 minutes
- Exercise 3: 45 minutes
- Exercise 4: 60 minutes
- Total: ~3.5 hours
You may complete exercises in any order based on your confidence level.
If you need clarification:
- Document your question
- Make a reasonable assumption
- Note the assumption in your
SOLUTION.md - Proceed with the exercise