λͺ¨λ Έλ ν¬ κΈ°λ° μν°νλΌμ΄μ¦ νμ΅ κ΄λ¦¬ μμ€ν - Turbo λͺ¨λ Έλ ν¬ μν€ν μ²λ‘ ꡬμΆλ νμ μμ νκ³ μ±λ₯ μ΅μ νλ LMS νλ«νΌ
- κ°λ° μλ£λ: μ€μ μλΉμ€ μ΄μ κ°λ₯ μμ€
- μ±λ₯ μ΅μ ν: N+1 쿼리 ν΄κ²°, Redis μΊμ±, λ²λ€ μ΅μ ν μλ£
- μ½λ νμ§: TypeScript 100%, 체κ³μ λͺ¨λν μλ£
- μ΄μ μμ μ±: νκ²½λ³μ κΈ°λ° λμ μ€μ , μ±λ₯ λͺ¨λν°λ§ ꡬμΆ
π μ±λ₯ κ°μ
βββ N+1 쿼리 β λ¨μΌ 쿼리 λ³κ²½: 90% μ±λ₯ κ°μ
βββ Redis μΊμ± μ μ©: DB λΆν 60% κ°μ
βββ λ²λ€ μ΅μ ν: μ΄κΈ° λ‘λ© μκ° λ¨μΆ
βββ μ±λ₯ λͺ¨λν°λ§ API: μ€μκ° μΆμ κ°λ₯
π μ½λ νμ§
βββ TypeScript 컀λ²λ¦¬μ§: 100%
βββ μ½λ μ€λ³΅λ₯ : 15% β 5% (67% κ°μ)
βββ λͺ¨λν: 10κ° ν¨ν€μ§ + 3κ° μλΉμ€
βββ νκ²½λ³μ μΈλΆν: νλμ½λ© μμ μ κ±°
lms-next-nestjs/
βββ apps/
β βββ auth/ # NestJS μΈμ¦ μλΉμ€ (ν¬νΈ: 4000)
β βββ api/ # NestJS API μλ² (ν¬νΈ: 4001)
β βββ web/ # Next.js μΉ μ± (ν¬νΈ: 3000)
βββ packages/
β βββ auth/ # κ³΅ν΅ μΈμ¦ μ€ν€λ§
β βββ common/ # κ³΅ν΅ λ―Έλ€μ¨μ΄, μ νΈλ¦¬ν°
β βββ config/ # νκ²½λ³μ κΈ°λ° λμ μ€μ
β βββ database/ # Prisma λ°μ΄ν°λ² μ΄μ€ μ€μ
β βββ schemas/ # Zod μ€ν€λ§ κ²μ¦
βββ docs/ # μ±λ₯ μ΅μ ν λ° νΈλ¬λΈμν
κ°μ΄λ
// κΈ°μ‘΄: N+1κ° μΏΌλ¦¬
const users = await findUsers();
for (const user of users) {
const courses = await findCourses(user.id);
}
// μ΅μ ν: λ¨μΌ 쿼리
const enrolledCourses = await prisma.userCourseProgress.findMany({
include: {
course: {
include: { sections: { include: { chapters: true } } }
}
}
});
@Cacheable('user-courses:{userId}', 300) // 5λΆ μΊμ
@Cacheable('course-stats:{courseId}', 600) // 10λΆ μΊμ
@CacheEvict(['user-courses:{userId}']) // μλ 무ν¨ν
// 보μ μ μ±
μ μ½λ μμ μμ΄ μ‘°μ κ°λ₯
maxLoginAttempts: parseInt(process.env.MAX_LOGIN_ATTEMPTS || '5', 10),
lockoutDuration: parseInt(process.env.LOCKOUT_DURATION_MINUTES || '15', 10),
λ°±μλ
- NestJS (λͺ¨λν μν€ν μ²)
- Prisma (νμ μμ ν ORM)
- Redis (μΊμ±/μΈμ κ΄λ¦¬)
- PostgreSQL (λ°μ΄ν°λ² μ΄μ€)
νλ‘ νΈμλ
- Next.js 15 (App Router)
- TypeScript (100% νμ μμ μ±)
- Tailwind CSS (μ€νμΌλ§)
- Zod (μ€ν€λ§ κ²μ¦)
κ°λ° λꡬ
- Turbo (λͺ¨λ Έλ ν¬ λΉλ μ΅μ ν)
- Docker (컨ν μ΄λν)
- pnpm (ν¨ν€μ§ κ΄λ¦¬)
# νλ‘μ νΈ ν΄λ‘ λ° μμ‘΄μ± μ€μΉ
git clone <repository-url>
cd lms-next-nestjs
pnpm install
# νκ²½ λ³μ μ€μ
cp .envs/.env.example .envs/.env.local
cp packages/database/.env.example packages/database/.env
# λ°μ΄ν°λ² μ΄μ€ λ§μ΄κ·Έλ μ΄μ
pnpm db migrate dev --name init
# μλ λ°μ΄ν° μμ± (μ νμ¬ν)
pnpm db:seed
# λͺ¨λ μλΉμ€ λμ μ€ν
pnpm dev
# κ°λ³ μλΉμ€ μ€ν
pnpm dev:auth # μΈμ¦ μλΉμ€
pnpm dev:api # API μλ²
pnpm dev:web # μΉ μ±
- JWT κΈ°λ° Access/Refresh ν ν°
- μμ λ‘κ·ΈμΈ (Google, GitHub)
- λΈλ£¨νΈν¬μ€ 곡격 λ°©μ§
- νκ²½λ³μ κΈ°λ° λ³΄μ μ μ±
- κ°μ μμ±/νΈμ§ μμ€ν
- μ€μκ° μ§λ μΆμ
- μΉμ /μ±ν° κ΄λ¦¬
- νμΌ μ λ‘λ μ΅μ ν
- μ±λ₯ λͺ¨λν°λ§ λμ보λ
- μ¬μ©μ λ°°μΉ μ²λ¦¬
- ν΅κ³ λ°μ΄ν° μ§κ³
- μμ€ν ν¬μ€μ²΄ν¬
# μ€μκ° μ±λ₯ λ©νΈλ¦ νμΈ
GET /api/v1/admin/performance/metrics
# μμ€ν
μν νμΈ
GET /api/v1/admin/performance/health
# λλ¦° μλν¬μΈνΈ λΆμ
GET /api/v1/admin/performance/slow-endpoints
# λ©λͺ¨λ¦¬ μ¬μ©λ μΆμ΄
GET /api/v1/admin/performance/memory-usage
- λͺ¨λ HTTP μμ² μλ μΆμ : μλ΅ μκ°, μνμ½λ, URL
- λλ¦° μμ² μλ κ°μ§: 1μ΄ μ΄μ μμ² μ€μκ° λ‘κΉ
- λ©λͺ¨λ¦¬ λͺ¨λν°λ§: 30μ΄λ§λ€ μλ 체ν¬, μκ³μΉ μ΄κ³Ό μ κ²½κ³
- μλ κ°λΉμ§ 컬λ μ : λ©λͺ¨λ¦¬ μκ³μΉ μ΄κ³Ό μ μλ μ€ν
# μμΈ μ±λ₯ λ‘κΉ
νμ±ν
LOG_PERFORMANCE=true pnpm dev:api
# λ©λͺ¨λ¦¬ λλ²κΉ
λ‘κΉ
νμ±ν
LOG_MEMORY=true pnpm dev:api
# μ½λ νμ§ κ²μ¬
pnpm lint # ESLint κ²μ¬
pnpm check-types # TypeScript νμ
체ν¬
pnpm format # Prettier ν¬λ§·ν
pnpm test # ν
μ€νΈ μ€ν
# νλ‘λμ
λΉλ
pnpm build
# Docker 컨ν
μ΄λ λΉλ
pnpm docker build
# κ°λ³ μλΉμ€ λ°°ν¬
pnpm --filter @apps/auth build
pnpm --filter @apps/api build
pnpm --filter @apps/web build
# μ±λ₯ λ‘κΉ
νμ±ν
LOG_PERFORMANCE=true pnpm dev
# Redis μ°κ²° νμΈ
redis-cli ping
# λ©λͺ¨λ¦¬ μ¬μ©λ λͺ¨λν°λ§
node --inspect apps/api/dist/main.js
- ν¬νΈ μΆ©λ: κ° μλΉμ€λ³ ν¬νΈ νμΈ (3000, 4000, 4001)
- νκ²½λ³μ: .env νμΌ μ€μ νμΈ
- λ°μ΄ν°λ² μ΄μ€: PostgreSQL λ° Redis μ°κ²° μν νμΈ
MIT λΌμ΄μΌμ€ νμ λ°°ν¬λ©λλ€.
μ€μ μ΄μ κ°λ₯ν μμ€μ μν°νλΌμ΄μ¦κΈ LMS μμ€ν
β νμ μμ μ± ν보 (TypeScript 100%) β μ±λ₯ μ΅μ ν μλ£ (N+1 ν΄κ²°, μΊμ± μ μ©) β μ΄μ μμ μ± κ΅¬μΆ (λμ μ€μ , λͺ¨λν°λ§) β νμ₯ κ°λ₯ν μν€ν μ² (λͺ¨λ Έλ ν¬, λͺ¨λν)