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.
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
.
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.
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:
- http://www.mongodb.org/downloads
- http://mongoosejs.com
SeaCat iOS tutorials in this series:
Most Recent Articles
- A beginner-friendly intro to the Correlator for effective cybersecurity detection
- Inotify in ASAB Library
- From State Machine to Stateless Microservice
- Entangled ways of product development in the area of cybersecurity #3 - LogMan.io
- Entangled ways of product development in the area of cybersecurity #2 - BitSwan
You Might Be Interested in Reading These Articles
Q&A: Mobile App Developers Asked How SeaCat Will Protect Their Apps, Backend, and the Data. Here Are the Answers
We've spent a great deal of time talking to mobile app developers to understand their approach to handling mobile application security. In this Q&A, we put together the answers to the most common questions asked by these app developers.
Published on May 07, 2015
Building High-Performance Application Servers - What You Need to Know
Using scalable and reliable software is vital for the success of any large-scale IT project. As increasing numbers of transactions are made, application infrastructure needs to stand strong and support that growth, and not be another source of problems.
Published on January 17, 2017
SeaCat trial for iOS on Mac OSX
This blog entry is meant to help you to start using SeaCat component on your Xcode iOS development environment. It contains instructions how to install and configure SeaCat gateway and how to integrate SeaCat client into your iOS application. SeaCat gateway is a secure gate to the restricted network. It allows access only to selected HTTP hosts and prevents exposure of others. It also secures communication with SeaCat clients that are typically in the Internet. SeaCat client becomes part of said mobile application and provides secured channel to SeaCat gateway and to target hosts in the restricted network. It ensures mutual security of the connection and transferred data.
Published on March 14, 2014