Before I was "enlightened", when I wanted to include JS or CSS in my regular ol' web app (built with Laravel), I would just include it in a
link tag in my HTML. Pretty simple. And, I was perfectly content doing this. I even used Gulp a little bit, mostly for minifying my own JS and compiling Sass stylesheets. But, it was becoming clear that there was this huge ecosystem of things happening in JS-land and I was feeling at risk of falling behind if I didn't at least try to learn some of it.
So, I incorporated some Vue into my existing project, without Webpack or build tools of any kind. I really enjoyed how Vue handled reactivity and DOM updates. Compared to jQuery, it was like magic. I was able to throw away so much spaghetti jQuery on a page and basically create a full-blown mini-app with just a simple Vue instance.
After that great experience, I started looking into how Vue is used out in the wild. While my particular use-case as a simple add-in to a traditional server-rendered web app was actually pretty common, it became evident that Vue is really built with single-page apps in mind. This led me down a rabbit hole of Webpack, Node, single-page app architecture, NPM, new authentication schemes, new data-fetching schemes (REST, GraphQL)…
Authentication is actually not very straightforward to implement, if I truly separate my API from the user-facing app. I have to be very careful how I store and use auth tokens (such as with OAuth and JWT), and I have to be conscious of more threats than with session-based backends protected by CSRF. In other words, there are a lot of ways to mess it up.
With a single-page app, usually it would fetch data from an API, or multiple APIs. However, I have to be careful to only grab the data I need, when I need it. I would often grab too much and have more data than I need, or, I would grab too little and have to make multiple, inefficient requests. Solutions like GraphQL are meant to address this, but it adds another layer of concern to both the front and back-end.
While this can be very beneficial if I want to offer a suite of end-user products (web, mobile, desktop, toaster, etc.), it does make it more expensive early in the project to basically have to build a minimum of two products in order to get to release. This makes distribution a bit more complicated, and can be a bottleneck early on when I really want to be moving as fast as possible.
I've only recently really embraced test-driven development when building my own web apps, but now I have to write tests for two apps (single-page app, API) instead of just one. End-to-end tests are easy on the API, but I have to always make sure the test mocks on the front-end match my API, assuming I want to test the SPA and API independently. A change in one of the apps usually means I have to change the other one as well.
With a "traditional" web app, all the unit and end-to-end tests can be included in the same test suite.
While I did learn a ton creating an API/single-page app architecture (and likely will again in the near future), I've decided to go back to doing things "old school", but with some "new school" sprinkled in for my current project. I'm doing a V3 rewrite as a Laravel app again, without an API. You could say I've embraced the Majestic Monolith.
However, the templates for the app will actually be a single-page app thanks to InertiaJS, an innovative library that allows me to write my templates in Vue, React, or Svelte without having to use an API. I'm literally getting the best of both worlds; the stability and simplicity of a traditional Laravel app, combined with the cutting-edge, interactive UI of a single-page app. Perhaps I'll write more in-depth about Inertia in the future, but in the meantime, you should check it out!