How to Write An iOS App that Uses a Node.js/MongoDB Web Service

Learn how to write an iOS app that uses Node.js and MongoDB for its back end. By Michael Katz.

Leave a rating/review
Save for later
Share

Welcome back to the second part of this two-part tutorial series on creating an iOS app with a Node.js and MongoDB back-end.

In the first part of this series, you created a simple Node.js server to expose MongoDB through a REST API.

In this second and final part of the series, you’ll create a fun iPhone application that lets users tag interesting places near them so other users can discover them.

As part of this process you’ll take the starter app provided and add several things: a networking layer using NSURLSession, support for geo queries and the ability to store images on the backend.

Getting Started

First things first: download the starter project and extract it to a convenient location on your system.

The zip file contains two folders:

  • server contains the javascript server code from the previous tutorial.
  • TourMyTown contains the starter Xcode project with the UI pre-built, but no networking code added yet.

Open TourMyTown\TourMyTown.xcodeproj and build and run. You should see something like this:

starter project

Right now not much happens, but here’s a sneak peek of what the app will look like when you finish this tutorial:

TourMyTown screenshot

TourMyTown screenshot

Users add new location markers to the app along with descriptions, categories and pictures. The Add button places a marker at the center of the map, and the user can drag the marker to the desired location. Alternatively, a tap and hold places a marker at the selected location.

The view delegate uses Core Location’s geo coder functionality to look up the address and place name of the location, if it’s available. Tapping the Info button on the annotation view presents the detail editing screen.

Edit point of interest data

Edit point of interest data

The app saves all data to the backend so that it can be recalled in future sessions.

The map with an annotation.

The map with an annotation.

You’ve got a lot of work to do to transition the app to this state, so let’s get coding!

Setting up Your Node.js Instance

If you didn’t complete the first part of this tutorial or don’t want to use your existing project, you can use the files contained in the server directory as a starting point.

The following instructions take you through setting up your Node.js instance; if you already have your working instance from Part 1 of this tutorial then feel free to skip straight to the next section.

Open Terminal and navigate to the MongoDB install directory — likely /usr/local/opt/mongodb/ but this may be slightly different on your system.

Execute the following command in Terminal to start the MongoDB daemon:

mongod

Now navigate to the server directory you extracted above. Execute the following command:

npm install

This reads the package.json file and installs the dependencies for your new server code.

Finally, launch your Node.js server with the following command:

node .
Note: The starter project is configured to connect to localhost, port 3000. This is fine when you’re running the app locally on your simulator, but if you want to deploy the app to a physical device you’ll have to change localhost to <mac-name>.local if your Mac and iOS device are on the same network. If they’re not on the same network, then you’ll need to set it to the IP address of your machine. You’ll find these values near the top of Locations.m.

The Data Model of Your App

The Location class of your project represents a single point of interest and its associated data. It does the following things:

  • Holds the location’s data, including its coordinates, description, and categories.
  • Knows how to serialize and deserialize the object to a JSON-compatible NSDictionary.
  • Conforms to the MKAnnotation protocol so it can be placed on an instance of MKMapView as a pin.
  • Has zero or more categories as defined in Categories.m.

The Locations class represents your application’s collection of Location objects and the mechanisms that load the objects from the server. This class is responsible for:

  • Serving as the app’s data model by providing a filterable list of locations via filteredObjects.
  • Communicating with the server by loading and saving items via import, persist and query.

The Categories class contains the list of categories that a Location can belong to and provides the ability to filter the list of locations by category. Categories also does the following:

  • Houses allCategories which provides the master list of categories. You can also add additional categories to its array.
  • Provides a list of all categories in the active set of locations.
  • Filters the locations by categories.

Loading Locations from the Server

Replace the stubbed-out implementation of import in Locations.m with the following code:

- (void)import
{
    NSURL* url = [NSURL URLWithString:[kBaseURL stringByAppendingPathComponent:kLocations]]; //1
    
    NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"GET"; //2
    [request addValue:@"application/json" forHTTPHeaderField:@"Accept"]; //3
    
    NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration]; //4
    NSURLSession* session = [NSURLSession sessionWithConfiguration:config];
    
    NSURLSessionDataTask* dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { //5
        if (error == nil) {
            NSArray* responseArray = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]; //6
            [self parseAndAddLocations:responseArray toArray:self.objects]; //7
        }
    }];
    
    [dataTask resume]; //8
}

Here’s what import does:

  1. The most important bits of information are the URL and request headers. The URL is simply the result of concatenating the base URL with the “locations” collections.
  2. You’re using GET since you’re reading data from the server. GET is the default method so it’s not necessary to specify it here, but it’s nice to include it for completeness and clarity.
  3. The server code uses the contents of the Accept header as a hint to which type of response to send. By specifying that your request will accept JSON as a response, the returned bytes will be JSON instead of the default format of HTML.
  4. Here you create an instance of NSURLSession with a default configuration.
  5. A data task is your basic NSURLSession task for transferring data from a web service. There are also specialized upload and download tasks that have specialized behavior for long-running transfers and background operation. A data task runs asynchronously on a background thread, so you use a callback block to be notified when the operation completes or fails.
  6. The completion handler checks for any errors; if it finds none it tries to deserialize the data using a NSJSONSerialization class method.
  7. Assuming the return value is an array of locations, parseAndAddLocations: parses the objects and notifies the view controller with the updated data.
  8. Oddly enough, data tasks are started with the resume message. When you create an instance of NSURLSessionTask it starts in the “paused” state, so to start it you simply call resume.

Still working in the same file, replace the stubbed-out implementation of parseAndAddLocations: with the following code:

- (void)parseAndAddLocations:(NSArray*)locations toArray:(NSMutableArray*)destinationArray //1
{
    for (NSDictionary* item in locations) { 
        Location* location = [[Location alloc] initWithDictionary:item]; //2
        [destinationArray addObject:location];        
    }

    if (self.delegate) {
        [self.delegate modelUpdated]; //3
    }
}

Taking each numbered comment in turn:

  1. You iterate through the array of JSON dictionaries and create a new Location object for each item.
  2. Here you use a custom initializer to turn the deserialized JSON dictionary into an instance of Location.
  3. The model signals the UI that there are new objects available.

Working together, these two methods let your app load the data from the server on startup. import relies on NSURLSession to handle the heavy lifting of networking. For more information on the inner workings of NSURLSession, check out the NSURLSession on this site.

Notice the Location class already has the following initializer which simply takes the various values in the dictionary and sets the corresponding object properties appropriately:

- (instancetype) initWithDictionary:(NSDictionary*)dictionary
{
    self = [super init];
    if (self) {
        self.name = dictionary[@"name"];
        self.location = dictionary[@"location"];
        self.placeName = dictionary[@"placename"];
        self.imageId = dictionary[@"imageId"];
        self.details = dictionary[@"details"];
        _categories = [NSMutableArray arrayWithArray:dictionary[@"categories"]];
    }
    return self;
}