Swift
REST API
Networking
iOS Development
HTTP Requests

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 is done using URLSession, Apple's built-in networking framework. It handles HTTP requests, response parsing, authentication, and background downloads without requiring any third-party libraries. This article covers GET, POST, PUT, and DELETE requests with proper error handling and JSON parsing using Codable.

Basic GET Request

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
13struct User: Codable {
14    let id: Int
15    let name: String
16    let email: String
17}

Validating the Response

Every API response should be checked for three things:

  • Data: Is there data returned from the server?
  • HTTP Status Code: Does the server respond with a success code (200-299)?
  • Error: Is there a network error (timeout, no connection)?
swift
1func fetchData(from urlString: String) async throws -> Data {
2    guard let url = URL(string: urlString) else {
3        throw URLError(.badURL)
4    }
5
6    let (data, response) = try await URLSession.shared.data(from: url)
7
8    guard let httpResponse = response as? HTTPURLResponse else {
9        throw URLError(.badServerResponse)
10    }
11
12    guard (200...299).contains(httpResponse.statusCode) else {
13        throw URLError(.badServerResponse)
14    }
15
16    return data
17}

POST Request

Send JSON data to create a resource:

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

PUT Request

Update an existing resource:

swift
1func updateUser(id: Int, name: String) async throws -> User {
2    let url = URL(string: "https://api.example.com/users/\(id)")!
3
4    var request = URLRequest(url: url)
5    request.httpMethod = "PUT"
6    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
7
8    let body = ["name": name]
9    request.httpBody = try JSONEncoder().encode(body)
10
11    let (data, _) = try await URLSession.shared.data(for: request)
12    return try JSONDecoder().decode(User.self, from: data)
13}

DELETE Request

swift
1func deleteUser(id: Int) async throws {
2    let url = URL(string: "https://api.example.com/users/\(id)")!
3
4    var request = URLRequest(url: url)
5    request.httpMethod = "DELETE"
6
7    let (_, response) = try await URLSession.shared.data(for: request)
8
9    guard let httpResponse = response as? HTTPURLResponse,
10          httpResponse.statusCode == 204 else {
11        throw URLError(.badServerResponse)
12    }
13}

Adding Authentication Headers

swift
1func fetchProtectedData() async throws -> Data {
2    let url = URL(string: "https://api.example.com/profile")!
3    var request = URLRequest(url: url)
4
5    // Bearer token
6    request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
7
8    // API key
9    request.setValue("your-api-key", forHTTPHeaderField: "X-API-Key")
10
11    let (data, _) = try await URLSession.shared.data(for: request)
12    return data
13}

Query Parameters

swift
1func searchUsers(query: String, page: Int) async throws -> [User] {
2    var components = URLComponents(string: "https://api.example.com/users")!
3    components.queryItems = [
4        URLQueryItem(name: "q", value: query),
5        URLQueryItem(name: "page", value: String(page)),
6        URLQueryItem(name: "limit", value: "20")
7    ]
8
9    let url = components.url!  // https://api.example.com/users?q=john&page=1&limit=20
10    let (data, _) = try await URLSession.shared.data(from: url)
11    return try JSONDecoder().decode([User].self, from: data)
12}

Building a Reusable API Client

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

Calling from SwiftUI

swift
1struct UsersView: View {
2    @State private var users: [User] = []
3    @State private var isLoading = true
4    @State private var error: String?
5
6    var body: some View {
7        List(users, id: \.id) { user in
8            Text(user.name)
9        }
10        .task {
11            do {
12                users = try await fetchUsers()
13                isLoading = false
14            } catch {
15                self.error = error.localizedDescription
16                isLoading = false
17            }
18        }
19    }
20}

Common Pitfalls

  • Main thread updates: URLSession callbacks run on a background thread. In SwiftUI, @State updates are automatically dispatched to the main thread, but in UIKit you must use DispatchQueue.main.async to update UI.
  • App Transport Security (ATS): iOS blocks HTTP (non-HTTPS) requests by default. For development with local servers, add an ATS exception in Info.plist, but always use HTTPS in production.
  • JSON key naming: APIs often use snake_case (user_name) while Swift uses camelCase (userName). Set decoder.keyDecodingStrategy = .convertFromSnakeCase to handle this automatically.
  • Timeout configuration: The default timeout is 60 seconds. For long-running requests, configure URLRequest.timeoutInterval or the session's URLSessionConfiguration.
  • Retain cycles with closures: When using completion handler-based APIs (pre-async/await), use [weak self] in closures to avoid retain cycles in view controllers.

Summary

HTTP MethodUse CaseStatus Code
GETFetch data200
POSTCreate resource201
PUTUpdate resource200
DELETERemove resource204
  • Use URLSession.shared.data(from:) for GET, data(for:) for other methods
  • Parse JSON with JSONDecoder and Codable models
  • Always validate HTTP status codes (200-299 range)
  • Use async/await (iOS 15+) for clean, readable networking code
  • Build a reusable APIClient to avoid duplication across your app

Course illustration
Course illustration

All Rights Reserved.