Sitemap

Mnemony

17 min readAug 17, 2025

--

Building Mnemony: A Full-Stack AI-Powered Learning Platform

Part 1: Backend Architecture

What is Mnemony?

At its core, Mnemony is a learning platform that combines two powerful concepts:

  • AI-generated content: Users can input any topic or prompt, and the system generates relevant quiz questions
  • Spaced repetition: The platform tracks user performance and schedules review sessions at optimal intervals for long-term retention

The platform allows users to:

  • Generate quizzes instantly from any topic using AI
  • Organize quizzes into collections
  • Track their learning progress with a personalized training system
  • Review material at scientifically-optimized intervals

High-Level Architecture Overview

Mnemony’s backend follows a modular monolithic architecture pattern, strategically designed to balance development velocity with scalability requirements. Here’s the bird’s-eye view of the system:

┌─────────────────────────────────────────────────────────────────┐
│ Client Layer │
│ (Web App, Mobile App, Third-party Integrations via SDK) │
└────────────────────────────┬────────────────────────────────────┘
│
HTTPS/REST API
│
┌─────────────────────────────┴────────────────────────────────────┐
│ API Gateway (NGINX Ingress) │
│ • SSL Termination │
│ • Load Balancing │
│ • Rate Limiting │
└─────────────────────────────┬────────────────────────────────────┘
│
─────────────────────────────┴─────────────────────────────────────
│ NestJS Application Core │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ API Layer │ │
│ │ • Controllers • Guards • Interceptors │ │
│ │ • Filters • Validators • OpenAPI/Swagger │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Business Logic Layer │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Quiz │ │ Training │ │Collection│ ... │ │
│ │ │ Service │ │ Service │ │ Service │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Data Access Layer (Repository) │ │
│ │ • MongoDB Repositories │ │
│ │ • Query Optimization │ │
│ │ • Transaction Management │ │
│ └──────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────
│
┌────────────────────┼───────────────────┐
│ │ │
┌───────┴──────┐ ┌────────┴──────┐ ┌────────┴──────┐
│ MongoDB │ │ External APIs│ │ Email Service│
│ Database │ │ (OpenAI/ │ │ (Brevo) │
│ │ │ DeepSeek) │ │ │
└──────────────┘ └───────────────┘ └───────────────┘

Technology Stack at a Glance

The backend is built on a robust foundation of modern technologies:

  • Runtime: Node.js with TypeScript for type safety
  • Framework: NestJS for enterprise-grade architecture
  • Database: MongoDB with Mongoose ODM
  • Authentication: JWT + Passport.js with Google OAuth 2.0
  • AI Integration: OpenAI/DeepSeek APIs for content generation
  • Container Orchestration: Kubernetes on DigitalOcean
  • CI/CD: GitLab CI with automated deployments
  • API Documentation: OpenAPI/Swagger with auto-generated client SDKs

Technology Choices

Core Stack:

  • Node.js + TypeScript: JavaScript’s ecosystem with type safety.
  • NestJS: NestJS’s opinionated structure and built-in architectural patterns were exactly what I needed
  • MongoDB: The flexible schema works well for a rapidly evolving project where I’m still figuring out data relationships

Key Dependencies:

  • Mongoose: Provides structure to MongoDB while maintaining flexibility
  • Passport + JWT: Battle-tested authentication solution
  • OpenAI API: For AI-powered quiz generation (with DeepSeek as a cost-effective alternative)
  • Brevo: For transactional emails (formerly SendinBlue)

Project Structure

One of the things I appreciate most about NestJS is how it encourages modular architecture. Here’s how I organized the codebase:

src/
├── collection/ # Collection management
│ ├── collection.controller.ts
│ ├── collection.service.ts
│ ├── collection.module.ts
│ ├── schemas/
│ ├── dtos/
│ └── repositories/
├── quiz/ # Quiz generation & management
│ ├── quiz.controller.ts
│ ├── quiz.service.ts
│ ├── schemas/
│ ├── dtos/
│ └── api/ # AI service integrations
├── training/ # Spaced repetition logic
│ ├── training.controller.ts
│ ├── training.service.ts
│ └── schemas/
├── guard/ # Authentication & user management
│ ├── auth/
│ ├── guard-auth/
│ └── user/
├── global/ # Shared utilities
│ ├── exceptions/
│ ├── filters/
│ ├── loggers/
│ └── constants/
└── main.ts # Application entry point

Each module is self-contained with its own controllers, services, DTOs, schemas, and repositories. This structure has made it easy to develop features independently and will simplify the process if I ever need to extract microservices.

Core Design Patterns

Throughout the backend, I’ve implemented several design patterns that have proven invaluable:

1. Repository Pattern

// Instead of directly accessing the database in services
// I use repositories to abstract data access
export class CollectionRepository {
async createCollection(dto: CreateCollectionDto): Promise<CollectionDocument> {
// Database logic isolated here
}
}

2. DTOs with Class Validator

// Every API endpoint has strictly typed and validated inputs
export class CreateQuizDto {
@IsString()
@IsNotEmpty()
prompt: string;
@IsNumber()
@Min(1)
@Max(20)
numberOfQuestions: number;
}

3. Custom Exception Handling

// Consistent error responses across the entire API
throw new MnemonyApiGlobalException({
exceptionMessage: "Quiz not found",
code: 404,
error: "QUIZ_NOT_FOUND"
});

API Design & Documentation

One of my priorities was creating a developer-friendly API. I achieved this through:

OpenAPI/Swagger Integration: Every endpoint is automatically documented with NestJS decorators. This powers the interactive API documentation at /api.

Auto-generated TypeScript Client: Using OpenAPI Generator, I automatically generate a TypeScript SDK that’s published to GitLab’s NPM registry. This means the frontend always has type-safe API calls that stay in sync with the backend.

// The frontend can simply import and use the generated client
import { QuizApi } from '@mnemony/mnemony-api';
const quizApi = new QuizApi(configuration);
await quizApi.createAIQuiz({ prompt: "Ancient Rome", numberOfQuestions: 10 });

Deployment & Infrastructure

The application is containerized and deployed on DigitalOcean’s Kubernetes service:

  • Docker: Multi-stage builds optimize the image size
  • Kubernetes: Handles orchestration, though honestly, it’s overkill for the current scale — but great for learning!
  • GitLab CI/CD: Automated builds and deployments triggered by version tags
  • SSL: Let’s Encrypt certificates managed by cert-manager

The entire deployment pipeline is automated:

  1. Push a version tag (e.g., v1.0.0)
  2. GitLab CI builds and pushes the Docker image
  3. Kubernetes deployment is updated with the new image
  4. Client library is generated and published for any client-v* tags

Current Limitations:

  • No caching layer yet (Redis is on the roadmap)
  • Database queries could be better optimized
  • Limited error tracking and monitoring
  • No comprehensive test coverage
  • The Kubernetes setup is definitely over-engineered for current needs

Core Architecture & Design Patterns

Modular Architecture

Mnemony follows NestJS’s module-based architecture, where each feature domain is encapsulated in its own module. This approach provides clear boundaries and dependencies between different parts of the application.

@Module({
imports: [
MongooseModule.forFeature([{ name: Collection.name, schema: CollectionSchema }]),
QuizModule,
TrainingModule,
],
controllers: [CollectionController],
providers: [CollectionService, CollectionRepository],
exports: [CollectionService]
})
export class CollectionModule {}

Each module contains:

  • Controller: HTTP route handlers
  • Service: Business logic implementation
  • Repository: Data access abstraction
  • DTOs: Data transfer objects for validation
  • Schemas: Database model definitions

Repository Pattern

The repository pattern abstracts database operations from business logic, making the codebase more testable and maintainable:

@Injectable()
export class CollectionRepository {
constructor(
@InjectModel(Collection.name)
private collectionModel: Model<CollectionDocument>
) {}
async createCollection(dto: CreateCollectionDto): Promise<CollectionDocument> {
return this.collectionModel.create({
ownerId: new mongoose.Types.ObjectId(dto.ownerId),
name: dto.name,
quizzes: []
});
}
async findByOwner(ownerId: string): Promise<CollectionDocument[]> {
return this.collectionModel
.find({ ownerId: new mongoose.Types.ObjectId(ownerId) })
.sort({ updatedAt: -1 });
}
}

DTO Pattern with Validation

Data Transfer Objects ensure type safety and validation at API boundaries:

export class CreateQuizDto {
@ApiProperty({ description: 'Prompt to generate AI quiz from' })
@IsString()
@IsNotEmpty()
prompt: string;

@ApiProperty({ description: 'Number of questions' })
@IsNumber()
@Min(1)
@Max(20)
numberOfQuestions: number;

@ApiPropertyOptional({ enum: ResponseDifficulty })
@IsEnum(ResponseDifficulty)
@IsOptional()
responseDifficulty?: ResponseDifficulty;
}

Service Layer Pattern

Services contain the core business logic, orchestrating between repositories and external services:

@Injectable()
export class QuizService {
constructor(
private readonly quizRepository: QuizRepository,
private readonly quizItemRepository: QuizItemRepository,
private readonly deepSeekApi: DeepSeekApi
) {}

async createAIQuiz(dto: CreateAiQuizDto): Promise<QuizDto> {
// Generate quiz using AI
const aiResponse = await this.deepSeekApi.generateQuiz(dto);

// Save to database
const quiz = await this.quizRepository.create({
prompt: dto.prompt,
items: aiResponse.questions
});

// Transform to DTO
return plainToInstance(QuizDto, quiz, {
excludeExtraneousValues: true
});
}
}

Dependency Injection

NestJS’s built-in dependency injection container manages service instantiation and lifecycle:

@Controller('collection')
export class CollectionController {
constructor(
private readonly collectionService: CollectionService,
private readonly trainingService: TrainingService
) {}
// Services are automatically injected
}

Exception Handling

A global exception filter provides consistent error responses across the API:

@Catch()
export class MnemonyHttpExceptionFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();

const status = exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const errorResponse = {
statusCode: status,
data: {
success: false,
error: {
message: exception.message,
timestamp: new Date().toISOString(),
path: request.url,
}
}
};
response.status(status).json(errorResponse);
}
}

Response Standardization

All API responses follow a consistent structure using a wrapper decorator:

@ApiResponseWrapper(CollectionDto)
@Post('create')
async createCollection(@Body() dto: CreateCollectionDto): Promise<CollectionDto> {
return this.collectionService.createCollection(dto);
}
// Produces response:
{
"statusCode": 200,
"data": {
"success": true,
"payload": { /* CollectionDto */ }
}
}

Authentication Guards

JWT-based authentication is implemented using NestJS guards:

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext) {
return super.canActivate(context);
}
}
// Applied at controller level
@UseGuards(JwtAuthGuard)
@Controller('quiz')
export class QuizController {}

Data Transformation

The class-transformer library handles DTO transformation and property exclusion:

export class UserDto {
@Expose()
id: string;
@Expose()
email: string;
@Exclude() // Never exposed in responses
password: string;
}
// Usage in service
return plainToInstance(UserDto, user, {
excludeExtraneousValues: true
});

These architectural patterns create a maintainable, scalable foundation that separates concerns and enables independent testing of each component.

Technology Stack Deep Dive

Core Framework and Runtime

Node.js v18 + TypeScript
The application runs on Node.js v18 with TypeScript for type safety. TypeScript configuration enforces strict typing, improving code reliability and developer experience.

Get Roman’s stories in your inbox

Join Medium for free to get updates from this writer.

NestJS v9.0
NestJS provides the architectural foundation with built-in support for dependency injection, modular structure, and decorators. Key features utilized include:

  • Module system for domain separation
  • Built-in validation pipes
  • Exception filters
  • OpenAPI integration

Database Layer

MongoDB with Mongoose
MongoDB serves as the primary database, chosen for its flexible schema and ease of development. Mongoose provides object modeling with TypeScript support:

@Schema({ timestamps: true })
export class Collection {
@Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true })
ownerId: mongoose.Types.ObjectId;

@Prop({ required: true })
name: string;

@Prop({ type: [CollectionItem], default: [] })
quizzes: CollectionItem[];
}
export const CollectionSchema = SchemaFactory.createForClass(Collection);

Authentication & Security

JWT Authentication
JWT tokens handle stateless authentication with configurable expiration:

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: configService.get<string>('JWT_SECRET'),
});
}
async validate(payload: any) {
return { userId: payload.sub, email: payload.email };
}
}

Google OAuth 2.0
Passport.js integrates Google authentication for social login:

@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor() {
super({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: '/guard-auth/google-success',
scope: ['email', 'profile'],
});
}
}

Password Security
Bcrypt handles password hashing with salt rounds:

const hashedPassword = await bcrypt.hash(password, 10);
const isValid = await bcrypt.compare(plainPassword, hashedPassword);

Data Encryption
Sensitive data uses AES-256-CBC encryption:

const cipher = createCipheriv('aes-256-cbc', key, iv);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');

External Service Integrations

AI Services (OpenAI/DeepSeek)
Quiz generation leverages AI language models through API integration:

async generateQuiz(prompt: string, count: number) {
const response = await axios.post(
'https://api.deepseek.com/v1/chat/completions',
{
model: 'deepseek-chat',
messages: [
{ role: 'system', content: 'Generate quiz questions...' },
{ role: 'user', content: prompt }
],
temperature: 0.7
},
{
headers: {
'Authorization': `Bearer ${this.apiToken}`,
'Content-Type': 'application/json'
}
}
);
return this.parseQuizResponse(response.data);
}

Email Service (Brevo)
Transactional emails for verification and password reset:

@Injectable()
export class EmailService {
constructor(@Inject('BREVO_CLIENT') private brevo: TransactionalEmailsApi) {}async sendVerificationCode(email: string, code: number) {
const sendSmtpEmail = new SendSmtpEmail();
sendSmtpEmail.to = [{ email }];
sendSmtpEmail.templateId = VERIFICATION_TEMPLATE_ID;
sendSmtpEmail.params = { code };

return this.brevo.sendTransacEmail(sendSmtpEmail);
}
}

API Documentation

OpenAPI/Swagger
Automatic API documentation generation using decorators:

@ApiTags('Quiz')
@Controller('quiz')
export class QuizController {
@ApiOperation({ summary: 'Create AI-generated quiz' })
@ApiBody({ type: CreateQuizDto })
@ApiResponse({ status: 201, type: QuizDto })
@Post('create-ai')
async createAIQuiz(@Body() dto: CreateQuizDto): Promise<QuizDto> {
return this.quizService.createAIQuiz(dto);
}
}

Client SDK Generation
OpenAPI specification drives TypeScript client generation:

npx @openapitools/openapi-generator-cli generate \
-i swagger.json \
-g typescript-axios \
-o client/

Environment Configuration

Configuration management through @nestjs/config:

@Module({
imports: [
ConfigModule.forRoot(),
MongooseModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (config: ConfigService) => ({
uri: config.get<string>('MONGO_URI'),
})
})
]
})

Environment variables managed through .env files with validation at startup. Critical configurations stored in Kubernetes secrets for production deployment.

Core Features Implementation

AI-Powered Quiz Generation

The quiz generation system integrates with DeepSeek API for cost-effective content creation. The implementation uses custom prompt templates with special delimiters (¥ for questions, ¤ for answers) to ensure consistent parsing. The system supports both traditional request-response mode and Server-Sent Events (SSE) streaming for real-time quiz generation, allowing users to see questions appear progressively. Token usage tracking and cost calculation are built into every API call to monitor expenses.

Spaced Repetition Algorithm

The training system implements a simplified spaced repetition algorithm based on the SM-2 formula. Each quiz item tracks repetition count, easiness factor, and next review date. Correct answers increase the interval between reviews (1, 2, 4, 7, 14, 30, 60 days), while incorrect answers reset the item for immediate review. The easiness factor adjusts based on performance, personalizing the review schedule for each user’s learning pace.

Collection & Quiz Management

Collections provide hierarchical organization for quizzes, with each user having a default “Uncategorized” collection. The system supports full CRUD operations with cascade deletion — removing a collection deletes all contained quizzes and their items. Quiz operations include moving between collections, bulk adding to training, and prompt editing. Each quiz maintains metadata about its container collection for efficient navigation and management.

Search Functionality

The search service provides unified search across three entity types: collections, quizzes, and quiz items. Using MongoDB regex queries, it performs parallel searches and combines results with type indicators. Pagination is implemented at the service level with skip/limit parameters. Search results include context information (parent collection for quizzes, parent quiz for items) to help users navigate to the desired content.

Training Queue Management

Training items are managed in a separate collection that references quiz items, tracking individual progress per user. The system supports bulk operations for adding/removing items from training, with automatic deduplication. Training retrieval prioritizes items due for review, sorted by next review date. Collection-based filtering allows users to focus training on specific topics.

Bulk Operations

The system optimizes database operations through MongoDB bulk writes for updating multiple quiz items simultaneously. Cascade operations handle parent-child relationships automatically — deleting a quiz removes all its items from both the quiz collection and users’ training queues. Transaction-like behavior ensures data consistency during complex multi-step operations.

Category-Based Generation

A pre-populated database of categories and topics enables quick quiz generation without user input. Categories include searchable aliases for improved discovery. The system randomly selects topics within a category to generate diverse quizzes. This feature addresses the “blank page problem” by providing instant quiz options for users unsure what to study.

API Design & Documentation

RESTful API Structure

The API follows RESTful conventions with resource-based URLs and standard HTTP methods. All endpoints are prefixed with resource names (e.g., /quiz, /collection, /training) and use POST for operations beyond basic CRUD to maintain consistency. Authentication is required for all endpoints except public auth routes, enforced through JWT Bearer tokens in request headers.

OpenAPI/Swagger Integration

NestJS decorators automatically generate OpenAPI specifications from code. Each controller uses @ApiTags for grouping, @ApiBearerAuth for security definitions, and @ApiBody for request validation. The Swagger UI is available at /api in development, providing interactive documentation and testing capabilities. DTOs include @ApiProperty decorators with descriptions and validation rules that appear in the generated documentation.

Response Standardization

All API responses follow a consistent wrapper structure through a custom @ApiResponseWrapper decorator. Responses include a status code, success flag, and payload containing the actual data. Error responses maintain the same structure with error details instead of payload. This standardization simplifies client-side error handling and response parsing across all endpoints.

Auto-Generated TypeScript Client

The OpenAPI specification drives automatic client SDK generation using OpenAPI Generator. The TypeScript-Axios client is published to GitLab’s NPM registry as @mnemony/mnemony-api. Client generation is triggered by git tags prefixed with client-v, ensuring version synchronization. The generated client includes full TypeScript types, API methods, and model definitions matching the backend exactly.

DTO Validation Pipeline

Request validation occurs automatically through NestJS validation pipes using class-validator decorators. DTOs define strict typing and validation rules including required fields, data types, string patterns, and numeric ranges. Validation errors return standardized 400 responses with detailed field-level error messages. The same DTOs generate both runtime validation and OpenAPI documentation, ensuring consistency.

API Versioning Strategy

The API uses URL path versioning with the version included in the OpenAPI specification. Breaking changes increment the major version while maintaining backward compatibility through parallel endpoint support. The client library version matches the API version for clear compatibility tracking. Non-breaking additions use minor version increments without affecting existing clients.

Streaming Endpoints

Server-Sent Events (SSE) enable real-time quiz generation streaming. Streaming endpoints bypass the standard response wrapper, sending typed events directly. The implementation sets appropriate headers for SSE including Content-Type: text/event-stream and connection keep-alive. Clients receive incremental quiz items as they're generated, improving perceived performance for AI operations.

Error Response Patterns

Custom exception filters catch all errors and transform them into consistent response formats. Business logic errors throw MnemonyApiGlobalException with specific error codes and messages. Validation errors return field-specific error arrays for precise client-side handling. Unexpected errors are logged internally while returning generic error messages to clients for security.

Infrastructure & DevOps

Deployment Architecture

┌─────────────────────────────────────────────────────────────┐
│ GitLab Repository │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Source Code │ Dockerfile │ K8s Manifests │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────┬────────────────────────────────────────┘
│ git push tag: v1.0.0
▼
┌─────────────────────────────────────────────────────────────┐
│ GitLab CI/CD Pipeline │
├─────────────────────────────────────────────────────────────┤
│ Build Stage Deploy Stage Client Gen Stage │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Docker │────▶│ Kubectl │ │ OpenAPI Gen │ │
│ │ Build │ │ Apply │ │ NPM Publish │ │
│ └──────────┘ └──────────┘ └──────────────┘ │
└────────┬──────────────────┬────────────────────┬───────────┘
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ GitLab │ │ DigitalOcean │ │ GitLab NPM │
│ Registry │ │ Kubernetes │ │ Registry │
└──────────────┘ └──────────────┘ └──────────────┘

Kubernetes Infrastructure

yaml

mnemony-namespace/
├── Deployment (mnemony-api)
│ ├── Pod Template
│ │ ├── Container: mnemony-api
│ │ ├── Ports: 3000
│ │ └── Environment (from Secrets)
│ └── Replicas: 1
├── Service (ClusterIP)
│ └── Port: 80 → 3000
├── Ingress (NGINX)
│ ├── Host: mnemony.org.in
│ ├── TLS: Let's Encrypt
│ └── Backend: mnemony-api:80
└── Secrets
├── my-secrets (DB, APIs)
├── brevo (Email)
└── mnemony-api-tls (SSL)

CI/CD Pipeline Flow

The deployment pipeline consists of three independent stages triggered by Git tags:

Backend Deployment (tags: v)*

  1. Build Stage → Docker image creation and push to registry
  2. Deploy Stage → Kubernetes deployment update with new image
  3. Rollout → Zero-downtime deployment with health checks

Client SDK Release (tags: client-v)*

  1. Generate → OpenAPI specification to TypeScript client
  2. Publish → NPM package to GitLab Package Registry
  3. Version → Automatic version synchronization with API

SSL Certificate Lifecycle

Let's Encrypt ──ACME──▶ cert-manager ──Creates──▶ Certificate
│ │
│ ▼
ClusterIssuer TLS Secret
│ │
│ ▼
HTTP Challenge Ingress (TLS)
│ │
▼ ▼
Auto-renewal HTTPS Traffic
(every 60 days) (mnemony.org.in)

Secret Management Architecture

Environment variables flow through multiple security layers:

Developer Workstation          GitLab CI/CD              Kubernetes
(local .env) (Protected Variables) (Secret Objects)
│ │ │
Never committed Masked in logs Mounted at runtime
│ │ │
▼ ▼ ▼
Development only Build & Deploy Production Pods

Critical secrets include:

  • Database: MongoDB connection string with authentication
  • API Keys: DeepSeek (AI), Brevo (Email), JWT signing key
  • Encryption: AES-256 parameters for sensitive data

Deployment Commands Cheatsheet

bash

# Manual deployment (emergency use only)
kubectl set image deployment/mnemony-api \
mnemony-api=registry.gitlab.com/mnemony/mnemony-api:v1.0.0
# Check deployment status
kubectl rollout status deployment/mnemony-api
# View logs
kubectl logs -f deployment/mnemony-api --tail=100
# Access secret management
kubectl create secret generic my-secrets \
--from-literal=MONGO="mongodb://..." \
--from-literal=JWT_SECRET="..."
# Certificate debugging
kubectl describe certificate mnemony-api-tls
kubectl describe clusterissuer letsencrypt-prod

Cost Optimization

The infrastructure is designed for efficiency:

  • Single replica deployment suitable for current traffic (can scale when needed)
  • 180MB container images reduce registry storage and transfer costs
  • Automatic certificate renewal eliminates manual intervention costs
  • GitLab’s free tier for CI/CD (400 minutes/month)
  • DigitalOcean’s managed Kubernetes at $12/month for small cluster

Potential Future Infrastructure Additions

  • Horizontal Pod Autoscaler: Scale replicas based on CPU/memory metrics
  • Redis Integration: Caching layer for frequently accessed data
  • Prometheus + Grafana: Advanced monitoring and visualization
  • Backup Strategy: Automated MongoDB backups to S3-compatible storage
  • Multi-environment: Separate staging environment for testing

Part 2: Frontend Architecture

What Powers the Frontend?

Architecture Overview

src/
├── modules/ # Feature modules
│ ├── tabs/ # Main app tabs
│ │ ├── quiz/ # Quiz creation & management
│ │ ├── training/ # Spaced repetition training
│ │ └── search/ # Search & collection management
│ ├── auth/ # Authentication flow
│ ├── user/ # User profile & settings
│ ├── notifications/ # Push notifications
│ └── security/ # App security checks
├── contexts/ # Global state providers
│ ├── AuthContext.tsx
│ ├── CoreApiContext.tsx
│ ├── ToastContext.tsx
│ └── ...
├── api/ # API client configuration
├── localization/ # i18n translations
├── theme/ # Design system
└── global/ # Shared utilities
├── components/
├── hooks/
└── utils/

Core Framework: React Native 0.79 with React 19 provides the foundation, chosen for its mature ecosystem and ability to deliver near-native performance. Expo SDK 53 accelerates development with pre-built native modules and over-the-air updates.

Navigation: React Navigation v7 handles the complex navigation hierarchy with native stack navigators for each tab, bottom tab navigation for the main interface, and modal presentations for overlays. The navigation structure supports deep linking and maintains separate navigation stacks per tab.

State Management: Rather than adopting a heavy state management library, it’s been opted for React Context API combined with custom hooks. This approach keeps the bundle size small while providing sufficient state management for authentication, API client distribution, and UI state coordination.

UI & Animation: The interface combines Expo’s Linear Gradient for vibrant backgrounds, React Native Reanimated v3 for performant animations, and Moti for declarative animation patterns. These libraries work together to create fluid, 60fps animations that enhance the learning experience without sacrificing performance.

Type Safety: TypeScript extends throughout the codebase with the auto-generated API client from the backend providing end-to-end type safety. This catches potential issues at compile time and enables confident refactoring.

Core Design Patterns

Feature Module Pattern

Each feature module encapsulates its entire functionality:

  • Screens: Complete page components that orchestrate data fetching and user interactions
  • Components: Reusable UI elements specific to the feature
  • Hooks: Custom hooks that encapsulate business logic and API calls
  • Navigator: Feature-specific navigation stack
  • Utils: Helper functions for the feature domain

This organization allows features to evolve independently while maintaining clear boundaries.

Custom Hook Architecture

Business logic lives in custom hooks that separate concerns from UI components. Each API operation has a dedicated hook that handles loading states, error handling, and success callbacks. This pattern has dramatically improved component readability and testability.

Context Provider Composition

Global state management uses a layered context approach. The app wraps providers in a specific order, with authentication at the base, followed by the API client provider, then UI-specific contexts like toast notifications. This hierarchy ensures dependencies are properly initialized.

Optimistic UI Updates

To create a responsive feel, the app implements optimistic updates for user actions. When adding items to training or moving quizzes between collections, the UI updates immediately while the API call happens in the background. If the operation fails, the UI rolls back with a clear error message.

Navigation Architecture

The navigation structure reflects the app’s information architecture with three primary tabs, each containing its own navigation stack:

Create Tab: Handles quiz generation through AI prompts or manual creation. The stack includes category selection, quiz preview, and save options screens.

Training Tab: Manages the spaced repetition learning flow with a swiper interface for reviewing cards and a progress dashboard showing learning statistics.

Search Tab: Provides unified search across collections, quizzes, and individual quiz items with drill-down navigation to preview and edit content.

Modal presentations overlay the tab navigation for operations like creating collections, editing quiz items, and displaying user profiles. This keeps the main navigation context while allowing focused interactions.

API Integration

The frontend consumes the auto-generated TypeScript client from the backend’s OpenAPI specification. This client provides:

  • Type-safe API calls: Every endpoint has typed parameters and responses
  • Automatic token management: The CoreApiContext handles authentication tokens
  • Consistent error handling: API errors transform into user-friendly messages
  • Request interceptors: For adding authentication headers and logging

The API client updates automatically when the backend deploys, ensuring frontend and backend remain synchronized.

Real-time Features

Server-Sent Events for AI Generation

Quiz generation uses SSE to stream questions as they’re created. Users see questions appear progressively rather than waiting for the entire quiz to generate. This creates a more engaging experience and reduces perceived latency.

Background Task Management

The app schedules daily training reminders using Expo’s background task API. Notifications trigger at the user’s preferred time, encouraging consistent practice for optimal retention.

Internationalization

The app supports multiple languages through i18next with React Native specific bindings. Language files load dynamically based on device settings, with English as the fallback. The translation system handles pluralization, formatting, and contextual translations.

Security & Authentication

The frontend implements multiple security layers:

  • Jailbreak/Root Detection: Prevents the app from running on compromised devices
  • Certificate Pinning: Validates API server certificates to prevent man-in-the-middle attacks
  • Secure Storage: Sensitive data like authentication tokens use encrypted storage

Google OAuth provides social authentication with fallback to anonymous accounts for users who prefer not to create accounts immediately.

--

--