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

Previously we set up some REST API Calls With Alamofire. 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.

Updated to Swift 3.1 and Alamofire 4.4.

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.

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 todos using JSONPlaceholder. JSONPlaceholder is a fake online REST API for testing and prototyping. It’s like image placeholders but for web developers.

Previously when we made networking calls with Alamofire we used code like this:


// Get first todo
let todoEndpoint = "https://jsonplaceholder.typicode.com/todos/1"
Alamofire.request(todoEndpoint)
  .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 /todos/1")
      print(response.result.error!)
      return
    }
    
    if let todoJSON = response.result.value as? [String: Any] {
      print("The todo is: " + todoJSON.description)
      if let title = todoJSON["title"] as? String {
        // to access a field:
        print("The title is: " + title)
      } else {
        print("error parsing /todos/1")
      }
    }
}

// Create new todo
let todosEndpoint = "https://jsonplaceholder.typicode.com/todos"
let newTodo: [String: Any] = ["title": "My First Todo", "completed": false, "userId": 1]
Alamofire.request(todosEndpoint, method: .post, parameters: newTodo, encoding: JSONEncoding.default)
  .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 /todos/1")
      print(response.result.error!)
      return
    }
    
    if let todoJSON = response.result.value as? [String: Any] {
      // handle the results as JSON, without a bunch of nested if loops
      print("The todo is: " + todoJSON.description)
    }
}

// Delete first todo
let firstTodoEndpoint = "https://jsonplaceholder.typicode.com/todos/1"
Alamofire.request(firstTodoEndpoint, method: .delete)
  .responseJSON { response in
    if let error = response.result.error {
      // got an error while deleting, need to handle it
      print("error calling DELETE on /todos/1")
      print(error)
    } else {
      print("deleted the todo item")
    }
}

The bits that we’ll be changing look like Alamofire.request(...).Currently we’re providing the URL as a string and the HTTP method (unless it’s the default .get). Instead of these two parameters Alamofire.request(...) can also take a URLRequestConvertible object. To be a URLRequestConvertible object, we need to have an asURLRequest() fucntion that returns 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 todo that we want to get.

We’ll also need the base URL for our API so we can use it to build up the URLRequest that we want to send:


import Foundation
import Alamofire

enum TodoRouter: URLRequestConvertible {
  static let baseURLString = "https://jsonplaceholder.typicode.com/"
  
  case get(Int)
  case create([String: Any])
  case delete(Int)
  
  func asURLRequest() throws -> URLRequest {
    // TODO: implement
  }
}

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

We’ll come back and implement the asURLRequest() function 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:


let todoEndpoint = "https://jsonplaceholder.typicode.com/todos/1"
Alamofire.request(todoEndpoint)

to


Alamofire.request(TodoRouter.get(1))

Don’t forget delete the line defining todoEndpoint. All of the URL string handling is now done within the router.

The post call is similar. Change:


let todosEndpoint = "https://jsonplaceholder.typicode.com/todos"
let newTodo: [String: Any] = ["title": "My First Todo", "completed": false, "userId": 1]
Alamofire.request(todosEndpoint, method: .post, parameters: newTodo, encoding: JSONEncoding.default)

to


let newTodo = ["title": "My first todo", completed: false, "userId": 1]
Alamofire.request(TodoRouter.create(newTodo))

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 firstTodoEndpoint = "https://jsonplaceholder.typicode.com/todos/1"
Alamofire.request(firstTodoEndpoint, method: .delete)

to


Alamofire.request(TodoRouter.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

Within the router we need a function so that our calls like Router.delete(1) give us a URLRequest that Alamofire.Request() knows how to use. The function where we do that is asURLRequest().

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 router function we have three cases: .get, .create, and .delete:


enum TodoRouter: URLRequestConvertible {
  static let baseURLString: String = "https://jsonplaceholder.typicode.com/"
  
  case get(Int)
  case create([String: Any])
  case delete(Int)

  func asURLRequest() throws -> URLRequest {
    // 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 asURLRequest() function we’ll need a few elements that we’ll combine to create the url request: the HTTP method, any parameters to pass, and the URL.

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


var method: HTTPMethod {
  switch self {
    case .get:
      return .get
    case .create:
      return .post
    case .delete:
      return .delete
  }
}

We’ll need to add the parameters to post for the .create case:


let params: ([String: Any]?) = {
  switch self {
    case .get, .delete:
      return nil
    case .create(let newTodo):
      return (newTodo)
  }
}()

Swift enums can have arguments, it’s called value binding. So when we use the .create case we can pass in the parameters (in this case, the dictionary of data for the new todo). Then we can access it using:


case .myCase(let argument):

We’re also using it to for the .get and .delete cases to pass in the ID numbers for the gists. We’ll retrieve those when we need them to create the URL:


case .get(let number):

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: URL = {
  // build up and return the URL for each endpoint
  let relativePath: String?
  switch self {
    case .get(let number):
      relativePath = "todos/\(number)"
    case .create:
      relativePath = "todos"
    case .delete(let number):
      relativePath = "todos/\(number)"
  }
  
  var url = URL(string: TodoRouter.baseURLString)!
  if let relativePath = relativePath {
    url = url.appendingPathComponent(relativePath)
  }
  return url
}()

We just set up the code to get the HTTP method, URL, and parameters for each case. Now we can put them together to create a URL request:


var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = method.rawValue

let encoding = JSONEncoding.default
return try encoding.encode(urlRequest, with: params)

First we create a mutable request using the URL. It’s mutable because we declared it with var, not let. That’s necessary so we can set the httpMethod on the next line. Then we encode any parameters and add them to the request. This web service uses JSON, like most these days, so we’re using JSONEncoding.default.

Finally, we return the request. Here’s it all together:


import Foundation
import Alamofire

enum TodoRouter: URLRequestConvertible {
  static let baseURLString = "https://jsonplaceholder.typicode.com/"
  
  case get(Int)
  case create([String: Any])
  case delete(Int)
  
  func asURLRequest() throws -> URLRequest {
    var method: HTTPMethod {
      switch self {
        case .get:
          return .get
        case .create:
          return .post
        case .delete:
          return .delete
      }
    }
    
    let params: ([String: Any]?) = {
      switch self {
      case .get, .delete:
        return nil
      case .create(let newTodo):
        return (newTodo)
      }
    }()
    
    let url: URL = {
      // build up and return the URL for each endpoint
      let relativePath: String?
      switch self {
        case .get(let number):
          relativePath = "todos/\(number)"
        case .create:
          relativePath = "todos"
        case .delete(let number):
          relativePath = "todos/\(number)"
      }
      
      var url = URL(string: TodoRouter.baseURLString)!
      if let relativePath = relativePath {
        url = url.appendingPathComponent(relativePath)
      }
      return url
    }()
    
    var urlRequest = URLRequest(url: url)
    urlRequest.httpMethod = method.rawValue
    
    let encoding = JSONEncoding.default
    return try encoding.encode(urlRequest, with: params)
  }
}

Here’s the example code on GitHub.

And our demo view controller that uses it:


import UIKit
import Alamofire

class ViewController: UIViewController {

  func getFirstPost() {
    let todoEndpoint = "https://jsonplaceholder.typicode.com/todos/1"
    Alamofire.request(todoEndpoint)
      .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 /todos/1")
          print(response.result.error!)
          return
        }
        
        if let todoJSON = response.result.value as? [String: Any] {
          print("The todo is: " + todoJSON.description)
          if let title = todoJSON["title"] as? String {
            // to access a field:
            print("The title is: " + title)
          } else {
            print("error parsing /todos/1")
          }
        }
    }
  }
  
  func createPost() {
    let todosEndpoint = "https://jsonplaceholder.typicode.com/todos"
    let newTodo: [String: Any] = ["title": "My First Todo", "completed": false, "userId": 1]
    Alamofire.request(todosEndpoint, method: .post, parameters: newTodo, encoding: JSONEncoding.default)
      .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 /todos/1")
          print(response.result.error!)
          return
        }
        
        if let todoJSON = response.result.value as? [String: Any] {
          // handle the results as JSON, without a bunch of nested if loops
          print("The todo is: " + todoJSON.description)
        }
    }
  }
  
  func deleteFirstPost() {
    let firstTodoEndpoint = "https://jsonplaceholder.typicode.com/todos/1"
    Alamofire.request(firstTodoEndpoint, method: .delete)
      .responseJSON { response in
        if let error = response.result.error {
          // got an error while deleting, need to handle it
          print("error calling DELETE on /todos/1")
          print(error)
        } else {
          print("deleted the todo item")
        }
    }
  }
  
  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