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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How to Write An iOS App that Uses a Node.js/MongoDB Web Service
40 mins
- Getting Started
- Setting up Your Node.js Instance
- The Data Model of Your App
- Loading Locations from the Server
- Saving Locations to the Server
- Saving Images to the Server
- Saving Images in your App
- A Quick Recap of File Handling
- Testing it Out
- Querying for Locations
- Using Queries to Filter by Category
- Where to Go From Here?
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:
Right now not much happens, but here’s a sneak peek of what the app will look like when you finish this tutorial:
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.
The app saves all data to the backend so that it can be recalled in future sessions.
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 .
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 ofMKMapView
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
andquery
.
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:
- 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.
- 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. - 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. - Here you create an instance of NSURLSession with a default configuration.
- 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. - The completion handler checks for any errors; if it finds none it tries to deserialize the data using a
NSJSONSerialization
class method. - Assuming the return value is an array of locations,
parseAndAddLocations:
parses the objects and notifies the view controller with the updated data. - 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 callresume
.
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:
- You iterate through the array of JSON dictionaries and create a new Location object for each item.
- Here you use a custom initializer to turn the deserialized JSON dictionary into an instance of Location.
- 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;
}