3️⃣POST Requests
Fall 2023 | Vin Bui
Creating a POST Request
Inside of our NetworkManager class, we write functions to be called by our application. These functions will use Alamofire to send HTTP requests.
Assume there is a struct called Member that represents an AppDev member.
struct Member: Codable {
let name: String
let subteam: String
let position: String
}We can write the following code to add a member to the AppDev roster store in the backend.
// 1. Create the function
func addToRoster(member: Member, completion: @escaping (Member) -> Void) {
// 2. Specify the endpoint
let endpoint = "<Enter URL String Here>"
// 3. Define the request body
let parameters: Parameters = [
"name": member.name,
"subteam": member.subteam,
"position": member.position
]
// 4. Create a decoder
let decoder = JSONDecoder()
// decoder.dateDecodingStrategy = .iso8601 // Only if needed
// decoder.keyDecodingStrategy = .convertFromSnakeCase // Only if needed
// 5. Create the request
AF.request(endpoint, method: .post, parameters: parameters, encoding: JSONEncoding.default)
.validate()
.responseDecodable(of: Member.self, decoder: decoder) { response in
// 5. Handle the response
switch response.result {
case .success(let member):
print("Successfully added member \(member.name)")
completion(member)
case .failure(let error):
print("Error in NetworkManager.addToRoster: \(error)")
}
}
}Let’s break down this code.
Create a function called
fetchRosterthat takes in a callback (completion handler) as an argument. This callback takes in aMemberobject. Note that this depends on what this POST request returns from the backend. However, we can expect the backend to return the member that we just added back to us.Specify the endpoint which is the URL that we will call to fetch the data. We can test this with Postman.
Define the request body. This POST request takes in three fields inside of the request body. Using the
memberparameter storing theMemberobject that we passed in, we give values to these fields. Note that we do not necessarily need to have thismemberparameter. If we want, we can have three separate arguments passed into the function instead of one.Create a decoder to decode the data. If our object contains a
Dateproperty, then we need to specify the date decoding strategy. If the JSON contains fields with snake_case, then we also need to specify the key decoding strategy.Create the request using Alamofire.
Pass in the endpoint and specify the method (
.getfor GET,.postfor POST, etc).Encode the request with JSON to be sent over the internet (POST).
Validate the request to ensure that the status code is 2xx and if the content type matches.
Decode the response to
Memberusing the decoder we created in Step 4.
Perform a switch statement on the response’s result.
If successful, pass to the callback the decoded response. A print statement is optional but recommended.
If failed, print an error statement about the error.
Let’s point out the differences between this and a GET request.
The function header usually contains a parameter storing some value that we want to send to the backend. In this case, we had a parameter called
memberstoring aMemberobject.We defined a dictionary whose type is
Parameters. Remember, these are key-value pairs.Our method is
.postinstead of.get. We also encode it usingJSONEncoding.default.We pass in
parametersto theAF.requestfunction. In the GET request version, we did not do this.
Calling the Network Request
NetworkManager.shared.addToRoster(member: someMemberVariable) { fetchedMember in
// Do something with the data
DispatchQueue.main.async {
// Perform UI updates
}
}Or if we plan on using self somewhere,
NetworkManager.shared.addToRoster(member: someMemberVariable) { [weak self] fetchedMember in
// Unwrap a strong reference
guard let self = self else { return }
// Do something with the data
DispatchQueue.main.async {
// Perform UI updates
}
}This is exactly the same as it was with a GET request except that we have to pass in an argument to the function we created.
Success or Failure?
In the code samples above, our callback took in a Member object. Sometimes the backend does not return this information, or we may not even need this information. Instead, it may be more useful for the callback to hold a more useful variable type: a Bool.
If we were to refactor our code, it would look something like this:
// 1. Create the function
func addToRoster(member: Member, completion: @escaping (Bool) -> Void) {
// 2. Specify the endpoint
let endpoint = "<Enter URL String Here>"
// 3. Define the request body
let parameters: Parameters = [
"name": member.name,
"subteam": member.subteam,
"position": member.position
]
// 4. Create a decoder
let decoder = JSONDecoder()
// decoder.dateDecodingStrategy = .iso8601 // Only if needed
// decoder.keyDecodingStrategy = .convertFromSnakeCase // Only if needed
// 5. Create the request
AF.request(endpoint, method: .post, parameters: parameters, encoding: JSONEncoding.default)
.validate()
.responseDecodable(of: Member.self, decoder: decoder ) { response in
// 5. Handle the response
switch response.result {
case .success(let member):
print("Successfully added member \(member.name)")
completion(true)
case .failure(let error):
print("Error in NetworkManager.addToRoster: \(error)")
completion(false)
}
}
}NetworkManager.shared.addToRoster(member: someMemberVariable) { success in
if success {
// Do something if the call succeeded
} else {
// Do something if the call failed
}
}The main difference here is that the callback takes in a Bool instead of the Member object. If the response succeeds, we use completion(true) to pass in true to the callback. If the response fails, we use completion(false).
When we call this function, we use success to access the value passed to the callback. We can then do whatever we need depending on the status of the network request.
Last updated
Was this helpful?