[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)