Connecting to MongoHq API with RestKit

RestKit for iOS


September 2013

$20.99

Link your apps and web services using RestKit

(For more resources related to this topic, see here.)

Let's take a base URL:

NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"];

Now:

[NSURL URLWithString:@"foo" relativeToURL:baseURL]; // Will give us http://example.com/v1/foo [NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL]; // -> http://example.com/v1/foo?bar=baz [NSURL URLWithString:@"/foo" relativeToURL:baseURL]; // -> http://example.com/foo [NSURL URLWithString:@"foo/" relativeToURL:baseURL]; // -> http://example.com/v1/foo [NSURL URLWithString:@"/foo/" relativeToURL:baseURL]; // -> http://example.com/foo/ [NSURL URLWithString:@"http://example2.com/" relativeToURL:baseURL]; // -> http://example2.com/

Having the knowledge of what an object manager is, let's try to apply it in a real-life example.

Before proceeding, it is highly recommend that we check the actual documentation on REST API of MongoHQ. The current one is at the following link:

http://support.mongohq.com/mongohq-api/introduction.html

As there are no strict rules on REST API, every API is different and does a number of things in its own way. MongoHQ API is not an exception. In addition, it is currently in "beta" stage.

Some of the non-standard things one can find in it are as follows:

  • The API key should be provided as a parameter with every request. There is an undocumented way of how to provide it in Headers, which is a more common approach.
  • Sometimes, if you get an error with the status code returned as 200 (OK), which is not according to REST standards, the normal way would be to return something in 4xx, which is stated as a client error.
  • Sometimes, while the output of an error message is a JSON string, the HTTP response Content-type header is set as text/plain.

To use the API, one will need a valid API Key. You can easily get one for free following a simple guideline recommended by the MongoHQ team:

  1. Sign up for an account at http://MongoHQ.com.
  2. Once logged in, click on the My Account drop-down menu at the top-right corner and select Account Settings.
  3. Look for the section labeled API Token. From there, take your token.
  4. We will put the API key into the MongoHQ-API-Token HTTP header. The following screenshot shows where one can find the API token key:

    API Token on Account Info page

So let's set up our configuration using the following steps:

You can use the AppDelegate class for putting the code, while I recommend using a separate MongoHqApi class for such App/API logic separation.

First, let's set up our object manager with the following code:

- (void)setupObjectManager { NSString *baseUrl = @"https://api.mongohq.com"; AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:
[NSURL URLWithString:baseUrl]]; NSString *apiKey = @"MY_API_KEY"; [httpClient setDefaultHeader:@"MongoHQ-API-Token" value:apiKey]; RKObjectManager *manager = [[RKObjectManager alloc]
initWithHTTPClient:httpClient]; [RKMIMETypeSerialization registerClass:[RKNSJSONSerialization class]
forMIMEType:@"text/plain"]; [manager.HTTPClient registerHTTPOperationClass:
[AFJSONRequestOperation class]]; [manager setAcceptHeaderWithMIMEType:RKMIMETypeJSON]; manager.requestSerializationMIMEType = RKMIMETypeJSON; [RKObjectManager setSharedManager:manager]; }

  1. Let's look at the code line by line and set the base URL. Remember not to put a slash (/) at the end, otherwise, you might have a problem with response mapping:

    NSString *baseUrl = @"https://api.mongohq.com";

  2. Initialize the HTTP client with baseUrl:

    AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL
    URLWithString:baseUrl]];

  3. Set a few properties for our HTTP client, such as the API key in the header:

    NSString *apiKey = @"MY_API_KEY"; [httpClient setDefaultHeader:@"MongoHQ-API-Token" value:apiKey];

    For the real-world app, one can show an Enter Api Key view controller to the user, and use a NSUserDefaults or a keychain to store and retrieve it.

  4. And initialize the RKObjectManager with our HTTP client:

    RKObjectManager *manager = [[RKObjectManager alloc]
    initWithHTTPClient:httpClient];

  5. MongoHQ APIs sometimes return errors in text/plain, thus we explicitly will add text/plain as a JSON content type to properly parse errors:

    [RKMIMETypeSerialization registerClass:[RKNSJSONSerialization class]
    forMIMEType:@"text/plain"];

  6. Register JSONRequestOperation to parse JSON in requests:

    [manager.HTTPClient registerHTTPOperationClass:[AFJSONRequestOperation
    class]];

  7. State that we are accepting JSON content type:

    [manager setAcceptHeaderWithMIMEType:RKMIMETypeJSON];

  8. Configure so that we want the outgoing objects to be serialized into JSON:

    manager.requestSerializationMIMEType = RKMIMETypeJSON;

  9. Finally, set the shared instance of the object manager, so that we can easily re-use it later:

    [RKObjectManager setSharedManager:manager];

Sending requests with object manager

Next, we want to query our databases. Let's first see how a database request will show us the output in JSON. To check this, go to http://api.mongohq.com/databases?_apikey=YOUR_API_KEY in your web browser YOUR_API_KEY. If a JSON-formatter extension (https://github.com/rfletcher/safari-json-formatter) is installed in your Safari browser, you will probably see the output shown in the following screenshot.

JSON response from API

As we see, the JSON representation of one database is:

[ { "hostname": "sandbox.mongohq.com", "name": "Test", "plan": "Sandbox", "port": 10097, "shared": true } ]

Therefore, our possible MDatabase class could look like:

@interface MDatabase : NSObject @property (nonatomic, strong) NSString *name; @property (nonatomic, strong) NSString *plan; @property (nonatomic, strong) NSString *hostname; @property (nonatomic, strong) NSNumber *port; @end

We can also modify the @implementation section to override the description method, which will help us while debugging the application and printing the object:

// in @implementation MDatabase - (NSString *)description { return [NSString stringWithFormat:@"%@ on %@ @ %@:%@", self.name, self.plan, self.hostname, self.port]; }

Now let's set up a mapping for it:

- (void)setupDatabaseMappings { RKObjectManager *manager = [RKObjectManager sharedManager]; Class itemClass = [MDatabase class]; NSString *itemsPath = @"/databases"; RKObjectMapping *mapping = [RKObjectMapping
mappingForClass:itemClass]; [mapping addAttributeMappingsFromArray:@[@"name", @"plan",
@"hostname", @"port"]]; NSString *keyPath = nil; NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass
(RKStatusCodeClassSuccessful); RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping method:RKRequestMethodGET pathPattern:itemsPath keyPath:keyPath statusCodes:statusCodes]; [manager addResponseDescriptor:responseDescriptor]; }

Let's look at the mapping setup line by line:

  1. First, we define a class, which we will use to map to:

    Class itemClass = [MDatabase class];

  2. And the endpoint we plan to request for getting a list of objects:

    NSString *itemsPath = @"/databases";

  3. Then we create the RKObjectMapping mapping for our object class:

    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:itemClass];

  4. If the names of JSON fields and class properties are the same, we will use an addAttributeMappingsFromArray method and provide the array of properties:

    [mapping addAttributeMappingsFromArray:@[@"name", @"plan", @"hostname",
    @"port"]];

  5. The root JSON key path in our case is nil. It means that there won't be one.

    NSString *keyPath = nil;

  6. The mapping will be triggered if a response status code is anything in 2xx:

    NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass
    (RKStatusCodeClassSuccessful);

  7. Putting it all together in response descriptor (for a GET request method):

    RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping method:RKRequestMethodGET pathPattern:itemsPath keyPath:keyPath statusCodes:statusCodes];

  8. Add response descriptor to our shared manager:

    RKObjectManager *manager = [RKObjectManager sharedManager]; [manager addResponseDescriptor:responseDescriptor];

Sometimes, depending on the architectural decision, it's nicer to put the mapping definition as part of a model object, and later call it like [MDatabase mapping], but for the sake of simplicity, we will put the mapping in line with RestKit configuration.

The actual code that loads the database list will look like:

RKObjectManager *manager = [RKObjectManager sharedManager]; [manager getObjectsAtPath:@"/databases" parameters:nil success:^(RKObjectRequestOperation *operation,
RKMappingResult *mappingResult) { NSLog(@"Loaded databases: %@", [mappingResult array]); } failure:^(RKObjectRequestOperation *operation,
NSError *error) { NSLog(@"Error: %@", [error localizedDescription]) }];

As you may have noticed, the method is quite simple to use and it uses block-based APIs for callbacks, which greatly improves the code readability, compared to using delegates, especially if there is more than one network request in a class. A possible implementation of a table view that loads and shows the list of databases will look like the following screenshot:

View of loaded Database items

Summary

In this article, we learned how to set up the RestKit library to work for our web service, we talked about sending requests, getting responses, and how to do object manipulations. We also talked about simplifying the requests by introducing routing. In addition, we discussed how integration with UI can be done and created forms.

Resources for Article:


Further resources on this subject:


Books to Consider

comments powered by Disqus