We explored the building of web apps using Spring MVC in Chapter 2, Building the Web Layer with Spring Web MVC. In Chapter 3, Accessing Data with Spring, we also learned how to persist data using Spring Data JPA. We are going to apply both these techniques again for building an API application for Taskify.
Since we have already learned the basics of creating Spring MVC applications with Spring Data JPA, at this point, we will go into detail only about the specifics of the API endpoints. Refer to Chapter 2, Building the Web Layer with Spring Web MVC for Spring MVC configuration and Chapter 3, Accessing Data with Spring for details about Spring Data JPA. Set up and configure the project with the following steps:
@EnableJpaRepositories(basePackages = "com.taskify.dao")
DataSource
, JdbcTemplate
, TransactionManager
, and EntityManager
with the flavor of your choice.The application has the following two models as domain objects:
Now we need to realize these as Java classes, annotated as JPA entities, so that we can persist them to a database, as follows:
User.java
package com.taskify.domain; import java.util.Date; ... @Entity @Table(name = "TBL_USER", uniqueConstraints = @UniqueConstraint(name = "UK_USER_USERNAME", columnNames = {"USER_NAME" }) ) public class User { @Id @GeneratedValue private Long id; @Column(name = "NAME", length = 200) private String name; @Column(name = "USER_NAME", length = 25) private String userName; @Column(name = "PASSWORD", length = 20) private String password; @Column(name = "DOB") @Temporal(TemporalType.TIMESTAMP) private Date dateOfBirth; ... //Getters and setters go here.. }
Task.java
package com.taskify.domain; import java.util.Date; ... @Entity @Table(name = "tbl_task") public class Task { @Id @GeneratedValue private Long id; @Column(name = "NAME", length = 500) private String name; @Column(name = "PRIORITY") private int priority; @Column(name = "STATUS") private String status; @ManyToOne(optional = true) @JoinColumn(name = "CREATED_USER_ID", referencedColumnName = "ID") private User createdBy; @Column(name = "CREATED_DATE") @Temporal(TemporalType.TIMESTAMP) private Date createdDate; @ManyToOne(optional = true) @JoinColumn(name = "ASSIGNEE_USER_ID", referencedColumnName = "ID") private User assignee; @Column(name = "COMPLETED_DATE") @Temporal(TemporalType.TIMESTAMP) private Date completedDate; @Column(name = "COMMENTS") private String comments; ... //Getters and setters go here.. }
Once the JPA entities are ready, create the DAOs for both User
and Task
—UserDAO
and TaskDAO
—annotated with @Repository
. As the best approach and for proper application layering, create the corresponding @Service
beans too. Since we already covered the JPA @Repository
and @Service
classes in the previous chapters, the code for these beans is not listed here. You can find the exact code in the code bundle provided with this book.
The purpose of the API server is to expose API endpoints for the consumption of clients, including the Taskify Ember frontend app. Let's build these web services in the REST model, with JSON data format support.
In this section, we will list two classes annotated with @RestController
: UserController
and TaskController
. The handler methods support asynchronous, non-blocking IO so that they are more scalable and faster. Handler methods are designed in the REST model. The HTTP methods GET
, POST
, PUT
, and DELETE
are mapped against the Create, Read, Update, and Delete (CRUD) operations.
UserController
exposes endpoints for CRUD operations on the User
entity. You can see the endpoints of UserController
accepting and producing JSON data appropriately in its code, which is as follows:
package com.taskify.web.controller; import java.util.List; ... /** * Handles requests for user related pages. */ @RestController @RequestMapping("/api/v1/user") @CrossOrigin public class UserController { private static final Logger = LoggerFactory.getLogger(UserController.class); @Autowired private UserService; @RequestMapping(method = RequestMethod.GET) @ResponseBody public Callable<List<User>> listAllUsers() { return new Callable<List<User>>() { @Override public List<User> call() throws Exception { return userService.findAllUsers(); } }; } @RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public Callable<User> createNewUser( @RequestBody CreateUserRequest request) { logger.info(">>>>>>>> Creating User, request - " + request); return new Callable<User>() { @Override public User call() throws Exception { return userService.createNewUser(request.getUser()); } }; } @RequestMapping(path = "/{id}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public Callable<User> updateUser(@PathVariable("id") Long id, @RequestBody UpdateUserRequest request) { logger.info(">>>>>>>> updateUser, request - " + request); return new Callable<User>() { @Override public User call() throws Exception { User existingUser = userService.findById(id); existingUser.setName(request.getUser().getName()); existingUser.setPassword(request.getUser().getPassword()); existingUser.setUserName(request.getUser().getUserName()); userService.updateUser(existingUser); return existingUser; } }; } @RequestMapping(path = "/{id}", method = RequestMethod.GET) public Callable<User> getUser(@PathVariable("id") Long id) { return new Callable<User>() { @Override public User call() throws Exception { return userService.findById(id); } }; } @RequestMapping(path = "/{id}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.NO_CONTENT) public Callable<Void> deleteUser(@PathVariable("id") Long id) { return new Callable<Void>() { @Override public Void call() throws Exception { userService.deleteUser(userService.findById(id)); return null; } }; } }
TaskController
maps request endpoints for CRUD operations around the Task
entity. Its code is as follows:
package com.taskify.web.controller; import java.util.List; ... @RestController @RequestMapping("/api/v1/task") @CrossOrigin public class TaskController { private static final Logger = LoggerFactory.getLogger(TaskController.class); @Autowired private UserService; @Autowired private TaskService; private static final int[] priorities = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; @RequestMapping(method = RequestMethod.GET) @ResponseBody public Callable<List<Task>> listAllTask() { return new Callable<List<Task>>() { @Override public List<Task> call() throws Exception { return taskService.findAllTasks(); } }; } @RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public Callable<Task> createNewTask( @RequestBody CreateTaskRequest request) { logger.info(">>>>>>>> Creating Task, request - " + request); return new Callable<Task>() { @Override public Task call() throws Exception { return taskService.createTask(request.getTask()); } }; } @RequestMapping(path = "/{id}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public Callable<Task> updateTask(@PathVariable("id") Long id, @RequestBody UpdateTaskRequest request) { logger.info(">>>>>>>> updateTask, request - " + request); return new Callable<Task>() { @Override public Task call() throws Exception { Task existingTask = taskService.findTaskById(id); existingTask.setName(request.getTask().getName()); existingTask.setPriority(request.getTask().getPriority()); existingTask.setStatus(request.getTask().getStatus()); existingTask.setCreatedBy(userService.findById( request.getTask().getCreatedBy().getId())); if(request.getTask().getAssignee() != null && request.getTask().getAssignee().getId() != null) { existingTask.setAssignee(userService.findById( request.getTask().getAssignee().getId())); } else { existingTask.setAssignee(null); } taskService.updateTask(existingTask); return existingTask; } }; } @RequestMapping(path = "/{id}", method = RequestMethod.GET) public Callable<Task> getTask(@PathVariable("id") Long id) { return new Callable<Task>() { @Override public Task call() throws Exception { return taskService.findTaskById(id); } }; } @RequestMapping(path = "/{id}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.NO_CONTENT) public Callable<Void> deleteTask(@PathVariable("id") Long id) { return new Callable<Void>() { @Override public Void call() throws Exception { taskService.deleteTask(id); return null; } }; } }
We have built all the necessary artifacts for the API server. You can package the application and deploy it. You should be able to access the UserController
handlers at http://<app-context-root>/api/v1/user
and the TaskController
handlers at http://<app-context-root>/api/v1/task/
. Now let's go build the frontend.