[Swift]GET,POST,PATCH,PUT(MacOS)

⭐️ ReactExample7 – server + swift

👉🏻 아래는 GET,POST,PATCH,PUT을 Swift에서 구현한 예제입니다.
Below is an example of implementing GET, POST, PATCH, and PUT in Swift.

👉🏻 리액트로 구현한 예제는 아래에 링크된 포스트를 참조하세요
For an example implemented with React, please refer to the post linked below.

👉🏻 Nodejs Express Server(Backend)

✔️Server.js

const express = require("express");
const app = express();
const PORT = 3000;

app.use(express.json());

// 가상의 데이터베이스 / Virtual Database
let users = [{ id: "1", name: "V", age: 20, nickname: "Cyberpunk2077" }];

// GET: 특정 유저 조회 / Specific user inquiry
app.get("/users/:id", (req, res) => {
  const user = users.find((u) => u.id === req.params.id);
  user
    ? res.json(user)
    : res.status(404).json({ message: "유저 없음/No user" });
});

// POST: 새로운 유저 생성 / Create new user
app.post("/users", (req, res) => {
  const newUser = { id: String(users.length + 1), ...req.body };
  users.push(newUser);
  res.status(201).json(newUser);
});

// PATCH: 데이터의 '일부'만 수정 (기존 데이터 유지)
// Modify only 'part' of the data (keep existing data)
app.patch("/users/:id", (req, res) => {
  const index = users.findIndex((u) => u.id === req.params.id);
  if (index !== -1) {
    // 기존 값 + 바뀐 값 합치기 / Merge existing value + changed value
    users[index] = { ...users[index], ...req.body };
    res.json(users[index]);
  } else {
    res.status(404).json({ message: "유저 없음/No user" });
  }
});

// PUT: 데이터 '전체'를 교체 (보내지 않은 필드는 사라짐)
// Replace 'all' data (unsent fields disappear)
app.put("/users/:id", (req, res) => {
  const index = users.findIndex((u) => u.id === req.params.id);
  if (index !== -1) {
    // 기존 내용 무시하고 덮어쓰기 / Ignore existing content and overwrite
    users[index] = { id: req.params.id, ...req.body };
    res.json(users[index]);
  } else {
    res.status(404).json({ message: "유저 없음/No user" });
  }
});

// DELETE: 데이터 삭제 / data deletion
app.delete("/users/:id", (req, res) => {
  users = users.filter((u) => u.id !== req.params.id);
  res.json({ message: `User ${req.params.id} 삭제 완료 / Deletion complete` });
});

app.listen(PORT, () => console.log(`Server: http://localhost:${PORT}`));

👉🏻 Swift(Frontend)

✔️ ContentView


import SwiftUI

// 데이터 모델 정의 / Data model definition
struct User: Codable, Identifiable {
    let id: Int?
    var name: String?
    var age: Int?
    var nickname: String?
}

struct MessageResponse: Codable {
    let message: String
}

struct ContentView: View {
    @State private var userId: String = "1"
        @State private var userData: User? = nil
        @State private var log: String = ""
        
        // 서버 기본 URL / Server Base URL
        let baseURL = "http://localhost:3000/users"

        var body: some View {
            ScrollView {
                VStack(alignment: .leading, spacing: 20) {
                    Text("HTTP Methods Playground")
                        .font(.title).bold()

                    TextField("User ID", text: $userId)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                        .keyboardType(.numberPad)

                    // 버튼 그룹 / Button Group
                    VStack(spacing: 10) {
                        HStack {
                            methodButton("GET (조회/Search)", color: Color(.systemGray5)) { getUser() }
                            methodButton("POST (생성/Creation)", color: Color(red: 0.8, green: 1, blue: 0.8)) { createUser() }
                        }
                        HStack {
                            methodButton("PATCH (일부수정/modifications)", color: Color(red: 1, green: 0.95, blue: 0.8)) { patchUser() }
                            methodButton("PUT (전체교체/replacement)", color: Color(red: 1, green: 0.8, blue: 0.8)) { putUser() }
                        }
                        methodButton("DELETE (삭제/Deletion)", color: Color(.systemGray6)) { deleteUser() }
                    }

                    HStack(alignment: .top, spacing: 20) {
                        
                        // 현재 데이터 상세 / Current Data State
                        VStack(alignment: .leading) {
                            Text("현재 데이터 상세 / Current data details").font(.headline)
                            if let user = userData {
                                VStack(alignment: .leading) {
                                    Text("ID: \(user.id ?? 0)")
                                    Text("이름/Name: \(user.name ?? "없음/none")")
                                    Text("나이/Age: \(user.age != nil ? "\(user.age!)" : "없음/none")")
                                    Text("닉네임/Nickname: \(user.nickname ?? "없음/none")")
                                }
                                .padding()
                                .border(Color.blue)
                            } else {
                                Text("조회된 데이터가 없습니다./No data was retrieved.")
                            }
                        }
                        .frame(maxWidth: .infinity, alignment: .leading)

                        // 서버 원본 응답 / Server original response(Raw JSON)
                        VStack(alignment: .leading) {
                            Text("서버 응답 / server response").font(.headline)
                            Text(log)
                                .font(.system(.caption, design: .monospaced))
                                .padding()
                                .frame(maxWidth: .infinity, alignment: .leading)
                                .background(Color(.systemGray6))
                        }
                        .frame(maxWidth: .infinity, alignment: .leading)
                    }
                }
                .padding()
            }
}

    // MARK: - HTTP Methods
    func getUser() {
       performRequest(url: "\(baseURL)/\(userId)", method: "GET")
    }

    func createUser() {
       let body: [String: Any] = ["name": "NewUser", "age": 30, "nickname": "SilverHand"]
       performRequest(url: baseURL, method: "POST", body: body)
    }

    func patchUser() {
       let body: [String: Any] = ["nickname": "Johnny"]
       performRequest(url: "\(baseURL)/\(userId)", method: "PATCH", body: body)
    }

    func putUser() {
       // PUT은 보통 리소스 전체를 갈아끼우므로 nickname을 생략하면 null이 될 가능성을 시뮬레이션합니다.
       // Since PUT usually replaces the entire resource, omitting nickname simulates the possibility that it will be null.
       let body: [String: Any] = ["name": "John", "age": 25]
       performRequest(url: "\(baseURL)/\(userId)", method: "PUT", body: body)
    }

    func deleteUser() {
        Task {
            // Task를 통해 메인 액터와 비동기 컨텍스트를 연결
            // Connect the main actor and the asynchronous context through Task

            guard let url = URL(string: "\(baseURL)/\(userId)") else { return }
            var request = URLRequest(url: url)
            request.httpMethod = "DELETE"
            
            do {
                let (data, _) = try await URLSession.shared.data(for: request)
                let decoded = try JSONDecoder().decode(MessageResponse.self, from: data)
                
                // UI 업데이트는 메인 스레드에서
                // UI updates are done on the main thread
                await MainActor.run {
                    self.userData = nil
                    self.log = decoded.message
                }
            } catch {
                print("Error: \(error)")
            }
        }
    }

    // MARK: - Helper Methods
    
    // 서버에 요청 / Request to server
    func performRequest(url urlString: String, method: String, body: [String: Any]? = nil) {
        guard let url = URL(string: urlString) else { return }
        var request = URLRequest(url: url)
        request.httpMethod = method
            
        if let body = body {
          request.addValue("application/json", forHTTPHeaderField: "Content-Type")
          request.httpBody = try? JSONSerialization.data(withJSONObject: body)
        }

        URLSession.shared.dataTask(with: request) { data, response, error in
            guard let data = data else { return }
                    
            // UI 업데이트는 메인 스레드에서
            // UI updates are done on the main thread
            DispatchQueue.main.async {
              self.log = String(data: data, encoding: .utf8) ?? ""
              if let decodedUser = try? JSONDecoder().decode(User.self, from: data) {
                 self.userData = decodedUser
              } else {
                 self.userData = nil
              }
            }
        }.resume()
    }

    func methodButton(_ title: String, color: Color, action: @escaping () -> Void) -> some View {
      Button(action: action) {
        Text(title)
            .font(.caption)
            .padding(10)
            .frame(maxWidth: .infinity)
            .background(color)
            .foregroundColor(.black)
            .cornerRadius(5)
      }
    }
}

#Preview {
    ContentView()
}

👉🏻 스크린샷/ScreenShot

Leave a Reply