A robust NestJS backend API for the Triads word association game. This application provides endpoints for managing triads (word groups with clues), triad groups, game logic, and bulk data import capabilities.
- Features
- Tech Stack
- Prerequisites
- Getting Started
- API Documentation
- Project Structure
- Database Schema
- Environment Variables
- Memory Optimization
- Development
- Production Deployment
- Get Cues: Retrieve all available cues for triads
- Get Hints: Get hints for specific triads
- Check Solution: Validate if provided cues match a triad
- Check Answer: Verify if an answer matches the correct triad keyword
- Fourth Triad Cues: Get cues for finding the fourth triad in a group
- List Groups: Paginated listing with search functionality
- Get Group Triads: Retrieve all triads in a specific group
- Create Group: Create new triad groups
- Update Group: Modify existing triad groups
- Delete Group: Remove triad groups
- Toggle Active Status: Activate/deactivate triad groups
- Excel Import: Bulk import triads from Excel files (
.xlsx) - File size limit: 10MB
- Supports structured data import for efficient data management
- Framework: NestJS v11
- Language: TypeScript
- Database: PostgreSQL 16.3
- ORM: Prisma
- Validation: class-validator, class-transformer
- Documentation: Swagger/OpenAPI
- Logging: Winston with daily rotation
- Security: Helmet, CORS, Throttler
- Process Management: PM2
- Containerization: Docker Compose
- Node.js >= 18.x
- pnpm (recommended) or npm
- PostgreSQL 16.3+ (or use Docker Compose)
- Docker & Docker Compose (optional, for database)
git clone <repository-url>
cd triads-backendpnpm installCreate a .env file in the root directory:
# Application
APP_PORT=3000
NODE_ENV=development
# Database
DATABASE_URL="postgresql://user:password@localhost:5432/triads_db?connection_limit=5&pool_timeout=20"
# PostgreSQL (for Docker Compose)
POSTGRES_PORT=5432
# CORS
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
# Throttler
THROTTLER_TTL=60000
THROTTLER_LIMIT=100For production, include both web origins, including https://triads-classic.gametrix.org, in CORS_ALLOWED_ORIGINS.
docker compose up -d postgrespnpm prisma migrate devpnpm prisma db seedpnpm start:devThe API will be available at http://localhost:3000
Once the server is running, access the interactive Swagger documentation at:
All endpoints are prefixed with /api
GET /api/public/triad-groups- Get every active triad group with all four complete triads
AI and browser-based consumers can fetch the live public inventory without authentication or an API key:
https://triads-api.gametrix.org/api/public/triad-groups
Without query parameters, the endpoint returns every currently active group without pagination or a guaranteed order. Each group includes its difficulty and four complete, position-labelled triads. Deactivating a group removes it from the next response.
For agents that cannot process the complete export at once, request a deterministic page of at most 50 groups:
https://triads-api.gametrix.org/api/public/triad-groups?offset=0&limit=50
Increase offset by the number of groups requested and stop when a response contains fewer than limit groups. The paged form is ordered by group ID; the original, unpaginated response remains unchanged. This public route allows cross-origin GET requests.
Warning: This endpoint intentionally exposes every active triad's keyword, cues, and full phrases. That includes content which can solve current and future Daily puzzles.
[
{
"id": 7,
"difficulty": "HARD",
"triads": [
{
"position": 1,
"id": 71,
"keyword": "APPLE",
"cues": ["PIE", "TREE", "JUICE"],
"fullPhrases": ["APPLE PIE", "APPLE TREE", "APPLE JUICE"]
}
]
}
]GET /api/triads/cues- Get all cuesGET /api/triads/standalone-classic/cues- Get unlimited Classic cues without Daily quota accountingGET /api/triads/hint- Get hint for a triadGET /api/triads/check-triad- Check if cues match a triadGET /api/triads/check-answer- Validate an answerGET /api/triads/fourth-triad-cues- Get fourth triad cues
GET /api/triads/groups- List triad groups (with pagination & search)GET /api/triads/groups/:id/triads- Get triads in a groupPOST /api/triads/groups- Create a new triad groupPUT /api/triads/groups/:id- Update a triad groupPATCH /api/triads/groups/:id/status- Toggle active statusDELETE /api/triads/groups/:id- Delete a triad group
POST /api/import/triads- Import triads from Excel file
id: Unique identifierkeyword: The main word/keywordcues: Array of clue words (String[])fullPhrases: Array of full phrases (String[])
id: Unique identifiertriad1Id,triad2Id,triad3Id,triad4Id: References to four triadsactive: Boolean flag for active/inactive status (default: true)
Each triad group consists of four related triads that share a common connection.
| Variable | Description | Default |
|---|---|---|
APP_PORT |
Application port | 3000 |
NODE_ENV |
Environment (development/production) | development |
DATABASE_URL |
PostgreSQL connection string | Required |
POSTGRES_PORT |
PostgreSQL port (Docker) | 5432 |
CORS_ALLOWED_ORIGINS |
Comma-separated allowed origins | Required |
THROTTLER_TTL |
Throttler time window (ms) | 60000 |
THROTTLER_LIMIT |
Max requests per TTL | 100 |
This application has been optimized for low-memory environments (2GB RAM VPS):
-
Database Connection Management
- PrismaService properly closes connections on module destroy
- Connection pooling configured via
DATABASE_URL
-
Query Limits
- Game and management list queries use limits or pagination to prevent loading excessive data
- The public active triad-group export intentionally returns the full active inventory in one request
-
Logging
- Production logging level set to
infoinstead ofsilly - Daily log rotation to manage disk space
- Production logging level set to
-
Lodash Optimization
- Only imports specific functions instead of the entire library
- Reduces bundle size
-
Excel Import
- File size limits (10MB max)
- Row processing limits to prevent memory spikes
-
PM2 Configuration
- Watch mode disabled in production
- Memory restart limit set to 1GB
- Automatic restart on memory threshold
For optimal performance on limited resources, configure your DATABASE_URL with connection pool parameters:
DATABASE_URL="postgresql://user:password@host:port/database?connection_limit=5&pool_timeout=20"Recommendation: Set connection_limit to 3-5 for 2GB RAM VPS.
# Development
pnpm start:dev # Start with hot reload
pnpm start:debug # Start with debugger
# Building
pnpm build # Build for production
# Code Quality
pnpm lint # Run ESLint
pnpm format # Format with Prettier
# Testing
pnpm test # Run unit tests
pnpm test:watch # Run tests in watch mode
pnpm test:cov # Run with coverage
pnpm test:e2e # Run end-to-end tests
# Database
pnpm prisma migrate dev # Run migrations
pnpm prisma generate # Generate Prisma Client
pnpm prisma studio # Open Prisma Studio
pnpm prisma db seed # Seed database- ESLint: Code linting with TypeScript support
- Prettier: Code formatting
- Commitlint: Conventional commit message validation
- Husky: Git hooks for pre-commit checks
- Lint-staged: Run linters on staged files
pnpm buildEnsure all environment variables are set correctly for production.
pnpm prisma migrate deploypm2 start ecosystem.config.js --env productionpm2 status
pm2 logs triads-backend
pm2 monitThe ecosystem.config.js includes:
- Memory limit: 1GB (auto-restart if exceeded)
- Watch mode: Disabled in production
- Auto-restart: Enabled
- Logging: Enabled with timestamps
Logs are stored in the logs/ directory:
combined/: All logs (daily rotation)error/: Error logs only (daily rotation)
- Helmet: Sets various HTTP headers for security
- CORS: Configurable allowed origins
- Throttler: Rate limiting to prevent abuse
- Validation: Input validation with class-validator
- Global Exception Filter: Centralized error handling
Happy Coding! π