Build a blog engine in 15 minutes in Java

Let's build a blog engine by using scooter framework.

Prerequisites

You need to follow the installation guide and download the Scooter framework. We put the file scooter.zip in c:\demo directory. If you use a UNIX-like OS, you may put the file under /home/your_account/demo/ directory.

You may use your own favoriate IDE to edit files. However, a simple text editor like Notepad or VI would be fine too. In development mode, the framework compiles and deploys the application automatically. All we need to do is just type (the source code) and click (the refresh button of a browser).

The framework comes with Eclipse setup files.

Project creation

Let's follow the instructions in startup guide to create the blog application.

Blog Creation

Now we can browse the site with this url: http://localhost:8080/blog.

Blog Home Page

During the rest of this development, we do not need to restart the web server.

Preparing a database

Scooter framework targets database-backed application development. It takes a bottom-up apprach, in that a database table must be created first. But you don't have to create a complete database schema before you can use Scooter, as Scooter can detect your database schema changes during development.

The database is specified in a configuration file, config/database.properties. In that file, you can specify a default database. When you create an application (see Step 1 in Startup), Scooter automatically configures database connections based on the type of database you choose.

Three types of databases are specified: development, test and production. For this demo, we use blog_development as our default database.

In this demo, we use MySQL database. We create a blog database by executing the following script.

> mysql -u root < blog_development.sql

The content of blog_development.sql for now is as follows:

CREATE DATABASE blog_development DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE blog_development;

CREATE TABLE posts (
  id int(11) NOT NULL auto_increment,
  name       varchar(255),
  title      varchar(255),
  content    text,
  created_at timestamp,
  updated_at timestamp,
  PRIMARY KEY  (id)
) ENGINE=InnoDB;

Here we created a blog database and a table for posts.

Adding a post module

Now let's add a post module to the blog application. Scooter's scaffolding code generator creates a complete set of code for CRUD operations of a model in a single command.

> java -jar tools/generate.jar blog scaffold post
Blog Scaffold Post Page

The scaffold code generator creates a controller, a model and some view files for post. It also adds posts as a resource in resources.properties. This allows us to access post in a restful way.

Scooter automatically detects and loads configuration changes. The newly added resource posts are already in the system. Clicking on the routes link on top of the screen, we can see all routes allowed by the applications including the posts related routes.

Blog Post Routes Page

We can view posts now by accessing url: http://localhost:8080/blog/posts.

Blog No Post Page

There is no post yet. Let's add one by clicking on the Add post link.

Blog Add Post Page

We save the post by clicking on the Create button.

Blog Post List Page

We can edit a post by clicking on edit link on the post list screen.

Blog Edit Post Page

The update was saved successfully.

Blog Show Post Page

Scooter supports i18n in all layers. We can even post in a different language such as Chinese:

Blog i18n Page

You may delete a post by clicking on delete link. Or you can display posts in a paginated way by clicking on the Paged list link. The default pagination style is Yahoo style. You also get Window style pagination links on the same screen as an example.

Blog Pagination Page

Validation

We can easily add validations in our post model. Open up src/blog/models/Post.java and paste the following code:

public class Post extends ActiveRecord {
    public void validatesRecord() {
    	validators().validatesPresenceOf("name, title, content");
    	validators().validatesLengthMaximum("name", 10);
    	validators().validatesLengthMaximum("content", 140);
        //Our posts are twitter friendly.
    }
}

Or you can use the built-in file browser to modify the Post.java file:

Blog Add Validation Page

After you save the update, you will see this:

Blog Show Validation Page

The above code indicates that

  1. A post must have a name, a title and some content.
  2. The name cannot be too long.
  3. And the length of the content must be twitter compatible.

Maybe in the future we want to forward posts to twitter automatically. Now let's see if it works. If I click the create button now, the application should remind me of entering name, title and content of my post.

Blog Validation Page

Apparently validation works.

Adding a comment module

A blog engine should allow others to comment a post. Let's add a comment module.

First, we create a table for comments. This can be done by opening MySQL client and executing the following script:

CREATE TABLE comments (
  id int(11) NOT NULL auto_increment,
  commenter  varchar(255),
  body       text,
  post_id    int(11),
  created_at timestamp,
  PRIMARY KEY  (id)
) ENGINE=InnoDB;
Blog Comment Table

The comments table has a column for commenter's name, comment body and a timestamp of the comment. The post_id field is a foreign key that maps to the related parent post record.

Let's add a comment model to the blog application. Run the following command in your terminal:

> java -jar tools/generate.jar blog model comment

This command generates two files for us:

  • src/blog/models/Comment.java: comment model
  • test/blog/models/CommentTest.java: comment model test

Associating Models

We now have a comment model. Let's associate it with the post model so that a post can find all its comments.

Open Post.java and paste the following code:

public class Post extends ActiveRecord {
    public void validatesRecord() {
    	validators().validatesPresenceOf("name, title, content");
    	validators().validatesLengthMaximum("name", 10);
    	validators().validatesLengthMaximum("content", 140);
        //Our posts are twitter friendly.
    }

    public void registerRelations() {
        hasMany("comments", "cascade:delete");
    }
}

This is a simple way of saying a post has many comments.

We can declare the link in comment model too. Open Comment.java, and paste the following:

public class Comment extends ActiveRecord {
    public void registerRelations() {
        belongsTo("post");
    }
}

The above code tells us that a comment belongs to a post. In this way, a comment object can find its associated post.

Scooter automatically compiles the code changes.

Configuring routes

Scooter uses routes to match HTTP requests to controller actions. Open config/routes.properties file and add the following:

resources.name.comments=\
    controller:comments; parents: strict posts

Again we can use the built-in file browser to modify the route.properties file:

Blog Add Comment Routes

Here we created a nested resource of comments. The keyword strict means we can only access comments through a post.

Scooter automatically detects and loads configureation changes. It should have loaded our update in the routes.properties file. We can verify this from the routes view by clicking on the routes link on top of browser screen. You should notice that seven new nested routes are added.

Blog Comment Routes

Adding comment list view

We plan to display all comments of a post directly under the post's show screen. Now let's add some code in the post show page to display comments of a post.

Open posts/show.jsp file and paste the following code:

<h2>Comments</h2>
<div id="comments">
<%for (Iterator it = post.allAssociated("comments").getRecords().iterator(); it.hasNext();)
{
    Object comment = it.next();%>
    <p>
        <b>Commenter: </b><%=O.property(comment, "commenter")%>
        <b>posted on  </b><%=O.property(comment, "created_at")%>
    </p>

    <p>
        <b>Comment:</b>
        <%=O.property(comment, "body")%>
    </p>
<%}%>
</div>

Don't forget to import java.util.Iterator class on top of the jsp page.

Because we have associated comment model with post model, we can easily load a list of comments for a particular post with the allAssociated method. This is a typical example of lazy loading. We only load objects when we need them.

But directly using post object here is not null-pointer safe. Scooter provides helper methods to deal with this. Let's refactor this code.

We replace the following code line:

for (Iterator it = post.allAssociated("comments").getRecords().iterator(); it.hasNext();) {

with the following line:

for (Iterator it = O.iteratorOf(O.allAssociatedRecordsOf("post.comments")); it.hasNext();) {

This is better. Class O provideds many helper methods for working with an object. Again, you need to import the class in the import statement of this jsp file.

Adding comment entry view

We also need a form for submitting a comment. We plan to add comment entry form right below the comment list view. Let me add some code here into post's show.jsp.

<h2>Add comment</h2>
<%=W.errorMessage("comment")%>

<%=F.formForOpen("posts", post, "comments", "comment")%>
  <p>
    <%=F.label("commenter")%><br />
    <input type="text" id="comment_commenter" name="commenter"
           value="<%=O.hv("comment.commenter")%>" size="80" />
  </p>
  <p>
    <%=F.label("body")%><br />
    <textarea id="comment_body" name="body" cols="60" rows="10">
           <%=O.hv("comment.body")%></textarea>
  </p>
  <input id="comment_submit" name="commit" type="submit"
           value="Create" />   <input type="reset"/>
<%=F.formForClose("comments")%>

The class F is a form utility class with helpers methods on html form. Its formForOpen method is used to create an HTML form for a post instance. Again, we need to import this class com.scooterframework.web.util.F.

The above code shows that a comment will be submitted for a specific post. The resource name of the comment is comments, while the resource name of post object is posts.

There are two input fields for comment: commenter name and comment body. Method O.hv gives us html-escaped value of an object's property.

Blog Add Comment Page

Creating a comment controller

We then need a comments controller to handle this comment entry form. We need a create action in the generated comments controller.

We can generate a comments controller with the following command:

> java -jar tools/generate.jar blog controller comments create

The above command creates two files, a controller and a view.

  • src/blog/controllers/CommentsController.java
  • webapps/blog/WEB-INF/views/comments/create.jsp

We can discard the view file as we don't need it. The generated create action in CommentsController is empty. Let's paste the following code in it:

package blog.controllers;

import static com.scooterframework.web.controller.ActionControl.*;

import com.scooterframework.admin.FilterManagerFactory;

import blog.models.Comment;
import blog.models.Post;

import com.scooterframework.orm.activerecord.ActiveRecord;
import com.scooterframework.web.util.R;

/**
 * CommentsController class handles comments related access.
 */
public class CommentsController extends ApplicationController {

    /**
     * create method creates a new comment record.
     */
    public String create() {
    	ActiveRecord post = Post.findById(p("post_id"));
    	setViewData("post", post);

        ActiveRecord newComment = null;
        try {
            newComment = Comment.newRecord();
            newComment.setData("commenter", p("commenter"));
            newComment.setData("body", p("body"));
            newComment.setData("post_id", p("post_id"));
            newComment.save();
            flash("notice", "Comment was successfully created.");

            return redirectTo(R.resourceRecordPath("posts", post));
        }
        catch(Exception ex) {
            log.error("Error in create() caused by " + ex.getMessage());
            flash("error", "There was a problem creating the comment record.");
        }

        setViewData("comment", newComment);
        return forwardTo(viewPath("posts", "show"));
    }

}

In this create() method, we first find the post object, then put the post object to the HTTP request scope so that it can be found by view. This is needed as we are displaying all comments of a specific post.

The findById, newRecord, p, and setViewData methods are convenient methods. The p method returns the value corresponding to HTTP parameter post_id.

Then we create a new comment based on the submitted parameters. After we save the comment successfully to database, we notify the client by using the flash method and then redirect it to the show page of the post object.

The resourceRecordPath method can give us a restful url to the post such as /posts/10 for post with id 10.

If it fails to save, we log the error and also notify the caller about the failure by using the flash method. After that, we forward the request back to the show page of the post. That is where we initially submit the comment.

To make the code compile, we need to import Post model class.

Now let's give it try. Just go back to show page and type a comment to a post. It should work and you should see something like the following.

Blog Added Comment Page

Adding validation in comment

Empty comment is meaningless. We can add validation in comment model to check if user has typed anything for the commenter name and comment body. We add the validatesRecord method in the comment model.

package blog.models;

import com.scooterframework.orm.activerecord.ActiveRecord;

/**
 * Comment class represents a comment record in database.
 */
public class Comment extends ActiveRecord {
    public void validatesRecord() {
    	validators().validatesPresenceOf("commenter, body");
    }

    public void registerRelations() {
        belongsTo("post", "counter_cache:true");
    }
}

Here we require that a comment must have a commenter name and comment body. Now if I click the create button without providing comment content, the application should remind me of doing that.

Blog Validate Comment Page

The validation apparently works.

Performance tuning

You should watch the screencast of creating a web blog in 15 minutes. Besides building a blog engine, the video also shows three ways to improve the site's performance:

  1. Picking which columns to retrieve through option ex_columns
  2. Counting number of comments per post by using counter_cache
  3. Deleting a post would automatically delete all its comments by using cascade:delete

Total code lines

The total number of code lines and files can be found with the following command:

> java -jar tools/codestats.jar webapps/blog/WEB-INF/src webapps/blog/WEB-INF/views/posts

-------------------------------------
                code    total   files
-------------------------------------
java            145     233     5
jsp             248     292     5
-------------------------------------
summary         393     528     10

There are only have 145 lines of Java code, and 248 lines of jsp code. Most of which are actually generated. That's not a lot.

Testing

Scooter generates unit test and functional test classes located under blog/WEB-INF/test/ directory. Once you've written a test, you can open a terminal and run the following ant commands:

Unit testing:

> ant app_test_unit -DappPath=webapps/blog

Functional testing:

> ant app_test_fun -DappPath=webapps/blog

The above ant command will perform the tests and generate a report under blog/static/docs/tests/.

Creating war file

We strongly recommend that you use the expanded directory structure for deployment. But if you want to deploy a war file for a production site, you can do so by using the provided ant script.

> ant war -DappPath=webapps/blog

The above ant command will create a blog.war file under build/war/ directory.

Other deployment options

Besides using embedded Jetty web server that comes with Scooter, you may also use Apache's Tomcat. You may deploy a war to other web servers too. For more details, please refer to Scooter's deployment document.