> For the complete documentation index, see [llms.txt](https://ios-course.cornellappdev.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ios-course.cornellappdev.com/resources/textbook/networking-ii/post-requests.md).

# POST Requests

### 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.

```swift
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.

```swift
// 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.

1. Create a function called `fetchRoster` that takes in a callback (completion handler) as an argument. This callback takes in a `Member` object. 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.
2. Specify the endpoint which is the URL that we will call to fetch the data. We can test this with Postman.
3. Define the request body. This POST request takes in three fields inside of the request body. Using the `member` parameter storing the `Member` object that we passed in, we give values to these fields. Note that we do not necessarily need to have this `member` parameter. If we want, we can have three separate arguments passed into the function instead of one.
4. Create a decoder to decode the data. If our object contains a `Date` property, 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.
5. Create the request using Alamofire.
   * Pass in the endpoint and specify the method (`.get` for GET, `.post` for 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 `Member` using the decoder we created in Step 4.
6. 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.

{% hint style="info" %}
**Note that we do not have to create the decoder inside of the function call. We can create a&#x20;**<mark style="color:red;">**`JSONDecoder`**</mark>**&#x20;object and store it as a property in our&#x20;**<mark style="color:red;">**`NetworkManager`**</mark>**&#x20;class to be used for all functions. The same can be applied to the endpoint variable containing a String value of our endpoint.**
{% endhint %}

Let’s point out the differences between this and a GET request.

1. 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 `member` storing a `Member` object.
2. We defined a dictionary whose type is `Parameters`. Remember, these are key-value pairs.
3. Our method is `.post` instead of `.get`. We also encode it using `JSONEncoding.default`.
4. We pass in `parameters` to the `AF.request` function. In the GET request version, we did not do this.

### Calling the Network Request

```swift
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,

```swift
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:

```swift
// 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)
	    }
        }
}
```

```swift
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.
