Fitz 소개: HTTP·Postgres·JWT·WebSockets를 문법에 포함한 언어

발행: (2026년 6월 6일 PM 08:09 GMT+9)
8 분 소요
원문: Dev.to

출처: Dev.to

Fitz는 Rust로 작성된 새로운 프로그래밍 언어이며, 점진적 타입 시스템을 갖춘 컴파일러를 제공합니다. 전제는 다음과 같습니다. FastAPI + SQLAlchemy + python‑jose + Celery + Pydantic + uvicorn + Alembic + typer와 같은 스택을 Python 위에 겹쳐 놓는 대신, 각각이 해결하는 모든 기능이 언어 내부에 존재한다는 것입니다: HTTP 라우팅, OpenAPI/AsyncAPI 자동 생성, async/await, JWT 인증, 비밀번호 해싱, 순수 Rust로 구현된 Postgres 드라이버를 갖춘 ORM, 스키마 마이그레이션, WebSocket, cron, 백그라운드 잡, CLI 빌더, 헬스체크, OpenTelemetry 기반 관측, 불투명 타입으로 다루는 시크릿, 그리고 fitz deploy 오케스트레이터까지. 단일 바이너리 하나. 코어 스택에 외부 의존성은 전혀 없습니다. 레포지터리: github.com/Thegreekman76/fitz · 문서: thegreekman76.github.io/fitz

수년간 나는 Python으로 API를 작성해 왔습니다—FastAPI와 함께 흔히 쓰는 스택: SQLAlchemy, JWT용 python‑jose, Argon2용 passlib, 잡용 Celery + Redis, 검증용 Pydantic, 서빙용 uvicorn, 마이그레이션용 alembic. 내가 제공하는 거의 모든 API는 거의 동일한 아홉 개의 라이브러리를 필요로 했으며, 각각은 자체적인 관례, 브레이킹 체인지, 다른 라이브러리와의 통합 방식을 가지고 있었습니다.

그때 나는 당연한 질문을 스스로에게 던졌습니다: “왜 이걸 언어 자체로 만들지 않겠어?”

그 질문이 바로 Fitz였습니다.

Fitz는 어떻게 보이는가

먼저 사진을 보여드리고, 그 뒤에 구성 요소들을 살펴보겠습니다.

@server(43928)
fn main() => 0

type User { id: Int, email: Str, name: Str, role: Str }
type Credentials { email: Str, password: Str }
type LoginResponse { token: Str }

let SECRET = "demo-secret-cambiame-en-prod"
let ADA_HASH = hash.password("secret-ada-123")

@auth_provider
fn check_token(headers: Map) -> Result {
    let auth: Str = match headers.get("authorization") {
        Ok(v) => v,
        Err(_) => return Err("falta header Authorization"),
    }
    let parts = auth.split(" ")
    if (parts.len() != 2 or parts[0] != "Bearer") {
        return Err("se esperaba 'Bearer '")
    }
    let claims = jwt.decode(parts[1], SECRET)?
    return find_user(claims["email"])
}

@post("/login")
fn login(creds: Credentials) -> LoginResponse {
    let user: User = match find_user(creds.email) {
        Ok(u) => u,
        Err(_) => return 401 { "error": "credenciales inválidas" },
    }
    if (not hash.verify(creds.password, ADA_HASH)) {
        return 401 { "error": "credenciales inválidas" }
    }
    let claims = { "email": user.email, "role": user.role }
    return LoginResponse { token: jwt.encode(claims, SECRET) }
}

@authenticated
@get("/me")
fn me(user: User) -> User => user

@admin
@get("/admin/users")
fn admin_list(user: User) -> List { ... }

이 코드는 한 줄도 import하거나 외부 의존성을 추가하지 않고 다음을 수행합니다:

  • 포트 43928에서 HTTP 서버를 실행합니다.
  • /openapi.json에 자동으로 OpenAPI 3.1 스키마를 생성합니다.
  • /docs에 Scalar UI를 제공하고, “Authorize” 버튼이 정상 작동합니다.
  • JWT(HS256/384/512)를 서명하고 검증합니다.
  • Argon2id(OWASP 권고, bcrypt 아님)로 비밀번호를 해시합니다.
  • @authenticated/@admin에 선언된 @auth_provider가 존재하고, 프로바이더가 올바른 User 타입을 반환하며, @admin 핸들러가 Userrole: Str 필드를 가지고 있음을 정적으로 검증합니다.
  • fitz build로 네이티브 바이너리를 컴파일하며, fitz run과 비트‑대‑비트 동일성을 보장합니다.

인증, 해시, JWT, bearerAuth 보안 스키마가 포함된 OpenAPI, 401/403 응답 등 모든 것이 fitz 바이너리 안에 내장됩니다. requirements.txt, package.json, 사용자 측 Cargo.toml 같은 것이 전혀 없습니다.

“퍼스트 클래스 시민”이 중요한 이유

“퍼스트 클래스 시민”이라는 표현은 금방 식지만, 여기서는 구체적으로 말합니다.

FastAPI에서는 @app.get("/users")인스턴스 메서드이며, 프레임워크는 선택적으로(opt‑in) 사용되는 라이브러리입니다. 라우터는 Python 데이터 구조이고, 인증은 Depends(...)로 구현됩니다. 타입 체커에게는 이 모든 것이 특별한 것이 아니라 단순히 함수 호출과 데코레이터에 불과합니다.

Fitz에서는 @get("/users")컴파일러가 직접 이해하는 데코레이터입니다. 체커는 경로 템플릿, 경로 파라미터 타입, 바디 타입, 반환 타입을 모두 검증합니다. OpenAPI 생성기는 런타임 객체를 introspect하지 않고 AST를 바로 탐색합니다—스스로를 “등록”하는 데코레이터가 필요 없습니다. 핸들러에서 반환하는 User 타입은 자동 생성된 스키마와 Scalar UI에 그대로 반영됩니다.

일주일 정도 사용해 보면 차이가 크게 느껴집니다. 이제 “왜 Pydantic이 SQLAlchemy와 필드 옵션이 달라서 충돌하나요?” 같은 문제에 시달릴 필요 없이 엔드포인트 작성에만 집중할 수 있습니다.

구성 요소

HTTP + OpenAPI + Scalar UI, 모두 자동

type Post { id: Int, title: Str, body: Str, tags: List }

@get("/posts")
fn list_posts() -> List { ... }

@post("/posts")
fn create_post(post: Post) -> Post { ... }

필요한 건 이것뿐입니다. /openapi.json/docs(Scalar)가 자동으로 생성됩니다. 경로 파라미터(/posts/{id})는 타입이 지정되고 강제 변환됩니다. JSON 바디 역직렬화는 required 검증, 기본값 적용, nullable 검증, 불필요한 필드 거부 등을 수행합니다. @server(docs=false)로 비활성화도 가능합니다.

타입이 지정된 WebSocket, AsyncAPI 자동 생성

type ChatMessage { from: Str, text: Str }

@server(43929, ws_heartbeat_secs=30)
fn main() => 0

@authenticated
@ws("/chat")
async fn chat(conn: WsConn, user: User) {
    loop {
        let msg = match conn.recv() {
            Ok(m) => m,
            Err(_) => break,
        }
        conn.broadcast(ChatMessage { from: user.name, text: msg.text })
    }
}

각 프레임은 선언된 타입으로 자동 직렬·역직렬됩니다. 인증은 WebSocket 업그레이드 전에 수행되어 토큰이 유효하지 않으면 401을 반환하고 소켓을 열지 않습니다. ping/pong 기반 하트비트(ws_heartbeat_secs)가 기본 Nginx 타

0 조회
Back to Blog

관련 글

더 보기 »

모바일 한여름 열풍

!Cover image for Mobile Midsommer Madnesshttps://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploa...