3 ms·
How do YOU do form validations? I use some server-side library to do it (take your pick for your language/framework of choice) which then sends it back to the
by nauticacom 5y ago
How do YOU do form validations?
I use some server-side library to do it (take your pick for your language/framework of choice) which then sends it back to the user, in most cases automatically wiring up old field values and error messages. Checking string lengths or whatever is equally easy in both cases, but what's really difficult is the part you conveniently hand-wave away:
usually, it's harder / more awkward to wire the API feedback to highlight the offending Form fields
This is the entire point of validations. When I render everything on the server, I have my entire application and its associated logic to play with; can this Person access that Document? Has this Person already enabled Notifications for new Revisions on this Document? etc. Most server-side libraries make it equally easy to surface validation errors for those questions as they do questions like "Is this Password no shorter than 10 characters." Every time I've tried to do this on the client there's been no easy way or you have to wire it up manually every time.
There is no difference in how an HTMX app would authenticate to its endpoints and how an SPA would
I've found this not to be the case. Here are the potential options I see:
- The client contains all the logic for who can view what, and redirects or shows a message appropriately using whatever mechanism is appropriate (e.g. Angular has route guards, I'm sure other routers have similar functionality). Because you want real data protection, this logic is also on the server, so now you have duplicated logic and you have to hit the server multiple times on every navigation, which is basically impractical.
- The server contains all the logic for who can view what, and when the client asks the server for some data, if it responds with 403 it can show a message or redirect or whatever. This is okay, but now you have so little information on what happened on the client side that it's a much worse experience. e.g. if I try to access a document that I don't have access to on the server-side, I can redirect_back and maybe show a pretty message that says "hey you can't access that, maybe click this link to ask for permission." If I just get a 403 from the server, all I can say is "uhh I don't know, you can't do whatever you just tried to do."
This is much easier if all your logic resides on the server. To check if a Person can access a Document, you just call person.owns?(document) or whatever. If they don't, you just redirect_to wherever, and since you haven't actually rendered anything yet it's all in a single HTTP request-response cycle.
Obviously, but you load the client version from the server at API request time
I'm not sure you understand my problem here; it seems like perhaps client-heavy people just aren't doing this anymore? Ember, Backbone, et al got this part right, and everything since then seems to have forgotten about the "M" part of MVC.
What I mean is that on the server I have a Document class, a Person class, etc. and the associated business logic for dealing with it. Maybe a document has a bunch of Revisions, and they're stored in an array or something and I can get the newest revision by calling document.latest_revision, or I can get properties of the latest revision by just doing document.latest.title. Maybe when I update a field on a Document I really want to create a new Revision or something. Whatever, I'm just making stuff up here, but you get the kind of business logic I'm talking about.
How do I do any of this on the client? Do I have a separate Document class that maintains all the same logic? Do I split the logic so some of it is on the client and some is on the server? Do I put it all on the client...oh but wait I can't do that because the server needs to do integrity checks or make database calls that the client simply can't do.
In an ideal world, what I want is essentially like my ORM on the server-side: a cache of every instance of every model that I've loaded from the server, normalized with references to one another and an easy way to retrieve an object with its relationships either from memory or from the database (network/API). It's then easy to update Document(id=45) in one place and have it propagate to the rest of the app, and if it's referenced by any related objects to have the update propagate to them, too. This is a hell of a lot of work to create, and basically none of the modern frameworks provide anything resembling this.
---
Your final few paragraphs are essentially all the rosy writing out there about client-heavy approaches: look how wonderful it is! Just fetch your data and we'll rerender it! I understand all that. And maybe it's evidence that we're just living in such different worlds that there's difficulty explaining requirements to one another.
If I was creating Desmos or something, where basically every ounce of logic exists on the client, and the client's job is just to do a bunch of stuff with client-generated data and display it, then yeah I'd probably reach for a client-centric approach. But I'm not. Most of the time I'm creating GitHub or Wikipedia or Fastmail or something, where there could be some nice benefits of having a fat-client that talks to an API, but the massive complexity increase just isn't worth it.
- nawgz 5y agousually, it's harder / more awkward to wire the API feedback to highlight the offending Form fields > This is the entire point of validations Now, let's be explicit - I can effortlessly show an error toast describing those errors, and my client-side validations are generally meant to be a 1:1 match. I also say "awkward" rather than "not possible" because you can directly access the validation engine in my paradigm and set error messages for individual fields, but it requires the API endpoint author to be returning a data structure and not 400 'Requires some other thing' style. And now I see what you are saying about doing this server-side, but the whole point of the client-side method is that for free you get this: > in most cases automatically wiring up old field values I get it in 100% of cases, for arbitrarily complex form elements. I also greatly object to this point: > Most server-side libraries make it equally easy to surface validation errors If I am using arbitrarily heavy code of course I wire these error messages perfectly. I will not concede any points about time-to-execute going forwards, I see it serves only to muddy the waters as you have strong capabilities There is no difference in how an HTMX app would authenticate to its endpoints and how an SPA would > I've found this not to be the case I don't really follow your objections / cases. You argue that the server is always aware of who can see what, and this is transparently true, but you try to argue that the client dynamically loading who can see what from the server is a problem due to network round-trips yet the server-rendered app where there's network round trips for literally every user action isn't a problem. That is where my uncharitable tone is sourced from; you can dynamically return this stuff to your client with ease, and all you've done in an HTMX-style paradigm is force the user to accept the "bad case" outcome for client-queries-server nav in the best case. You will have to go deeper if I am meant to see why the client couldn't just load some batched map of legal routes they can and can't see based on their authorization and update any nav options around the app (sidebar, page-to-page, etc.) accordingly. I regularly load configuration batches to configure my client-side applications, and you clearly execute extremely similar code to bridge the UI layer and ACL layer on your server. Moving this to the client is entirely unoffensive and standard web development work To be precise, your example / motivation is something like: > This is much easier if all your logic resides on the server. To check if a Person can access a Document In order for the user to have a copy of this document to click on at all, I have made a network request to get some data about this document. If ACLs are important for this interaction, I load them at the time I load the document's data. For example, I have "view" vs "edit" functionality in some collaborative modeling app I've built, which is based on whether the user has read-only or write permissions to the model. To nest this functionality to gate parts of the model graph would be trivial, once the server implementation was complete. Thus I struggle to understand the difference between the server/client paradigm wrt this topic. Obviously, but you load the client version from the server at API request time > I'm not sure you understand my problem here Again, I struggle to understand what the difference is between the client & server paradigms here. > what I mean is that on the server I have a Document class, a Person class, etc. and the associated business logic for dealing with it. Maybe a document has a bunch of Revisions Any relational data (and as discussed, associated ACLs) is transparent to deal with on the UI; the model is the data, and any exposed action gets a method that you will write to suffice its needs. If the data is somehow non-relational or requires something awkward that your server paradigm magically solves, I simply load it from an API your server exposes which can describe these things to me as it did to you and then I have those same capabilities. Have I omitted something in this case with this claim from your view? > How do I do any of this on the client? Do I have a separate Document class that maintains all the same logic? Do I split the logic so some of it is on the client and some is on the server? Whatever you like. Why would I prescribe this to you? I don't prescribe how you've done it on the server. I personally create MobX stores to hold the data & relations (generally implemented using maps sending instance IDs to instances), and MobX classes at the "semantic layer" (so they might include a small part of the relational graph) and instantiate them with their instance data. As I discussed, I prefer the singleton pattern, so I do not keep multiple copies of the relational graph ever. MobX is nice because it ties the view layer updates directly to data updates, preventing unnecessary renders (fast), but the key is of course to integrate these objects with a stateful layer that will correctly trigger re-renders. When it comes to observations like "the server needs to do integrity checks or make database calls that the client simply can't do" then it is clear you use an API at this point. While I understand you possibly have this "API call" for free as some server-sided library at the logic time of executing the action, the ease is only for you as the developer and the end-user will see identical performance between a client-side and server-rendered app in this case in terms of action completion time. In my described MobX world, things which are client-side manipulations are implemented as methods on the client-side, and of course if it is something that will require multiple server-side actions I just offload it to an API so you fall back gracefully into however you would already have done this. Again, if I am omitting something somehow please say it, but I find it immediately clear from what you describe a trivial solution to client-siding your code would be: the exact times to make an API call are when your server code uses your nice server-library-provided capabilities, and the times it doesn't can be implemented as a client-side method. > In an ideal world, what I want is essentially like my ORM on the server-side: a cache of every instance of every model that I've loaded from the server, normalized with references to one another and an easy way to retrieve an object with its relationships either from memory or from the database (network/API). It's then easy to update Document(id=45) in one place and have it propagate to the rest of the app, and if it's referenced by any related objects to have the update propagate to them, too Yes, this is what I accomplish with MobX stores & classes. I have 1 copy of everything, I keep the lists of relationships on the class or it's directly query-able thru my stores (& their maps), and anything which "walks the graph" to render some related object of course actually loads the related object's class instance in order to not be stale. > This is a hell of a lot of work to create, and basically none of the modern frameworks provide anything resembling this. You see why my tone was uncharitable then, perhaps. > Your final few paragraphs I appreciate your in-depth response, I truly do, and I hope this conversation helps you understand why I hold my view and perhaps find yours difficult to understand. Any server-side-magic you use, which I truly recognize exists, is the place where API calls happen in my clients. It's really that simple, and the net result is way less or way better disguised network round trips than HTMX from the user perspective. > Most of the time I'm creating GitHub or Wikipedia or Fastmail or something Most of the time I'm creating what I've come to hear referred to as "dataflow editors", so I would literally have people murdering me if I had to introduce network delays to every action and could never let their happy path be lag-free. This is why I vehemently loathe HTMX and it's overbaked claims, it's a reduced user experience for anything with moderate complexity IMO. If it would be valuable for you to see what I mean, I'm happy to create you a small React client with MobX stores & classes so you might start to play with these things. It is certainly a high-complexity process to create strong client capabilities like you and I discuss and describe, but now that I have them the act of making a new app is very much not complex, not any more than creating your servers is given that you have your libraries & capabilities of choice.