PROBLEM
Let’s assume we invoke person.destroy()
to delete a person:-
var person = Backbone.Model.extend( { ... } );
person.destroy( {
contentType : 'application/json',
success : function () {
console.log('success');
},
error : function () {
console.log('error');
}
} );
… and let’s assume when person.destroy()
is invoked, it will call /person/{personId} DELETE
. Here’s how the Spring MVC controller API might look like:-
@Controller
public class PersonController {
...
@RequestMapping(value = "/person/{personId}", method = RequestMethod.DELETE)
public ResponseEntity delete(@PathVariable Long personId) {
Person person = personService.getPerson(personId);
if (person == null) {
return new ResponseEntity<UnexpectedErrorBean>(new UnexpectedErrorBean("Invalid person ID"),
HttpStatus.BAD_REQUEST);
}
personService.remove(person);
return new ResponseEntity(HttpStatus.OK);
}
}
When we execute person.destroy()
, the error
callback function will always get called even though the HTTP status is a 200 OK
.
Why?
SOLUTION
The reason is because Backbone expects a JSON payload from the web service call. In the example above, we return a 200 OK
but without a JSON payload. Thus, Backbone will treat this as an error and invoke the error
callback function instead of success
callback function regardless of the HTTP status.
There are three solutions to this problem.
Solution 1: Add a JSON Payload
@Controller
public class PersonController {
...
@RequestMapping(value = "/person/{personId}", method = RequestMethod.DELETE)
public ResponseEntity delete(@PathVariable Long personId) {
Person person = personService.getPerson(personId);
if (person == null) {
return new ResponseEntity<UnexpectedErrorBean>(new UnexpectedErrorBean("Invalid person ID"),
HttpStatus.BAD_REQUEST);
}
personService.remove(person);
return new ResponseEntity<Long>(personId, HttpStatus.OK);
}
}
In this approach, we add a JSON payload to satisfy Backbone. In the above example, we return the value of personId
back to the client. It can be an empty object too.
I don’t like this approach because that piece of information is useless to the client side. Even if I return an empty object, the solution seems very brittle because the next team member inheriting my code will not understand why there’s a need to return an empty object back to the client.
Solution 2: Accept Payload as Text
var person = Backbone.Model.extend( { ... } );
person.destroy( {
contentType : 'application/json',
dataType : 'text',
success : function () {
console.log('success');
},
error : function () {
console.log('error');
}
} );
Another approach is to accept the data as text from the server. This solution works fine if we are not expecting any complex data structure back from the server.
Solution 3: Return a Different HTTP Status
@Controller
public class PersonController {
...
@RequestMapping(value = "/person/{personId}", method = RequestMethod.DELETE)
public ResponseEntity delete(@PathVariable Long personId) {
Person person = personService.getPerson(personId);
if (person == null) {
return new ResponseEntity<UnexpectedErrorBean>(new UnexpectedErrorBean("Invalid person ID"),
HttpStatus.BAD_REQUEST);
}
personService.remove(person);
return new ResponseEntity(HttpStatus.NO_CONTENT);
}
}
The approach that I truly recommend is to return a 204 No Content
instead of 200 OK
. This way, the client side will not expect any payload from the server, and thus, Backbone will invoke the success
callback function.
Leave a Reply