Fitz로 30분 안에 HTTP·Postgres·인증 URL 단축기 만들기

발행: (2026년 6월 9일 PM 07:11 GMT+9)
10 분 소요
원문: Dev.to

출처: Dev.to

Fitz를 이용한 단계별 튜토리얼입니다. fitz new 로 시작해 Docker에서 실행되는 네이티브 바이너리까지 완성합니다. 외부 의존성 없이 진행합니다. pip install 도 필요 없습니다. 단순히 Postgres, JWT 인증, 그리고 OpenAPI 자동 생성만 사용합니다.

우리가 만들고자 하는 것

실제 API에 꼭 필요한 네 가지 요소를 갖춘 URL 단축기:

  • HTTP 엔드포인트 – 생성, 리다이렉트, 통계 조회용.
  • Postgres 영속성 – 링크와 클릭 카운터 저장.
  • JWT 인증 – 로그인한 사용자만 짧은 URL을 만들 수 있음.
  • 네이티브 바이너리fitz build 로 만든 뒤 컨테이너에 바로 넣을 수 있음.

최종 규모: 약 120줄의 Fitz 코드. requirements.txt도, package.json도, cargo add도 없습니다. 오직 fitz와 Postgres만 있으면 됩니다.

완성 후 제공되는 API:

  • POST /login → 자격 증명을 JWT로 교환.
  • POST /shorten (인증 필요) → 짧은 코드 반환.
  • GET /{code} → 원본 URL로 리다이렉트하고 카운터 증가.
  • GET /stats/{code} (인증 필요) → 클릭 수와 생성 일자 표시.
  • /openapi.json 에 자동 생성된 OpenAPI 3.1 스키마.
  • /docs 에 Scalar UI 로 브라우저에서 테스트 가능.

그럼 시작해봅시다.

설정 (2분)

Fitz 설치:

# Linux / macOS / WSL
curl -sSf https://thegreekman76.github.io/fitz/install.sh | sh

# Windows (PowerShell)
irm https://thegreekman76.github.io/fitz/install.ps1 | iex

또는 릴리즈 페이지에서 미리 컴파일된 바이너리를 다운로드합니다.

VSCode 확장 (이 튜토리얼에 강력히 권장 – 타입 힌트, 자동 완성, 시그니처 도움, 저장 시 포맷팅 제공): 같은 릴리즈 페이지에서 OS에 맞는 fitz-lang-.vsix 를 받아 설치합니다:

code --install-extension fitz-lang-.vsix --force

Language Server 가 .vsix 안에 포함돼 있어 별도 설치가 필요 없습니다. 설치 후 VSCode 를 한 번 재로드하세요.

터미널을 다시 열어 PATH 변경이 적용됐는지 확인합니다:

fitz --version
# fitz 0.15.0

Postgres도 실행 중이어야 합니다. 가장 빠른 방법은 Docker:

docker run -d --name pg-shortener \
  -e POSTGRES_PASSWORD=demo \
  -e POSTGRES_DB=shortener \
  -p 5432:5432 \
  postgres:16

프로젝트 생성:

fitz new url-shortener --http
cd url-shortener

--http 플래그는 @get@server 가 이미 연결된 main.fitz 템플릿을 만들어 줍니다. main.fitz 를 열고 편집을 시작합니다.

1단계 — Hello world HTTP

main.fitz 를 아래 최소 코드로 교체합니다:

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

@get("/health")
fn health() -> Str => "ok"

실행:

fitz dev

fitz dev 는 파일을 감시하고 저장할 때마다 재시작합니다 — uvicorn --reload 와 같은 역할입니다. 다른 터미널에서:

curl localhost:8080/health
# ok

브라우저에서 http://localhost:8080/docs 를 열어 보세요. GET /health 가 문서화된 Scalar UI 가 이미 표시됩니다. 데코레이터를 라우터에 수동으로 등록할 필요도, app.include_router(...) 같은 코드도 없습니다. 컴파일러가 AST 를 읽어 스키마를 자동 생성합니다.

2단계 — 도메인 모델링

Link 는 짧은 코드, 원본 URL, 클릭 카운터, 그리고 소유자를 나타냅니다.

type Link {
    @primary id: Int,
    code: Str,
    target_url: Str,
    user_email: Str,
    clicks: Int,
    created_at: Str,
}

@primary 데코레이터는 기본 키임을 표시합니다. Int 는 생성된 바이너리에서는 i64 로, Postgres에서는 bigint 로 매핑됩니다. ORM 은 컴파일 타임에 타입 정보를 읽어 이해합니다.

하지만 아직 이 type 이 Postgres 테이블이라는 것을 Fitz 에 알려주지 않았습니다. @table 을 추가합니다:

@table("links")
type Link {
    @primary id: Int,
    code: Str,
    target_url: Str,
    user_email: Str,
    clicks: Int,
    created_at: Str,
}

이제 Link.all(db), Link.insert(db, l), Link.where(...), .preload(...) 등 다양한 메서드가 자동으로 제공됩니다. 컴파일러는 .where(...) 안의 클로저가 존재하는 필드만 참조하도록 정적 검사를 수행합니다. 오타는 컴파일 타임에 잡히므로 프로덕션에서 오류가 발생하지 않습니다.

3단계 — Postgres 연결

db.connect(url) 은 커넥션 풀을 엽니다. 부팅 시 한 번만 호출합니다:

let DB_URL = env_or("DATABASE_URL", "postgres://postgres:demo@localhost:5432/shortener")
let db = db.connect(DB_URL)

env_or 는 내장 함수로, 환경 변수를 읽고 없으면 기본값을 사용합니다. 로컬 개발과 Docker 환경을 조건문 없이 모두 지원합니다.

테이블이 존재해야 합니다. 여기서는 스키마 마이그레이션 도구를 사용합니다 — Fitz 에는 fitz db difffitz db migrate 가 내장돼 있습니다. @table 타입을 선언하고 마이그레이터에게 SQL 생성을 맡깁니다:

@table("links")
type Link {
    @primary id: Int,
    code: Str,
    target_url: Str,
    user_email: Str,
    clicks: Int = 0,
    created_at: Str = "",
}

첫 실행:

$ fitz db diff
+ CREATE TABLE links (
+     id BIGSERIAL PRIMARY KEY,
+     code TEXT NOT NULL,
+     target_url TEXT NOT NULL,
+     user_email TEXT NOT NULL,
+     clicks BIGINT NOT NULL DEFAULT 0,
+     created_at TEXT NOT NULL DEFAULT ''
+ );

$ fitz db migrate
 applied migration_20260530_links.sql

fitz db diff 는 코드에 있는 @table 타입을 읽고 현재 DB 상태와 비교해 동기화에 필요한 SQL 을 계산한 뒤, 멱등적인 마이그레이션 파일을 생성합니다. fitz db migrate 는 아직 적용되지 않은 마이그레이션을 순서대로 실행합니다. Alembic 과 비슷하지만, 별도의 SQL 파일을 관리할 필요 없이 타입 정의 자체가 진실의 원천이 됩니다.

예를 들어 나중에 Linkname: Str 를 추가하면 fitz db diffALTER TABLE links ADD COLUMN name TEXT NOT NULL 를 생성하고, 다시 fitz db migrate 하면 적용됩니다. 손으로 DDL 을 작성할 필요가 없습니다.

이 튜토리얼에서는 서버를 시작하기 전에 fitz db difffitz db migrate 를 한 번씩 실행합니다.

4단계 — 생성 + 리다이렉트

두 개의 엔드포인트를 구현합니다. 코드가 얼마나 간결한지 보세요:

type ShortenRequest { target_url: Str }
type ShortenResponse { code: Str, short_url: Str }

@post("/shorten")
async fn shorten(db: DbConn, req: ShortenRequest, user: User) -> ShortenResponse {
    let code = generate_code()
    let link = Link {
        id: 0,
        code: code,
        target_url: req.target_url,
        user_email: user.email,
        clicks: 0,
        created_at: "",  // DB 기본값이 채워짐
    }
    Link.insert(db, link).await
    return ShortenResponse {
        code: code,
        short_url: "http://localhost:8080/{code}",
    }
}

세 가지 주목할 점:

  • db: DbConn 파라미터 — 런타임이 자동으로 커넥션을 주입합니다. Fitz 는 타입만 보고 전역 풀에서 가져오는 것을 알아냅니다.
  • user: User 파라미터 — 인증으로부터 제공됩니다. 다음 단계에서 인증 로직을 연결합니다. 컴파일러는 핸들러가 @authenticated 로 표시돼야 함을 정적으로 검증합니다.
  • id: 0 — ORM 에게 “첫 번째 삽입이니 이 필드는 무시하고 BIGSERIAL 이 자동 할당하게 해라” 라는 신호입니다. ORM 은 자동으로 INSERT 문에서 해당 필드를 제외합니다.

리다이렉트 엔드포인트:

@get("/{code}")
async fn redirect(db: DbConn, code: Str) -> Result {
    let link: Link = match Link.where(fn(l) => l.code == code).first(db).await {
        Ok(l) => l,
        Err(_) => return Err("not found"),
    }
    // 클릭 카운터 증가
    Link.where(fn(l) => l.id == link.id)
        .update(db, { clicks: link.clicks + 1 })
        .await?;
    // 리다이렉트 응답 반환
    return Ok(Redirect(link.target_url))
}

위 코드는 다음을 수행합니다:

  1. codeLink 레코드를 조회합니다. 없으면 404 오류 반환.
  2. 조회된 레코드의 clicks 를 1 증가시킵니다.
  3. 원본 URL 로 리다
0 조회
Back to Blog

관련 글

더 보기 »

Eidentic 소개

Today we're releasing Eidentic, an open-source TypeScript SDK for building AI agents with self-improving memory and the production fundamentals built in — not b...

Typescript의 타입

Introdução Tipos são uma forma de definir a “forma” ou o contrato dos dados que estamos usando no código. Pensando em Javascript puro, ele é dinâmico: você pode...