Swift
REST API
Networking
API Call
iOS Development

Make REST API call in Swift

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Making REST API calls in Swift uses URLSession, Apple's built-in networking framework. It supports all HTTP methods (GET, POST, PUT, DELETE), handles JSON encoding/decoding, and works asynchronously to keep the UI responsive. Swift 5.5+ also supports async/await for cleaner networking code.

HTTP Methods

  • GET: Retrieve data from a server
  • POST: Send data to a server to create a resource
  • PUT: Update a resource on a server
  • DELETE: Remove a resource from a server

GET Request

Completion Handler (Traditional)

swift
1func fetchUsers(completion: @escaping (Result<[User], Error>) -> Void) {
2    let url = URL(string: "https://api.example.com/users")!
3
4    URLSession.shared.dataTask(with: url) { data, response, error in
5        if let error = error {
6            completion(.failure(error))
7            return
8        }
9
10        guard let data = data else {
11            completion(.failure(URLError(.badServerResponse)))
12            return
13        }
14
15        do {
16            let users = try JSONDecoder().decode([User].self, from: data)
17            completion(.success(users))
18        } catch {
19            completion(.failure(error))
20        }
21    }.resume()
22}

Async/Await (Swift 5.5+)

swift
1func fetchUsers() async throws -> [User] {
2    let url = URL(string: "https://api.example.com/users")!
3    let (data, response) = try await URLSession.shared.data(from: url)
4
5    guard let httpResponse = response as? HTTPURLResponse,
6          httpResponse.statusCode == 200 else {
7        throw URLError(.badServerResponse)
8    }
9
10    return try JSONDecoder().decode([User].self, from: data)
11}
12
13// Usage
14Task {
15    do {
16        let users = try await fetchUsers()
17        print(users)
18    } catch {
19        print("Error: \(error)")
20    }
21}

POST Request

swift
1struct CreateUserRequest: Encodable {
2    let name: String
3    let email: String
4}
5
6func createUser(name: String, email: String) async throws -> User {
7    let url = URL(string: "https://api.example.com/users")!
8    var request = URLRequest(url: url)
9    request.httpMethod = "POST"
10    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
11
12    let body = CreateUserRequest(name: name, email: email)
13    request.httpBody = try JSONEncoder().encode(body)
14
15    let (data, response) = try await URLSession.shared.data(for: request)
16
17    guard let httpResponse = response as? HTTPURLResponse,
18          (200...299).contains(httpResponse.statusCode) else {
19        throw URLError(.badServerResponse)
20    }
21
22    return try JSONDecoder().decode(User.self, from: data)
23}

PUT and DELETE Requests

swift
1// PUT - Update a resource
2func updateUser(id: Int, name: String) async throws -> User {
3    let url = URL(string: "https://api.example.com/users/\(id)")!
4    var request = URLRequest(url: url)
5    request.httpMethod = "PUT"
6    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
7    request.httpBody = try JSONEncoder().encode(["name": name])
8
9    let (data, _) = try await URLSession.shared.data(for: request)
10    return try JSONDecoder().decode(User.self, from: data)
11}
12
13// DELETE - Remove a resource
14func deleteUser(id: Int) async throws {
15    let url = URL(string: "https://api.example.com/users/\(id)")!
16    var request = URLRequest(url: url)
17    request.httpMethod = "DELETE"
18
19    let (_, response) = try await URLSession.shared.data(for: request)
20    guard let httpResponse = response as? HTTPURLResponse,
21          httpResponse.statusCode == 204 else {
22        throw URLError(.badServerResponse)
23    }
24}

Codable Models

Define models that map to your JSON:

swift
1struct User: Codable {
2    let id: Int
3    let name: String
4    let email: String
5    let createdAt: String
6
7    enum CodingKeys: String, CodingKey {
8        case id, name, email
9        case createdAt = "created_at"
10    }
11}

Adding Headers and Authentication

swift
1var request = URLRequest(url: url)
2request.setValue("application/json", forHTTPHeaderField: "Content-Type")
3request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
4request.setValue("gzip", forHTTPHeaderField: "Accept-Encoding")

Generic Network Layer

swift
1class APIClient {
2    static let shared = APIClient()
3    private let session = URLSession.shared
4    private let baseURL = "https://api.example.com"
5
6    func request<T: Decodable>(
7        path: String,
8        method: String = "GET",
9        body: Encodable? = nil
10    ) async throws -> T {
11        let url = URL(string: "\(baseURL)\(path)")!
12        var request = URLRequest(url: url)
13        request.httpMethod = method
14        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
15
16        if let body = body {
17            request.httpBody = try JSONEncoder().encode(body)
18        }
19
20        let (data, response) = try await session.data(for: request)
21
22        guard let httpResponse = response as? HTTPURLResponse,
23              (200...299).contains(httpResponse.statusCode) else {
24            throw URLError(.badServerResponse)
25        }
26
27        let decoder = JSONDecoder()
28        decoder.keyDecodingStrategy = .convertFromSnakeCase
29        return try decoder.decode(T.self, from: data)
30    }
31}
32
33// Usage
34let users: [User] = try await APIClient.shared.request(path: "/users")

Common Pitfalls

  • Main thread updates: URLSession callbacks run on a background thread. Update UI on the main thread: DispatchQueue.main.async { } or use @MainActor.
  • ATS (App Transport Security): iOS blocks non-HTTPS connections by default. Add exceptions in Info.plist if needed, or preferably use HTTPS.
  • Forgetting .resume(): URLSession.dataTask() returns a suspended task. Call .resume() to start it. The async/await API does not require this.
  • JSON key mismatch: Swift uses camelCase; APIs often use snake_case. Use keyDecodingStrategy = .convertFromSnakeCase or CodingKeys enum.
  • Retain cycles: In completion handler closures, capture [weak self] to avoid retain cycles in view controllers.

Summary

  • Use URLSession.shared.data(from:) with async/await for clean networking code
  • Configure URLRequest with HTTP method, headers, and body for POST/PUT/DELETE
  • Use Codable for automatic JSON encoding/decoding
  • Always handle errors and check HTTP status codes
  • Update UI on the main thread after network responses

Course illustration
Course illustration

All Rights Reserved.