In my previous post, I wrote about how to design a RESTful API in a correct way under the lightning of best practices. In this post, I will show you how to consume a RESTful API using a famous networking libray Alamofire using Swift language.
The most important elements for the RESTful APIs are the consepts of a Resource and the Actions on them. The actions for a RESTful API is the CRUD operations on any resource. So in this tutorial, I will cover get (GET), create (POST), update (PUT) and delete (DELETE) operations on a resource serving from a designated API endpoints.
Let’s say we have API endpoints for a patient resource that for a hospital API.
- api.resthospital.com/api/patients (GET)
- api.resthospital.com/api/patient (POST)
- api.resthospital.com/api/patients/{id} (PUT)
- api.resthospital.com/api/patients/{id} (DELETE)
Since RESTful architecture is based on HTTP request we should leverage the HTTP status codes. The following piece of code enumurates some known status codes:
public enum EHttpStatusCode : Int {
case OK = 200 // /GET, /DELETE result is successful
case CREATED = 201 // /POST or /PUT is successful
case NOT_MODIFIED = 304 // If caching is enabled and etag matches with the server
case BAD_REQUEST = 400 // Possibly the parameters are invalid
case INVALID_CREDENTIAL = 401 // INVALID CREDENTIAL, possible invalid token
case NOT_FOUND = 404 // The item you looked for is not found
case CONFLICT = 409 // Conflict - means already exist
case AUTHENTICATION_EXPIRED = 419 // Expired
}
RESTful API will provide us data with a format like Json (or Xml, …). Since we obtain data in one of these format, we should somehow convert the data into models (classes/structs defined to model the domain). We can write our model converters providing from/to json/xml converters or use beatiful open-source libraries. ObjectMapper is one of those beautiful libraries with a clear usage. Here, i used AlamofireObjectMapper which is an Alamofire extension which converts JSON response data into swift objects using ObjectMapper.
For instance, we can model a patient like below (aware the Mappable protocol)
import Foundation
import ObjectMapper
class Country: Mappable {
var id = ""
var name = ""
var age = ""
init(id: String, name: String, age: String) {
self.id = id
self.name = name
self.age = age
}
required init?(map: Map){
}
func mapping(map: Map) {
id <- map["id"]
name <- map["name"]
age <- map["age"]
}
}
Okay, we have the domain model, and thanks to ObjectMapper library for the json conversions. Before creating our ApiService class, let’s create a protocol named ApiServiceProtocol to see the blueprints of our Api Service
protocol ApiServiceProtocol {
func get(url: String, headers: [String : String]?, callback: @escaping (ECallbackResultType) -> Void)
func update(url: String, parameters: [String : Any]?, headers: [String : String]?, callback: @escaping (ECallbackResultType) -> Void)
func create(url: String, parameters: [String : Any]?, headers: [String : String]?, callback: @escaping (ECallbackResultType) -> Void)
func delete(url: String, parameters: [String : Any]?, headers: [String : String]?, callback: @escaping (ECallbackResultType) -> Void)
}
Let’s create our ApiService class. Create a class named ApiService conforming to ApiServiceProtocol protocol. Do you see the
class ApiService<T: BaseMappable>: ApiServiceProtocol {
// Implement the protocol
}
Let’s start to implement for fetching data from a restful api
1. Fetching data (GET request)
GET request is best fit for fetching data with its idempotent behaviour, no affect on status of the api.
/**
This method sends a GET request to given url in order to fetch data
- parameters:
- url: API Endpoint
- headers: Required HTTP header
- callback: The callback handler to provide the result of the fetched data
*/
func get(url: String, headers: [String : String]?, callback: @escaping (ECallbackResultType) -> Void) {
// Send the GET request
Alamofire.request(url, method: .get, headers: headers).responseObject { (response: DataResponse<T>) -> Void in
// Get the status code of response
if (response.response != nil) {
let status = response.response!.statusCode;
switch status {
case EHttpStatusCode.OK.rawValue:
// Get the response body
if let value = response.result.value {
// call callback with no error
callback(ECallbackResultType.Success(value as T!))
}
default:
callback(ECallbackResultType.Failure(ECallbackErrorType.UnknownError))
}
} else {
callback(ECallbackResultType.Failure(ECallbackErrorType.APIEndpointNotAvailable))
}
}
}
The critical points here are using DataResponse
2. Updating data (PUT request)
This is the same with get function except http method: PUT
/**
This method sends a PUT request to given url in order to update data
- parameters:
- url: API endpoint
- parameters: The data to be updated
- headers: Required HTTP header
- callback: The callback handler to provide the result of the update operation
*/
func update(url: String, parameters: [String : Any]?, headers: [String : String]?, callback: @escaping (ECallbackResultType) -> Void) {
// Send the POST request
Alamofire.request(url, method: .put, parameters: parameters, encoding: JSONEncoding.default, headers: headers)
.responseJSON { response in
if (response.response != nil) {
// Get the status code of response
let status = response.response!.statusCode;
switch status {
case EHttpStatusCode.OK.rawValue:
// Successful - No error
callback(ECallbackResultType.Success(nil))
case EHttpStatusCode.INVALID_CREDENTIAL.rawValue:
// invalid credentials
callback(ECallbackResultType.Failure(ECallbackErrorType.InvalidCredentials))
case EHttpStatusCode.CONFLICT.rawValue:
// Resource already exist
callback(ECallbackResultType.Failure(ECallbackErrorType.AlreadyExist))
case EHttpStatusCode.NOT_FOUND.rawValue:
// The resource does not exist
callback(ECallbackResultType.Failure(ECallbackErrorType.NotExist))
case EHttpStatusCode.BAD_REQUEST.rawValue:
// Invalid parameters
callback(ECallbackResultType.Failure(ECallbackErrorType.InvalidParameters))
default:
callback(ECallbackResultType.Failure(ECallbackErrorType.UnknownError))
}
} else {
callback(ECallbackResultType.Failure(ECallbackErrorType.APIEndpointNotAvailable))
}
}
}
3/4. Creating and Deleting data
It is your homework :)