SeaCat Tutorial - Chapter 4: Using MongoDB with REST Integration (iOS)

Foreword

The previous article of our tutorial series introduced several concepts and help you to understand the basics of REST API integration with iOS client written in Swift.

There were several limitations. Probably the most important one was related to data storage. The data was stored in memory, and in the case of any problem with the host, everything was lost. It was possible to play with default values, but there was no permanent solution in place.

In this article, we provide instructions on how to overcome this limitation.

MongoDB database and Mongoose wrapper

There are many ways how to store data. People can choose from different paradigms and vendors. We choose MongoDB [1] for its simplicity. With MongoDB's Document-Oriented Storage, you can write JSON objects to manipulate with data objects. There is also a very straightforward integration integration with Node.js - Mongoose [2]. The following tutorial covers the basic of both MongoDB and Mongoose and shows how to use these technologies in a Node.js application.

Node.js host

The code below is an extension from the basic structure developed in the last tutorial. The main focus is related to data handling with a strong utilization of Mongoose wrapper.

Required packages

There are 4 NPM packages used in the application. MongoDB is also required to be installed.

  • Express - The main framework for Node.js helps simplify the most common HTTP operations. Type npm install express in the command line to install this package.
  • MongoDB - NoSQL database where we store data. Instructions related to the installation are in the download section.
  • Mongoose - Wrapper for simplyfing the work with MongoDB. Type npm install mongoose in the command line to install this package.
  • MongooseAutoIncrement - This package helps handle IDs in Mongo structures automatically. Type npm install mongoose-auto-increment in the command line to install this package.
  • BodyParser - A very useful package for parsing data in requests. Type npm install body-parser in the command line to install this package.

Script initialization

Once the packages are installed (in the same directory), we can simply incorporate them in our script (everything will be in one).

var express = require('express');
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var autoIncrement = require('mongoose-auto-increment');

var opts = {
        server: {
        socketOptions: {keepAlive: 1}
    }
};

// Create Schema variable. 
var Schema = mongoose.Schema;

// Define local connection string with MongoDB.
var connectionString = 'mongodb://localhost/mymovies';

// Connect to local MongoDB. 
var connection = mongoose.connect(connectionString, opts);

// Use auto increment module.
autoIncrement.initialize(connection);

// Initialize app by using Express framework.
var app = express();

// Use Body Parser (Helps to process incoming requests).
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

// Set default port 1337 or custom if defined by user externally.
app.set('port', process.env.PORT || 1337);

Schema definition

Before we store any data, it is necessary to define the schema. With Mongoose, this can be done very easily.

// Definition of the movie schema
var movieSchema = new Schema({
    name: String,
    director: String,
    release: Number
});

// Bind autoincrement plugin with schema.
movieSchema.plugin(autoIncrement.plugin, 'Movie');

// Prepare object for export.
var Movie = connection.model('Movie', movieSchema);

Initial data

It's generally a good idea to provide initial data. The following piece of code populates three objects if the application is launched for the first time. Othewise, the code is ignored.

// Seeding Initial Data. Populates only once, then the code is skipped. 
Movie.find(function(err, movies){
    // If intial data already exists, skip the rest of the method.
    if (movies.length) {
        return;
    }

    new Movie({
        name: "Forrest Gump",
        director: "Robert Zemeckis",
        release: 1994
    }).save();

    new Movie({
        name: "Donnie Darko",
        director: "Richard Kelly",
        release: 2001
    }).save();

    new Movie({
        name: "Inception",
        director: "Christopher Nolan",
        release: 2010
    }).save();
});

REST methods

The last part is related to REST API methods which are simply extended (and simplified) from a basic example covered in the previous article. We are going to create an interface that is able to work with following:

  • GET /api/movies - get a list of all movies stored in memory.
  • GET /api/movies/:id - get the detail of a particular movie using its id.
  • POST /api/movies - create a new movie entry.
  • PUT /api/movies/:id - update an existing movie entry.
  • DELETE /api/movies/:id - delete an existing movie entry.

Method to GET all records.

app.get('/api/movies', function(request, response, next){
    Movie.find(function(error, movies){
    // In case of any error, forward request to error handler.
    if (error) {
        next();
    }
    // List of all records from db.
    response.json(movies.map(function(movie){
        return {
                id: movie._id,
                name: movie.name,
                director: movie.director,
                release: movie.release
            }
        }));
    });
});

Method to GET a record based on ID. If not found, forward the request to 404 - not found.

app.get('/api/movies/:id', function(request, response, next){  
    Movie.findById(request.params.id, function(error, movie){
        if (movie != null)
        {
            //render the content.
            response.json({
                id: movie._id,
                name: movie.name,
                director: movie.director,
                release: movie.release
            });
        }
        // In case of any error, forward request to error handler.
        else {
            next();
        }
    });
});

Method to CREATE a new record. Automatically increase the ID value.

app.post('/api/movies', function(request, response, next){
    // Complete request body.
    var requestBody = request.body;

    // Prepare new data.
    var movie = new Movie({
        name: requestBody.name,
        director: requestBody.director,
        release: requestBody.release
    });

    movie.save(function(error, movie){
        // In case of any error, forward request to error handler.
        if (error) {
            next();
        }
        // Return ID of element in response.
        response.json({id: movie._id});
        // Prepare status code 200 and close the response.
        response.status(200).end();
    });  
});

Method to UPDATE an existing record based on ID.

app.put('/api/movies/:id', function(request, response, next){
// Complete request body.
var requestBody = request.body;
// Find particular movie by ID.
Movie.findById(request.params.id,function(error, movie){
    // If the ID is successfully found (movie is not null), assign the request attributes to object.
    if (movie != null) {
        movie.name = requestBody.name;
        movie.director = requestBody.director;
        movie.release = requestBody.release;

        movie.save(function(error, movie){
            if (error) {
            // In case of an issue forward the response to ERROR handles. 
                next();        
            }
            // Return ID of element in response.
            response.json({id: movie._id});        
            // Element successfuly updated. Prepare status code 200 and close the response.
            response.status(200).end();
        });      
    }
    else {
    // In case of an issue forward the response to ERROR handles. 
    next();
    }
 });
});

Method to DELETE an existing record based on ID.

app.delete('/api/movies/:id', function(request, response, next){
// Find particular movie by Id.
Movie.findById(request.params.id,function(error, movie){
    if (movie != null) {
        movie.remove(function(error){
            if (error) {
                // In case of an issue forward the response to ERROR handles. 
                next();        
            }
            response.json({id: movie._id});        
            // Element successfuly updated.
            response.status(200).end();
        })}
        else {
            // In case of an issue forward the response to ERROR handles. 
            next();
        }
    });
});

Script finalization

This last method is related to error handling in case the request is invalid.

// Use Express midleware to handle 404 and 500 error states.
app.use(function(request, response){
    // Set status 404 if none of above routes processed incoming request. 
    response.status(404); 
    // Generate the output.
    response.send('404 - not found');
});

// 500 error handling. This will be handled in case of any internal issue on the host side.
app.use(function(err, request, response){
    // Set response type to application/json.
    response.type('application/json');
    // Set response status to 500 (error code for internal server error).
    response.status(500);
    // Generate the output - an Internal server error message. 
    response.send('500 - internal server error');
});

// Start listening on defined port, this keep running the application until hit Ctrl + C key combination.  
app.listen(app.get('port'), function(){
    console.log("Host is running and listening on http://localhost:" + app.get('port') + '; press Ctrl-C to terminate.');  
});

The complete script in our GIT repository.

Running the scripts

Once the initial implementation is done, it's time to configure the environment and keep the scripts up and running.

Running MongoDB instance

After the MongoDB is successfully installed on the computer, running it is very straightforward. Open the command line and type ./mongod to run the service.

MongoDB running

Running Node.js host

Run Node.js script is also straightforward. Open the command line and type node NodeRESTHostWithMongo.js (in case your script has a different name, just replace the NodeRESTHostWithMongo.js parameter accordingly). The result looks like following.

Running SeaCat Gateway

SeaCat configuration is very easy. First of all, please make sure that the seacat-trial.conf in your SeaCat directory contains following settings. (our host listens on port 1337).

[host:nodejshost]
uri=http://127.0.0.1:1337

Once the previous step is done, the last step is to run SeaCat Gateway in the command line terminal by typing ./seacatd-trial.

SeaCat running

iOS Client

The following section assumes that you have completed the iOS application from the previous tutorial. If so, run the app in the Simulator and play around. As we didn't change the interface, your application should work without any change.

IOS1 running

IOS2 running

IOS3 running

You can download the whole application from GitHub.

Conclusion

In this tutorial, we extended the Node.js application and added the support for MongoDB storage. With no change on the iOS side, we were able to utilize this change very smoothly. The iOS client improvement will be covered in the next part of our tutorial series.

Reference:

  1. http://www.mongodb.org/downloads
  2. http://mongoosejs.com

SeaCat iOS tutorials in this series:

  1. Chapter 1: Hello World
  2. Chapter 2: Simple Post
  3. Chapter 3: Introduction to REST Integration
  4. Chapter 4: Using MongoDB with REST Integration
  5. Chapter 5: Using Parse.com with REST Integration

About the Author

Ales Teska

TeskaLabs’ founder and CEO, Ales Teska, is a driven innovator who proactively builds things and comes up with solutions to solve practical IT problems.




You Might Be Interested in Reading These Articles

SP-Lang: Category theory in the wild

We recently encountered several interesting problems that demonstrate how seemingly abstract category theory finds its practical applications and helps us solve these problems sustainably.

Continue reading ...

splang tech

Published on August 20, 2022

Entangled ways of product development in the area of cybersecurity #1 - Asynchronous or parallel?

I started working at TeskaLabs at the beginning of autumn 2017 as a student at the Faculty of Information Technology of CTU. In the job advertisement, I was particularly interested in the fact that it is a small, product-based company that does not focus on just one technology or one programming language.

Continue reading ...

development tech premek

Published on November 15, 2022

Engaged with ASAB

About microservices, coroutines, failures and enthusiasm. And most of all, about ASAB. ASAB is the first thing that probably every newcomer to TeskaLabs gets fond of.

Continue reading ...

asab development tech eliska

Published on June 15, 2022