본문으로 건너뛰기

Vapor 백엔드에 검증과 컨트롤러 구조 적용하기 (4)

Adding Some Clean Up and Validation to My First Vapor Backend in 2026 pt. 4

swiftsips.dev

핵심 요약

  1. Signup·Login·링크 요청 DTO에 Validatable 규칙을 추가해 이메일, 문자열 길이, URL 등 요청 데이터의 조건을 route 로직에서 처리하기 전에 확인한다.
  2. Link 모델을 직접 응답으로 반환하지 않고 LinkResponse로 공개 필드를 제한하며, 토큰 생성은 실패 시 오류를 던지도록 바꿔 보안 관련 처리를 명시한다.
  3. 인증과 링크 처리를 AuthController·LinkController로 옮기고 routes.swift에는 경로 등록과 인증 그룹만 남겨 API 구조를 읽기 쉽게 정리한다.

도입

LinkVault는 사용자 가입과 로그인, bearer token 인증, 사용자별 링크 저장을 구현한 상태다. 하지만 기능을 routes.swift에 계속 추가하면서 라우트 파일이 복잡해졌다. 이 단계에서는 요청 DTO에 입력 검증을 붙이고, 공개 응답 DTO를 분리하며, 인증과 링크 처리를 각각 컨트롤러로 이동한다. 토큰 생성 오류 처리도 명시한다.

상세 요약

요청 DTO에서 입력 검증

기존 DTO는 JSON을 Swift 타입으로 디코딩했지만, 값이 API 규칙에 맞는지는 확인하지 않았다. 각 요청 타입에 Validatable을 적용하면 요청 본문을 디코딩하기 전에 검증할 수 있다.

  • SignupRequest: 이메일 형식, 이름 필수 및 250자, 비밀번호 8128자
  • LoginRequest: 이메일 형식과 비어 있지 않은 비밀번호
  • CreateLinkRequest: 필수 제목 1~120자, URL 형식, 선택 메모 최대 500자
  • UpdateLinkRequest: 모든 필드가 선택 사항이며, 전달된 제목·URL·메모에는 생성 요청과 같은 조건을 적용하고 isRead는 Bool로 검증

로그인 비밀번호에는 가입 시의 길이 규칙을 적용하지 않는다. 기존 계정의 비밀번호를 저장된 해시와 대조하기 때문이다. 업데이트는 PATCH 요청이므로 isRead 하나만 보내거나 제목만 보내는 부분 변경을 허용한다. 각 핸들러는 Request.validate(content:)를 호출한 뒤 요청 데이터를 디코딩한다.

공개 응답과 토큰 생성

링크 응답에 Fluent 모델을 그대로 사용하지 않도록 LinkResponse를 추가한다. 이 DTO는 id, title, url, note, isRead, createdAt만 포함한다. Link.asPublicResponse()가 모델을 응답 타입으로 변환하며, 링크에 ID가 없으면 내부 서버 오류를 던진다. 모델은 저장과 관계를, 응답 DTO는 클라이언트에 공개할 표현을 담당한다.

토큰 생성 함수는 32개의 secure random 바이트를 Base64로 인코딩해 반환하고, 실패할 수 있도록 throws로 바뀐다. 따라서 토큰을 만드는 가입·로그인 처리에서는 try TokenGenerator.generate()를 사용한다. 생성에 실패하면 요청 처리를 계속하지 않고 내부 서버 오류를 반환한다.

컨트롤러로 역할 분리

AuthController와 LinkController는 RouteCollection을 준수하고 boot(routes:)에서 각 경로를 등록한다. 인증 컨트롤러는 /auth/signup과 /auth/login을 맡는다. 가입과 로그인에는 이메일 정규화, 사용자 조회, 비밀번호 해시 검증, 토큰 저장 및 공개 사용자 응답 반환이 포함된다.

LinkController는 링크 생성·목록·단건 조회·수정·삭제를 처리한다. 목록은 현재 사용자의 링크만 생성일 내림차순으로 반환한다. 단건 조회와 수정, 삭제는 링크 ID뿐 아니라 인증된 사용자의 ID도 조건에 포함한다. 따라서 다른 사용자의 링크는 ID를 알고 있어도 조회하거나 변경할 수 없다. 생성과 수정 응답은 LinkResponse로 변환한다.

routes.swift와 동작 확인

routes.swift에는 /health 경로, /api 그룹, 컨트롤러 등록이 남는다. 인증 경로는 AuthController에 등록하고, UserTokenAuthenticator를 적용한 그룹에는 /me와 /links를 둔다. 링크 컨트롤러 자체는 /api/links에 탑재된다는 사실을 알 필요가 없다.

글은 앱과 Docker 데이터베이스를 실행한 뒤 curl로 확인하는 흐름을 제시한다. 잘못된 가입 요청에서는 이메일·이름·비밀번호 검증 오류가 반환되고, 로그인은 bearer token과 공개 사용자 정보를 돌려준다. 인증 토큰으로 링크 목록을 조회하고 링크를 생성할 수 있으며, 빈 제목 같은 잘못된 링크 입력은 검증 오류로 거부된다.

다음 단계에서 다룰 컬렉션, 태그, 관계 및 필터링은 Vapor 백엔드에 컬렉션·태그·필터링·페이지네이션 추가하기 (5)에서 이어진다.

결론

요청 검증은 잘못된 값이 route 로직과 데이터베이스에 도달하는 것을 막고, LinkResponse는 데이터베이스 모델과 공개 API 표현을 분리한다. 컨트롤러가 관련 경로와 처리를 맡으면서 routes.swift는 API의 구성과 인증 경계를 보여주는 파일로 축소된다. 글의 다음 단계는 컬렉션·태그와 관계 및 필터링 기능을 추가하는 것이다.