러스트에서 장고를 꿈꾸다: 풀스택 웹 프레임워크 'Runique' 개발기가 주는 실무 인사이트
2026. 6. 15.
러스트에서 장고를 꿈꾸다: 풀스택 웹 프레임워크 'Runique' 개발기가 주는 실무 인사이트
장고를 써본 사람이라면 그 특유의 '감동'을 알 겁니다. 단 하나의 명령어로 관리자 패널, ORM, 폼 유효성 검사, 미들웨어, 세션 관리 등 모든 게 착착 맞춰지는 그 느낌 말이죠. 그러다 러스트로 웹 개발을 시작하면, 다시 백지상태로 돌아가 모든 조각을 직접 조립해야 하는 현실과 마주하게 되죠.
바로 이 지점에서 제가 Runique를 만들게 되었습니다. 장고의 철학에서 영감을 받아 Axum과 Tokio 위에 구축한, 모든 기능이 포함된(batteries-included) 러스트 웹 프레임워크입니다.
왜 "러스트를 위한 장고"인가요?
기존 러스트 웹 프레임워크들은 각자의 영역에서 탁월합니다. Axum은 빠르고 조합성이 뛰어나며, Actix-Web은 성능의 괴물이죠. 하지만 이들은 설계 자체가 로우레벨 지향입니다. ORM, 세션 저장소, CSRF 보호, 심지어 관리자 인터페이스까지 개발자가 직접 선택하고 통합해야 합니다. 물론 많은 프로젝트에서 이런 방식이 적절한 트레이드오프일 수 있습니다.
하지만 Runique는 정반대의 접근 방식을 택했습니다. 바로 설정보다 관례(convention over configuration)! 러스트의 안전성과 성능을 희생하지 않으면서도, 구조화되고 견고한 기본 설정을 통해 '제로'에서 '운영 환경 준비 완료'까지 빠르게 도달할 수 있도록 돕는 것을 목표로 합니다.
빌더: 검증된 애플리케이션 구축 파이프라인
제가 Runique를 개발하면서 가장 자랑스럽게 생각하는 부분은 바로 애플리케이션 빌더입니다. 유연한(fluent) API를 통해 컴포넌트들을 어떤 순서로든 선언할 수 있고, 마지막에 .build() 메서드를 호출하면 시작 시점에 고정되고 검증된 구축 파이프라인이 실행됩니다.
검증 → DB 연결 → 템플릿 → 엔진 → 관리자 → 미들웨어 → 정적 파일
일반적인 앱 설정은 다음과 같습니다:
let app = RuniqueAppBuilder::new(config)
.routes(url::routes())
.with_database_config(db_config)
.with_mailer_from_env()
.statics()
.middleware(|m| m
.with_session_memory_limit(5 * 1024 * 1024, 10 * 1024 * 1024)
.with_session_cleanup_interval(5)
.with_allowed_hosts(|h| h.enabled(true).host("example.com"))
.with_csp(|c| c
.policy(SecurityPolicy::strict())
.with_header_security(true)
.scripts(vec!["'self'", "'strict-dynamic'"]))
.with_anti_bot())
.with_admin(|a| a.routes(admins::routes("/admin")))
.build()
.await?;
app.run().await?;
.build() 호출은 모든 컴포넌트, 심지어 컴포넌트 간의 상호 의존성까지 구축 전에 검증합니다. 만약 SECRET_KEY가 운영 환경에서 기본 설정값을 사용하고 있다면, 단 하나의 요청이 처리되기도 전에 빌드가 명확한 오류 메시지와 함께 수정 제안을 보여주며 실패합니다.
[Security] SECRET_KEY가 기본 보안 취약값을 사용하고 있습니다.
→ .env 파일에 32자 이상의 임의의 문자열로 SECRET_KEY를 설정하세요.
이는 장고의 check 프레임워크와 유사하지만, 운영 환경에서 발견되는 것이 아니라 애플리케이션 시작 시점에 강제로 검증된다는 점이 다릅니다. 제가 실무에서 겪었던 수많은 "운영 환경에서만 터지는" 배포 사고들을 생각해 보면, 이렇게 초기부터 단단하게 검증해 주는 시스템은 개발자의 심리적 안정감을 한층 높여줍니다.
기본으로 포함된 보안 기능
Runique에서 보안은 나중에 추가하는 플러그인이 아닙니다. 아예 구축 파이프라인의 핵심적인 부분으로 자리 잡고 있습니다.
- CSP (Content Security Policy) — 설정 가능한 프로필, 요청별 논스(nonce), 관리자 패널 활성화 시 HTMX 해시 병합 기능
- CSRF 보호 — 미들웨어 스택에 내장, 경로별 예외 처리 가능
- 호스트 유효성 검사 — 라우팅 전에 허용된 호스트 확인
- 신뢰할 수 있는 프록시 — 암묵적인 신뢰 대신 명시적인 설정 요구
- 정적 파일에 대한 보안 헤더 —
X-Content-Type-Options,Strict-Transport-Security,X-Frame-Options,Referrer-Policy가 자동으로 적용
// ServeDir을 직접 연결할 필요 없습니다 — `.statics()`가 알아서 처리하며,
// 모든 정적/미디어 경로에 보안 헤더를 자동으로 적용합니다.
.statics()
// → X-Content-Type-Options: nosniff
// Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
// X-Frame-Options: DENY
// Referrer-Policy: strict-origin-when-cross-origin
// + cache-control
미들웨어 스택은 **번호가 매겨진 슬롯(numbered slots)**을 사용해 애플리케이션 순서를 보장합니다. 세션 미들웨어보다 CSRF 미들웨어가 먼저 적용되는 식의 실수(이는 흔히 발견되는 보안 취약점의 원인이죠)를 원천적으로 방지합니다.
포함된 주요 기능
- ORM (SeaORM 기반, 선택적 기능 플래그)
- 템플릿 엔진 (Tera, 커스텀 필터 및 이름 있는 경로를 위한 URL 레지스트리 제공)
- 관리자 패널 (모델에서 자동 생성, 미들웨어 스택 이전에 병합되어 항상 세션/인증 컨텍스트를 가짐)
- 폼 엔진 (유효성 검사가 포함된 타입 지정 구조체, v2 개발 중)
- 세션 처리 (메모리 우선 저장소에 데이터베이스 폴백,
tower-sessions기반, 주기적인 정리 기능 포함) - i18n (FR/EN 이중 언어 문서 사이트 자체가 Runique로 구축됨)
- 정렬된 슬롯을 갖춘 미들웨어 시스템
- 비밀번호 재설정 (플러그인 방식, 빌드 시 자동으로 경로 등록)
- 디버그 오류 페이지 — 접을 수 있는 섹션과 스택 트레이스 복사 기능을 갖춘 스타일링된 페이지
한 가지 구체적인 예시: 관리자 라우터 병합 순서
여기 Runique의 철학을 보여주는 미묘하지만 중요한 설계 결정이 있습니다. Axum에서 .layer()는 호출 시점에 존재하는 라우터에만 적용됩니다. 이는 미들웨어를 적용한 후에 관리자 라우터를 병합하면, 관리자 경로가 세션, CSRF 또는 확장 컨텍스트 없이 실행되어 침묵하고 디버그하기 어려운 오류로 이어진다는 의미입니다.
Runique는 빌더에서 이 문제를 다음과 같이 처리합니다:
// 4b단계: 관리자 + 비밀번호 재설정 — 미들웨어 스택 이전에 병합됩니다.
// Axum의 `.layer()`는 호출 시점에 존재하는 라우터에만 적용됩니다;
// 미들웨어 적용 후 병합하면 관리자 라우터가 세션/CSRF/확장 기능 없이 실행됩니다.
let router = if self.admin.enabled {
router.merge(admin_router)
} else {
router
};
// 5단계: 미들웨어가 나중에 적용되어 관리자 라우터를 포함한 모든 것을 커버합니다.
let (router, session_store) = middleware.apply_to_router(router, config, engine, tera);
이런 세부 사항에 대해 개발자가 일일이 고민할 필요가 없습니다. 파이프라인이 알아서 처리합니다. 제가 실무에서 이런 종류의 "적용 순서" 문제로 며칠 밤낮을 새며 씨름했던 경험이 있어서, Runique가 제공하는 이런 안전망은 개발자의 정신 건강에 정말 큰 도움이 된다고 확신합니다.
현재 상태
Runique는 현재 v2.1.x 버전이며, MIT 라이선스를 따르고 있습니다. runique.io에서 영어/프랑스어 이중 언어 문서를 제공하고 있습니다.
이 프레임워크는 레스토랑 관리 시스템의 운영 환경에서 직접 사용되고 있습니다. 가장 직접적인 '도그푸딩(dogfooding)'을 통해 검증되고 있는 셈이죠.
솔직하게 말씀드리는, 아직 부족한 점들 (장고 스타일에 가깝게):
- 아직 비동기 태스크 큐는 없습니다 (백그라운드 작업은 순수한
tokio::spawn으로 실행). - 이메일 발송 기능은 아직 부분적입니다.
- OAuth는 계획 중이지만 아직 구현되지 않았습니다.
- 테스트 커버리지는 약 61%입니다 (계속 증가 중).
직접 사용해 보기
Cargo.toml에 의존성을 추가하세요:
[dependencies]
runique = "2.1"
링크 및 자료
GitHub 저장소: seb-alliot/runique
- 문서: runique.io
- Crates.io: crates.io/crates/runique
어떻게 생각하시나요?
특히 다음 분들의 의견이 궁금합니다:
- 장고 개발자 — 이 프레임워크 구조가 친숙하게 느껴지시나요, 아니면 러스트 생태계에는 "너무 과하다"고 생각하시나요?
- 러스트 전문가 — 미들웨어 순서 처리를 다르게 구현한다면 어떤 방식이 있을까요?
댓글에서 함께 논의해 봐요!
원문: https://dev.to/seballiot/i-built-a-django-inspired-web-framework-in-rust-heres-what-i-learned-5bbf 수집일: 2026-06-15 02:28:57