<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Juha</title>
    <description>The latest articles on Forem by Juha (@vallu).</description>
    <link>https://forem.com/vallu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1202983%2F84b6305f-39f7-4d3e-8570-97452522c116.png</url>
      <title>Forem: Juha</title>
      <link>https://forem.com/vallu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/vallu"/>
    <language>en</language>
    <item>
      <title>pSEO - Programmatic SEO Quick Intro with Examples</title>
      <dc:creator>Juha</dc:creator>
      <pubDate>Fri, 07 Jun 2024 09:00:57 +0000</pubDate>
      <link>https://forem.com/vallu/programmatic-seo-pseo-quick-start-examples-41n3</link>
      <guid>https://forem.com/vallu/programmatic-seo-pseo-quick-start-examples-41n3</guid>
      <description>&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; pSEO automates the creation of web pages at scale for specific keywords. It's useful for businesses targeting various locations or services. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Tyre shops in &lt;code&gt;&amp;lt;city&amp;gt;&lt;/code&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Stock price of &lt;code&gt;&amp;lt;company&amp;gt;&lt;/code&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Alternatives to &lt;code&gt;&amp;lt;product&amp;gt;&lt;/code&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a powerful tool, but there are risks. If done carelessly you can be flagged as spam.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is Programmatic SEO and Do I Need It?
&lt;/h1&gt;

&lt;p&gt;Programmatic SEO (pSEO) is a strategic approach that involves the automatic or semi-automatic generation of web pages to target specific keywords relevant to your business.&lt;/p&gt;

&lt;p&gt;This technique leverages data and automation to create numerous pages that are optimized for search engines, thus enhancing the chances of appearing in search results for a broad range of keywords.&lt;/p&gt;

&lt;p&gt;For example, you might have seen websites with individual pages for all cities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;barber shops barcelona&lt;/li&gt;
&lt;li&gt;barber shops madrid&lt;/li&gt;
&lt;li&gt;barber shops &lt;code&gt;&amp;lt;city_name&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The page template is identical between the hundreds of city pages, but dynamic parts have been programmed into them. That means parts of the pages change between cities but for the most part they are identical copies.&lt;/p&gt;

&lt;p&gt;So what's it good for and do you need it?&lt;/p&gt;

&lt;h3&gt;
  
  
  Why You Might Need Programmatic SEO:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scalability:&lt;/strong&gt; If your business covers a wide range of topics or products, manually creating pages for each keyword can be time-consuming and inefficient. pSEO allows for scalable content creation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Efficiency:&lt;/strong&gt; Automation reduces the workload on your content creation team, freeing them up to focus on more strategic tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitive Edge:&lt;/strong&gt; By covering more keywords, you can stay ahead of competitors who rely solely on manual SEO efforts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  How Does Programmatic SEO Get You More Visitors?
&lt;/h1&gt;

&lt;p&gt;Programmatic SEO boosts visitor numbers by systematically targeting a multitude of high-volume keywords. This is achieved through:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Broad Keyword Coverage:&lt;/strong&gt;&lt;br&gt;
pSEO enables you to cover a wide range of keywords that might be overlooked in manual processes. Each automatically generated page targets a specific keyword, e.g. all major cities relevant to your business.&lt;/p&gt;

&lt;p&gt;For example you can use pSEO to create individual city pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tyre shops helsinki &lt;em&gt;(/tyre-shops/helsinki)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;tyre shops stockholm &lt;em&gt;(/tyre-shops/stockholm)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;tyre shops oslo &lt;em&gt;(/tyre-shops/oslo)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This increases the likelihood of appearing in search results, as users are typically interested in city specific results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Efficient Use of Resources:&lt;/strong&gt;&lt;br&gt;
With pSEO, you can create thousands of pages in the time it would take to manually produce a handful. This efficiency translates to a more comprehensive online presence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Data-Driven Content:&lt;/strong&gt;&lt;br&gt;
By utilizing structured data, pSEO can help you fill each page with relevant and valuable information, enhancing user experience and boosting search engine rankings.&lt;/p&gt;

&lt;p&gt;Of course there are risks of appearing spammy if pSEO isn't done thoughtfully.&lt;/p&gt;

&lt;h1&gt;
  
  
  Example of a website heavily using pSEO
&lt;/h1&gt;

&lt;p&gt;Dictionaries and synonym databases are a type of website that understandably rely almost entirely on programmatic SEO.&lt;/p&gt;

&lt;p&gt;As their content is in an easily accessible format in a database, it can be pulled to create pages for each word.&lt;/p&gt;

&lt;p&gt;As an example take a look at &lt;em&gt;anotherwordfor.xyz&lt;/em&gt; which utilizes pSEO to generate pages like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://anotherwordfor.xyz/important"&gt;Another Word for Important&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://anotherwordfor.xyz/because"&gt;Another Word for Because&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://anotherwordfor.xyz/however"&gt;Another Word for However&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition to this, they also have manual SEO pages like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://anotherwordfor.xyz/adjectives-to-describe-person"&gt;Perfect adjectives to describe a person&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you look closely, you might identify parts even on manually created SEO pages that can be semi-automated. Have a look at the adjective lists on the above page.&lt;/p&gt;

&lt;h1&gt;
  
  
  How to Get Started with Programmatic SEO?
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;1. Keyword Research:&lt;/strong&gt;&lt;br&gt;
Identify high-volume, low-difficulty keywords relevant to your business. Tools like Ahrefs and SEMrush can help pinpoint these keywords.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Data Collection (super important!):&lt;/strong&gt;&lt;br&gt;
Gather data that can be used to populate your programmatically generated pages. This could include product details, reviews, user-generated content, or other structured data relevant to your industry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Template Creation:&lt;/strong&gt;&lt;br&gt;
Develop templates for your pages that can dynamically incorporate your collected data. Ensure these templates are SEO-friendly and provide a good user experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Automation Tools:&lt;/strong&gt;&lt;br&gt;
Use automation tools and platforms to generate the pages. Examples include Python scripts, pSEO plugins, or custom solutions tailored to your needs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Where to Find Keywords for Programmatic SEO?
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;1. Keyword Research Tools:&lt;/strong&gt;&lt;br&gt;
Utilize tools like Ahrefs and SEMrush to discover keywords with high search volume and low competition. These tools also offer insights into related keywords and search trends.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Google Autofill and Search Suggestions:&lt;/strong&gt;&lt;br&gt;
Google’s search bar suggestions and related searches at the bottom of search results pages are excellent sources for identifying commonly searched keywords.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Competitor Analysis:&lt;/strong&gt;&lt;br&gt;
Analyze the keywords your competitors are targeting. Tools like SEMrush and Ahrefs can provide detailed reports on competitor keywords, helping you identify gaps and opportunities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. SERP Analysis:&lt;/strong&gt;&lt;br&gt;
Review the search engine results pages (SERPs) for your target keywords to understand the type of content that ranks well. This can guide your content creation strategy.&lt;/p&gt;

&lt;h1&gt;
  
  
  How Much Does Programmatic SEO Increase Visitor Count?
&lt;/h1&gt;

&lt;p&gt;Programmatic SEO can significantly increase visitor count by allowing your site to appear in search results for a vast number of keywords. The key benefits include:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Enhanced Visibility:&lt;/strong&gt;&lt;br&gt;
More pages targeting relevant keywords mean more opportunities for users to find your site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Increased Traffic:&lt;/strong&gt;&lt;br&gt;
With more targeted keywords, you attract a broader audience, which can lead to higher traffic volumes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Better User Engagement:&lt;/strong&gt;&lt;br&gt;
By providing content that meets user needs and search intent, you can improve engagement metrics, such as time on site and pages per visit, which further boosts SEO performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Measurable Results:&lt;/strong&gt;&lt;br&gt;
Track the performance of your programmatically generated pages using analytics tools to refine and optimize your strategy continuously.&lt;/p&gt;

&lt;h1&gt;
  
  
  What Drawbacks Does Programmatic SEO Have?
&lt;/h1&gt;

&lt;p&gt;While powerful, programmatic SEO comes with potential drawbacks that need careful consideration:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Risk of Low-Quality Content:&lt;/strong&gt;&lt;br&gt;
Automated content can sometimes lack the depth and quality of manually created content. It’s essential to ensure that the generated pages provide value to users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Potential for Penalties:&lt;/strong&gt;&lt;br&gt;
If programmatic SEO is done in a spammy or manipulative way, it can lead to penalties from Google. Ensure your practices adhere to Google’s guidelines and focus on user experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Over-Reliance on Automation:&lt;/strong&gt;&lt;br&gt;
While automation is beneficial, relying solely on programmatic SEO can neglect the nuanced and creative aspects of content creation. A balanced approach, combining both manual and automated efforts, is often the most effective.&lt;/p&gt;

&lt;p&gt;By understanding and addressing these potential pitfalls, marketers and entrepreneurs can effectively leverage programmatic SEO to enhance their online presence and drive more traffic to their websites.&lt;/p&gt;

</description>
      <category>pseo</category>
      <category>seo</category>
      <category>webdev</category>
      <category>automation</category>
    </item>
    <item>
      <title>Convert your WordPress site to static HTML with wget</title>
      <dc:creator>Juha</dc:creator>
      <pubDate>Wed, 29 Nov 2023 07:37:08 +0000</pubDate>
      <link>https://forem.com/vallu/convert-your-wordpress-site-to-static-html-19n7</link>
      <guid>https://forem.com/vallu/convert-your-wordpress-site-to-static-html-19n7</guid>
      <description>&lt;p&gt;This technique not only &lt;strong&gt;accelerates load times&lt;/strong&gt; but also &lt;strong&gt;strengthens site security&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The downside? You'll sacrifice the dynamic content management capabilities of WordPress, along with features like interactive feedback forms and built-in search functionality.&lt;/p&gt;

&lt;p&gt;Perfect for making a browsable copy of your website content.&lt;/p&gt;

&lt;p&gt;In this quick guide, we'll explore the practical steps to seamlessly transform your WordPress site into an optimized, static HTML using tools like wget. Perfect for developers and tech-savvy individuals, this guide will help you streamline your web presence with a focus on performance and security.&lt;/p&gt;

&lt;p&gt;Let's get started on converting your dynamic WordPress site to static HTML.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pros and cons of converting WordPress to static HTML
&lt;/h2&gt;

&lt;p&gt;The pros 👍&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speed&lt;/strong&gt;: Static HTML pages load faster as they don't require server-side processing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: Reduced vulnerability to hacking and malware, as there's no database or PHP to exploit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity&lt;/strong&gt;: Easier to maintain with fewer moving parts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosting Price&lt;/strong&gt;: Can be hosted on any server, including inexpensive or free hosting services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: Better handles high traffic without requiring complex caching or database optimizations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependability&lt;/strong&gt;: Less downtime since there are fewer components that could fail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cons 😯&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Loss of Dynamic Features&lt;/strong&gt;: No real-time content updates, comments, or search capabilities inherent to WordPress.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual Content Management&lt;/strong&gt;: Content updates require manual HTML editing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limited Interactivity&lt;/strong&gt;: No built-in support for interactive elements like forms unless integrated with external services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you know what you're getting into keep reading! 🤩&lt;/p&gt;

&lt;h2&gt;
  
  
  Download your WordPress with wget
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--mirror&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--convert-links&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--adjust-extension&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--page-requisites&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--no-parent&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--no-clobber&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--wait&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.5 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--reject-regex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;".*/(&lt;/span&gt;&lt;span class="se"&gt;\?&lt;/span&gt;&lt;span class="s2"&gt;p=|wp-json|feed|xmlrpc).*"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--domains&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;example.com &lt;span class="se"&gt;\&lt;/span&gt;
https://www.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--mirror&lt;/code&gt;: Enables options suitable for mirroring websites, like infinite recursion depth.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--convert-links&lt;/code&gt;: After downloading, converts the links in the documents for local viewing.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--adjust-extension&lt;/code&gt;: Adjusts the extensions of the downloaded files (like adding .html to text/html or .css to text/css).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--page-requisites&lt;/code&gt;: Downloads all the elements required to properly display a page (images, CSS, etc.).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--no-parent&lt;/code&gt;: Ensures that links to the parent directory are not followed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--no-clobber&lt;/code&gt;: Prevents downloading the same files multiple times.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--wait=0.5&lt;/code&gt;: Waits 0.5 seconds between server requests.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--reject-regex=".*/(\?p=|wp-json|feed|xmlrpc).*"&lt;/code&gt;: Don't download URLs matching the specified regular expression pattern.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--domains=example.com&lt;/code&gt;: Don't wander off to other websites via external hyperlinks. Restricts the download to the specified domain.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tweak hyperlinks in your newly created static HTML documents
&lt;/h2&gt;

&lt;p&gt;Want to browse offline? Skip this step.&lt;/p&gt;

&lt;p&gt;This step removes index.html from all hyperlinks. Go through with it only if you're going to run the site in a web server, e.g. Nginx or Apache.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.html"&lt;/span&gt; &lt;span class="nt"&gt;-exec&lt;/span&gt; &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s|/index.html"|/"|g'&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt; +
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find all .html files and execute a search and replace on their contents. E.g. &lt;code&gt;&amp;lt;a href="/my-article/index.html"&amp;gt;&lt;/code&gt; → &lt;code&gt;&amp;lt;a href="/my-article/"&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Your Journey to a Static HTML Site
&lt;/h2&gt;

&lt;p&gt;You've successfully transformed your WordPress site into a static HTML version, unlocking faster load times and enhanced security. While some dynamic features of WordPress are now absent, the benefits in performance and maintenance simplicity are substantial.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>performance</category>
      <category>security</category>
      <category>html</category>
    </item>
    <item>
      <title>How to import node-persist into a Node.js ES6 project</title>
      <dc:creator>Juha</dc:creator>
      <pubDate>Tue, 14 Nov 2023 17:57:11 +0000</pubDate>
      <link>https://forem.com/vallu/how-to-import-node-persist-using-node-es6-5bin</link>
      <guid>https://forem.com/vallu/how-to-import-node-persist-using-node-es6-5bin</guid>
      <description>&lt;p&gt;Working on a Node.js project with ES6 syntax can sometimes present challenges, especially when incorporating modules like &lt;a href="https://www.npmjs.com/package/node-persist"&gt;node-persist&lt;/a&gt; that aren't natively aligned with the ES6 import style.&lt;/p&gt;

&lt;p&gt;Fortunately, there's a workaround to use CommonJS modules within ES6 scripts, and here's how you can apply it to node-persist.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nodePersist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-persist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodePersist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;foobar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key Takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dynamic import() function:&lt;/strong&gt; This approach utilizes the dynamic import() function, which is essential for loading CommonJS modules in an ES6 environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The default property:&lt;/strong&gt; After importing, it's crucial to access the default property. This is where the module's main functionality resides. Hence, &lt;code&gt;nodePersist.default.create()&lt;/code&gt; is used to create a new instance.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding the differences is key: CommonJS modules (&lt;code&gt;require()&lt;/code&gt;) represent the traditional standard in Node.js, while ES6 modules (&lt;code&gt;import&lt;/code&gt;) offer a more modern approach.&lt;/p&gt;

&lt;p&gt;Bridging these two with dynamic import ensures that your project can leverage the best of both worlds.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Sending Email with Amazon SES on NodeJS</title>
      <dc:creator>Juha</dc:creator>
      <pubDate>Mon, 06 Nov 2023 15:16:46 +0000</pubDate>
      <link>https://forem.com/vallu/sending-email-with-amazon-ses-on-nodejs-9jo</link>
      <guid>https://forem.com/vallu/sending-email-with-amazon-ses-on-nodejs-9jo</guid>
      <description>&lt;p&gt;Here's a quick example of sending an email with Amazon SES (Simple Email Service).&lt;/p&gt;

&lt;p&gt;In this example we are using a few libraries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodemailer.com/"&gt;Nodemailer&lt;/a&gt; provides support for embedded images etc.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ejs.co/"&gt;EJS&lt;/a&gt; is a templating library, which we use to build our HTML email&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/dotenv"&gt;dotenv&lt;/a&gt; loads environment variables from a &lt;code&gt;.env&lt;/code&gt; file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @aws-sdk/client-ses dotenv ejs nodemailer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Provide your AWS region and secrets in your &lt;code&gt;.env&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;AWS_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eu-central-1
&lt;span class="nv"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;AKIAX...
&lt;span class="nv"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;HMZHQ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a simple HTML email template &lt;code&gt;my-email.ejs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hi there&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This email is sent using Amazon SES with NodeJS. Nice!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the javascript &lt;code&gt;index.js&lt;/code&gt; for sending the email:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-ses&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nodemailer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ejs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ejs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sesClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SESClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;accessKeyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;secretAccessKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;SES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;ses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sesClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mailHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ejs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./my-email.ejs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;transporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendMail&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sender@example.org&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;receiver@example.org&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello there via Amazon SES&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mailHtml&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;envelope&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messageId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you get trouble with the &lt;code&gt;import&lt;/code&gt; statements, you might need to add this to your &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There we go. This is a quick reference for developers looking to integrate Amazon SES using NodeJS.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>email</category>
      <category>node</category>
      <category>ses</category>
    </item>
    <item>
      <title>Handle Lemon Squeezy webhooks with Node.js Express</title>
      <dc:creator>Juha</dc:creator>
      <pubDate>Mon, 06 Nov 2023 09:09:46 +0000</pubDate>
      <link>https://forem.com/vallu/handle-lemon-squeezy-webhooks-with-nodejs-express-4c3b</link>
      <guid>https://forem.com/vallu/handle-lemon-squeezy-webhooks-with-nodejs-express-4c3b</guid>
      <description>&lt;p&gt;You’ve set up your store on Lemon Squeezy and need to handle webhook events? Let's look at a quick 'n simple NodeJS example.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are webhooks good for?
&lt;/h2&gt;

&lt;p&gt;Webhook events are instrumental in tracking various elements such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Orders&lt;/li&gt;
&lt;li&gt;Subscriptions&lt;/li&gt;
&lt;li&gt;Payments&lt;/li&gt;
&lt;li&gt;License Keys&lt;/li&gt;
&lt;li&gt;And more...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In essence, webhooks are about efficiency and responsiveness. They provide a way for your Lemon Squeezy store to communicate with your server or other applications in real-time.&lt;/p&gt;

&lt;p&gt;Webhooks cut down on the need for manual checks and processing. This real-time data transfer enables systems to react instantly to events as they occur, which is essential for a seamless and automated online venture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example of Webhooks on Node &amp;amp; Express
&lt;/h2&gt;

&lt;p&gt;Let's look at what the webhook listener generally looks like.&lt;/p&gt;

&lt;p&gt;This example requires two installed packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;dotenv express
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The listener needs your &lt;strong&gt;signing secret&lt;/strong&gt;, which you get when setting up the webhook endpoint at your Lemon Squeezy dashboard. Place the signing secret into your &lt;code&gt;.env&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"SIGNING_SECRET=MySecret"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the actual webhook listener with ExpressJS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webhook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Make sure the request is actually from Lemon Squeezy&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hmac&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SIGNING_SECRET&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;sigOK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;sigOK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timingSafeEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ERROR: timingSafeEqual: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nx"&gt;sigOK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ERROR: Invalid signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ERROR: Invalid signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Parse the webhook's data as JSON&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event_name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; webhook received`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Explore the event object's contents, and do your thing with it&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server listening on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look up the data included in different webhook events from &lt;a href="https://docs.lemonsqueezy.com/help/webhooks"&gt;Lemon Squeezy docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This was a quick reference designed for developers who need a refresher on integrating Lemon Squeezy webhooks with Node.js. It's the nuts and bolts of the setup process to get you started.&lt;/p&gt;

</description>
      <category>node</category>
      <category>express</category>
      <category>javascript</category>
      <category>backend</category>
    </item>
    <item>
      <title>Monitor Website Response Time with cURL [command example]</title>
      <dc:creator>Juha</dc:creator>
      <pubDate>Mon, 06 Nov 2023 08:41:15 +0000</pubDate>
      <link>https://forem.com/vallu/how-to-measure-website-speed-with-curl-mg1</link>
      <guid>https://forem.com/vallu/how-to-measure-website-speed-with-curl-mg1</guid>
      <description>&lt;p&gt;Discover how to measure your website's response time using the &lt;code&gt;curl&lt;/code&gt; command, and how it can help you &lt;strong&gt;increase traffic&lt;/strong&gt; and performance of your webpage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Server Response Time
&lt;/h2&gt;

&lt;p&gt;Server response time, also known as Time to First Byte (TTFB), is the time it takes for the server to process a web request and send the first byte back to the user.&lt;/p&gt;

&lt;p&gt;Think of it as the pause before your web pages begin their journey to the user's screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Measure The Response Time?
&lt;/h2&gt;

&lt;p&gt;Server response time is a critical part of overall website performance and loading times.&lt;/p&gt;

&lt;p&gt;Paying attention to response times has huge benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance Bottlenecks:&lt;/strong&gt; Server response time helps identify potential performance issues on the server, such as slow database queries, excessive load, or under-resourced infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Experience:&lt;/strong&gt; Quickly loading pages enhance user experience because people do not want to wait for slow-loading pages. A fast response time increases user satisfaction and the desire to use your site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search Engine Visibility:&lt;/strong&gt; Search engines like Google evaluate a site's loading speed as part of its search engine optimization (SEO). Faster pages receive a higher ranking in search results, which helps increase organic traffic and your visibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Measuring Response Time with cURL
&lt;/h2&gt;

&lt;p&gt;cURL is a versatile tool commonly found in MacOS and Linux operating systems. It can be used to make various types of network requests.&lt;/p&gt;

&lt;p&gt;Server response time is measured with cURL as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{time_starttransfer}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://www.enstai.fi/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;cURL measures how long it takes for the web page at &lt;a href="https://www.enstai.fi/"&gt;https://www.enstai.fi/&lt;/a&gt; to generate a response. The command returns the response time in seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;juha@MacBook ~ % curl -s -o /dev/null -w "%{time_starttransfer}\n" https://www.enstai.fi/
0.152016
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the response time is 0.152016 seconds, or 152 milliseconds (ms).&lt;/p&gt;

&lt;p&gt;According to Google, &lt;strong&gt;a good response time is under 200 ms&lt;/strong&gt;, or less than 0.2 seconds.&lt;/p&gt;

&lt;p&gt;It seems that enstai.fi was quicker to respond, which is great!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In the digital age, speed is of the essence. Monitoring your website's speed with cURL isn't just good practice — it's essential. This diligence is a part of what separates successful websites from the rest.&lt;/p&gt;

&lt;p&gt;Keep in mind that each millisecond shaved off your response time can significantly elevate user satisfaction and enhance your position in SEO rankings.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>curl</category>
      <category>website</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
