Developing Single Page Web Applications using Java 8, Spark, MongoDB, and AngularJS

In this post you will learn how to use a micro framework called Spark to build a RESTful backend. The RESTful backend is consumed by a single page web application using AngularJS and MongoDB for data storage. I’ll also show you how to run Java 8 on OpenShift.

Application Use Case

You will develop a todo application which allows users to create and list todo items. The application will do the following:

  • When a user goes to the ‘/’ url of the application, they will see a list of all todos stored in the application database. Behind the curtain, AngularJS makes a REST(/api/v1/todos) call to fetch all the todo items.

TodoApp List All Todos

  • When a user clicks on the checkbox then a todo is marked done. AngularJS makes a PUT request and update the todo item.

TodoApp

  • Finally, a user can add a new todo by navigating to http://todoapp-shekharblogs.rhcloud.com/#/create. This makes a POST call to the RESTful backend and saves the todo in the MongoDB datastore.

TodoApp Add New Todo

What is Spark?

Spark is a Java based microframework for building web applications with minimum fuss. It is inspired by a framework written in Ruby called Sinatra. It has a minimalist core providing all the essential features required to build a web application quickly with little code.

Prerequisite

To build the sample application developed in this blog, you would need the following on your machine.

  1. Download and install JDK 8
  2. Download and install Eclipse or any other IDE. Eclipse users make sure you have Eclipse with Java 8 support.
  3. Download and install Apache Maven
  4. Download and install MongoDB

Github Repository

The code for today’s demo application is available on github: todoapp-spark.

Step 1: Create a Maven project

Open a new command-line terminal and navigate to the location where you want to create the project. Execute the command shown below to generate the template application.

$ mvn archetype:generate -DgroupId=com.todoapp -DartifactId=todoapp -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

Step 2: Update the maven project to use Java 8

Import the project into your IDE and replace the pom.xml with the one shown below.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.todoapp</groupId>
    <artifactId>todoapp</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>todoapp</name>
    <url>http://maven.apache.org</url>
 
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
 
    <dependencies>
 
    </dependencies>
</project>

In the pom.xml shown above, we changed the following:

  1. You updated Maven to use Java 8 by defining maven.compiler.source and maven.compiler.target properties.
  2. You removed the JUnit dependency from the original pom.xml file.

Delete App.java and AppTest.java files as we don’t need them.

Step 3: Add Spark and other dependencies

The application uses the Spark framework and a MongoDB database. So, update the dependencies section of pom.xml with the one shown below.

<dependencies>
        <dependency>
            <groupId>com.sparkjava</groupId>
            <artifactId>spark-core</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.5</version>
        </dependency>
        <dependency>
            <groupId>org.mongodb</groupId>
            <artifactId>mongo-java-driver</artifactId>
            <version>2.11.3</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.2.4</version>
        </dependency>
</dependencies>

Spark uses SLF4J for logging, so we added slf4j-simple binding in the dependencies. This is required to view the log and error messages. Also, we added the gson library as its used to convert objects to and from JSON.

Step 4: Make Spark Say Hello World

Create a new class called Bootstrap and add the following code to it.

package com.todoapp;
 
import spark.Request;
import spark.Response;
import spark.Route;
 
import static spark.Spark.*;
 
public class Bootstrap {
 
    public static void main(String[] args) {
        get("/", new Route() {
            @Override
            public Object handle(Request request, Response response) {
                return "Hello World!!";
            }
        });
    }
}

This code:

  1. Imports the required classes from the Spark library.
  2. Creates a new class called Bootstrap and defines a main method.
  3. Defines a route that tells Spark that when an HTTP GET request is made to ‘/’, return “Hello World”. You use Spark’s get() method to define the mapping from the URL to the callback.

To see the application in action, run the main program using your IDE. The application will start the embedded Jetty server at http://0.0.0.0:4567. When you open this link in your web browser, you will see “Hello World!!”.

Take advantage of Java 8 lambda expressions to make your code more concise and clean. Spark is a modern Java web framework that takes advantage of Java 8 features.

package com.todoapp;
 
import spark.Request;
import spark.Response;
import spark.Route;
 
import static spark.Spark.*;
 
public class Bootstrap {
 
    public static void main(String[] args) {
        get("/", (request, response) -> "Hello World");
    }
}

Step 5: Defining the Domain Model and Service class

Our goal is an application that stores and manages ToDo items. Our simple ToDo class is shown below.

package com.todoapp;
 
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import org.bson.types.ObjectId;
 
import java.util.Date;
 
public class Todo {
 
    private String id;
    private String title;
    private boolean done;
    private Date createdOn = new Date();
 
    public Todo(BasicDBObject dbObject) {
        this.id = ((ObjectId) dbObject.get("_id")).toString();
        this.title = dbObject.getString("title");
        this.done = dbObject.getBoolean("done");
        this.createdOn = dbObject.getDate("createdOn");
    }
 
    public String getTitle() {
        return title;
    }
 
    public boolean isDone() {
        return done;
    }
 
    public Date getCreatedOn() {
        return createdOn;
    }
}

Our TodoService class implement methods that use CRUD operations on the Todo object. It basically Creates, Reads, and Updates Todo documents stored in MongoDB.

package com.todoapp;
 
import com.google.gson.Gson;
import com.mongodb.*;
import org.bson.types.ObjectId;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
 
public class TodoService {
 
    private final DB db;
    private final DBCollection collection;
 
    public TodoService(DB db) {
        this.db = db;
        this.collection = db.getCollection("todos");
    }
 
    public List<Todo> findAll() {
        List<Todo> todos = new ArrayList<>();
        DBCursor dbObjects = collection.find();
        while (dbObjects.hasNext()) {
            DBObject dbObject = dbObjects.next();
            todos.add(new Todo((BasicDBObject) dbObject));
        }
        return todos;
    }
 
    public void createNewTodo(String body) {
        Todo todo = new Gson().fromJson(body, Todo.class);
        collection.insert(new BasicDBObject("title", todo.getTitle()).append("done", todo.isDone()).append("createdOn", new Date()));
    }
 
    public Todo find(String id) {
        return new Todo((BasicDBObject) collection.findOne(new BasicDBObject("_id", new ObjectId(id))));
    }
 
    public Todo update(String todoId, String body) {
        Todo todo = new Gson().fromJson(body, Todo.class);
        collection.update(new BasicDBObject("_id", new ObjectId(todoId)), new BasicDBObject("$set", new BasicDBObject("done", todo.isDone())));
        return this.find(todoId);
    }
}

This code does the following:

  1. The TodoService class constructor receives the MongoDB database object and stores that in the instance variable. You use the db.getCollection() method to fetch the todos collection. All of the operation are done on the todos collection.
  2. The findAll() method fetches all of the todo documents from the MongoDB database. The documents fetched from MongoDB are of the DBObject type. You iterated over the DBCursor object and converted the individual documents to Todo objects and then added them to a List. Finally, you returned the list of todos.
  3. The createNewTodo() method receives a JSON string representing the Todo item. You used GSON to convert the JSON to a Todo object. Finally, you inserted the BasicDBObject into the todos collection.
  4. The find() method finds the Todo item corresponding to a given id.
  5. The update() method updates the Todo document for the given todo Id. It also updates the done field of the document.

Step 6: Creating the Resource class

It is generally not a good idea to add all of the code to one class so we will move the application REST endpoints to another class. This new class is called TodoResource and exposes CRUD operations over Todo objects.

package com.todoapp;
 
import com.google.gson.Gson;
import spark.Request;
import spark.Response;
import spark.Route;
 
import java.util.HashMap;
 
import static spark.Spark.get;
import static spark.Spark.post;
import static spark.Spark.put;
 
public class TodoResource {
 
    private static final String API_CONTEXT = "/api/v1";
 
    private final TodoService todoService;
 
    public TodoResource(TodoService todoService) {
        this.todoService = todoService;
        setupEndpoints();
    }
 
    private void setupEndpoints() {
        post(API_CONTEXT + "/todos", "application/json", (request, response) -> {
            todoService.createNewTodo(request.body());
            response.status(201);
            return response;
        }, new JsonTransformer());
 
        get(API_CONTEXT + "/todos/:id", "application/json", (request, response)
 
                -> todoService.find(request.params(":id")), new JsonTransformer());
 
        get(API_CONTEXT + "/todos", "application/json", (request, response)
 
                -> todoService.findAll(), new JsonTransformer());
 
        put(API_CONTEXT + "/todos/:id", "application/json", (request, response)
 
                -> todoService.update(request.params(":id"), request.body()), new JsonTransformer());
    }
 
 
}

The code shown above exposes the TodoService CRUD methods as REST API’s.

JsonTransformer in the code shown above is an implementation of Spark’s ResponseTransformer interface. It allows you to convert response objects to other formats like JSON.

package com.todoapp;
 
import com.google.gson.Gson;
import spark.Response;
import spark.ResponseTransformer;
 
import java.util.HashMap;
 
public class JsonTransformer implements ResponseTransformer {
 
    private Gson gson = new Gson();
 
    @Override
    public String render(Object model) {
        return gson.toJson(model);
    }
 
}

Step 7: Bootstrap the application

Now we will write the entry point for our application. The Bootstrap class shown below configures all of the components. When you run this class as a Java application, it starts the Jetty server and start listening to requests.

package com.todoapp;
 
import com.mongodb.*;
 
import static spark.Spark.setIpAddress;
import static spark.Spark.setPort;
import static spark.SparkBase.staticFileLocation;
 
public class Bootstrap {
    private static final String IP_ADDRESS = System.getenv("OPENSHIFT_DIY_IP") != null ? System.getenv("OPENSHIFT_DIY_IP") : "localhost";
    private static final int PORT = System.getenv("OPENSHIFT_DIY_PORT") != null ? Integer.parseInt(System.getenv("OPENSHIFT_DIY_PORT")) : 8080;
 
    public static void main(String[] args) throws Exception {
        setIpAddress(IP_ADDRESS);
        setPort(PORT);
        staticFileLocation("/public");
        new TodoResource(new TodoService(mongo()));
    }
 
    private static DB mongo() throws Exception {
        String host = System.getenv("OPENSHIFT_MONGODB_DB_HOST");
        if (host == null) {
            MongoClient mongoClient = new MongoClient("localhost");
            return mongoClient.getDB("todoapp");
        }
        int port = Integer.parseInt(System.getenv("OPENSHIFT_MONGODB_DB_PORT"));
        String dbname = System.getenv("OPENSHIFT_APP_NAME");
        String username = System.getenv("OPENSHIFT_MONGODB_DB_USERNAME");
        String password = System.getenv("OPENSHIFT_MONGODB_DB_PASSWORD");
        MongoClientOptions mongoClientOptions = MongoClientOptions.builder().build();
        MongoClient mongoClient = new MongoClient(new ServerAddress(host, port), mongoClientOptions);
        mongoClient.setWriteConcern(WriteConcern.SAFE);
        DB db = mongoClient.getDB(dbname);
        if (db.authenticate(username, password.toCharArray())) {
            return db;
        } else {
            throw new RuntimeException("Not able to authenticate with MongoDB");
        }
    }
}

Step 8 : Setup AngularJS and Twitter Bootstrap

Create a new directory with name public and place the javascript and css files in it. You can checkout the required directory structure from the Github repository. Download the latest copy of AngularJS and Bootstrap from their respective official websites, or you can copy the resources from this project github repository.

Step 9: Create index.html

Create a new file called index.html inside of the src/main/resources/public directory and place the following content into it.

<!DOCTYPE html>
<html ng-app="todoapp">
<head>
    <title>Todo App</title>
    <link rel="stylesheet" href="/css/bootstrap.css">
    <link rel="stylesheet" href="/css/main.css">
</head>
<body>
<div class="navbar navbar-inverse">
    <div class="container-fluid">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">TodoApp</a>
        </div>
    </div>
</div>
 
<div class="container" ng-view="">
 
 
</div>
 
<script src="/js/jquery.js"></script>
<script src="/js/angular.js"></script>
<script src="/js/angular-route.js"></script>
<script src="/js/angular-cookies.js"></script>
<script src="/js/angular-sanitize.js"></script>
<script src="/js/angular-resource.js"></script>
 
<script src="/scripts/app.js"></script>
</body>
</html>

In the html shown above:

  1. You imported all of the required libraries. Our application code is in /scripts/app.js. The app.js file will be created in step 10.
  2. In Angular, you defined the scope of the project using the ng-app directive. You used ng-app on the html element but we can use it with any other element as well. Using the ng-app directive with html element means that AngularJS is available on the whole index.html. The ng-app directive can take a name. This name is the module name. I used todoapp as this application module name.
  3. The last interesting thing in the index.html is the use of the ng-view directive. The ng-view directive renders the template corresponding to the current route inside index.html. So that everytime you navigate to a route only the ng-view portion changes.

Step 10: Write the Angular application

The app.js houses all of the application specific JavaScript. All of the application routes are defined inside it. In the code shown below, we have defined two routes and each has a corresponding Angular controller.

/**
 * Created by shekhargulati on 10/06/14.
 */
 
var app = angular.module('todoapp', [
    'ngCookies',
    'ngResource',
    'ngSanitize',
    'ngRoute'
]);
 
app.config(function ($routeProvider) {
    $routeProvider.when('/', {
        templateUrl: 'views/list.html',
        controller: 'ListCtrl'
    }).when('/create', {
        templateUrl: 'views/create.html',
        controller: 'CreateCtrl'
    }).otherwise({
        redirectTo: '/'
    })
});
 
app.controller('ListCtrl', function ($scope, $http) {
    $http.get('/api/v1/todos').success(function (data) {
        $scope.todos = data;
    }).error(function (data, status) {
        console.log('Error ' + data)
    })
 
    $scope.todoStatusChanged = function (todo) {
        console.log(todo);
        $http.put('/api/v1/todos/' + todo.id, todo).success(function (data) {
            console.log('status changed');
        }).error(function (data, status) {
            console.log('Error ' + data)
        })
    }
});
 
app.controller('CreateCtrl', function ($scope, $http, $location) {
    $scope.todo = {
        done: false
    };
 
    $scope.createTodo = function () {
        console.log($scope.todo);
        $http.post('/api/v1/todos', $scope.todo).success(function (data) {
            $location.path('/');
        }).error(function (data, status) {
            console.log('Error ' + data)
        })
    }
});

The code shown above does the following:

  1. First, it configures the Angular module named todoapp and defines all of it’s dependencies.
  2. It defines the routes that this application will respond to.
  3. When a user makes request to ‘/’, the ListCtrl will be invoked. The ListCtrl will make an HTTP GET request to ‘/api/v1/todos’ to fetch all of the todo items. The todo items are put on the scope. The list.html uses the ng-repeat directive to iterate over all of the todo items and generate a table.
  4. When a user clicks on the checkbox next to the item, the todoStatusChanged() function is invoked. This function makes an HTTP PUT request to update the todo item.
  5. When a user makes a GET request to ‘/create’, create.html is rendered. The create.html renders a bootstrap form. The form HTML element uses the ng-submit directive. On form submit the createTodo function is invoked. The createTodo function makes an HTTP POST request to create a new todo item.

You can find the respective views for different routes in the application’s Github repository.

Step 11: Run the application

You can either run the application using your IDE or package this application as an executable jar and then run it from the command-line. Add the following plugin to your project pom.xml to create an executable JAR. This plugin provides the capability to package the artifact in an uber-jar, including its dependencies and to shade – i.e. rename – the packages of some of the dependencies.

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <createDependencyReducedPom>true</createDependencyReducedPom>
                    <filters>
                        <filter>
                            <artifact>*:*</artifact>
                            <excludes>
                                <exclude>META-INF/*.SF</exclude>
                                <exclude>META-INF/*.DSA</exclude>
                                <exclude>META-INF/*.RSA</exclude>
                            </excludes>
                        </filter>
                    </filters>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.todoapp.Bootstrap</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
</build>

To create an executable jar, run the mvn clean install command. This creates a todoapp-1.0-SNAPSHOT.jar in the target directory.

To run the application:

$ java -jar target/todoapp-1.0-SNAPSHOT.jar

This would print following lines in the terminal.

== Spark has ignited ...
>> Listening on localhost:8080
[Thread-2] INFO org.eclipse.jetty.server.Server - jetty-9.0.z-SNAPSHOT
[Thread-2] INFO org.eclipse.jetty.server.ServerConnector - Started ServerConnector@5bbf3bfb{HTTP/1.1}{localhost:8080}

Step 12: Deploying on OpenShift

This blog would not be complete if I didn’t show you how to run this application on OpenShift. Today OpenShift does not support JDK 8 but that doesn’t mean you can’t run Java 8 applications. You can use the DIY cartridge and install your own JDK version. The next command creates the todo application you created in the above mentioned steps. It installs JDK 8 on the DIY gear and configures other settings.

$ rhc app create todoapp diy mongodb-2.4 --repo=todoapp-os --from-code=https://github.com/shekhargulati/spark-openshift-quickstart.git

After this commands successfully finishes, you will see the todo app running at http://todoapp-{domain-name}.rhcloud.com. Please replace {domain-name} with your OpenShift account domain name.

Next Steps

Automatic Updates

Stay informed and learn more about OpenShift by receiving email updates.

Get blog updates by email
Categories
Java, MongoDB, OpenShift Online
Tags
,
  • http://www.subtleimages.net/ Dexter Legaspi

    the live demo like does not work.

  • OpenShift by Red Hat

    Hi Dexter,

    Thanks for noting that. We’ve removed reference to the demo so others don’t have the same experience.

  • Lucas David

    I’m stuck at step 4. I run Bootstrap.java and Eclipse outputs something like “server is running at 0.0.0.0:4567″, but when I go to the browser and try to access this IP address it shows me “Firefox can’t establish a connection to the server at 0.0.0.0:4567.” (the regular message when a server is unavailable). Any ideas why that might be happening? I’ve followed your tutorial rigorously.

    • Lucas David

      Oh, never mind. Just realized it’s working perfectly at ‘127.0.0.1:4567’! Thank you! :)

  • Shurap1

    Hi Shekhar, I really liked this blog. It helped me in understanding how to use angular js with spark java framework. I have one question, when I click on the Done check box, then the TODO item is getting striked out. Looking into the list.hml and app.js, I am unable to understand how this has been achieved. Can you please let me know how this is done?

    • Shurap1

      Sorry submitted too soon…I got it. It is done using the class name dynamically.

      class=”success-{{todo.done}}”

  • Alex Mills

    Can you explain how the System.getenv map gets populated with the OpenShift info?

  • Alex Mills

    In order to get this to work, I had to remove one argument from TodoResource — public class TodoResource {

    private static final String API_CONTEXT = “/api/v1″;

    private final TodoService todoService;

    public TodoResource(TodoService todoService) {
    this.todoService = todoService;
    setupEndpoints();
    }

    private void setupEndpoints() {
    post(API_CONTEXT + “/todos”, “application/json”, (request, response) -> {
    todoService.createNewTodo(request.body());
    response.status(201);
    return response;
    } /***** remove this, new JsonTransformer()****/);

  • Marcellus Easley

    I get:
    Exception in thread “main” java.lang.NoClassDefFoundError: spark/Route

    Any ideas?