본문으로 건너뛰기

Vapor와 Fluent로 LinkVault CRUD 백엔드 만들기

Building My First Vapor Backend in 2026 pt. 2

swiftsips.dev

핵심 요약

  1. Fluent 모델과 AsyncMigration을 정의해 links 테이블을 만들고, Postgres 연결과 마이그레이션을 Vapor 앱에 등록한다.
  2. 요청 DTO를 데이터베이스 모델과 분리하고, 링크 생성·목록·단건 조회·수정·삭제를 처리하는 HTTP 경로를 구현한다.
  3. Docker로 Postgres를 실행하고 마이그레이션을 적용한 뒤 curl로 CRUD 동작을 확인해 LinkVault의 첫 상태 저장형 백엔드를 완성한다.

도입

이 글은 Vapor로 LinkVault의 첫 번째 실제 백엔드를 구현하는 과정을 다룬다. Fluent를 ORM으로 사용해 Swift 모델과 데이터베이스 스키마를 연결하고, Postgres에 링크 데이터를 저장한다. 구현 범위는 링크 모델과 마이그레이션, 데이터베이스 설정, 요청 DTO, 그리고 링크를 생성·조회·수정·삭제하는 API다.

상세 요약

Fluent 모델과 마이그레이션

Fluent는 Vapor에서 데이터베이스를 타입 기반으로 다루는 ORM이다. 앱의 모델은 데이터베이스 구조를 나타내고, 마이그레이션은 스키마 변경을 정의한다. 마이그레이션은 데이터베이스 구조의 변경 이력을 관리하며, 반복 가능한 방식으로 스키마를 구성하고 되돌릴 수 있게 한다.

Link 모델은 links 테이블에 대응한다. id는 UUID 기본 키이고, title과 url은 필수 문자열이다. note는 선택 문자열이며, isRead는 필수 Boolean이다. createdAt은 생성 시각을 자동 기록한다. Model은 Fluent 모델임을 나타내고, Content는 응답 JSON 인코딩을 지원한다. @ID, @Field, @OptionalField, @Timestamp 래퍼가 각 필드의 역할을 지정한다.

CreateLink는 AsyncMigration으로 links 테이블과 각 열을 정의한다. prepare에서 테이블을 만들고, revert에서 테이블을 삭제한다. 글은 async/await 기반 앱에 AsyncMigration 사용을 권장하는 Fluent 문서를 언급한다.

Postgres 실행과 설정

configure.swift에서 DatabaseConfigurationFactory.postgres를 사용해 Postgres 연결을 설정하고 CreateLink()를 마이그레이션으로 등록한다. 호스트, 포트, 사용자 이름, 비밀번호, 데이터베이스 이름은 각각 DATABASE_HOST, DATABASE_PORT, DATABASE_USERNAME, DATABASE_PASSWORD, DATABASE_NAME 환경 변수에서 읽는다. 환경 변수가 없을 때는 localhost, Postgres 포트, vapor_username, vapor_password, vapor_database를 기본값으로 사용한다.

글은 Docker와 Homebrew를 데이터베이스 실행 방법으로 제시하고, 튜토리얼에서는 Docker를 선택한다. Docker Desktop을 실행한 뒤 docker compose up -d로 서비스를 백그라운드에서 시작하고 docker compose ps로 실행 상태를 확인한다. 이어 docker compose build와 docker compose up db 명령을 소개한다. 마이그레이션은 swift run LinkVault migrate로 적용하며, 서버 시작 시 자동 적용하려면 --auto-migrate 옵션을 사용한다.

요청 DTO와 CRUD 경로

CreateLinkRequest는 title, url, note를 받는다. UpdateLinkRequest는 title, url, note, isRead를 모두 선택 필드로 둔다. 두 DTO는 Content를 채택하므로 경로 처리기에서 req.content.decode(...)로 요청 본문을 디코딩한다. 요청 입력 타입을 데이터베이스 모델과 별도로 두는 구조다.

/api/links 경로는 다음 동작을 제공한다.

  • POST는 요청 데이터를 디코딩해 Link를 만들고 저장한다. 새 링크의 isRead는 false로 설정한다.
  • GET은 링크 목록을 createdAt 내림차순으로 반환한다.
  • GET /:linkID는 UUID로 링크를 찾는다.
  • PUT /:linkID는 요청에 포함된 값을 모델에 반영하고 저장한다.
  • DELETE /:linkID는 레코드를 삭제하고 204 No Content를 반환한다.

경로 매개변수를 UUID로 읽을 수 없으면 400 Bad Request를 던진다. 해당 UUID의 링크가 없으면 404 Not Found를 반환한다. 별도의 /health 경로는 { "status": "ok" }를 반환한다.

curl로 동작 확인

앱을 실행한 뒤 POST /api/links에 제목, URL, 메모를 JSON으로 보내 링크를 생성한다. GET /api/links에서는 저장된 링크와 ID, 읽음 여부, 생성 시각을 확인할 수 있다. 이어 PUT /api/links/:linkID에 isRead: true를 보내 읽음 상태를 수정하고, DELETE 요청으로 레코드를 삭제한다. 삭제 뒤 목록은 빈 배열이 된다.

이 단계에서 LinkVault는 데이터베이스를 이용해 링크의 생성·조회·수정·삭제를 처리한다. 다음 편에서는 사용자 모델과 비밀번호 해시, 가입·로그인, 인증이 필요한 경로, 사용자별 링크 소유권을 추가할 예정이다. Vapor 백엔드에 사용자와 bearer-token 인증 추가하기 (3)

결론

완성된 API는 Postgres에 링크 레코드를 저장하고, 목록과 단건 조회로 읽으며, 수정과 삭제를 수행한다. 요청 DTO는 API 입력 형식을 데이터베이스 모델과 분리하고, 경로 매개변수의 UUID가 잘못되었거나 레코드가 없을 때 각각 400과 404 오류를 반환한다. 다음 단계에서는 사용자 모델, 비밀번호 해시, 가입·로그인과 인증을 추가할 예정이다.