I get what you are trying to say, but: not really. Something like cowboy is an Erlang "application", and cowboy most assuredly does not crash on one bad request: it has a try/catch in order to deal with errors without tearing everything down.
Somewhere in an Erlang system, there has to be a judicious use of try/catch in order to keep things running when some kind of persistent error occurs.
You are technically correct and wrong in the same time.
Cowboy is an application, but cowboy is not the "web server". It is an application that spawns and supervises multiple "web servers". Every one of them can crash, without bringing cowboy down, because that's the way its supervisor is configured. It's called simple_one_for_one strategy.
And no, it is not mandatory to have "try/catch". Well, if you mean in the general sense, yes of course. But that is done by OTP or cowboy (I'm not sure about cowboy). What is significant though is that the programmer does not have to deal with that at all. There is a huge difference in the way you write Erlang code and PHP code. And that's the whole point of jlouis' article.
We try to not have "persistent errors". Like things that crash thousands of times. We usually crash only in processes that don't live forever. In fact we try to have most our processes as disposable.
Also, what he refers to as "kernel" is a bit hard to explain to non-erlangers. It is usually either a library or a process/server that is strictly deterministic and testable. So we don't expect to have bugs/crashes there and we almost never have. If we cannot make it this way, then we have several communicating kernels, that are again deterministic per se.
We try to have the "crashing parts" in non-persistent processes. Like the http handlers. That is usually the huge part of the code and there we can safely "let it crash". But for example we don't want to have shaky code in the parts dealing with the database. So we have something like:
1. db communicators. Can't crash. Very little code without bugs. This is one kernel
2. data converters. Usually deterministic with unit tests. Does not crash but is written in "intentional style" as jlouis calls it. If this crashes, then we have a bigger problem. We should fix the bug
3. Data dispatchers. For example, per user database lock (to a non ACID data store in our case). This is a process that does not crash (because of problems in its code). A second kernel
4. http handlers. Can crash and should crash, especially on unexpected events, for example - corrupted user input. Written in very aggressive intentional style
So what we usually do is in 4. we make sure to crash if something unexpected happens. Then 4. sends a message to 3. like "execute function A (part of 2.) with user's data, here are the arguments, we are pretty sure they are correct". 3. retrieves user data from 1., applies it (with arguments) to function A of 2., saves the result and returns a reply to 4.
Currently we don't shield 3. from errors in 2. If we have a bug in 2. we want to know about it. And especially we don't want 3. to write corrupted data, which is very possible if we catch errors from 2. So if something unexpected happens in 2., data dispatcher (3.) will crash, we see the logs and fix the bug.
And please note. All this is (except for 1.) one user only! Even if we have some persistent problem with that user, others are usually not affected. We fix the user's account and write some edge case code to fix the bug.
Cowboy uses try/catch because even with supervisors, if the child crashes enough, the supervisor will eventually crash too! LYSE talks about using try catch in the error kernel.
> db communicators. Can't crash. Very little code without bugs. This is one kernel
DB connections most certainly can go down. I do know a thing or two about that:
Somewhere in an Erlang system, there has to be a judicious use of try/catch in order to keep things running when some kind of persistent error occurs.