Node.js, Express 및 MongoDB로 Zodiac Compatibility API를 구축한 방법
Source: Dev.to
번역을 진행하려면 번역하고자 하는 전체 텍스트를 제공해 주시겠어요? 텍스트를 주시면 요청하신 대로 한국어로 번역해 드리겠습니다.
간단한 아이디어에서 프로덕션‑레디 API까지
데이트 앱이나 별자리 플랫폼이 백엔드에서 별자리 궁합을 어떻게 계산하는지 궁금해 본 적 있나요? 프론트엔드에서 보이는 간단한 “매치 점수”는 종종 정교하게 구조화된 데이터, 잘 설계된 알고리즘, 그리고 확장 가능한 백엔드 아키텍처의 결과입니다.
저는 실제 프로덕션 웹사이트에 사랑‑궁합 기능을 구축하면서 바로 이 문제에 직면했습니다. 목표는 명확했습니다: 두 별자리 표기를 입력받아 의미 있는 궁합 점수와 함께 상황에 맞는 인사이트를 반환하는 것. 이를 구현하기 위해서는 데이터 모델링, API 구조, 캐싱, 성능 최적화 등에 대한 신중한 설계 선택이 필요했습니다.
이 가이드에서는 Node.js, Express, MongoDB를 사용해 별자리 궁합 API를 구축하는 과정을 살펴봅니다—초기 아키텍처 결정부터 배포 고려 사항까지. 이 API는 현재 프로덕션에서 활발히 사용되고 있으며, 실제 궁합 검증을 지원하는 수천 건의 일일 요청을 처리하고 있습니다.
끝까지 읽으시면 다음을 이해하게 됩니다:
- 깔끔한 REST API 설계 방법
- 결정론적 궁합 알고리즘 구현 방법
- 확장성을 위한 프로젝트 구조화 방법
- 프로덕션 환경에서 서비스 배포 및 모니터링 방법
기술 스택 및 사전 요구 사항
핵심 기술
- Node.js – 런타임 환경
- Express.js – REST API 프레임워크
- MongoDB – 호환성 매트릭스 및 메타데이터 저장소
- Mongoose – MongoDB용 ODM
- Redis (옵션) – 캐싱 레이어
- External Astrology API (옵션) – 일일 운세 보강용
사전 요구 사항
다음에 익숙해야 합니다:
- JavaScript (ES6+)
- RESTful API 개념
- 기본 MongoDB 쿼리
- Express 미들웨어 패턴
프로젝트 아키텍처 및 폴더 구조
깨끗한 구조는 장기적인 유지 보수에 필수적입니다. 아래는 제가 사용한 레이아웃입니다:
/zodiac-compatibility-api
│
├─ src
│ ├─ controllers # Request handling logic
│ ├─ models # Mongoose schemas
│ ├─ routes # Express routers
│ ├─ services # Business logic (e.g., compatibility engine)
│ ├─ utils # Helper functions (validation, caching)
│ └─ index.js # Application entry point
│
├─ config # Environment‑specific settings
├─ tests # Unit / integration tests
└─ Dockerfile, docker‑compose.yml, etc.
이러한 분리 덕분에 라우팅, 로직, 데이터, 유틸리티 각 계층이 독립적으로 발전할 수 있습니다.
핵심 API 엔드포인트 정의
API는 단순성과 확장성을 염두에 두고 설계되었습니다.
| 메서드 | 엔드포인트 | 설명 |
|---|---|---|
GET | /compatibility/:sign1/:sign2 | 두 별자리의 궁합 점수와 설명을 반환합니다 |
POST | /calculate-match | 별자리(및 선택적 사용자 데이터)를 포함한 JSON 페이로드를 받아 매치 결과를 반환합니다 |
GET | /daily-horoscope/:sign (optional) | 응답에 오늘의 운세를 추가합니다 |
GET | /health | 모니터링을 위한 간단한 상태 확인입니다 |
각 엔드포인트는 단일 책임에 집중하며 예측 가능한 JSON 응답을 반환합니다.
MongoDB에서 별자리 궁합 모델링
모든 궁합 로직을 하드코딩하는 대신, 궁합 매트릭스를 MongoDB에 저장했습니다. 이를 통해 애플리케이션을 재배포하지 않고도 향후 조정이 가능해집니다.
스키마 예시 (단순화):
// models/Compatibility.js
const { Schema, model } = require('mongoose');
const CompatibilitySchema = new Schema({
signA: { type: String, required: true, uppercase: true },
signB: { type: String, required: true, uppercase: true },
score: { type: Number, required: true }, // 0‑100
shortDescription: { type: String },
longDescription: { type: String },
// optional: { shortTermScore, longTermScore, ... }
}, { timestamps: true });
CompatibilitySchema.index({ signA: 1, signB: 1 }, { unique: true });
module.exports = model('Compatibility', CompatibilitySchema);
컬렉션이 지원하는 기능:
- 양방향 조회 – 별자리 순서와 관계없이 쿼리를 수행합니다.
- 풍부한 설명 – UI에 표시할 짧은 텍스트와 긴 텍스트를 제공합니다.
- 향후 확장 – 예를 들어 단기와 장기 관계에 대한 별도 점수 등을 추가할 수 있습니다.
Basic Scoring Function
핵심 알고리즘은 의도적으로 결정론적이며 조정하기 쉽습니다.
// services/compatibilityService.js
/**
* Calculate compatibility between two zodiac signs.
* @param {string} sign1 - First sign (case‑insensitive)
* @param {string} sign2 - Second sign (case‑insensitive)
* @returns {Promise} Compatibility document
*/
async function getCompatibility(sign1, sign2) {
const [a, b] = [sign1.toUpperCase(), sign2.toUpperCase()];
// Try direct order first, then reversed (bidirectional)
let result = await Compatibility.findOne({ signA: a, signB: b });
if (!result) {
result = await Compatibility.findOne({ signA: b, signB: a });
}
if (!result) {
throw new Error('Compatibility data not found for the given signs.');
}
return result;
}
결정론적 출력 → 사용자는 매번 동일한 점수를 확인합니다.
쉬운 튜닝 → score 또는 설명을 데이터베이스에서 직접 조정합니다.
Express 컨트롤러 구현
컨트롤러는 입력을 검증하고, 서비스를 호출하며, 응답을 포맷합니다.
// controllers/compatibilityController.js
const { getCompatibility } = require('../services/compatibilityService');
const { validateSign } = require('../utils/validation');
exports.getCompatibility = async (req, res) => {
try {
const { sign1, sign2 } = req.params;
// Input validation
if (!validateSign(sign1) || !validateSign(sign2)) {
return res.status(400).json({ error: 'Invalid zodiac sign supplied.' });
}
const data = await getCompatibility(sign1, sign2);
res.json({
signA: data.signA,
signB: data.signB,
score: data.score,
shortDescription: data.shortDescription,
longDescription: data.longDescription,
});
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Internal server error.' });
}
};
캐싱을 이용한 성능 최적화
호환성 결과는 거의 변하지 않으므로 캐싱에 이상적인 후보입니다.
Redis 전략
- 캐시 키:
compatibility:{signA}:{signB}(별자리는 대문자로 저장) - TTL: 24 시간
- 패턴: Cache‑aside – 먼저 Redis를 확인하고, MongoDB로 백업한 뒤 캐시를 채웁니다.
// utils/cache.js
const redis = require('redis');
const client = redis.createClient({ url: process.env.REDIS_URL });
async function getCachedCompatibility(key) {
const cached = await client.get(key);
return cached ? JSON.parse(cached) : null;
}
async function setCachedCompatibility(key, value) {
await client.setEx(key, 86400, JSON.stringify(value)); // 24h TTL
}
운영 환경에서는 트래픽 급증 시 데이터베이스 부하를 70 % 이상 감소시켰습니다.
오류 처리 및 검증
- 화이트리스트 검증 – 12개(오피우쿠스를 포함하면 13개) 표준 별자리만 허용됩니다.
- 대소문자 정규화 –
cancer,Cancer,CANCER는 동일하게 처리됩니다. - 일관된 HTTP 상태 코드 – 클라이언트 오류는
400, 해당 쌍을 찾을 수 없을 때는404, 서버 오류는500을 반환합니다.
// utils/validation.js
const VALID_SIGNS = [
'ARIES','TAURUS','GEMINI','CANCER','LEO','VIRGO',
'LIBRA','SCORPIO','SAGITTARIUS','CAPRICORN','AQUARIUS','PISCES'
];
function validateSign(sign) {
return VALID_SIGNS.includes(sign.toUpperCase());
}
module.exports = { validateSign };
배포 전략
API는 컨테이너에서 실행되므로 다양한 환경에서 이식성이 보장됩니다.
배포 스택
| Component | Service |
|---|---|
| Runtime | Node.js (LTS) |
| Database | MongoDB Atlas (managed) |
| Cache | Redis (managed or self‑hosted) |
| CI/CD | GitHub Actions – build, test, push Docker image |
| Orchestration | Docker Compose (local) / Kubernetes (production) |
| Config | Environment variables (dotenv) |
Zero‑downtime 배포는 로드 밸런서 뒤에서 롤링 업데이트를 수행함으로써 달성됩니다.
배운 교훈
- “재미있는” 기능이라도 견고한 엔지니어링 기반이 필요합니다.
- 결정론적 로직은 사용자 신뢰를 구축합니다.
- 규모가 커질수록 캐싱은 필수입니다.
- 깨끗한 아키텍처는 장기적으로 시간을 절약합니다.
- 실제 사용에서는 튜토리얼에서 거의 다루지 않는 엣지 케이스가 드러납니다.
이 API의 향후 방향
계획된 개선 사항:
- 맞춤형 출생 차트 분석 – 태양, 달, 상승 별자리 결합.
- 복합 호환성 점수 – 단기 및 장기 지표를 혼합.
- 머신러닝 지원 튜닝 – 사용자 피드백을 사용해 점수를 조정.
- 공개 API 문서 – 외부 개발자를 위한 Swagger/OpenAPI 사양.
Conclusion
Node.js, Express, MongoDB를 사용하여 별자리 궁합 API를 구축하는 것은 창의성과 체계적인 엔지니어링을 어떻게 결합할 수 있는지를 보여줍니다. 틈새 기능으로 시작했던 것이 매일 실제 사용자들을 지원하는 확장 가능한 백엔드 서비스로 발전했습니다.
점성술, 데이터 모델링, 백엔드 개발의 교차점에 관심이 있다면, 이 프로젝트는 여러분이 자신의 아이디어에 적용할 수 있는 실용적인 청사진을 제공합니다.