티스토리 뷰

 

지난 포스트에서 Fluent와 PostgreSQL을 연결하고 URL을 통해 그 결과를 확인했다.

 

이번 글에서는 기본으로 제공되는 메소드를 분석해보고, 추가로 메소드를 작성해 CRUD를 할 것이다.

 

 

[Vapor] Swift로 서버 만들기 05 - Fluent 통해서 PostgreSQL 사용하기

이번 포스트에서는 PostgerSQL 데이터베이스를 만들고, Fluent를 사용해 데이터를 다뤄볼 것이다. Fluent 개념과 PostrgreSQL에 대해서는 이전 글들을 통해 확인할 수 있다. [Swift] Swift로 서버 만들기 Vapor 0

malchafrappuccino.tistory.com

 

 

Postman

API 테스트를 위해 가장 많이 사용하는 Postman을 사용할 것이다.

 

프로젝트를 실행하고 Postman에 들어가서 기본 호출을 해본다.

에러 발생

에러가 발생한다.

 

뭐지? 프로젝트 안 켰나하고 크롬창에 쳐보면

It works!

잘만 나온다.

 

 

 

Postman Agent 설치

현재 로컬 머신에서 프로젝트를 실행하고 있다. 에러 메시지에 보이는 것처럼 그냥 postman을 사용하면 로컬 호스트에 접속할 수가 없다.

 

로컬호스트에 접속하기 위해서 Postman Agent라는 추가적인 프로그램을 깔아줘야 한다.

 

 

Postman Agent: For Mac, Windows, & Linux

The Postman agent overcomes the Cross-Origin Resource Sharing (CORS) limitations of browsers, and facilitates API request sending from your browser version of Postman.

www.postman.com

 

설치가 완료되면 프로그램을 클릭해서 실행해준다. 우측 상단에서 실행되고 있는 것을 확인할 수 있다.

 

포맨 에이전트. 미션 수행 중.

프로그램을 실행하고 다시 메소드를 호출해보면

 

It works!

 

응답이 잘 오는 것을 확인할 수 있다.

 

 

 

TodoController 기본 메소드

Vapor 프로젝트 생성할 때 Fluent를 같이 생성하면 TodoController.Swift 파일이 같이 생성된다.

 

struct TodoController: RouteCollection {
    func boot(routes: RoutesBuilder) throws {
        let todos = routes.grouped("todos")
        todos.get(use: index)
        todos.post(use: create)
        todos.group(":todoID") { todo in
            todo.delete(use: delete)
        }
    }

    func index(req: Request) async throws -> [Todo] {
        //...
    }

    func create(req: Request) async throws -> Todo {
        //...
    }

    func delete(req: Request) async throws -> HTTPStatus {
        //...
    }
}

하나하나 살펴보자

 

 

boot(routes:)

boot 함수는 TodoController를 시작하는 함수로써 RouteCollection을 채택하면 반드시 구현해주어야 하는 함수이다.

 

새로운 routes를 등록한다.

 

boot함수 안을 살펴보자

 

func boot(routes: RoutesBuilder) throws {
    let todos = routes.grouped("todos")
    todos.get(use: index)
    todos.post(use: create)
    todos.group(":todoID") { todo in
        todo.delete(use: delete)
    }
}

 

grouped 메소드를 호출한다. 이 메소드는 routes URL에 Path를 더해주는 함수이다.

 

현재 기본 route URL은 http://127.0.0.1:8080 이다.

 

grouped 메소드를 통해 path에 경로를 붙여주었으므로 todos 프로퍼티는 http://127.0.0.1:8080/todos 가 된다.

 

 

todos에 메소드를 사용해 HTTP 메소드를 등록한다. get(use:), post(use:) 은 클로저를 인자로 받는다.

 

클로저를 인자로 받음

 

해당 HTTP 메소드 호출시 실행될 응답 클로저를 인자에 넣어준다.

 

 

get 메소드

func index(req: Request) async throws -> [Todo] {
    try await Todo.query(on: req.db).all()
}

get 메소드는 index 함수를 인자로 받는다.

 

index 함수는 Todo 모델이 저장된 db에서 모든 데이터를 배열로 리턴한다.

 

Postman을 통해 메소드를 날려보면

 

 

빈 배열을 확인할 수 있다. (아직 아무 데이터도 추가하지 않음)

 

 

Post 메소드

todos.post(use: create)

post 메소드는 create 함수를 인자로 받는다.

 

func create(req: Request) async throws -> Todo {
    let todo = try req.content.decode(Todo.self)
    try await todo.save(on: req.db)
    return todo
}

 

  1. 요청(request)의 content(=body)에서 Todo 모델을 디코드 한다.
  2. DB에 저장한다.
  3. 응답으로 저장한 Todo를 리턴한다.

 

1번을 위해 HTTP post메소드 body에 JSON 형식으로 Todo 모델을 넣어준다.

 

포맨 설정

 

그리고 응답을 확인하면?

 

 

응.답. 성공적.

 

Todo 모델이 잘 도착했다!  잉 근데 나는 title이랑 descrition만 작성했는데?? 

 

앞선 포스트에서 작성했듯이 id를 설정하지 않으면 Fluent가 자동으로 id를 생성해준다.

 

createdAt과 updatedAt과 같은 TimeStamp 필드도 Fluent가 상황에 맞게 자동으로 업데이트 해준다.

 

 

 

get 메소드를 통해 전체 DB를 확인해보면

 

get 메소드 날리기

 

잘 저장된 것을 확인할 수 있다.

 

 

delete 메소드

todos.group(":todoID") { todo in
    todo.delete(use: delete)
}

delete 메소드는 getpost와는 다르게 생겼다.

 

group 메소드를 통해 추가 path를 받고 해당 path에 todoID를 넣어준다.

 

http://127.0.0.1:8080/todos/{todoID를 넣어줌}

 

todo의 delete를 살펴보자

 

func delete(req: Request) async throws -> HTTPStatus {
    guard let todo = try await Todo.find(req.parameters.get("todoID"), on: req.db) else {
        throw Abort(.notFound)
    }
    try await todo.delete(on: req.db)
    return .noContent
}

 

메소드의 todoID로 받은 인자를 사용해 db에서 해당하는 Todo모델을 찾는다. -> 만약 해당하는 모델이 없다면 notFound 에러를 응답으로 보낸다.

 

db에서 모델을 삭제한 후 컨텐츠 없이 응답만 리턴해준다. 여기서 에러 메시지를 커스텀하고 싶으면 리턴 값을 수정해주면 된다.

 

todos 뒤에 id를 추가해줌

 

메소드를 호출해보면

 

 

204 No Content 응답을 확인할 수 있다.

 

데이터가 삭제된 상태에서 한 번 더 호출하면?

 

 

404 Not Found 에러를 확인할 수 있다.

 

delete후 get

 

다시 get해 보면 DB에서 다 사라진 것을 확인할 수 있다.

 

 

커스텀 HTTP 메소드 추가하기

TodoController의 기본 메소드에는 업데이트가 없다. Todo 모델 업데이트 함수를 작성해보자.

 

func update(req: Request) async throws -> Todo {
    let newTodo = try req.content.decode(Todo.self)
    
    guard let todo = try await Todo.find(newTodo.id, on: req.db) else {
        throw Abort(.notFound)
    }
    
    todo.title = newTodo.title
    todo.description = newTodo.description
    try await todo.update(on: req.db)
    
    return todo
}

update 함수는 총 5단계를 거친다.

 

  1. 우선 수정할 모델 newTodo을 요청으로 받는다.
  2. newTodoid를 기반으로 DB에서 수정할 모델 todo를 찾는다.
  3. todotitledescription을 newTodo 걸로 수정해준다.
  4. DB를 업데이트 한다.
  5. 수정된 모델을 응답으로 리턴한다.

 

boot 함수에 PUT메소드도 추가해준다.

func boot(routes: RoutesBuilder) throws {
    let todos = routes.grouped("todos")
    todos.get(use: index)
    todos.post(use: create)
    todos.group(":todoID") { todo in
        todo.delete(use: delete)
    }
    
    todos.put(use: update) // PUT 추가하기
}

 

메소드 작성 완료. Postman에서 확인해보자

 

Post하기

 

POST를 사용해 새로운 내용을 추가해주었다.

 

PUT 메소드를 추가하자

 

id 필수

 

여기서 post의 body와 다른 점은 id를 필수로 추가해주어야 한다는 것이다. 그래야 id로 db에서 찾을 수 있으니까

 

 

메소드를 호출하고 응답을 확인하면

 

 

titledescription이 수정된 것을 확인할 수 있다. 또 Fluent가 업데이트를 감지하고 자동으로 updateAt 필드를 업데이트 해주었다.

 

 

 

+ Post를 통해 모델을 몇 개 더 추가해주었다.

 

 

 

이제 조금 DB 다워졌다.

 

 

정리

TodoController의 기본 메소드를 살펴보고 Update함수를 추가로 작성해주었다.

 

처음 사용할 때는 Controller 사용법을 몰라 routes 함수에 메소드를 다 때려넣었는데, Controller를 사용하니 훨씬 깔끔해지고 코드 분리가 명확해졌다.

 

API를 만드는 것을 처음해보는데 재밌다.

 

다음에는 쿼리 날리는 공부를 해봐야겠다.

 

 

끗!

 

 

 

다음 포스트 - 쿼리(Query) 만들기 Filter, Sort, Count, Paginate 등

 

 

 

[Vapor] Swift로 서버 만들기 07 - Fluent 쿼리(Query) 만들기 Filter, Sort, Count, Paginate 등

지난 포스트에서 HTTP 메소드를 만들어 모델에 대해 CRUD를 했다. Fluent의 query API는 데이터베이스에서 모델을 CRUD할 뿐만아니라 필터링, 조인, 청크, 집계 등을 지원한다. 이번 글에서는 Fluent 공식

malchafrappuccino.tistory.com

 

 

 

출처

 

Vapor: Fluent → Model

Models Models represent data stored in tables or collections in your database. Models have one or more fields that store codable values. All models have a unique identifier. Property wrappers are used to denote identifiers, fields, and relations. Below is

docs.vapor.codes

 

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함