Good question, and one that could use some discussion (e.g. in blog posts). Perhaps I'll write one.
I think some redundancy is a practical necessity, regardless of your approach (using database constraints or only application code). Consider registering a unique username: even if you check for availability beforehand, you still need to handle the race condition. That's guaranteed to be a somewhat awkward user interaction because they thought it was available but you found out that it was taken by a concurrent user.
So, your application always needs to have a nice way to avoid errors when they can be caught early (e.g. javascript check while filling out the form), and a less-nice way of handling errors when they can't (e.g. "sorry, that name has already been registered" after submitting the form).
There are SQL-standard codes for many constraint errors, so those can be turned into exceptions.
For things like CHECK constraints, think of those more like an assert: intentionally redundant.