That's for one-offs and debugging connections, though. If you're shipping a product, then suddenly the economics change and the software-heavy solutions start to look good again.
1. Trust your vendor's libraries/ROM drivers/example code. This is the fastest to bring up, the least flexible, the buggiest (it's vendor code!) and the hardest to debug. (Trust me on the debugging part.)
2. Use a library like tinyUSB. This can actually go pretty quickly, since you've got full source control. If your part is popular enough that someone else has already done most of the work, this is by far the best choice. Even if you have to get personal with the hardware, I still like this option. (tinyUSB is much better than what came before... I wish it'd been around last time I had to do this.)
3. Write your own. Don't do this. (You might have to do parts of this if you tried #1 and it isn't panning out.)
All of these are doable. Any of them are enough work that if you're only making one, just put the FTDI down and call it done.
Lots of ARM chips come with USB DFU mode firmware baked in ROM, those don’t need one.