iOS
HTTP POST
networking
Swift
iOS development

iOS how to perform a HTTP POST request?

Master System Design with Codemia

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

Introduction

On iOS, the standard way to perform an HTTP POST request is to build a URLRequest, set its method to POST, attach a body, and send it with URLSession. The details matter because servers usually expect specific headers and body formats such as JSON or form-encoded data.

Most modern Swift code uses URLSession directly. If you understand URLRequest, status-code handling, and JSON encoding, you can build the majority of POST calls without another networking library.

Build a JSON POST Request

A JSON API call typically needs a URL, the POST method, a Content-Type header, and a JSON-encoded body.

swift
1import Foundation
2
3struct LoginRequest: Encodable {
4    let email: String
5    let password: String
6}
7
8func sendLogin() async {
9    guard let url = URL(string: "https://httpbin.org/post") else {
10        print("Invalid URL")
11        return
12    }
13
14    let payload = LoginRequest(email: "[email protected]", password: "secret123")
15
16    var request = URLRequest(url: url)
17    request.httpMethod = "POST"
18    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
19
20    do {
21        request.httpBody = try JSONEncoder().encode(payload)
22
23        let (data, response) = try await URLSession.shared.data(for: request)
24
25        guard let httpResponse = response as? HTTPURLResponse else {
26            print("Unexpected response type")
27            return
28        }
29
30        print("Status:", httpResponse.statusCode)
31        print(String(data: data, encoding: .utf8) ?? "No response body")
32    } catch {
33        print("Request failed:", error)
34    }
35}

This example posts JSON to a public echo endpoint so you can run it and inspect the returned body. In a real app, replace the URL and payload model with your actual API contract.

Call It from UIKit Code

If you are inside a view controller, launch the async function from a task.

swift
1import UIKit
2
3final class LoginViewController: UIViewController {
4    @IBAction func loginTapped(_ sender: UIButton) {
5        Task {
6            await sendLogin()
7        }
8    }
9}

If the request result should update the UI, move those updates back onto the main actor. Networking itself does not need the main thread, but UIKit does.

Handle Status Codes and Response Data

Do not assume every completed request was successful. URLSession only throws for transport-level failures such as connectivity problems. A server can still return 400, 401, or 500 and the call will technically complete.

swift
1if (200...299).contains(httpResponse.statusCode) {
2    print("Success")
3} else {
4    print("Server returned status:", httpResponse.statusCode)
5}

If the server returns JSON, decode it with JSONDecoder rather than manually parsing strings. That keeps the code safer and easier to maintain.

POST Form Data When the API Requires It

Not every server expects JSON. Some endpoints still use URL-encoded form bodies.

swift
1import Foundation
2
3func sendFormRequest() async {
4    guard let url = URL(string: "https://httpbin.org/post") else {
5        return
6    }
7
8    var request = URLRequest(url: url)
9    request.httpMethod = "POST"
10    request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
11
12    let body = "username=mark&role=admin"
13    request.httpBody = body.data(using: .utf8)
14
15    do {
16        let (_, response) = try await URLSession.shared.data(for: request)
17        print((response as? HTTPURLResponse)?.statusCode ?? -1)
18    } catch {
19        print(error)
20    }
21}

The request body format must match what the server expects. Sending JSON to a form endpoint, or the reverse, often leads to confusing validation errors.

Completion-Handler Version for Older Codebases

If your project is not using Swift concurrency yet, URLSession.dataTask still works well.

swift
1import Foundation
2
3func sendPostWithCompletion() {
4    guard let url = URL(string: "https://httpbin.org/post") else {
5        return
6    }
7
8    var request = URLRequest(url: url)
9    request.httpMethod = "POST"
10    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
11    request.httpBody = #"{"message":"hello"}"#.data(using: .utf8)
12
13    let task = URLSession.shared.dataTask(with: request) { data, response, error in
14        if let error = error {
15            print("Network error:", error)
16            return
17        }
18
19        let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1
20        print("Status:", statusCode)
21        print(String(data: data ?? Data(), encoding: .utf8) ?? "")
22    }
23
24    task.resume()
25}

This is still common in existing UIKit projects, especially when code was written before async and await support.

Common Pitfalls

The most common mistake is forgetting task.resume() when using completion handlers. Without it, the request is configured but never starts.

Another issue is sending a body without setting the correct Content-Type header. Many APIs will reject the request or parse it incorrectly.

Developers also sometimes treat every non-throwing call as success and forget to inspect the HTTP status code. That hides server-side failures.

Finally, make sure your endpoint uses https in production. Plain http URLs can be blocked by App Transport Security unless you add exceptions, and those exceptions should be deliberate rather than casual defaults.

Summary

  • Build POST calls with URLRequest and send them through URLSession.
  • Set the method, headers, and body to match the API contract exactly.
  • Check HTTP status codes in addition to transport errors.
  • Use JSON encoding for JSON APIs and form encoding only when the endpoint expects it.
  • Update UIKit views on the main actor after the network response is processed.

Course illustration
Course illustration

All Rights Reserved.