If you glance at most of Dream's examples that serve an interactive client, you will see that they serve JavaScript:
https://github.com/aantron/dream/tree/master/example/k-webso...
You can use anything you want for the client, and I use the JS ecosystem heavily in my own usage.
There are several full-stack OCaml examples, but that's entirely optional:
https://github.com/aantron/dream/tree/master/example#full-st...
As for server-side comparisons, OCaml is much easier for me to work with than, for example, TypeScript for many reasons. The first is that I can compile several hundred OCaml files, in the rare case of a full rebuild, in less time than running tsc incrementally on a small project, resulting in a much better iteration experience.
Underneath, Typescript has a goopier type system than OCaml. Formally speaking. Therefore Typescript ends up being less consistent and a less efficient tool for thought ... for me personally.
That's the part underneath. On the surface we can use any syntax we care to.
The Rescript language, formerly known as ReasonML, is a JavaScript-style syntax frontend for the OCaml compiler, plus a backend that compiles to quite readable and fast JavaScript: https://rescript-lang.org/
The end result is a very readable language that feels comfortable. We can quickly write robust programs in it. (There's more inherent robustness than in TypeScript!) It also interoperates very elegantly with the JS ecosystem and runs on JavaScript runtimes.
OCaml code and ReScript language can be transpiled to each other, so depending on how Dream talks to the external world – i.e. what's the shape of the system call interface at the end of Dream's world? - then maybe Dream the OCaml web framework can be used as a JavaScript web framework and run on Node.js. Which is interesting and I want to understand now that I know to ask the question – thanks!
There is already work underway to port Dream to Mirage, to run in unikernels: https://github.com/aantron/dream/pull/22
Lwt, Dream's promise library, is itself getting ported to run on top of libuv: https://github.com/ocsigen/lwt/issues/813
libuv is, of course, the I/O library that powers Node, so it might be practical to run Dream as a native node module very soon after doing this.
(As an aside, I'm supposed to work on that libuv project, but instead I've been working on Dream :P)