HTTP Requests in Swift in 2025: From Beginner to PRO in 20 Minutes

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why Do Swift Developers Still Google HTTP Requests?



If you've ever written a Swift app that communicates with a server, you've definitely encountered this question. It seems simple, right? Send a request, get a response. But in practice, every second developer (even experienced ones) opens Stack Overflow and searches: "How to make HTTP request in Swift?". And that's normal.



Why? Because the Swift ecosystem is changing. Yesterday everyone was writing closures and delegates. Today it's async/await. And tomorrow, something else might appear. In 2025, approaches to network requests have stabilized, but the devil, as always, is in the details: thread management, error handling, request cancellation, testing.



In this article, we'll cover real best practices for making HTTP requests in Swift — from basic URLSession to advanced techniques with async/await and libraries. You'll learn how to write clean, safe, and performant networking code. Let's go!



1. URLSession: The Foundation Everyone Should Know



Let's start with the main thing. The Swift standard library (Foundation) has everything you need for working with HTTP — the URLSession class. Remember: for 90% of tasks, you don't need third-party libraries. URLSession is a powerful, flexible, and native tool.



1.1. Simple GET Request (Old Style)



Let's recall how it was done before. You might still encounter this code in legacy projects:



let url = URL(string: "https://api.pythonlib.ru/users")!let task = URLSession.shared.dataTask(with: url) { data, response, error in    if let error = error {        print("Error: \\(error.localizedDescription)")        return    }    guard let data = data else {        print("No data")        return    }    // Work with data    print("Bytes received: \\(data.count)")}task.resume()


This code works. But it has drawbacks:

  • Callback hell — if you need to chain requests, the code becomes unreadable.
  • Memory leaks — it's easy to forget weak self in the closure.
  • Hard to cancel — you need to keep a reference to the task.


In 2025, we write differently.



1.2. Modern GET Request with async/await



Starting with Swift 5.5 (iOS 15, macOS 12), we have structured concurrency. This changes everything:



func fetchUsers() async throws -> [User] {    let url = URL(string: "https://api.pythonlib.ru/users")!    let (data, response) = try await URLSession.shared.data(from: url)        guard let httpResponse = response as? HTTPURLResponse,          (200...299).contains(httpResponse.statusCode) else {        throw URLError(.badServerResponse)    }        return try JSONDecoder().decode([User].self, from: data)}


Beautiful, right? No closures, no resume(). The code reads like synchronous code but executes asynchronously. This is the de facto standard in 2025.



2. POST Request: Sending Data to the Server



Now let's send some data. Suppose we need to create a new user.



2.1. POST with JSON Body



func createUser(name: String, email: String) async throws -> User {    let url = URL(string: "https://api.pythonlib.ru/users")!    var request = URLRequest(url: url)    request.httpMethod = "POST"    request.setValue("application/json", forHTTPHeaderField: "Content-Type")        let body = ["name": name, "email": email]    request.httpBody = try JSONSerialization.data(withJSONObject: body)        let (data, response) = try await URLSession.shared.data(for: request)    // Handle response...    return try JSONDecoder().decode(User.self, from: data)}

                
                

Blogs

Book Recommendations