Can.JS is a nice client side (javascript) library providing a MVC solution out of the box. The model is often backed by some JSON server and in Java(EE) case by JAX-RS.
Can.JS has a shortcut for CRUD models called ‘resource’ based on a default mapping. Let see how to implement it on a Java server side easily.
If you go to can.Model.resource you’ll see that you can define a resource as simply as:
var User = can.Model.extend({ resource: "/api/user" }, {});
And then you can use it to find a user, find all users etc..
User.findOne({id: 473629}, function (userFound) { /* do something */}); User.findAll().then(function (users) { /* do something */}); new User({name: 'Can.JS', first: 'Can', last: 'last'}).create().then(function (createdUser) { /*...*/}); // ...
However, no explicit mapping of operations means your server mapping matches conventions of Can.JS.
To do it with JAX-RS, use this kind of resource:
import javax.transaction.Transactional; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import java.util.Collection; import java.util.Locale; import java.util.Optional; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; @Transactional // this sample uses JPA @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Path("user") public class UserResource { @PersistenceContext private EntityManager em; @POST public UserView post(final UserView view) { em.persist(mergeView(view, new User())); em.flush(); return view; } @PUT @Path("{id}") public UserView put(@PathParam("id") final String id, final UserView view) { return toView(mergeView(view, em.find(User.class, id))); } @DELETE @Path("{id}") public void delete(@PathParam("id") final String id) { em.remove(em.getReference(User.class, id)); } @GET @Path("{id}") public UserView get(@PathParam("id") final String id) { return toView(em.find(User.class, id)); } @GET public Collection getAll() { return em.createNamedQuery("User.findAll", User.class) .getResultList().stream() .map(this::toView) .collect(toList()); } private User mergeView(final UserView view, final User user) { user.setName(requireNonNull(view.getName())); // all setters actually or a mapper return user; } private UserView toView(final User user) { final UserView view = new UserView(); view.setName(user.getName()); view.setId(user.getId()); return view; } }
As you already guessed, this can easily be abstracted requiring you to simply implement the mapping functions…but this is another story 😉