What would be the proper way to handle a undelete request? Would it be a PATCH request that unsets the deleted_at timestamp?
This would be for models with soft deletes.
Hi! I think this is totally down to your application's logic - unsetting the deleted at timestamp via a patch request would definitely be one way of doing it.
I haven't implemented any generic way of doing this because it is very application-specific. For example, we have a production application where models are soft-deleted, but the client never knows this - i.e. when the client deletes a resource, the model is soft-deleted and never returned to the client again. The logic here would differ from an application where a client is allowed to both soft-delete and remove resources.
If there is a demand for a feature that supports handling soft-deleted models, I'd be open to adding it in. Generally my approach would be for a soft-delete to be done via the PATCH method (i.e. setting/unsetting the deleted timestamps via a PATCH request would toggle a resource's soft-deleted status, or using a pseudo property such as "archived") and then a DELETE request would hard delete a resource. To me this is the most semantic way to do it, because DELETE means DELETE - the resource should not exist (from the client's perspective) if the client has asked for it to be deleted. However my only concern with adding something in is this is certainly straying into application-specific logic.
In EloquentController::destroy(), we call $model->delete().
This can trigger either a soft or hard delete. In both cases, we return 204.
If it was a soft delete, should we return 202 instead?
https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7
A successful response SHOULD be 200 (OK) if the response includes an entity describing the status, 202 (Accepted) if the action has not yet been enacted, or 204 (No Content) if the action has been enacted but the response does not include an entity.
After all, a soft delete means that we've accepted the deletion request, but it is not yet (completely) enacted?
Hey there.. quite an old issue, but i will provide some thoughts on this one..
From my perspective, you should always use DELETE /posts/1 in order to delete a model - it does not matter (from the client perspective) if this model is _really_ deleted or just _soft_-deleted. Actually, it is best, if the client does not know. However, PATCHing the resource in order to "delete" it, feels kind of weird from a RESTful perspective.. At least that is my opinion...
Restoring a model, however, is tricky with just the regular HTTP verbs (GET / POST / PUT / PATCH / DELETE). In this case, i would argue for a PATCH request - but that is just my personal opinion..
In another application we added a DELETE /posts/1?force=true query param in order to apply a forced delete (i.e., no soft-delete). However, this query param is removed via a middleware from "normal" (non-admin) users..
Regarding the 202 / 204 discussion:
I would always argue, that a DELETE should return 204 (no content) - becaues it actually does not return a content - and the action has been executed. 202 (accepted) should be used for "long-running actions", e.g., preparing a web-service call to a 3rd-party service or complex calculations. In this case you may provide a location header to the client where you can add a custom URI.. This URI, in turn, may provide information regarding the progress of the execution or provide actual data (once the calculation is finished).
Hope this helps ;)
Yeah, great input.
From my perspective, you should always use DELETE /posts/1 in order to delete a model - it does not matter (from the client perspective) if this model is really deleted or just soft-deleted.
This is the key problem from my perspective. For me the thing that makes most sense is that if you do DELETE /posts/1 then subsequent calls to either GET /posts/1 or PATCH /posts/1 should return a 404. I.e. the resource from the client's perspective is deleted, even if that's actually soft-deleted in the database.
So if you are using soft-delete to allow a client to archive/restore resources, then DELETE should mean hard-delete and the soft deletion (archive/restore) should probably happen through the PATCH.
But if you you are using soft-delete to remove the resource from your API, the client cannot restore it, and it will never appear in the API again (e.g. in a relationship), then DELETE should do a soft-delete not a hard-delete.
This is what I mean by it depends on your application logic. In theory if we could find a neat way of doing it, then it would be good for the package to provide both approaches. The main thing to work out is how we would do allowing the client to do an archive/restore kind of operation.
Maybe the place to start is see if there's any APIs out there that do offer an archive/restore type function and see how they've implemented it from the client's perspective?
Hey @lindyhopchris , thank you for your detailled thoughts on this issue here..
In an API i worked at, we decided to have different "API realms". Let me explain briefly what i mean by this - consider the following example:
A Manager may create (digital) Products (e.g., e-books) that are sold within an online store. However, he may also delete those products (i.e., if they are outdated). However, Users that have already bought those Products still have access to them. Finally, there are Admins that may restore some of the deleted Products, if necessary.
Obviously, everything i explained directly deals with the Product resource, so - to fully comply with JSON:API you should use the /products route. However, there needs to be different "realms" (depending on the User Role (Customer, Manager, Admin) that expose / hide certain routes and provide different functionality.
We decided to create different route segments depending on the role, like this:
/api/v1/shop/customer/* -> everything the customer has access to
/api/v1/shop/manager/* -> everything the manager has access to
/api/v1/shop/admin/* -> everything the admin has access to
For example, the manager is allowed to call DELETE /api/v1/shop/manager/products/1234; in our use-case, as described above, this has to be a soft-delete, as customers still need to access the Product, if they purchased it.
So the route GET /api/v1/shop/customers/purchases returned a list of all purchased products withTrashed() to include the soft-deleted models as well!
The admin panel, in turn, would also return all (including the soft-deleted models) when calling GET /api/v1/shop/admin/products because he may need to "restore" deleted products..
What do you think of this approach?
The problem i have with this approach is, that - technically - it is not JSON:API compliant (at least i think so), as the standard does not tell you, how to deal with such URIs.. Right?
All the best,
Johannes
Yeah, that sounds similar to how I've split a production application into two APIs - one for general users, and one for admins.
The problem with all of this is it's not really for this package to provide that solution as it's so tied to the logic of a specific application.
I'll keep thinking about this but at most I think this package needs to provide options or guidance on different options, rather than a default solution.
what do you mean exactly with "_guidance on different options_"? Are we talking about the different "API Realms"?
How did you manage to solve this issue? Did you develop 2 separate APIs (applications) that are hosted on 2 different domains (or folders within the same domain)? Or was it one "large" application that had different route-groups?
All the best
Sorry, I mean guidance on different options for handling soft-delete.
In our solution we have one API at /data/users/* and one at /data/admins/*. That's one of the main reasons this package supports having multiple APIs. We built it as two separate APIs because it wasn't just the logic of access rights and what a client could do that was different between the two, but also schema for the resources in each API varied. I.e. the resources served in the admin API had more attributes and relationships in them exposing the secret information.
So effectively if their was a resource called posts a public version of it was accessible at /data/users/posts/1 whereas a secret version of it was accessible at /data/admins/posts/1.
It's not the only way of architecting a solution but that worked best for our use case.
Dear @lindyhopchris ,
yeah, that was my initial idea, that i may provide different API configurations, depending on the realm. So i may have one json-api-user and a json-api-manager and a json-api-admin configuration file each with different endpoints, right?!
All the best
This is potentially becoming an issue that I'm going to get a lot more questions about, so need to implement something for 1.0.
Basically we need:
Optionally we could provide something on the adapter to restore a resource in a PATCH request.
Fyi I've put how we're planning to implement this in #250
Most helpful comment
This is potentially becoming an issue that I'm going to get a lot more questions about, so need to implement something for 1.0.
Basically we need:
Optionally we could provide something on the adapter to restore a resource in a
PATCHrequest.