Skip to content

Instantly share code, notes, and snippets.

@alfchee
Created October 30, 2025 23:01
Show Gist options
  • Select an option

  • Save alfchee/f14cdd5a5be419b3da4e1e910cff49c2 to your computer and use it in GitHub Desktop.

Select an option

Save alfchee/f14cdd5a5be419b3da4e1e910cff49c2 to your computer and use it in GitHub Desktop.

Node.js Developer Technical Assessment

Backend Development - Practical Exercises

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.


Technology Stack Overview

  • Backend Framework: NestJS (TypeScript)
  • Database: MongoDB with Mongoose ODM
  • Testing: Jest
  • Architecture: Microservices
  • External Services: Azure Speech/Translator, SendGrid Email
  • Build Tool: Nx Monorepo

Exercise 1: Service Enhancement & Error Handling (45 minutes)

Objective: Demonstrate NestJS service patterns, error handling, and MongoDB integration.

Scenario

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.

Given Code Structure

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);

Tasks

  1. 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
  2. Implement proper error handling:

    • Throw NotFoundException when user doesn't exist
    • Handle MongoDB errors gracefully
    • Add logging using NestJS Logger

Evaluation Criteria

  • ✅ 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

Exercise 2: Asynchronous Processing & Retry Logic (60 minutes)

Objective: Demonstrate understanding of async operations, retry mechanisms, and error handling.

Scenario

You need to implement a utility function that calls an external API (Azure Speech Service) with retry logic for transient failures.

Given Interface

interface AzureApiError {
  statusCode: number;
  message: string;
  retryAfter?: number; // seconds
}

interface SpeechRecognitionResult {
  transcription: string;
  confidence: number;
  duration: number;
}

Tasks

  1. 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 retryAfter property, respect that delay
    • Log each retry attempt with context
  2. 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

Evaluation Criteria

  • ✅ 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

Exercise 3: Database Query Optimization & Aggregation (45 minutes)

Objective: Demonstrate MongoDB/Mongoose proficiency and query optimization skills.

Scenario

You need to create an aggregated report of model requests with filtering, pagination, and user information.

Given Schema

// 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)

Tasks

  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;
    }>
  2. 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)
  3. Optimize the query:

    • Use MongoDB aggregation pipeline (not multiple queries)
    • Suggest appropriate indexes
    • Handle empty results gracefully

Evaluation Criteria

  • ✅ Efficient use of MongoDB aggregation pipeline
  • ✅ Proper pagination implementation
  • ✅ Clean query structure (avoiding N+1 queries)
  • ✅ Correct TypeScript interfaces
  • ✅ Index recommendations with reasoning

Exercise 4: Code Refactoring & Bug Fix (60 minutes)

Objective: Demonstrate code quality, refactoring skills, and debugging ability.

Scenario

The following code sends notification emails but has code duplication and a subtle bug. Refactor it and fix the issues.

Given Code (Problematic)

// 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}`);
  }
}

Tasks

  1. Identify the bugs:

    • What happens if SENDGRID_API_KEY or FROM_EMAIL are not set?
    • What happens if SendGrid API call fails?
    • Are there any potential XSS vulnerabilities?
  2. Refactor the code:

    • Eliminate code duplication
    • Create reusable helper functions for email templates
    • Create a generic sendEmail method
    • Improve error handling and validation
  3. Add proper configuration:

    • Use NestJS ConfigService for environment variables
    • Validate configuration on service initialization
    • Add proper error types

Evaluation Criteria

  • ✅ 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

Submission Guidelines

Code Quality Expectations

  • Follow NestJS and TypeScript best practices
  • Use strict TypeScript (avoid any types)
  • Add comments for complex logic
  • Use meaningful variable and function names
  • Follow SOLID principles

What to Submit

  1. Code files for each exercise:

    • Service implementations
    • Any additional utility files
  2. A SOLUTION.md file 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

Time Guidelines

  • 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.


Questions & Clarifications

If you need clarification:

  1. Document your question
  2. Make a reasonable assumption
  3. Note the assumption in your SOLUTION.md
  4. Proceed with the exercise

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment