Since the dawn of times the web, humans created CRUD APIs.
And we were instructed that modification verbs should return the modified resource in response.
But, should they?
A basic CRUD API consists of 5 verbs.
Let’s go over them quickly.
The 5 verbs
GET
The GET verb is used to retrieve the resource, or a collection of resources.
It should not make any modification on the server, and therefor it’s idempotent.
POST
The POST verb is used to create a resource on the server.
It also can be used to create a collection of resources.
From what you understood, POST changes the state on the server, and is not idempotent.
PUT
Contrary to POST, PUT is used to replace a resource, or a collection or resources.
Like POST, PUT modified the state on the server.
However, unlike POST, PUT is idempotent.
PATCH
PATCH is not a well known verb.
Some confuse PATCH with PUT, but there is a difference.
While PUT replaces the entire resource, PATCH is designed to update parts of a resource.
To give you a better example, imagine we have the following resource:
I can issue a PUT request.
But this will require me to submit both email and handle fields, because PUT used to replace the resource.
I can issue a PATCH request.
This way, I can submit a partial JSON that will contain only the new email.
PATCH, as you probably guessed, modifies the state on the server.
However, if you assumed that PATCH is idempotent, just like PUT, you are wrong.
I won’t go into details here, but you can check this 8-year-old StackOverflow question, where I asked about the same thing.
DELETE
Last, but not least, is DELETE.
It’s used to delete a resource from the server.
It modifies the state on the server, and is idempotent.
On POST, PUT, and PATCH and their responses
There is a verbal agreement that POST, PUT, and PATCH operations should return the created/updated resource.
In the rest of this article, I want to provide reasons why they shouldn’t.
Reason 1: API consistency
Consistent APIs are easier to work with.
It doesn’t matter if you plan on third-parties to integrate with your API, or it’s used internally by your frontend/mobile teams.
By returning a newly created resource inside POST request—you will have to return a response in all POST requests.
Otherwise, the people who integrate with your API, will have to handle endpoint-by-endpoint use-cases.
Returning response in every POST endpoint is, however, unfeasible.
Why?
Here are some examples.
What would your return on POST endpoint that accepts batch data from CSV?
Let’s say you are able to process the CSV in-place, without delayed processing.
Are you going to return all the newly created resources?
What if there are hundreds of them?
Do you return them paginated?
What would you return on a POST endpoint that schedules an async job?
Let’s say you send s CSV, but instead of parsing it in-place, you schedule the parsing for a later stage using some queue.
Do you return a job object?
A job ID?
Do you even want to expose your API consumers to the concept of asynchronous processing?
Imagine you have a resource such as blog post.
This blog post can have tags.
After issuing a PATCH request to add a new tag, what would you return?
Do you return the entire blog post object, or only the array of tags?
Or maybe the newly created tag?
As I demonstrated, there are some ambiguities you need to solve in order to support returning the resource on create/update.
This complicates your API, and makes it harder for your consumers.
What is the alternative?
There is one, but first, let’s discuss some more issues with returning resources.
Reason 2: Offline support
One reason to return the resource on create/update operations—is to avoid the additional GET call to retrieve the newly created/updated resource, that the client will have to do.
However, when designing an API to be consumed by a client that can work in offline mode, the client needs to backfill the newly created resource anyway.
When a user creates a resource, but the application is offline, you want to have optimistic flow.
The UI updates accordingly, but the resource is queued to be created when the client goes back online.
In that case, the application already knows the structure of the POST response.
Hence, it’s unnecessary to return anything when issuing create/update.
When the client will be back online, it will execute all the create/update operations to let the server know what happened, and will issue a GET request to update the view for the user.
But Dmitry, I don’t care about offline!
Too bad.
But let’s say you really don’t care about offline.
In today’s world, of zero patience, users expect stuff to happen instantly.
And even if you issue a POST request, and wait for a response from it, there could be a noticeable latency that your users won’t like.
One way to fix this latency, is to do an optimistic update.
The user fills the form, and click submit.
Your UI framework creates a dummy resource and immediately appends it to the list of resources, as if it was created successfully, hence updating the UI.
The user is happy.
In the background, you issue a POST request to create the resource on the server.
There is a high probability that the request succeeds, hence the name—optimistic update.
If it fails, we can notify the user via a pop-up, or retry again up to a certain number of retires.
Reason 3: It violates DRY
DRY, which is an abbreviation for don’t repeat yourself, is a principle that states that your code should be free of repetitions.
Repetition make the code hard to maintain, as now there are few places that do the same, or depend on the same method.
By introducing a response to create/update operations, you violate DRY, because you have to return the same resource as the one you return in GET.
And if the resources are different between create/update and GET calls, your API becomes inconsistent, which violates the first reason about API consistency.
Reason 4: It violates SRP
SRP, which is an abbreviation for single responsibility principle, states that each module in your code should be responsible for one action.
GET returns the resource.
DELETE removes the resource.
But when you introduce response to create/update operations, they no longer have single responsibility.
This can get complicated.
Consider a use-case of adding a new tag to a blog post.
The entire logic (excluding authentication and validation), can be narrowed to one particular SQL:
INSERT INTO blog_post_tags (post_id, tag) VALUES ($1, $2) ON CONFLICT IGNORE;
So when your POST operation finishes, you don’t hold the entire blog post resource.
Therefore, in-order to return a proper response during POST, you either need to call GET on the same resource internally (many frameworks support issuing internal HTTP requests).
Or you need to refactor the resource retrieval, and serialization, to an external function which both the GET and the POST handlers will call.
Now, they need not only create or update the said resource, but also serialize it in the same way as GET would, and return it to the client.
What to do instead
Well, nothing!
You just don’t return anything.
The same way we do DELETE requests, which return 200 OK.
However, for resource creation you should use 201 Created or 202 Accepted if the resource will be created asynchronously.
If you want a really nice API design, you can return a header named Content-Location which will point the client to the location of the newly created resource.
This creates a very nice decoupling inside your code, because retrieving the resource becomes a matter of reading this header, rather than hard-coding a specific end-point.
This, obviously, won’t work for batch operations.
While you can use another header, Link, this will quickly get ugly if you have more than a few resources.
You can return a Content-Location header that points to the collection, instead of an individual entity.
However, I think it’s better to not return anything.
But what about the subsequent GET request?
Well, for starters, if you rely on optimistic update (and you should), your UI already reflects the change.
There is no need to fetch the created resource.
In general, GET on collection should be performed only once—when the user opens the list of tasks/todos/whatever.
GET on a particular resource, should be performed when the user opens the page of that particular resource.
In all other cases, you better rely on optimistic update, especially if you have mobile clients with offline capabilities.
And if you really have to fetch the resource, just issue the GET request.
It’s not that big of deal.
That’s it.
That’s all I wanted to share with you today.
Let me know what do you think about this approach.
I’m available via Email, Twitter, Mastodon, and (for the brave corporate folks among you) LinkedIn.