โญ๏ธ 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
