Restful Web services backend using Spring MVC and Spring Boot

Let’s see how to build a Restful Web services backend using Spring MVC, also building it on top of Spring Boot ( Data + Web ).

I strongly recommend you to take a look at my previous post Building a stand-alone Spring Boot Web Application from scratch to become familiar with the technology if you haven’t played with it !

I am going to build a simple “Blog” application, where we will be able to interact as a Restful Web service. We will have 2 services :

  1. Posts ( Has multiple comments. One to Many relationship )
  2. Comments ( Belongs to a post. Many to One relationship )

Our project is going to be a Maven project, we only need a directory called “restful-blog”, and here goes the pom.xml file :

<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>au.com.riosoftware.blog</groupId>
    <artifactId>restful-blog</artifactId>
    <packaging>jar</packaging>
    <version>1.0</version>
    <name>restful-blog</name>
    <description>Our Restful blog backend !</description>
    <url>http://maven.apache.org</url>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.7.RELEASE</version>
    </parent>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>

    </dependencies>

    <properties>
        <java.version>1.8</java.version>
    </properties>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-releases</id>
            <url>https://repo.spring.io/libs-release</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-releases</id>
            <url>https://repo.spring.io/libs-release</url>
        </pluginRepository>
    </pluginRepositories>

</project>

I’m using a basic Spring Boot configuration, also adding spring-boot-starter-web for Spring MVC support, spring-boot-starter-data-jpa for JPA support and h2 for our in memory database.

Next step is to define our Domain classes, which are JPA annotated entities

Post.java

package au.com.riosoftware.blog.domain;

import com.fasterxml.jackson.annotation.JsonManagedReference;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Entity
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private String title;
    private String message;

    @JsonManagedReference
    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private List<Comment> comments;

    public Post() {
        initializeComments();
    }

    public Post(String title, String message) {
        initializeComments();
        this.title = title;
        this.message = message;
    }

    private void initializeComments(){
        this.comments = new ArrayList<>();
    }

    public long getId() {
        return id;
    }

    public String getTitle() {
        return message;
    }

    public String getMessage() {
        return message;
    }

    public boolean addComment(Comment comment) {
        return comments.add(comment);
    }

    public boolean removeComment(Comment comment) {
        return this.comments.remove(comment);
    }

    public Iterable<Comment> getComments() {
        return Collections.unmodifiableList(comments);
    }

    @Override
    public String toString() {
        return "Post{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", message='" + message +
                '}';
    }

}

Simple JPA entity, having an auto-generated field for id, and a list for its comments, which I’m making it fetch eagerly ( it always loads all comments after loading a post ), and I’m also deleting all comments when deleting a Post, as there is no reason to leave orphan comments. Next one is our Comments model.

Comment.java

package au.com.riosoftware.blog.domain;

import com.fasterxml.jackson.annotation.JsonBackReference;

import javax.persistence.*;

@Entity
public class Comment {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private String message;

    @JsonBackReference
    @ManyToOne
    private Post post;

    public Comment() {
    }

    public Comment(Post post, String message) {
        this.post = post;
        this.message = message;
    }

    public long getId() {
        return this.id;
    }

    public String getMessage() {
        return this.message;
    }

    public Post getPost() {
        return this.post;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Comment comment = (Comment) o;

        if (id != comment.id) return false;
        if (!message.equals(comment.message)) return false;
        return post.equals(comment.post);

    }

    @Override
    public int hashCode() {
        int result = (int) (id ^ (id >>> 32));
        result = 31 * result + message.hashCode();
        result = 31 * result + post.hashCode();
        return result;
    }

    @Override
    public String toString() {
        return "Comment{" +
                "id=" + id +
                ", message='" + message + '\'' +
                ", post=" + post +
                '}';
    }

}

Very well, we will also need to pull and push models to/from the database, so we need the repositories to do that work for us.

PostRepository.java

package au.com.riosoftware.blog.domain;

import org.springframework.data.repository.CrudRepository;

public interface PostRepository extends CrudRepository<Post, Long> {
}

CommentRepository.java

package au.com.riosoftware.blog.domain;

import org.springframework.data.repository.CrudRepository;

public interface CommentRepository extends CrudRepository<Comment, Long> {
}

It is now time to define our controllers, which are the components responsible for handling our requests. Here they come :

PostController.java

package au.com.riosoftware.blog.controllers;

import au.com.riosoftware.blog.domain.Post;
import au.com.riosoftware.blog.domain.PostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/posts")
public class PostController {

    @Autowired
    PostRepository postRepository;

    @RequestMapping(value="", method=RequestMethod.GET)
    public Iterable<Post> listAll() {
        return postRepository.findAll();
    }

    @RequestMapping(value="/{id}", method=RequestMethod.GET)
    public Post getPostById(@PathVariable long id) {
        return postRepository.findOne(id);
    }

    @RequestMapping(value="", method=RequestMethod.POST)
    public ResponseEntity createNew(@RequestBody Post post) {
        postRepository.save(post);
        return new ResponseEntity(post, HttpStatus.CREATED);
    }

    @RequestMapping(value="", method=RequestMethod.PUT)
    public ResponseEntity update(@RequestBody Post post) {
        if (postRepository.exists(post.getId())) {
            postRepository.save(post);
        } else {
            return new ResponseEntity<String>("Post #" + post.getId() + " does not exist.", HttpStatus.NOT_FOUND);
        }

        return new ResponseEntity<Post>(post, HttpStatus.OK);
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public ResponseEntity delete(@PathVariable long id) {
        if (postRepository.exists(id)) {
            postRepository.delete(id);
        }else{
            return new ResponseEntity<String>("Post #" + id + " does not exist.", HttpStatus.NOT_FOUND);
        }
        return new ResponseEntity(HttpStatus.OK);
    }

}

We will have the following paths available for our posts resource :

GET
  • http://localhost:8080/posts –> return all posts with comments
  • http://localhost:8080/posts/1 –> return post #1
POST
  • http://localhost:8080/posts –> create a new post
PUT
  • http://localhost:8080/posts –> update an existing post
DELETE
  • http://localhost:8080/posts/3 –> delete a post and all comments

A similar work need to be done for our comments controller in order to handle comments for posts :

CommentController.java

package au.com.riosoftware.blog.controllers;

import au.com.riosoftware.blog.domain.Comment;
import au.com.riosoftware.blog.domain.CommentRepository;
import au.com.riosoftware.blog.domain.Post;
import au.com.riosoftware.blog.domain.PostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/posts/{postId}/comments")
public class CommentController {

    @Autowired
    CommentRepository commentRepository;

    @Autowired
    PostRepository postRepository;

    @RequestMapping(value="", method=RequestMethod.POST)
    public ResponseEntity createNew(@PathVariable long postId, @RequestBody Comment newComment) {
        Post post = postRepository.findOne(postId);
        Comment comment = new Comment(post, newComment.getMessage());
        commentRepository.save(comment);
        post.addComment(comment);
        postRepository.save(post);
        return new ResponseEntity(comment, HttpStatus.CREATED);
    }

    @RequestMapping(value="", method=RequestMethod.PUT)
    public ResponseEntity update(@RequestBody Comment comment) {

        if (commentRepository.exists(comment.getId())) {
            commentRepository.save(comment);
        } else {
            return new ResponseEntity<String>("Comment #" + comment.getId() + " does not exist.", HttpStatus.NOT_FOUND);
        }

        return new ResponseEntity(comment, HttpStatus.OK);

    }

    @RequestMapping(value = "/{commentId}", method = RequestMethod.DELETE)
    public ResponseEntity delete(@PathVariable long commentId) {
        if (commentRepository.exists(commentId)) {
            Comment comment = commentRepository.findOne(commentId);
            comment.getPost().removeComment(comment);
            postRepository.save(comment.getPost());
            commentRepository.delete(comment);
        }else{
            return new ResponseEntity<String>("Comment #" + commentId + " does not exist.", HttpStatus.NOT_FOUND);
        }
        return new ResponseEntity(HttpStatus.OK);
    }

}

We will have the following paths available for our comments resource :

POST
  • http://localhost:8080/posts/2/comments –> create a new comment
PUT
  • http://localhost:8080/posts/1/comments –> update an existing comment
DELETE
  • http://localhost:8080/posts/2/comments/5 –> delete a comment of a post

The last piece we need is the Application bootstrap to run our services. As I’m using SpringBoot, it is going  to be an executable POJO, which I will also fill in some data to start with :

Application.java

package au.com.riosoftware.blog;

import au.com.riosoftware.blog.domain.Comment;
import au.com.riosoftware.blog.domain.CommentRepository;
import au.com.riosoftware.blog.domain.Post;
import au.com.riosoftware.blog.domain.PostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application implements CommandLineRunner {

    @Autowired
    PostRepository postRepository;

    @Autowired
    CommentRepository commentRepository;

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... strings) throws Exception {

        Post post1 = new Post("My Post #1", "This post totally rocks v.1");
        Post post2 = new Post("My Post #2", "This post totally rocks v.2");
        Post post3 = new Post("My Post #3", "This post totally rocks v.3");

        postRepository.save(post1);
        postRepository.save(post2);
        postRepository.save(post3);

        Comment c1 = new Comment(post1, "Comment1");
        Comment c2 = new Comment(post1, "Comment2");
        Comment c3 = new Comment(post1, "Comment3");
        Comment c4 = new Comment(post3, "Comment1");

        commentRepository.save(c1);
        commentRepository.save(c2);
        commentRepository.save(c3);
        commentRepository.save(c4);

        post1.addComment(c1);
        post1.addComment(c2);
        post1.addComment(c3);
        post3.addComment(c4);

        postRepository.save(post1);
        postRepository.save(post2);
        postRepository.save(post3);

    }

}

Now let’s build and run our application with maven :

  • mvn package
  • java -jar target/restful-blog-1.0.jar

Let’s make some calls to our services and see the results. I am using CURL to send my requests.

curl -i -X GET http://localhost:8080/posts

[  
   {  
      "id":1,
      "title":"This post totally rocks v.1",
      "message":"This post totally rocks v.1",
      "comments":[  
         {  
            "id":1,
            "message":"Comment1"
         },
         {  
            "id":2,
            "message":"Comment2"
         },
         {  
            "id":3,
            "message":"Comment3"
         }
      ]
   },
   {  
      "id":2,
      "title":"This post totally rocks v.2",
      "message":"This post totally rocks v.2",
      "comments":[  

      ]
   },
   {  
      "id":3,
      "title":"This post totally rocks v.3",
      "message":"This post totally rocks v.3",
      "comments":[  
         {  
            "id":4,
            "message":"Comment1"
         }
      ]
   }
]

curl -i -X POST -H “Content-Type:application/json” -d ‘{  “title” : “My new Post !!!”,  “message” : “Saving through method Post ;-)” }’ http://localhost:8080/posts

{  
   "id":4,
   "title":"Saving through method Post ;-)",
   "message":"Saving through method Post ;-)",
   "comments":[  

   ]
}

curl -i -X PUT -H “Content-Type:application/json” -d ‘{ “id” : “1”, “title” : “ZZZZZZZZZZZ”,  “message” : “OOOOOOOOOOOOOO” }’ http://localhost:8080/posts

{  
   "id":1,
   "title":"OOOOOOOOOOOOOO",
   "message":"OOOOOOOOOOOOOO",
   "comments":[  
   ]
}

curl -i -X DELETE http://localhost:8080/posts/3

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Length: 0
Date: Mon, 09 Nov 2015 11:04:26 GMT

curl -i -X POST -H “Content-Type:application/json” -d ‘{  “message” : “XYZ” }’ http://localhost:8080/posts/2/comments

+

curl -i -X GET http://localhost:8080/posts/2

{  
   "id":2,
   "title":"This post totally rocks v.2",
   "message":"This post totally rocks v.2",
   "comments":[  
      {  
         "id":5,
         "message":"XYZ"
      }
   ]
}

curl -i -X PUT -H “Content-Type:application/json” -d ‘{  “id”: “1”, “message” : “ABCDEFG” }’ http://localhost:8080/posts/1/comments

+

curl -i -X GET http://localhost:8080/posts/1

{  
   "id":1,
   "title":"This post totally rocks v.1",
   "message":"This post totally rocks v.1",
   "comments":[  
      {  
         "id":1,
         "message":"ABCDEFG"
      },
      {  
         "id":2,
         "message":"Comment2"
      },
      {  
         "id":3,
         "message":"Comment3"
      }
   ]
}

curl -i -X DELETE -H “Content-Type:application/json” http://localhost:8080/posts/2/comments/5

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Length: 0
Date: Mon, 09 Nov 2015 11:21:02 GMT

To wrap it up, this is a simple yet real world restful web service implementation using Spring MVC and Spring Boot. Many things could be done in this example, such as validations, running through more scenarios, loading test data through scripts and so on, but for the sake of simplicity, clarity and not having an article larger than life I let them out 😉

You can also find this source code on my github repository : https://github.com/marciomarinho/restful-blog

 

Leave a Reply

Your email address will not be published. Required fields are marked *