Sending an HTTP POST request to an API using Swift

Hi, I'm trying to send a POST request within Swift and I cannot get it to work properly. I can send a generic GET request that seems to work fine but a POST request is giving me no end of grief.

This is what I have which works perfectly fine as a GET request.

guard let url = URL(string: "https://example.com/api/my-endpoint") else {
	return
}

URLSession.shared.dataTask(with: url) { (data, response, error) in
	let collection = try! JSONDecoder().decode(APIResponse.self, from: data!)
	
	print(collection)
}.resume()

However, I need to send a POST request with two parameters; email and id but all of the tutorials and questions I've searched through on Google are just too complicated and go way over my head. I'm hoping someone can explain clearly how I can achieve this?

Create an instance of URLRequest, which encapsulates the URL, http method, and body of a request (among other things), and pass it into URLSession.dataTask(with:completionHandler:).

Simple example used in an async function to post JSON content from a message which is Codable:

      let url = URL(string: address)!
      var request = URLRequest(url: url)
      request.setValue("application/json", forHTTPHeaderField: "Content-Type")
      request.httpMethod = "POST"
      let encoder = JSONEncoder()
      let data = try encoder.encode(message)
      request.httpBody = data

      let (responseData, response) = try await URLSession.shared.upload(for: request, from: data, delegate: self)
      // etc...

Finally converting to Mac and Swift after years of Flutter, ...

I would have loved just to see the '// etc...' expanded by just a line or two. Sigh.

Updated sample from a small demo chat app. observer is a protocol implemented elsewhere, in an @Observable model for SwiftUI.

func sendMessage(_ message: ChatMessage, to address: String) async throws {
	var request = createRequest(for: address)
	request.httpMethod = "POST"
	let encoder = JSONEncoder()
	encoder.dateEncodingStrategy = .formatted(DateFormatter.fullISO8601)
	let data = try encoder.encode(message)
	request.httpBody = data
		
	let (responseData, response) = try await URLSession.shared.data(for: request)
	if let httpResponse = response as? HTTPURLResponse {
		await MainActor.run {
			switch httpResponse.statusCode {
			case 200..<300:
				observer.handleSuccessfulDelivery(of: message)
			case 400..<500:
				let string = String(bytes: responseData, encoding: .utf8) ?? "???"
				observer.handleClientError(code: httpResponse.statusCode, description: string)
			case 500...:
				let string = String(bytes: responseData, encoding: .utf8) ?? "???"
				observer.handleServerError(code: httpResponse.statusCode, description: string)
			default:
				observer.handleClientError(code: httpResponse.statusCode, description: "Unknown error")
			}
		}
	}
}
	
private func createRequest(for address: String) -> URLRequest {
	let url = URL(string: address)!
	logger.debug("http request with address \(address)")
	var request = URLRequest(url: url)
	let loginData = observer.getUserCredentials().data(using: .utf8)!
	let base64LoginString = loginData.base64EncodedString()
	request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
	request.setValue("application/json", forHTTPHeaderField: "Content-Type")
	return request
}
1 Like

Thank you!