Using a Router to Organize Your Alamofire API Calls
April 01, 2016

Previously we set up some REST API Calls With Alamofire & SwiftyJSON. While it’s a bit of overkill for those simple calls we can improve our code by using an Alamofire router. The router will put together the URL requests for us which will avoid having URL strings throughout our code. A router can also be used to apply headers, e.g., for including an OAuth token or other authorization header.

Yes, I see the date on this post. But well organized code is no joke. Be nice to your future self by making it easy to understand and work with your code. You’ll thank yourself later.

Thank you written on shop door
(image by Matt Jones)

This tutorial is an excerpt from my iOS Apps with REST APIs guide.

It has been written using Swift 2.2, iOS 9, Xcode 7.3, Alamofire 3.3, and SwiftyJSON 2.3.

Using a router with Alamofire is good practice since it helps keep our code organized. The router is responsible for creating the URL requests so that our API manager (or whatever makes the API calls) doesn’t need to do that along with all of the other responsibilities that it has. A good rule of thumb is to split out code if you can explain what each new object will be responsible for.

Our previous simple examples included calls to get, create, and delete posts using JSONPlaceholder. JSONPlaceholder is a fake online REST API for testing and prototyping. It’s like image placeholders but for web developers.

Here’s our previous code:


// Get first post
let postEndpoint: String = "http://jsonplaceholder.typicode.com/posts/1"
Alamofire.request(.GET, postEndpoint)
  .responseJSON { response in
    guard response.result.error == nil else {
      // got an error in getting the data, need to handle it
      print("error calling GET on /posts/1")
      print(response.result.error!)
      return
    }
      
    if let value: AnyObject = response.result.value {
      // handle the results as JSON, without a bunch of nested if loops
      let post = JSON(value)
      // now we have the results, let's just print them 
      // though a table view would definitely be better UI:
      print("The post is: " + post.description)
      if let title = post["title"].string {
        // to access a field:
        print("The title is: " + title)
      } else {
        print("error parsing /posts/1")
      }
    }
}

// Create new post
let postsEndpoint: String = "http://jsonplaceholder.typicode.com/posts"
let newPost = ["title": "Frist Psot", "body": "I iz fisrt", "userId": 1]
Alamofire.request(.POST, postsEndpoint, parameters: newPost, encoding: .JSON)
  .responseJSON { response in
    guard response.result.error == nil else {
      // got an error in getting the data, need to handle it
      print("error calling GET on /posts/1")
      print(response.result.error!)
      return
    }
      
    if let value: AnyObject = response.result.value {
      // handle the results as JSON, without a bunch of nested if loops
      let post = JSON(value)
      print("The post is: " + post.description)
    }
}

// Delete first post
let firstPostEndpoint: String = "http://jsonplaceholder.typicode.com/posts/1"
Alamofire.request(.DELETE, firstPostEndpoint)
  .responseJSON { response in
    if let error = response.result.error {
      // got an error while deleting, need to handle it
      print("error calling DELETE on /posts/1")
      print(error)
    }
}

The bits that we’ll be changing look like Alamofire.request(...). Currently we’re providing the URL as a string, like http://jsonplaceholder.typicode.com/posts/1, and the HTTP method, like .GET. Instead of these two parameters Alamofire.request(...) can also take a URLRequestConvertible. To be a URLRequestConvertible object, we need to have a URLRequest method that returns the NSMutableURLRequest that contains the request we want to send. That’s what we’ll take advantage of to create our router.

Using an Alamofire Router

To start we’ll declare a router. It’ll be an enum with a case for each type of call we want to make. A convenient feature of Swift enums is that the cases can have arguments. For example, our .Get case can have an Int argument so we can pass in the ID number of the post that we want to get.

We’ll also need the base URL for our API. We can use a computed property to generate the NSMutableURLRequest, which is another nice feature of Swift enums:


enum PostRouter: URLRequestConvertible {
  static let baseURLString = "http://jsonplaceholder.typicode.com/"
  
  case Get(Int)
  case Create([String: AnyObject])
  case Delete(Int)
  
  var URLRequest: NSMutableURLRequest {
    ...
    // TODO: implement
  }
}

If you’re working from scratch you’ll need to add Alamofire 3.3 and SwiftyJSON 2.3 to your project. CocoaPods is the easiest way to do that.

We’ll come back and implement the URLRequest computed property in a bit. First let’s see how we need to change our existing calls to use the router.

For the GET call all we need to do is to change:


Alamofire.request(.GET, postEndpoint)

to


Alamofire.request(PostRouter.Get(1))

We can also delete this line since all of the URL string handling is now done within the Router.

The .POST call is similar. Change:


let postsEndpoint: String = "http://jsonplaceholder.typicode.com/posts"
let newPost = ["title": "Frist Psot", "body": "I iz fisrt", "userId": 1]
Alamofire.request(.POST, postsEndpoint, parameters: newPost, encoding: .JSON)

to


let newPost = ["title": "Frist Psot", "body": "I iz fisrt", "userId": 1]
Alamofire.request(PostRouter.Create(newPost))

You can see there that the router has abstracted away the encoding as well as the endpoint from this function. The encoding is part of creating a URL request so it rightly belongs in the router, not the code making the API calls.

And for the .DELETE call, change:


let firstPostEndpoint: String = "http://jsonplaceholder.typicode.com/posts/1"
Alamofire.request(.DELETE, firstPostEndpoint)

to


Alamofire.request(PostRouter.Delete(1))

Now our calls are a bit easier to read. We could make them even clearer by naming the Router cases more descriptively, like Router.DeletePostWithID(1).

Generating the URL Requests

The code in this section is pretty Swift-y so if it looks a little odd at first just keep reading. We’ll work through it and explain it all as we go.

Within the router we need a computed property so that our calls like Router.Delete(1) give us an NSMutableURLRequest that Alamofire.Request() knows how to use.

We’ve defined the Router as an enum with a case for each of our 3 calls. You’ll need a separate case if the URL, HTTP method (like GET or POST), or argument type is different. So within our URLRequest computed property we can use those 3 cases: .Get, .Create, and .Delete.


enum PostRouter: URLRequestConvertible {
  static let baseURLString:String = "http://jsonplaceholder.typicode.com/"
  
  case Get(Int)
  case Create([String: AnyObject])
  case Delete(Int)

  var URLRequest: NSMutableURLRequest {
    // TODO: implement
  }
}

We’ve also included the base URL since it’s the same in all of the calls. That’ll avoid the chance of having a typo in that part of the URL for one of the calls.

Within the computed property to generate the NSMutableURLRequest we’ll need a few properties: the HTTP method, the URL, and any parameters to pass.

Since we’re using an enum, we can use a switch statement to define the HTTP methods for each case:


var URLRequest: NSMutableURLRequest {
  var method: Alamofire.Method {
    switch self {
    case .Get:
      return .GET
    case .Create:
      return .POST
    case .Delete:
      return .DELETE
    }
  }
  // ...
}

We’ll need to add the parameters to post to for .Create case. We can use another computed variable for that:


let params: ([String: AnyObject]?) = {
  switch self {
  case .Get, .Delete:
    return (nil)
  case .Create(let newPost):
    return (newPost)
  }
}()

Swift enums can have arguments. So when we use the .Create case we can pass in the parameters (i.e., the dictionary of data for the new post). Then we can access it using:


.Case(let argument)

Now we can start to build up the URL request. First we’ll need the URL. We have the base URL added above so we can combine it with the relative path for each case to get the full URL:


let url:NSURL = {
  // build up and return the URL for each endpoint
  let relativePath:String?
  switch self {
    case .Get(let postNumber):
      relativePath = "posts/\(postNumber)"
    case .Create:
      relativePath = "posts"
    case .Delete(let postNumber):
      relativePath = "posts/\(postNumber)"
  }

  var URL = NSURL(string: PostRouter.baseURLString)!
  if let relativePath = relativePath {
    URL = URL.URLByAppendingPathComponent(relativePath)
  }
  return URL
}()

So we have the HTTP method, URL, and parameters for each case. We can put them together to create a URL request:


let URLRequest = NSMutableURLRequest(URL: url)
      
let encoding = Alamofire.ParameterEncoding.JSON
let (encodedRequest, _) = encoding.encode(URLRequest, parameters: params)

encodedRequest.HTTPMethod = method.rawValue

return encodedRequest

First we create a mutable request using the URL (from our computed property above). Then we encode any parameters and add them to the request. Our web service uses JSON, like most these days, so we’re using Alamofire.ParameterEncoding.JSON.

Finally, we set the HTTP method and return the request. Here’s the router all together:


import Foundation
import Alamofire

enum PostRouter: URLRequestConvertible {
  static let baseURLString = "http://jsonplaceholder.typicode.com/"
  
  case Get(Int)
  case Create([String: AnyObject])
  case Delete(Int)
  
  var URLRequest: NSMutableURLRequest {
    var method: Alamofire.Method {
      switch self {
      case .Get:
        return .GET
      case .Create:
        return .POST
      case .Delete:
        return .DELETE
      }
    }
    
    let params: ([String: AnyObject]?) = {
      switch self {
      case .Get, .Delete:
        return (nil)
      case .Create(let newPost):
        return (newPost)
      }
    }()
    
    let url:NSURL = {
      // build up and return the URL for each endpoint
      let relativePath:String?
      switch self {
      case .Get(let postNumber):
        relativePath = "posts/\(postNumber)"
      case .Create:
        relativePath = "posts"
      case .Delete(let postNumber):
        relativePath = "posts/\(postNumber)"
      }
      
      var URL = NSURL(string: PostRouter.baseURLString)!
      if let relativePath = relativePath {
        URL = URL.URLByAppendingPathComponent(relativePath)
      }
      return URL
    }()
    
    let URLRequest = NSMutableURLRequest(URL: url)
    
    let encoding = Alamofire.ParameterEncoding.JSON
    let (encodedRequest, _) = encoding.encode(URLRequest, parameters: params)
    
    encodedRequest.HTTPMethod = method.rawValue
    
    return encodedRequest
  }
}

And our demo view controller that uses it:


import UIKit
import Alamofire
import SwiftyJSON

class ViewController: UIViewController {
  func getFirstPost() {
    // Get first post
    let request = Alamofire.request(PostRouter.Get(1))
      .responseJSON { response in
        guard response.result.error == nil else {
          // got an error in getting the data, need to handle it
          print("error calling GET on /posts/1")
          print(response.result.error!)
          return
        }
        
        if let value: AnyObject = response.result.value {
          // handle the results as JSON, without a bunch of nested if loops
          let post = JSON(value)
          // now we have the results, let's just print them though a tableview would definitely be better UI:
          print("The post is: " + post.description)
          if let title = post["title"].string {
            // to access a field:
            print("The title is: " + title)
          } else {
            print("error parsing /posts/1")
          }
        }
    }
    debugPrint(request)
  }
  
  func createPost() {
    let newPost = ["title": "Frist Psot", "body": "I iz fisrt", "userId": 1]
    Alamofire.request(PostRouter.Create(newPost))
      .responseJSON { response in
        guard response.result.error == nil else {
          // got an error in getting the data, need to handle it
          print("error calling GET on /posts/1")
          print(response.result.error!)
          return
        }
        
        if let value: AnyObject = response.result.value {
          // handle the results as JSON, without a bunch of nested if loops
          let post = JSON(value)
          print("The post is: " + post.description)
        }
    }
  }
  
  func deleteFirstPost() {
    Alamofire.request(PostRouter.Delete(1))
      .responseJSON { response in
        if let error = response.result.error {
          // got an error while deleting, need to handle it
          print("error calling DELETE on /posts/1")
          print(error)
        }
    }
  }
  
  override func viewDidLoad() {
    super.viewDidLoad()
    // pick which method you want to test here
    // comment out the other two
    
    getFirstPost()
    
    //createPost()
    
    //deleteFirstPost()
  }
}

Save and test out our code. Un-comment each of the function calls in viewDidLoad to test out each of the API calls. The console log should display the post titles or success messages without showing any errors.

We’ve created a simple Alamofire router that you can adapt to your API calls :)

Here’s the example code on GitHub.

iOS Apps with REST APIs Cover

This tutorial is an excerpt from my iOS Apps with REST APIs guide. It’ll teach you how to build Swift apps that get their data from web services without hundreds of pages about flatmap and Core Whatever. iOS Apps with REST APIs guide is the book I wish I had on my first iOS contract when I was struggling to figure out how to get the API data showing up in the app I was building.

Start Building iOS Apps with REST APIs now for $29

(Buying for a group? Grab the discounted Team License from LeanPub.)

Learn More about the iOS Apps with REST APIs guide

Other Posts You Might Like