[HN Gopher] Making a newsletter backend
       ___________________________________________________________________
        
       Making a newsletter backend
        
       Author : codyd51
       Score  : 35 points
       Date   : 2023-09-09 08:12 UTC (1 days ago)
        
 (HTM) web link (axleos.com)
 (TXT) w3m dump (axleos.com)
        
       | chrismorgan wrote:
       | > _Some JavaScript on this site that'll ask the backend to sign
       | up a new user._
       | 
       | This makes me sad. The browser has perfectly good form submission
       | stuff that doesn't need JavaScript, and there's seldom any actual
       | reason to _require_ JavaScript. Yet I find it increasingly common
       | that web developers, despite perhaps (hopefully!) still using
       | <form> and <input type=submit>, are somehow unaware of this and
       | assume JavaScript _must_ be used, and use JSON encoding, and trip
       | up over CORs, and... and... Whereas in fact, you only need
       | JavaScript to support seamless content changes based on the form
       | submission (providing feedback or whatever without loading a new
       | version of the page with just that alteration), and it's normally
       | perfectly reasonable to support normal form submission with just
       | a slight enhancement if JavaScript runs.
       | 
       | Here's an alteration of the newsletter form, which assumes that
       | /subscribe is a regular HTTP form submission endpoint, accepting
       | application/x-www-form-urlencoded form data and returning a basic
       | message as an HTML fragment (or even plain text):
       | <div class="hover-div blog-post-container pastel-depth-4 lift-on-
       | hover" id="newsletter_signup" style="border-radius: 14px;">
       | <h3 style="margin-top: 6px">Newsletter</h3>       <form
       | id="sign_up_for_newsletter_form" method="post"
       | action="https://axleos-blog-
       | newsletter.ew.r.appspot.com/subscribe" target="_blank">
       | [... seven unchanged lines omitted for brevity ...]       </form>
       | <script>
       | sign_up_for_newsletter_form.addEventListener("submit", async
       | function(event) {               event.preventDefault();
       | let response = await fetch(this.action, {
       | method: "POST",                   body: new URLSearchParams(new
       | FormData(this)),               });
       | newsletter_signup.innerHTML =                   response.ok
       | ? "<p>Signup successful!</p>"                       : `<p>Signup
       | failed: ${response.statusText} / ${await response.text()}<p>`;
       | });       </script>
       | 
       | A few notes:
       | 
       | * I've added the method, action and target attributes to the
       | <form> element. The consequence of this is that regular form
       | submission, if the JavaScript isn't run for whatever reason, will
       | submit the data to that URL in a new window, the idea being that
       | the user just gets a new page containing "signup successful" and
       | nothing else. (By contrast, on the current page, submitting the
       | form will submit to the current URL with ?email=... added,
       | appearing to the user as though the page has just reloaded, and
       | it won't be clear whether or not it worked.)
       | 
       | * I've shifted the <script> element from before the form to after
       | it, so that the form exists when the script is run, so that it
       | can do its thing immediately rather than being deferred until the
       | page finishes loading by wrapping it in
       | $(document).ready(function(){...}) (or
       | addEventListener("DOMContentLoaded", function(){...}) would be
       | non-jQuery equivalent, so long as it's still an inline script).
       | This is very desirable if the scripts are inline anyway, and
       | quantities like this should basically always be inline for the
       | best performance (seriously, inlining all your JS and CSS yields
       | the best performance until surprisingly large sizes--far past 100
       | KB in practically all deployment and access scenarios, _even with
       | a warm cache_ ). You only want ready dances if you're
       | deliberately loading the script asynchronously (which I will also
       | defend as not unreasonable for something like this: the document
       | is fully interactive without it, it can run later or even not at
       | all and cause no problem).
       | 
       | * I've removed jQuery usage because it's simply not necessary
       | ($("#...") - document.getElementById("..."), .on() -
       | .addEventListener(), $.ajax() - fetch()); all the native
       | alternatives have excellent browser support. I've also used
       | native async/await because it makes things a tad nicer. (It's not
       | a compatibility hazard, because ancient browsers will just call
       | it a syntax error and not execute the script, and thus get the
       | no-JS regular form submission behaviour.)
       | 
       | * I dislike this use of innerHTML with the response text; a
       | little too fragile if you try sending plain text but it ends up
       | including HTML syntax, or if you can get things like unexpected
       | server error HTML from Google App Engine or gunicorn or whatever
       | and now you're dropping a whole <html>...</html> in there with
       | unknown contents. But I'm matching the original.
       | 
       | * I've written just sign_up_for_newsletter_form and
       | newsletter_signup with no document.getElementById("...") wrapping
       | just for the fun of it, showing off my favourite HTML+JavaScript
       | golfing technique. It's a matter of very ancient history, but
       | part of the spec <https://html.spec.whatwg.org/multipage/nav-
       | history-apis.html...> and it works everywhere. Decide for
       | yourself whether it's wise. (Personally I think the concerns
       | about it _in cases like these_ are somewhat overblown, but at the
       | very least it's certainly a technique that should not be scaled.)
       | 
       | * The `new URLSearchParams(...)` wrapping is to get
       | application/x-www-form-urlencoded, for consistency with normal
       | form submission. Just `body: new FormData(this)` uses
       | multipart/form-data encoding, which regular form submission will
       | only use if you're uploading files via <input type="file">. A
       | little janky, in my opinion, being different from normal form
       | submission, even if simply splitting on type makes a deal of
       | sense. Anyway, all serious HTTP server libraries will support
       | both encodings of form data, so you aren't likely to _need_ it,
       | but I figured I'd make it consistent anyway, because otherwise
       | experience says mistakes get made. The goal is to minimise the
       | differences between the no-JS and JS pathways, to minimise the
       | chances of the no-JS way accidentally breaking.
       | 
       | * I replaced the button click handler with a form submit handler.
       | Personal preference, to a considerable extent, with no actual
       | practical difference since the button is an <input
       | type="submit">. (Pressing Enter from the email field will trigger
       | clicking on the first submit button:
       | <https://html.spec.whatwg.org/multipage/form-control-
       | infrastr...>.)
       | 
       | * I hold out hope that _eventually_ we might get some kind of
       | auto-resizing iframe (one starting point on the topic:
       | <https://github.com/whatwg/html/issues/555>), so that you could
       | put a response iframe below the form and make it the target. This
       | exposes another approach: put the form inside an iframe, and then
       | just use normal submission entirely. The only problem (and it's
       | not a tiny problem, unfortunately) is this matter of sizing the
       | iframe.
       | 
       | --***--
       | 
       | I would also suggest adding a simple honeypot field to the form:
       | https://news.ycombinator.com/item?id=37058847
        
         | codyd51 wrote:
         | Really interesting and actionable, thanks for the information
         | and for the example code!
         | 
         | I don't have any web development expertise, and it's not where
         | my interests lie, so I've been following a "google it and move
         | on as quickly as possible" approach. That said, since I'm now
         | making web tools, I do have an intellectual responsibility to
         | learn the platform. I reached for jQuery just because I had a
         | vague sense that it was a "web development thing", and didn't
         | bother to learn the fundamentals of what exactly it was
         | bridging, and whether those bridges were still relevant in
         | today's browser landscape. I appreciate the callout, and will
         | use it as an opportunity to learn more about the web platform.
         | 
         | Like you realised, I thought JavaScript was appropriate because
         | I wanted to ensure that the user wouldn't be navigated to a new
         | page when they submitted the form. I appreciate the quite tidy
         | approach you've shared that achieves this when JS is available,
         | and still does something reasonable when it's not!
        
           | meheleventyone wrote:
           | You might like HTMX which essentially does the above with
           | some declarative tags and a small JS library. It's
           | surprisingly powerful.
        
         | [deleted]
        
       | caesil wrote:
       | Anyone know of any good cheap options for making a newsletter
       | _frontend_ that looks nearly professional-media quality? The
       | closest thing I can find is Ghost and Wordpress templates that
       | are nearly $100 to license.
        
         | joshmn wrote:
         | [dead]
        
         | jskherman wrote:
         | Maybe some of the themes on https://jamstackthemes.dev/ might
         | work for you?
        
         | rsingel wrote:
         | Try Ghost's free headline theme, or if you want something
         | really good splurge for a Ghost theme from Bright Themes.
        
       | singhrac wrote:
       | I saw Google App Engine when skimming and had to check the date.
       | The last time I launched a GAE project was in 2012.
        
         | codyd51 wrote:
         | Hello, author here! I used GCP as it was familiar and quick to
         | set up on that basis -- we use it as our cloud provider at my
         | day job -- but I've got no special allegiance otherwise. I'm
         | curious what you would've expected in its place? A
         | corresponding AWS offering? A more 'bare' hosted VM from a
         | commodity provider?
        
           | condensedtea wrote:
           | [dead]
        
           | orbz wrote:
           | Hey if it works, it works. GAE still has a generous free
           | tier. Most new stuff I've seen has been migrating to Cloud
           | Run for more configuration and options.
        
       | rsingel wrote:
       | If you want the same thing with more controls and less
       | building/maintenance, you can run Ghost on Digital Ocean for $5 a
       | month
        
         | petercooper wrote:
         | https://sendy.co/ is also a fantastic option in this area. It
         | wasn't quite for us in the end (we have quite complex needs)
         | but I gave it a good workout and it's pretty neat.
        
           | vladsanchez wrote:
           | Great! Thanks for sharing
        
       ___________________________________________________________________
       (page generated 2023-09-10 23:01 UTC)