One extra tip, which I have found useful, is to make the deleted column a time data type (just like created and updated), but nullable. That way, your Boolean check just needs to change to an IS NULL check, but you get the additional 'when' information without using an extra column.
And then I wonder if I should track updates too. There are auditing solutions to record all that, but the ones I know are (rightly) not really designed for building application logic on top of.
The idea of a relational schema having some kind of temporal dimension letting you get at changes is something that's been on my mind a lot lately.
Yes, a big problem with table-level audits is that you lose all kinds of information about the other entities in the system. Sure, now you have an audit log of when a row was changed, but you don't really know anything about the state of all the other pieces of the database at that time, so you can't really usefully reconstruct what the entity looked like at the time it was modified.
In theory you could parse through the whole audit log to reconstruct the state of the DB but in practice it gets very complicated.
This is one place where document stores really shine as you generally keep everything in a single place. When you update a document you don't have to worry about the values of all the foreign keys, you just save the current version which contains all your values.
You may want to check out Datomic. It uses an immutable, time-based model that covers deleted_at and many more scenarios (e.g., it's effortless to ask, "what was the state of this object last month?" without the need for looking at old backups.)
It's really too bad - if they offered paid support and otherwise-sane licensing, I'd be all over it.
If you have 2 tables, A and B, where B references A Ideally I couldn't set deleted on row in A until all the rows in B that reference it have also been set to deleted.
If I wasn't using soft deletes, I'd just use a foreign key from B (a_id) to A (a_id), but with soft-deletion, that constraint doesn't get enforced.
This problem is pervasive, and it's not easy to solve.