Designing an HTTP API for CRUD operations

Nearly every developer designing his/her first HTTP 1.1 API makes the mistake of mapping the create-read-update-delete (CRUD) operations very simplistically to the verbs POST-GET-PUT-DELETE respectively. This rather crude approach is likely to confuse the developers building client applications for that API, especially when it comes to the update and replace operations. 

If we examine the meaning of each of these HTTP verbs in some detail, then we can see the "correct" way to map CRUD operations to verbs. It might seem to be rather pedantic, but client developers tend to be much happier if the HTTP APIs they use behave according to normal HTTP conventions.

POST

In a nutshell, use POST to apply the given data to the resource identified by the given URI, following the rules documented for the resource media type.

So in terms of CRUD, it's fine to map the create operation to POST. It's also the right verb for any operation that isn't standardised by HTTP.

By convention, a successful POST request used to create a new resource should result in a 201 Created response, along with the associated data and a URI for the new resource. In ASP.NET Web API and C#, this is done by calling the ApiController method CreatedAtRoute:

_________________________________________________________________________________________

// Return URI for new person, plus their details.   
return this.CreatedAtRoute("GetPersonById", new { id = nextId }, person);

_________________________________________________________________________________________

GET

In a nutshell, use GET to request data associated with the resource identified by the given URI.

So in terms of CRUD, it's fine to map the read operation to GET. Note that according to RFC2616, GET should be idempotent. That's a fancy word meaning that the state of the server should stay the same after multiple identical requests. Idempotency is really important in distributed systems, because it allows the client to resend a message without any side-effects if the client perceives a partial or complete failure of the original request. Of course, idempotency only refers to the server state - you're free to return a different status code if the same request is made multiple times.

By convention, a successful GET request should result in a 200 Successful response, along with the requested data. Again using ASP.NET Web API, this is done by calling the ApiController method Ok. For example:

_________________________________________________________________________________________

return this.Ok(person);

_________________________________________________________________________________________

If the given resource can't be found, you might want the response to distinguish between a resource that no longer exists (410 Gone) and a resource that never existed (404 Not Found).

PUT

In a nutshell, use PUT to replace the resource identified by the given URI, ignoring what's already there (if anything).

So in terms of CRUD, it's fine to map the create operation to the PUT verb. But you should only map the update operation to PUT if you're completely replacing that resource with no checks before the replacement. If you want to partially update the resource rather than replace it, don't use PUT. And if you want to run any checks (for example, a concurrency check on the version number) before replacing the resource, then again you shouldn't use PUT. Finally, and quite different to PATCH as discussed below, PUT should be an idempotent method. The client should be able to send a specific PUT request multiple times without damaging the server state.

By convention, a successful PUT request should result in either a 201 Created response (when creating a new resource) or a 200 Successful response (when replacing an existing resource). In either case, the associated data should also be returned.

PATCH

In a nutshell, use PATCH to update an existing resource, providing that the resource has the same state it had the last time the client looked.

So in terms of CRUD, it's fine to map the update operation to the PATCH verb. This allows you to update the resource partially, and to run any checks before doing the update. In fact, it obliges you to check that the resource hasn't changed between the read and the attempted update.

By convention, a successful PATCH request should result in a 200 Successful response, along with the modified resource data.

DELETE

In a nutshell, use DELETE to remove the resource identified by the given URI.

So in terms of CRUD, it's fine to map the delete operation to the DELETE verb. As with GET, this method should be idempotent, so the client is free to delete the same resource multiple times without affecting the server state.

IN SUMMARY

Your API should use:

  • POST and/or PUT to create a new resource. If there are specific creation rules, then it should use POST.
  • PATCH to update an existing resource. Be sure to check that the data hasn't changed between the read and update operations.
  • PUT to replace a resource without checking. If the resource doesn't currently exist, then a new resource can optionally be created.
  • DELETE to remove a resource. Be sure to check that the data hasn't changed between the read and delete operations.

HTTP status codes

I've found Wikipedia's list of HTTP status codes to be very useful for finding the right code to return for a given situation. For example, differentiating between 404 Not Found and 410 Gone. Or between 401 Unauthorized and 403 Forbidden. Again, this is about using HTTP in a way that respects the HTTP conventions and thereby provides the least number of surprises for API client developers.