<?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: Sean C Davis</title>
    <description>The latest articles on Forem by Sean C Davis (@seancdavis).</description>
    <link>https://forem.com/seancdavis</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%2F377657%2F4c4ef1a9-f7e5-4b6f-884c-84ef63cbbb05.jpg</url>
      <title>Forem: Sean C Davis</title>
      <link>https://forem.com/seancdavis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/seancdavis"/>
    <language>en</language>
    <item>
      <title>Netlify Dynamic Site Challenge - Help Thread!</title>
      <dc:creator>Sean C Davis</dc:creator>
      <pubDate>Thu, 02 May 2024 21:01:44 +0000</pubDate>
      <link>https://forem.com/netlify/netlify-dynamic-site-challenge-help-thread-2081</link>
      <guid>https://forem.com/netlify/netlify-dynamic-site-challenge-help-thread-2081</guid>
      <description>&lt;p&gt;Looking for help with your &lt;a href="https://dev.to/challenges/netlify"&gt;Netlify DEV Challenge Submission&lt;/a&gt;?&lt;/p&gt;

&lt;p&gt;We're here to assist anyone who encounters issues or has questions about Netlify!&lt;/p&gt;

&lt;p&gt;Feel free to post your questions or concerns in the comments below, and we'll do our best to provide timely and helpful responses.&lt;/p&gt;

&lt;p&gt;Just hearing about this challenge? Read the announcement post here:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/devteam" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1%2Fd908a186-5651-4a5a-9f76-15200bc6801f.jpg" alt="The DEV Team"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3%2F13d3b32a-d381-4549-b95e-ec665768ce8f.png" alt=""&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/devteam/join-us-for-the-netlify-dynamic-site-challenge-3000-in-prizes-3mfn" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Join us for the Netlify Dynamic Site Challenge: $3,000 in Prizes!&lt;/h2&gt;
      &lt;h3&gt;dev.to staff for The DEV Team ・ May 1&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#netlifychallenge&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#devchallenge&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#javascript&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>netlifychallenge</category>
      <category>netlify</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Five Reasons I'm Excited about Astro</title>
      <dc:creator>Sean C Davis</dc:creator>
      <pubDate>Tue, 12 Oct 2021 18:56:24 +0000</pubDate>
      <link>https://forem.com/seancdavis/five-reasons-im-excited-about-astro-129c</link>
      <guid>https://forem.com/seancdavis/five-reasons-im-excited-about-astro-129c</guid>
      <description>&lt;p&gt;When &lt;a href="https://astro.build/"&gt;Astro&lt;/a&gt; first appeared on the scene, I rolled my eyes.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Another static site generator. Really? Don't we already have enough to choose from?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Astro Fills in the Gaps
&lt;/h2&gt;

&lt;p&gt;At first, it felt like it could have been an &lt;a href="https://www.11ty.dev/"&gt;Eleventy&lt;/a&gt; plugin (similar to what &lt;a href="https://slinkity.dev/"&gt;Slinkity&lt;/a&gt; is doing). I was frustrated that the Astro team felt like they needed to add a whole new thing to the ecosystem.&lt;/p&gt;

&lt;p&gt;But then I started tinkering with it and realized that Astro fills in the gaps that I've found in the other tools I've been working with recently. And that's when I got super excited!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Right Amount of Opinion
&lt;/h2&gt;

&lt;p&gt;For me, what it boils down to is that I feel like Astro brings the right degree of opinion to its framework. It knows that there are widespread preferences for lower-level tooling, and so it leaves options. But it provides a solid framework for implementing those options.&lt;/p&gt;

&lt;p&gt;Now, granted, I've only built a few proofs of concept, but these are the five reasons I'm so excited about Astro:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Static-first, with progressive enhancement&lt;/li&gt;
&lt;li&gt;Supporting multiple component frameworks&lt;/li&gt;
&lt;li&gt;Native markdown support&lt;/li&gt;
&lt;li&gt;A foundation for styling&lt;/li&gt;
&lt;li&gt;Reduced boilerplate code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's take a quick look at each of these ideas.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Progressive Enhancement / Partial Hydration
&lt;/h2&gt;

&lt;p&gt;Astro lets you build your website with modern front-end component frameworks, like React and Vue, but it doesn't assume you want to run JavaScript in the browser.&lt;/p&gt;

&lt;p&gt;That's my struggle with frameworks like Gatsby and Next.js. They are super powerful, but they assume interactivity and always include JavaScript files in your built site.&lt;/p&gt;

&lt;p&gt;Astro assumes the opposite. It makes you, the developer, tell it when you &lt;em&gt;need&lt;/em&gt; interactivity. Otherwise it renders static HTML. That means that even though there is &lt;em&gt;some&lt;/em&gt; magic that happens during the build, you are still in control of your HTML code &lt;em&gt;for the most part&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Bring Your Own Framework (BYOF)
&lt;/h2&gt;

&lt;p&gt;It's unlikely we're going to see one component framework to rule them all. React is hugely popular, yes, but Vue and Svelte have a major following that isn't going away.&lt;/p&gt;

&lt;p&gt;Astro doesn't just say you can choose one of these frameworks, but that you can use any of them at any point. Meaning you could have a Vue component &lt;em&gt;and&lt;/em&gt; a React component in the same project. That's not necessarily a great idea, but it leaves the developer to make that decision. Astro just provides the foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Native Markdown Support
&lt;/h2&gt;

&lt;p&gt;Pages can be &lt;a href="https://docs.astro.build/core-concepts/astro-components"&gt;&lt;code&gt;.astro&lt;/code&gt; components&lt;/a&gt; or &lt;a href="https://docs.astro.build/guides/markdown-content"&gt;&lt;code&gt;.md&lt;/code&gt; (markdown) files&lt;/a&gt;. It also provides &lt;a href="https://docs.astro.build/guides/markdown-content#astros-markdown-component"&gt;a markdown component&lt;/a&gt; for use in Astro components, when you only need a little markdown in a larger component.&lt;/p&gt;

&lt;p&gt;This feels like an inspiration from Eleventy — which will render markdown pages right out of the box, with the option to inject plugins as necessary.&lt;/p&gt;

&lt;p&gt;I like this in comparison to Gatsby, which requires a clunky GraphQL query and custom JavaScript code to be able to render markdown pages. Or Next.js, which intentionally takes no opinion on data fetching or page content and requires that you do all the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. A Foundation for CSS
&lt;/h2&gt;

&lt;p&gt;Like BYOF, Astro provides an array of options for &lt;a href="https://docs.astro.build/guides/styling"&gt;implementing styles in the project&lt;/a&gt;, including directly in a component, using CSS modules, PostCSS, Tailwind, Sass, and more.&lt;/p&gt;

&lt;p&gt;This is great because, well, CSS is hard. And everyone has their own way of staying organized. And yet, in comparison to Eleventy, which makes no opinion on styling, a foundation of the popular options today is available for you in Astro.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Reduced Boilerplate Code
&lt;/h2&gt;

&lt;p&gt;I love that all the logic for Astro components lives in the component. I'm not a Vue guy, and I've only tinkered with Svelte, so the concept was a bit odd to me at first.&lt;/p&gt;

&lt;p&gt;But after playing around with it, I'm in love. With support for &lt;a href="https://docs.astro.build/guides/data-fetching#top-level-await"&gt;top-level await&lt;/a&gt;, you can write only the JavaScript you need within a component.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Place for Eleventy
&lt;/h2&gt;

&lt;p&gt;While it originally felt to me like Astro used Eleventy as a basis and then built something else on top of it, I'm looking at the relationship of the two differently after working with both.&lt;/p&gt;

&lt;p&gt;Eleventy is very much about reducing the amount of code you have to write in general. It supports a number of &lt;a href="https://www.11ty.dev/docs/languages/"&gt;server-side templating languages&lt;/a&gt;, and does not go after front-end component frameworks. There's a lot of power in this simplicity. It makes it a little easier to focus on the content.&lt;/p&gt;

&lt;p&gt;But the big remaining benefit of Eleventy is that you have 100% control over the HTML that is rendered to the page. You, the developer, are in absolute control of the performance of your site. And that can't be understated. You'll have to do a bit of work to maintain that and to stay organized as your site grows, but you have the control.&lt;/p&gt;

&lt;p&gt;Will I settle on one over the other? Maybe. But I think both have their place for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Power in Developers' Hands
&lt;/h2&gt;

&lt;p&gt;So that's it. Astro provides a foundation that combines popular opinions today. It shows developers what those options are, and then asks the developer to pick one, or more. It says, "Here are three different hammers. You can pick the one that feels right, or you can use all three at different times. You just have to tell me what you want to do."&lt;/p&gt;

&lt;p&gt;And that is really exciting!&lt;/p&gt;

</description>
      <category>astro</category>
      <category>javascript</category>
      <category>jamstack</category>
      <category>ssg</category>
    </item>
    <item>
      <title>A Guide on Transforming an Idea into a Website</title>
      <dc:creator>Sean C Davis</dc:creator>
      <pubDate>Thu, 09 Sep 2021 18:42:32 +0000</pubDate>
      <link>https://forem.com/stackbit/a-guide-on-transforming-an-idea-into-a-website-4oco</link>
      <guid>https://forem.com/stackbit/a-guide-on-transforming-an-idea-into-a-website-4oco</guid>
      <description>&lt;p&gt;At its core, Jamstack was a revolution. It took the best part of the first 25+ years of website development and combined them into a powerful pattern that is used widely today. It has created an explosion of ideas and innovations that have helped developers across the world build websites that are more performant, more secure, less expensive, easier to scale, and (most importantly) fun to build!&lt;/p&gt;

&lt;p&gt;Instead of taking you on a theoretical journey and digging into the technical nuances of the Jamstack, we're going someplace else. We'll take a more tangible journey, as we step through the entire process of building a Jamstack website, from the little light bulb in your brain all the way to a physical website that real people can visit.&lt;/p&gt;

&lt;p&gt;These are the stops we'll make along the way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Bring the design to life&lt;/li&gt;
&lt;li&gt;Find a developer&lt;/li&gt;
&lt;li&gt;Build the site (write the code)&lt;/li&gt;
&lt;li&gt;Review and test the site&lt;/li&gt;
&lt;li&gt;Deploy, edit, and repeat, repeat, repeat&lt;/li&gt;
&lt;li&gt;Or ... do it the Stackbit way!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Buckle up. Here we go!&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop #1: Design
&lt;/h2&gt;

&lt;p&gt;The first stop on the Jamstack journey is design — the process of visually representing your ideas.&lt;/p&gt;

&lt;h3&gt;
  
  
  From Spec to Delivery
&lt;/h3&gt;

&lt;p&gt;The way this typically works is that you provide some form of &lt;em&gt;spec&lt;/em&gt; to a designer. The designer then takes that spec and turns it into one or more design files that show what should be built. That can include page layouts, individual components, or global patterns like colors, fonts, and typography.&lt;/p&gt;

&lt;h3&gt;
  
  
  Delivering a Good Spec
&lt;/h3&gt;

&lt;p&gt;The “spec” you provide the designer should be more then, “I need a website.” Before you bring a designer into your project, consider spending time to gather the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A list of pages on the site, along with the purpose of each page. Bonus points if you also include content, as you'll reduce the variability of the design when it is transformed into code.&lt;/li&gt;
&lt;li&gt;  Brand assets or other visual specifications you have — logos, colors, typography, etc.&lt;/li&gt;
&lt;li&gt;  A list of websites that you really like and why you like them. The designer will want you to be happy, and the more you can tell them what you like, the higher the chance that they will deliver on your vision.&lt;/li&gt;
&lt;li&gt;  A list of competitors. Even if the designer doesn't use inspiration from your competitors' sites, it's nice for them to know where the competition stands and what it'll take to be noticed in the crowd.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The designer may ask you for more information, but this is a good start to get the conversation rolling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Locating the Right Designer
&lt;/h3&gt;

&lt;p&gt;If you have a designer already working with you, that's a bonus! If not, the cost and abilities of designers vary &lt;em&gt;widely&lt;/em&gt;. You could use a service like &lt;a href="https://www.fiverr.com/" rel="noopener noreferrer"&gt;Fiverr&lt;/a&gt; and have a decent-sized website designed for a few hundred dollars (USD). Or you could hire a fancy agency and spend tens of thousands of dollars.&lt;/p&gt;

&lt;p&gt;Generally speaking, cost tends to be &lt;em&gt;somewhat&lt;/em&gt; proportional to experience and the quality you're going to get back. But that doesn't mean the inexpensive designer is going to provide an awful design. This is how I usually think about it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  If I hire a designer on the cheap, I'm going to have to do more work if I want to get something worthwhile out of it. I'll have to drive the process. I'll have to deliver a super tight spec so that they give me exactly what I'm looking for.&lt;/li&gt;
&lt;li&gt;  If I hire a fancy and expensive firm, I should expect to still have to put in time and effort, but should be led through the process and be left with something truly great.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Looking Toward Development
&lt;/h3&gt;

&lt;p&gt;What's interesting about this process is that while design tends to be a fraction of the cost of development, it's also largely responsible for determining the complexity of the build. Granted, a developer can spend your money in a silly way and &lt;em&gt;over-engineer&lt;/em&gt; a website for you. But, &lt;em&gt;most of the time&lt;/em&gt;, the time a developer is going to spend building your site is determined by &lt;em&gt;their specification&lt;/em&gt; — the design files.&lt;/p&gt;

&lt;p&gt;The more interactive the design, the more it's going to cost to develop. The more unique each page is from the others, the more it's going to cost to develop.&lt;/p&gt;

&lt;p&gt;You don't have to be a fortune teller. Instead, leave a little room in your budget for going back to the designer after consulting with your developer. If the developer says you can save a thousand dollars by changing a feature, maybe you'd want to spend another hundred to have the designer reimagine and simplify it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Deliverable
&lt;/h3&gt;

&lt;p&gt;After going through the design phase, you're left with the &lt;em&gt;deliverables&lt;/em&gt; — artifacts you can pass on to the developer as their specification. &lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/tree/main/01-design" rel="noopener noreferrer"&gt;Here's an example for a simple site&lt;/a&gt; that includes just four files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://raw.githubusercontent.com/seancdavis/stackbit-jamstack-journey/main/01-design/unmute--homepage--desktop.png" rel="noopener noreferrer"&gt;Home page for large screens&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://raw.githubusercontent.com/seancdavis/stackbit-jamstack-journey/main/01-design/unmute--homepage--mobile.png" rel="noopener noreferrer"&gt;Home page for small screens&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://raw.githubusercontent.com/seancdavis/stackbit-jamstack-journey/main/01-design/unmute--content-page.png" rel="noopener noreferrer"&gt;Interior pages&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://raw.githubusercontent.com/seancdavis/stackbit-jamstack-journey/main/01-design/unmute--style-tiles.png" rel="noopener noreferrer"&gt;Style tiles&lt;/a&gt; (i.e. global style definitions)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this is an example of what a style tiles spec might look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fseancdavis%2Fstackbit-jamstack-journey%2Fmain%2F01-design%2Funmute--style-tiles.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fseancdavis%2Fstackbit-jamstack-journey%2Fmain%2F01-design%2Funmute--style-tiles.png" alt="An example of style tiles — styles that apply to all (or most) pages on the site"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(This example is from &lt;a href="https://www.unmutedstories.com/" rel="noopener noreferrer"&gt;Unmute&lt;/a&gt;, a side project I'm involved with.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Stops #2-4: Development
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Toot toot!&lt;/em&gt; This train keeps on rolling. The next several steps encompass the development phase of the project. Our Jamstack train makes three stops here because there are &lt;em&gt;usually&lt;/em&gt; three steps in this process, though the amount to which you are involved in each will vary depending on the developer you hire.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Process
&lt;/h3&gt;

&lt;p&gt;This process itself is similar to the design process in that it goes through these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Locate &amp;amp; Estimate:&lt;/strong&gt; Developer provides estimate based on specification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build:&lt;/strong&gt; Developer &lt;em&gt;builds&lt;/em&gt; the site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review:&lt;/strong&gt; The developer delivers the code and you review to make sure it works.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Locate a Developer
&lt;/h3&gt;

&lt;p&gt;You can go through much the same process as you did with design. If you have an in-house developer, great! If not, you can use a service like Fiverr and hire devs on the cheap. Or you can look to a more formal agency or dev shop to suit your needs.&lt;/p&gt;

&lt;p&gt;In this case, you don't have to build a spec for the developer. You already did that! The design files are your specification.&lt;/p&gt;

&lt;p&gt;Finding the right developer is crucial to this process, as well. And that's why I've broken out the build portion (below) as three steps. Some developers (usually those you hire on the cheap) may only complete the first step in the process. But for you to truly achieve a useful and powerful Jamstack site, you'll want someone who can take you through all three.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Build
&lt;/h3&gt;

&lt;p&gt;These are the steps in the process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Static build&lt;/li&gt;
&lt;li&gt;Templatizing the code&lt;/li&gt;
&lt;li&gt;Separating content from presentation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can spend very little on web development and still get something that you can deploy out of it. Often, that means the developer only went through the first step and gave you static files. That's totally fine, but it will make adding new pages or editing existing content super difficult.&lt;/p&gt;

&lt;p&gt;The second step — &lt;em&gt;templatizing&lt;/em&gt; — aims to take the static code and turn it into modular pieces that can be reused. This process is explained in great length and technical detail &lt;a href="https://www.stackbit.com/blog/jamstack-journey-templatize-static-html/" rel="noopener noreferrer"&gt;in this guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After the code has been modularized, it can be further adjusted to extract all the content and put it in a single place, such as a content management system. This process is explained in great length &lt;a href="https://www.stackbit.com/blog/jamstack-journey-separate-content/" rel="noopener noreferrer"&gt;in this guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And the end of these three steps, you'll be left with a system that you can use to edit content without worrying about messing up the code. In fact, you ought to be able to edit the content without even &lt;em&gt;seeing&lt;/em&gt; the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reviewing the Site
&lt;/h3&gt;

&lt;p&gt;While the review process for design was more conceptual, with development, it's up to you to actually test that the website behaves as you'd expect. In other words, you're &lt;em&gt;trying to break it&lt;/em&gt;. The developer should respond by fixing any bugs discovered within a certain timeframe.&lt;/p&gt;

&lt;p&gt;While the developer may not &lt;em&gt;deploy&lt;/em&gt; the code for you, they ought to be able to help you get it running so that you can test and send notes back to them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop #5: Deploy &amp;amp; Use!
&lt;/h2&gt;

&lt;p&gt;At this point you have a site that is ready to go to production, so that's the next step. I recommend using that same developer to help you through that process, but I've broken it out here because it tends to require more effort from you than the development phase of the project.&lt;/p&gt;

&lt;p&gt;Here are a few of the crucial steps in this process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Choose a host and setup an account&lt;/li&gt;
&lt;li&gt;  Buy and/or configure a domain name&lt;/li&gt;
&lt;li&gt;  Add analytics tooling&lt;/li&gt;
&lt;li&gt;  Add SEO content&lt;/li&gt;
&lt;li&gt;  Use it!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's look at each of these briefly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hosting
&lt;/h3&gt;

&lt;p&gt;The term "Jamstack" was coined by a startup called &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;. And they continue to lead the way in terms of deploying and hosting Jamstack websites. But there are other great tools out there, like &lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;, &lt;a href="https://pages.github.com/" rel="noopener noreferrer"&gt;GitHub Pages&lt;/a&gt;, &lt;a href="https://azure.microsoft.com/en-us/services/app-service/static/" rel="noopener noreferrer"&gt;Azure Static Web Apps&lt;/a&gt;, and many more.&lt;/p&gt;

&lt;p&gt;Whatever you choose, you'll want to set up an account for yourself. Most of these services have a free (or low cost) tier that is enough to accommodate small websites.&lt;/p&gt;

&lt;p&gt;Once you have an account, you can add your developer, and they will hook it up so that the code is deployed. And then you will have a working website!&lt;/p&gt;

&lt;h3&gt;
  
  
  Domain
&lt;/h3&gt;

&lt;p&gt;You'll want a domain name for your site, otherwise you'll be left with default names from your hosting provider. If you already have one, that's great!&lt;/p&gt;

&lt;p&gt;Your developer can tell you what you need to do to point the domain to your host, and then you'll be able to use your domain name to access your new website!&lt;/p&gt;

&lt;h3&gt;
  
  
  Analytics
&lt;/h3&gt;

&lt;p&gt;Analytics is a crucial piece of the puzzle, because you want to know who is visiting your website. Some hosting providers (like Netlify) offer what they call server-side analytics, which tend to be the most accurate, though there are limitations to them. But if you're just getting started, it's easy enough to start with &lt;a href="https://marketingplatform.google.com/about/analytics/" rel="noopener noreferrer"&gt;Google Analytics&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Tell your developer you want to install Google Analytics and they can help you through that process.&lt;/p&gt;

&lt;h3&gt;
  
  
  SEO
&lt;/h3&gt;

&lt;p&gt;Of all these pre-launch steps, perhaps the trickiest to deal with is SEO. Usually I'd expect a developer to prepare my site to support SEO. That means automatically generating a sitemap file and providing the ability to customize SEO meta values for any given page. (It would be worthwhile to mention this when first setting up the arrangement with the developer so that they are prepared.)&lt;/p&gt;

&lt;p&gt;You'll want to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Register your site (and sitemap) with &lt;a href="https://developers.google.com/search" rel="noopener noreferrer"&gt;Google Search Console&lt;/a&gt;. This will help provide you with analytics on where you land in search results. It'll also help Google know where your site is and what its pages are.&lt;/li&gt;
&lt;li&gt;Add custom SEO values for every page on your site, including adding an image.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you don't feel confident in this step, you can also hire an SEO expert to help you through the process. These types of consultants also have a huge price range, from Fiverr to big agency. If you're just getting started, I recommend toward the cheaper option — just have someone give you a quick audit and make the changes they suggest.&lt;/p&gt;

&lt;p&gt;(Insider secret: Mostly what these SEO pros are doing is using some expensive tool, hooking up your site, running a report, and giving you the output of that report. The funny thing is, some of the people are often less expensive than buying the tools and doing the work yourself.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Editing
&lt;/h3&gt;

&lt;p&gt;When all of those last bits are in place, you should have a live site and it's time to start promoting it and keeping it up to date. Make a few changes to the content. Tell the world about it. and give yourself a pat on the back.&lt;/p&gt;

&lt;p&gt;BUT WAIT! Before we wrap this up, I want to spend a little time giving you what I think is a better way to get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop #6: An Easier Approach
&lt;/h2&gt;

&lt;p&gt;If you're new to the world of creating websites, I think you should take a look at &lt;a href="https://www.stackbit.com/" rel="noopener noreferrer"&gt;Stackbit&lt;/a&gt;! (Of course I do, I work there and am writing on their blog.)&lt;/p&gt;

&lt;p&gt;Stackbit is a site builder that specializes in Jamstack websites.&lt;/p&gt;

&lt;p&gt;Instead of going through that entire process (the first five stops on the Jamstack journey), you could do it all yourself in much less time for much less money. After &lt;a href="https://app.stackbit.com/login" rel="noopener noreferrer"&gt;creating an account&lt;/a&gt;, Stackbit will give you a list of templates from which you can build your site. You'll then walk through the process of customizing it just for you.&lt;/p&gt;

&lt;p&gt;The catch here is that Stackbit isn't just another WordPress or Squarespace, even though it may feel like it at first. Stackbit works to be super transparent about where your code and content live. What that means is that you can start this journey on your own, without a designer or a developer, at no cost other than your time. And you'll still have access to the code and content that powers your site. Once you're up and running, if you need a little help, &lt;em&gt;that's&lt;/em&gt; when you can bring in a designer, developer, or SEO expert to make some suggestions or additions to your new site.&lt;/p&gt;

&lt;p&gt;If that sounds like a walk in the park, &lt;a href="https://app.stackbit.com/create" rel="noopener noreferrer"&gt;give it a try&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;In any case, I hope you've enjoyed your ride, and I'd love to learn more about your &lt;em&gt;Jamstack Journey&lt;/em&gt;. &lt;a href="https://twitter.com/seancdavis29" rel="noopener noreferrer"&gt;Let's chat&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>jamstack</category>
      <category>design</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Separate Content from Website Code for Easier Editing</title>
      <dc:creator>Sean C Davis</dc:creator>
      <pubDate>Tue, 24 Aug 2021 18:10:55 +0000</pubDate>
      <link>https://forem.com/stackbit/separate-content-from-website-code-for-easier-editing-1ig3</link>
      <guid>https://forem.com/stackbit/separate-content-from-website-code-for-easier-editing-1ig3</guid>
      <description>&lt;p&gt;When it comes to turning a website design into a real thing (a website), a static site generator is an invaluable tool. It provides you with the ability to abstract reusable parts so you can write code more efficiently and more accurately.&lt;/p&gt;

&lt;p&gt;We covered the process of moving from static HTML files to a &lt;a href="https://www.seancdavis.com/blog/wtf-is-ssg/" rel="noopener noreferrer"&gt;static site generator&lt;/a&gt; (SSG) &lt;a href="https://www.stackbit.com/blog/jamstack-journey-templatize-static-html/" rel="noopener noreferrer"&gt;in the precursor to this article&lt;/a&gt;. Here we're going to look at taking your SSG game to the next level by transforming it into content-driven engine. Together, these two steps make up a few parts of &lt;em&gt;The Jamstack Journey&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Let's dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  How a Content-Driven Website Works
&lt;/h2&gt;

&lt;p&gt;There are two main steps in the process of transforming a templatized site into one that is content-driven:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Separate content from presentation&lt;/li&gt;
&lt;li&gt;Abstract repeatable components&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Separate Content from Presentation
&lt;/h3&gt;

&lt;p&gt;The first step in the process is to separate content from presentation. Your templatized website likely has a number of HTML files representing the content of your site. (In our templatized example, we also used &lt;code&gt;.njk&lt;/code&gt; files.)&lt;/p&gt;

&lt;p&gt;For example, you might have a blog post represented as &lt;code&gt;my-first-post.html&lt;/code&gt; that looks something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.stackbit.com%2Fimages%2F210824--post-after-templatizing.png" class="article-body-image-wrapper"&gt;&lt;img alt="Blog post after templatizing" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.stackbit.com%2Fimages%2F210824--post-after-templatizing.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It doesn't have any layout elements because those have all been abstracted away in your template, but it still has HTML code, which effects the structure — the &lt;em&gt;presentation&lt;/em&gt; — of the page.&lt;/p&gt;

&lt;p&gt;Converting this to a content driven approach means we can convert the HTML file into a content-based file, like markdown (e.g. &lt;code&gt;my-first-post.md&lt;/code&gt;). And then that content can be fed into the main post layout. Like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.stackbit.com%2Fimages%2F210824--after-transformation.png" class="article-body-image-wrapper"&gt;&lt;img alt="Blog-post after content-driven transformation" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.stackbit.com%2Fimages%2F210824--after-transformation.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If this doesn't make sense, don't worry yet. We're going to go through a real-world example.&lt;/p&gt;

&lt;h3&gt;
  
  
  Abstract Repeatable Components
&lt;/h3&gt;

&lt;p&gt;This process forces us to develop a &lt;em&gt;shape&lt;/em&gt; for our content. Doing so will help us uncover opportunities to further simplify templates by combining and abstracting repeatable structures. If this also has your head spinning, it's okay. We'll look at an example of this in action, too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of a Content-Driven Website
&lt;/h2&gt;

&lt;p&gt;The process of separating content from presentation has a number of benefits. These are the primary reasons I go through through this effort:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It reduces the potential for introducing new bugs through content. When you force your editors to write HTML code just to add content to your site, you are greatly increasing your risk for new bugs that come from unexpected or bad HTML code.&lt;/li&gt;
&lt;li&gt;Less technical content editors can contribute without learning code.&lt;/li&gt;
&lt;li&gt;It's more pleasant to author content. Markdown is built to get out of your way. Instead of wrapping a top-level heading in an &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; tag, you just precede the line with &lt;code&gt;#&lt;/code&gt;. There are no paragraph tags — they are inferred.&lt;/li&gt;
&lt;li&gt;Markdown is faster to write, too! (At least once you get the hang of it.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What about Content Management Systems?
&lt;/h2&gt;

&lt;p&gt;If we're talking about separating content from presentation, shouldn't we be talking about content management systems (CMS), too?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Great question, Sean.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Yes, this would be the perfect time to introduce a CMS into your project. What we're doing here is substituting a more formal CMS with markdown files. Think of markdown files as the data coming from your CMS. In fact, you could technically pull content from a CMS &lt;em&gt;and&lt;/em&gt; convert it to markdown files. I like this approach because a) it's simpler than wiring up a whole CMS, but b) can still work well with your setup if you choose to use a CMS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: The Templatized Site
&lt;/h2&gt;

&lt;p&gt;With that, let's get started!&lt;/p&gt;

&lt;p&gt;Following the templatizing tutorial, we were left with &lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/tree/6e9cb47140e5818b7b97ff314b8f89f9162b88ed/03-templated-site" rel="noopener noreferrer"&gt;this code&lt;/a&gt;. That's where we're going to begin for this exercise.&lt;/p&gt;

&lt;p&gt;Add the contents of the example templatized project to some directory on your machine. &lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/archive/refs/tags/v1-draft.zip" rel="noopener noreferrer"&gt;Here's a link to download the larger example&lt;/a&gt;. After doing that, you can find the appropriate files in the &lt;code&gt;03-templated-site&lt;/code&gt; directory. Move these files into a new directory on your machine. Your folder's contents &lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/tree/5c82f41/04-content-driven-site" rel="noopener noreferrer"&gt;should look like this&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now we're ready to get started. We're going to do a series of abstractions to get to our final content-driven site. What we'll focus on below are four steps that represent a smaller part of a larger effort. The code I'll share in the end will have made more abstractions, but we'll aim to arm you with the tools you need to get there.&lt;/p&gt;

&lt;p&gt;To make sure everything is in order before we get going, it'd be safe to reinstall your dependencies and then start the development server.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then visit localhost:8000 in your browser and you should see the site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Simplify Pages
&lt;/h2&gt;

&lt;p&gt;The easiest step in this process is setting up our interior pages. Right now, our privacy and terms pages are &lt;code&gt;.njk&lt;/code&gt; files, which means they are littered with HTML code. The beauty of Eleventy is that if we simply change the file extension in these files to &lt;code&gt;.md&lt;/code&gt; they will work immediately.&lt;/p&gt;

&lt;p&gt;Give it a try. Rename &lt;code&gt;privacy.njk&lt;/code&gt; to &lt;code&gt;privacy.md&lt;/code&gt; and &lt;code&gt;terms.njk&lt;/code&gt; to &lt;code&gt;terms.md&lt;/code&gt;. Notice that you don't even have to do anything and they work perfectly fine! That's the magic of markdown. Technically, HTML is valid markdown code.&lt;/p&gt;

&lt;p&gt;But, even though it works, we have an opportunity to drastically simplify this content by removing the HTML. I ran the HTML code &lt;a href="https://www.browserling.com/tools/html-to-markdown" rel="noopener noreferrer"&gt;through an online converter&lt;/a&gt;. You could do the same or you could reference the new files (&lt;a href="https://raw.githubusercontent.com/seancdavis/stackbit-jamstack-journey/63eb311e7d6a0bbd8f4d942ecbc9a8bb4b8997a2/04-content-driven-site/terms.md" rel="noopener noreferrer"&gt;Terms here&lt;/a&gt;, and &lt;a href="https://raw.githubusercontent.com/seancdavis/stackbit-jamstack-journey/63eb311e7d6a0bbd8f4d942ecbc9a8bb4b8997a2/04-content-driven-site/privacy.md" rel="noopener noreferrer"&gt;Privacy here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Notice that the little YAML markdown snippet at the top of these files did not change.&lt;/strong&gt; That piece is still relevant so that Eleventy knows which template to use and how to pass on the title of each page to that template.&lt;/p&gt;

&lt;p&gt;After these changes, your code &lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/tree/63eb311/04-content-driven-site" rel="noopener noreferrer"&gt;should look like this&lt;/a&gt; and you should be able to visit localhost:8000/terms and localhost:8000/privacy with both looking as they did before you made the change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Shared Global Data
&lt;/h2&gt;

&lt;p&gt;We could jump to the home page next, but that one is a doozy, so let's move on to something a little simpler first — shared global data. There is often content that you want to share across multiple pages or templates. Eleventy is built to support this with its &lt;a href="https://www.11ty.dev/docs/data-global/" rel="noopener noreferrer"&gt;global data files&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One example I see in looking at the site as it is right now are the details about the next event. So what I would do is place these details in their own data file in &lt;code&gt;_data/next_event.json&lt;/code&gt;. Create that file and add the details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"October 1, 2021"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7 PM ET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Virtual"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cost"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Free"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're using these in two places today — the home page jumbotron and the header in the content page. Since both files (&lt;code&gt;index.njk&lt;/code&gt; and &lt;code&gt;_includes/content-page.njk&lt;/code&gt;) are using the Nunjucks templating language, you can render this value using a &lt;a href="https://mozilla.github.io/nunjucks/templating.html#variables" rel="noopener noreferrer"&gt;Nunjucks variable&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For example, the code in the content page template would look like this:&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;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hidden lg:inline-block lg:mr-2 xl:mr-3"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ next_event.date }}&lt;span class="nt"&gt;&amp;lt;/span&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hidden lg:inline-block lg:mx-2 xl:mx-3"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ next_event.time }}&lt;span class="nt"&gt;&amp;lt;/span&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hidden xl:inline-block lg:mx-2 xl:mx-3"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ next_event.location }}&lt;span class="nt"&gt;&amp;lt;/span&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hidden lg:inline-block lg:mx-2 xl:mx-3"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ next_event.cost }}&lt;span class="nt"&gt;&amp;lt;/span&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The home page can follow a similar pattern. &lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/commit/737599767da0ce5ad044985499811dd1d76c01bb" rel="noopener noreferrer"&gt;Here is a summary of the changes I made&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's a good idea to keep this pattern in mind and employ it whenever you find opportunities to share &lt;em&gt;content&lt;/em&gt; (not code) among multiple pages or templates. For example, another use case might be to build a list of social media account URLs so that you can separate the links from the markup and style of the icons.&lt;/p&gt;

&lt;p&gt;Give your browser another look to make sure that those values were updated as you'd expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: The Home Page
&lt;/h2&gt;

&lt;p&gt;Let's take a look at the home page next. We won't abstract this in quite the same way as the interior pages. This is because when we templatized the site, we used a super generic template and put all the unique stuff directly in the home page.&lt;/p&gt;

&lt;p&gt;What we want to do here is now extract the content from the home page so that it is trivial to edit without scrolling through the hundreds of lines of code. But we're in a good position to do that.&lt;/p&gt;

&lt;p&gt;First, move your &lt;code&gt;index.njk&lt;/code&gt; file to &lt;code&gt;_includes/home.njk&lt;/code&gt;. Doing this effectively removes our home page. We still need that index page. But now we want it to be a content page. So let's create a new file in the root of the project and call in &lt;code&gt;index.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Refresh your browser and see that there's nothing there! That's because Eleventy is now reading this as your home page, but there's no content and you haven't specified a layout. So let's do that. Change the file to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;home&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now refresh your browser and we're back.&lt;/p&gt;

&lt;p&gt;Then we can move through the file meticulously and extract elements one at a time until we have separated all our content from the template.&lt;/p&gt;

&lt;p&gt;For example, say we wanted to adjust the copy under "Unmute yours" in the jumbotron.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.stackbit.com%2Fimages%2F210824--unmute-jumbotron.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.stackbit.com%2Fimages%2F210824--unmute-jumbotron.png" alt="Unmute website jumbotron"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Maybe we make a new object for &lt;code&gt;jumbotron&lt;/code&gt; and then add a &lt;code&gt;body&lt;/code&gt; section to it. Your &lt;code&gt;index.md&lt;/code&gt; file would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;home&lt;/span&gt;
&lt;span class="na"&gt;jumbotron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|-&lt;/span&gt;
    &lt;span class="s"&gt;Join us for an evening of storytelling with some of your favorite fellow&lt;/span&gt;
    &lt;span class="s"&gt;nerds. We'll laugh. We'll cry. We may even hear a story without any&lt;/span&gt;
    &lt;span class="s"&gt;industry jargon!&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you could replace the section in &lt;code&gt;_includes/home.njk&lt;/code&gt; to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"max-w-md mb-8 mx-auto md:mx-0"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;jumbotron&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="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a long and often tedious process, but it's a game-changer when it comes to editing your site down the road. I'll leave the rest up to you and share where I ended up after going through the exercise. But first I want to talk about the process of abstracting components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Abstracting Components
&lt;/h2&gt;

&lt;p&gt;Extracting content from your pages and templates requires that you define the structure of your data. In Step 4, we could see the beginning of that structure — e.g. &lt;code&gt;jumbotron&lt;/code&gt; was going to be its own section, presumably with multiple properties within it.&lt;/p&gt;

&lt;p&gt;The resulting structure can help you determine where you can tighten up your template code further by looking for repeatable patterns.&lt;/p&gt;

&lt;p&gt;For example, in this home page, each of the speakers has a similar shape to its content. So, even though you could define each speaker individually, it probably makes sense to develop a repeatable pattern, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;speakers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Speaker Name&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Title / Company&lt;/span&gt;
    &lt;span class="na"&gt;fun_fact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Fun Fact&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/images/speakers/speaker-01.png&lt;/span&gt;
    &lt;span class="na"&gt;social&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;twitter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#"&lt;/span&gt;
      &lt;span class="na"&gt;facebook&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#"&lt;/span&gt;
      &lt;span class="na"&gt;instagram&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Speaker Name&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Title / Company&lt;/span&gt;
    &lt;span class="na"&gt;fun_fact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Fun Fact&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/images/speakers/speaker-02.png&lt;/span&gt;
    &lt;span class="na"&gt;social&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;twitter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#"&lt;/span&gt;
      &lt;span class="na"&gt;facebook&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Speaker Name&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Title / Company&lt;/span&gt;
    &lt;span class="na"&gt;fun_fact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Fun Fact&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/images/speakers/speaker-03.png&lt;/span&gt;
    &lt;span class="na"&gt;social&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;twitter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Speaker Name&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Title / Company&lt;/span&gt;
    &lt;span class="na"&gt;fun_fact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Fun Fact&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/images/speakers/speaker-04.png&lt;/span&gt;
    &lt;span class="na"&gt;social&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;twitter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#"&lt;/span&gt;
      &lt;span class="na"&gt;facebook&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#"&lt;/span&gt;
      &lt;span class="na"&gt;instagram&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This repeatable pattern hints that we could also abstract the code. Instead of having individual HTML for each speaker, we could create another &lt;em&gt;include&lt;/em&gt; template and render the content of each speaker.&lt;/p&gt;

&lt;p&gt;Our &lt;code&gt;_includes/home.njk&lt;/code&gt; file would include this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-{{ lineup.speakers.length }}"&amp;gt;
  {% for speaker in speakers %}
    {% include "speaker.njk" %}
  {% endfor %}
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then &lt;code&gt;_includes/speaker.njk&lt;/code&gt; could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="mx-4 mb-8 lg:mb-0 template-home--speaker"&amp;gt;
  &amp;lt;span class="block mb-2"&amp;gt;
    &amp;lt;img src="{{ speaker.image }}" alt=""/&amp;gt;
  &amp;lt;/span&amp;gt;
  &amp;lt;strong class="block mb-2 text-lg"&amp;gt;{{ speaker.name }}&amp;lt;/strong&amp;gt;
  &amp;lt;span class="block text-sm"&amp;gt;
    &amp;lt;em&amp;gt;{{ speaker.position }}&amp;lt;/em&amp;gt;
  &amp;lt;/span&amp;gt;
  &amp;lt;span class="block text-sm"&amp;gt;{{ speaker.fun_fact }}&amp;lt;/span&amp;gt;

  &amp;lt;div class="flex items-center justify-center mt-4"&amp;gt;
    {% if speaker.social.twitter %}
      &amp;lt;a href="{{ speaker.social.twitter }}" class="mx-1 hover:opacity-75 transition-all duration-300"&amp;gt;
        &amp;lt;span class="inline-block bg-red text-white rounded-full p-2 overflow-hidden h-8 w-8 component--icon"&amp;gt;
          {% include "svg/twitter.svg" %}
        &amp;lt;/span&amp;gt;
      &amp;lt;/a&amp;gt;
    {% endif %}
    {% if speaker.social.facebook %}
      &amp;lt;a href="{{ speaker.social.facebook }}" class="mx-1 hover:opacity-75 transition-all duration-300"&amp;gt;
        &amp;lt;span class="inline-block bg-red text-white rounded-full p-2 overflow-hidden h-8 w-8 component--icon"&amp;gt;
          {% include "svg/facebook.svg" %}
        &amp;lt;/span&amp;gt;
      &amp;lt;/a&amp;gt;
    {% endif %}
    {% if speaker.social.instagram %}
      &amp;lt;a href="{{ speaker.social.instagram }}" class="mx-1 hover:opacity-75 transition-all duration-300"&amp;gt;
        &amp;lt;span class="inline-block bg-red text-white rounded-full p-2 overflow-hidden h-8 w-8 component--icon"&amp;gt;
          {% include "svg/instagram.svg" %}
        &amp;lt;/span&amp;gt;
      &amp;lt;/a&amp;gt;
    {% endif %}
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's 30-some lines of HTML, instead of more than 100! And if you want to change the presentation of a speaker, you only have to change it in one place.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/commit/b12ed710e55919ce73181c85f110c5ec027eac9a" rel="noopener noreferrer"&gt;Here are the changes I made to the home page&lt;/a&gt;. And &lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/tree/b12ed71/04-content-driven-site" rel="noopener noreferrer"&gt;here is what the resulting code looks like&lt;/a&gt;. At this point, you have a website that is not just easy to edit its structure or presentation, but it's also trivial to create new pages or adjust values on the home page without searching through a bunch of HTML code to get there.&lt;/p&gt;

&lt;p&gt;Go ahead, try it! Create a new &lt;code&gt;.md&lt;/code&gt; file. Give it a &lt;code&gt;content-page&lt;/code&gt; layout (like Terms and Privacy) and &lt;code&gt;title&lt;/code&gt;, then see it come to life!&lt;/p&gt;

&lt;p&gt;Now that you now how to templatize a static HTML site &lt;em&gt;and&lt;/em&gt; you can separate its content from presentation, you have two very important tools necessary to create rock solid websites with the Jamstack.&lt;/p&gt;

</description>
      <category>markdown</category>
      <category>cms</category>
      <category>jamstack</category>
      <category>eleventy</category>
    </item>
    <item>
      <title>How to Convert Static HTML into Powerful Templates</title>
      <dc:creator>Sean C Davis</dc:creator>
      <pubDate>Tue, 10 Aug 2021 17:54:52 +0000</pubDate>
      <link>https://forem.com/stackbit/how-to-convert-static-html-into-powerful-templates-373i</link>
      <guid>https://forem.com/stackbit/how-to-convert-static-html-into-powerful-templates-373i</guid>
      <description>&lt;p&gt;You've gone through the design process in building a website, but now you're left with a handful of HTML files, along with some images and one or more CSS and JavaScript files, and you want to know what to do with them? Great! Let's talk about it.&lt;/p&gt;

&lt;p&gt;You &lt;em&gt;could&lt;/em&gt; deploy (i.e. publish) them, and eventually you will, otherwise no one will see your site. But deploying at this point is only going to get you so far. Inevitably, you’re going to want to change something with the site, and then what? Are you going to go all the way through the design process again? That sounds … &lt;em&gt;painful.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A more sustainable approach is to take the static HTML content you were given and &lt;em&gt;templatize it!&lt;/em&gt; Make it easier for you to work with — to make changes and to create new pages — when the need arises.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why templatize a website?
&lt;/h2&gt;

&lt;p&gt;This process has one major benefit — &lt;em&gt;reusability&lt;/em&gt;. It makes the act of adjusting structure or content relatively trivial when compared with having to work with individual HTML files. That process can be further enhanced by separating content from presentation, so that when all you need to do is change a few words, you know right where to go and never have to mess with any of the code. (We have a detailed guide for that, but you’ll want to follow this one first.)&lt;/p&gt;

&lt;p&gt;Consider two typical and simple content pages that most sites have — &lt;em&gt;Terms &amp;amp; Conditions&lt;/em&gt; and &lt;em&gt;Privacy Policy&lt;/em&gt;. They often have the same header and footer. It’s just the content in the middle of the page that differs (though the &lt;em&gt;structure&lt;/em&gt; is likely similar). With those pages as separate HTML files, when you want to change something in a shared section — like the header or footer — you have to make the change in both places.&lt;/p&gt;

&lt;p&gt;Expand that example to a site that has hundreds of pages with a similar structure (like blog posts). You’d be making that change hundreds or &lt;em&gt;thousands&lt;/em&gt; of times, depending on the size of your site.&lt;/p&gt;

&lt;p&gt;Following a software development principle called &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself"&gt;&lt;em&gt;don’t repeat yourself&lt;/em&gt;&lt;/a&gt; (often referred to as &lt;em&gt;DRY code&lt;/em&gt;), you could build the site with reusable pieces. Then you can make each change once and have it work everywhere. This is the magic of templatizing.&lt;/p&gt;

&lt;h2&gt;
  
  
  How templatizing works
&lt;/h2&gt;

&lt;p&gt;When you have no templates and only HTML, the code on each page is unique and lives within its own file. Take a super simple site that has a home page (&lt;code&gt;index.html&lt;/code&gt;), along with Terms (&lt;code&gt;terms.html&lt;/code&gt;) and Privacy (&lt;code&gt;privacy.html&lt;/code&gt;) pages. Your files look like this, where the dark blue boxes represent the code unique to each page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--u9VnzgiR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.stackbit.com/images/210720--templatizing-before.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u9VnzgiR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.stackbit.com/images/210720--templatizing-before.png" alt="Before templatizing, each page is unique."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let’s say the header on the home page is unique, but the header on the Terms and Privacy (i.e. &lt;em&gt;interior&lt;/em&gt;) pages could be shared. And maybe the footer is the same on every page. Then we could make the header and footer into their own files and share them among multiple pages. Like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Zq2rhOBC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.stackbit.com/images/210720--templatizing-after.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Zq2rhOBC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.stackbit.com/images/210720--templatizing-after.png" alt="After templatizing, the header and footer can be shared where appropriate."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After creating these templates, changes to the footer only need to be made once and every page is updated to reflect the new changes.&lt;/p&gt;

&lt;p&gt;This is a super simplistic view to show the power that even small abstractions can add to the longevity of your site. What we’ll do here will be slightly different in practice.&lt;/p&gt;

&lt;p&gt;But how do we actually do this?&lt;/p&gt;

&lt;h2&gt;
  
  
  Static site generators are here to save the day!
&lt;/h2&gt;

&lt;p&gt;Unfortunately, the web isn’t built to work with templates. We can’t actually just add a &lt;code&gt;footer.html&lt;/code&gt; file and then tell every page to include it and have the browser work. In the end, the browser expects a &lt;em&gt;single&lt;/em&gt; HTML file. In other words, the browser actually &lt;em&gt;wants&lt;/em&gt; the non-templatized version of the two scenarios above.&lt;/p&gt;

&lt;p&gt;But that’s not how we want to work. It’s tedious and prone to errors. If you want the footer to look the same everywhere, it should &lt;em&gt;be&lt;/em&gt; the same everywhere. Otherwise, you risk making inadvertent changes on random pages. To help with this process we can use a tool called a &lt;a href="https://www.seancdavis.com/blog/wtf-is-ssg/"&gt;static site generator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jamstack.org/generators/"&gt;There are many to choose from&lt;/a&gt;, perhaps including some popular ones you may have heard of, such as &lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt;, &lt;a href="https://www.gatsbyjs.com/"&gt;Gatsby&lt;/a&gt;, &lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt;, or &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt;. Here we’re going to use one called &lt;a href="https://www.11ty.dev/"&gt;Eleventy&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s so great about Eleventy?
&lt;/h2&gt;

&lt;p&gt;Eleventy isn’t as popular as some of its competition, but it is supreme in its approach, which is simplicity. It is modeled after Jekyll, which provides a super low barrier to getting started when compared with the rest of the field. But unlike Jekyll, it is written entirely in JavaScript, which makes it a great fit for folks getting started with building websites.&lt;/p&gt;

&lt;p&gt;Eleventy can also be super powerful. It takes more customization as your site grows in complexity, but it can grow with you. &lt;a href="https://www.seancdavis.com/"&gt;My site&lt;/a&gt; has several hundred pages and, at the time of writing this, is built entirely through Eleventy.&lt;/p&gt;

&lt;p&gt;Okay, &lt;em&gt;now&lt;/em&gt; are you convinced you should templatize those HTML files?&lt;/p&gt;

&lt;p&gt;Hooray! Me too.&lt;/p&gt;

&lt;p&gt;Let’s do it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: The Static Site
&lt;/h2&gt;

&lt;p&gt;Let’s work through this process together using a real-world example. We’re going to build a super simple version of &lt;a href="https://www.unmutedstories.com/"&gt;the Unmute website&lt;/a&gt;. (Unmute is a real thing — a side project I’m working on with a few nerdy friends.)&lt;/p&gt;

&lt;p&gt;We’ll have a unique &lt;a href="https://stackbit-jamstack-journey.netlify.app/"&gt;home&lt;/a&gt; &lt;a href="https://stackbit-jamstack-journey.netlify.app/"&gt;page&lt;/a&gt; along with two similar content pages, mimicking a &lt;a href="https://stackbit-jamstack-journey.netlify.app/terms/"&gt;Terms &amp;amp; Conditions page&lt;/a&gt; and a &lt;a href="https://stackbit-jamstack-journey.netlify.app/privacy/"&gt;Privacy Policy page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let’s say the output of the whatever process you went through to obtain the files for your site left you with &lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/tree/2c600cc/02-static-site/www"&gt;this mess&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;css/styles.css&lt;/code&gt; to hold all your styling for the site.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;js/bundle.js&lt;/code&gt; which handles the carousel at the bottom of the home page.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;images&lt;/code&gt; as a house for all visual assets.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;index.html&lt;/code&gt; to represent your home page.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;content-page.html&lt;/code&gt; to represent all other internal pages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Gotcha!&lt;/strong&gt;&lt;br&gt;
Take note that we’re making a big assumption here. We’re assuming that you won’t need to mess with your &lt;code&gt;styles.css&lt;/code&gt; or &lt;code&gt;bundle.js&lt;/code&gt; file. They were bundled up nicely for you by the freelance dev and you won’t need to make changes.&lt;/p&gt;

&lt;p&gt;In the real world, it can be a tricky process to take big, bulky, obfuscated CSS and JS files and create a method for adding to them. And it gets even more complicated if you ever have to go back to the developer for changes &lt;em&gt;after&lt;/em&gt; you’ve customized one of these.&lt;/p&gt;

&lt;p&gt;As a result, we’re considering that process outside the scope of this guide. However, if this need arises, I’ve written a couple relatively simple guides on how to achieve this for both &lt;a href="https://www.seancdavis.com/blog/getting-started-with-postcss/"&gt;CSS&lt;/a&gt; and &lt;a href="https://www.seancdavis.com/blog/javascript-webpack-build-pipeline/"&gt;JavaScript&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: Create a New Project
&lt;/h2&gt;

&lt;p&gt;Add the contents of the example static project to some directory on your machine. &lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/archive/refs/tags/v1-draft.zip"&gt;Here’s a link to download the larger example&lt;/a&gt;. After doing that, you can find the appropriate files in the &lt;code&gt;02-static-site/www&lt;/code&gt; directory. Move these files into a new directory on your machine. Your folder’s contents &lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/tree/88e200d/03-templated-site"&gt;should look like this&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3: Setup Eleventy
&lt;/h2&gt;

&lt;p&gt;We’re going to assume you have a computer that is setup for web development. (If not, &lt;a href="https://www.seancdavis.com/blog/new-mac-dev-guide/"&gt;here’s a guide&lt;/a&gt; I wrote on setting up a new Mac for development.)&lt;/p&gt;

&lt;p&gt;Once you’re ready to go, open up your command line or terminal application, change into the project directory and install Eleventy:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# navigate to your new project
cd path/to/my/project

# setup project for Eleventy
npm init -y

# install eleventy
npm install -D @11ty/eleventy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Note: If you’re tracking your changes with&lt;/em&gt; &lt;a href="https://git-scm.com/"&gt;&lt;em&gt;Git&lt;/em&gt;&lt;/a&gt;&lt;em&gt;, this is a great spot to initialize the repository (&lt;/em&gt;&lt;code&gt;git init&lt;/code&gt;&lt;em&gt;) and add&lt;/em&gt; &lt;code&gt;node_modules&lt;/code&gt; &lt;em&gt;to a&lt;/em&gt; &lt;code&gt;.gitignore&lt;/code&gt; &lt;em&gt;file. If you’re not working with Git, don’t worry about this right now.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I like to add a few shortcuts after this installation to make working with sites more consistent across my machine. Open your &lt;code&gt;package.json&lt;/code&gt; file and add the following to the &lt;code&gt;scripts&lt;/code&gt; section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eleventy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"clean"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rm -rf _site"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eleventy --serve --port 8000"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/tree/1c80367/03-templated-site/package.json#L6-L10"&gt;Here’s what that file should look like at this point&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now we have a way to run an Eleventy development server. Run the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# start eleventy server
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You should now have a dev server running at localhost:8000. And you’ll notice you now have a new &lt;code&gt;_site&lt;/code&gt; directory in your project. Eleventy did this automatically for you.&lt;/p&gt;

&lt;p&gt;You can open your browser and visit localhost:8000 to see your site, and … &lt;em&gt;something doesn’t look right.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R42cla44--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.stackbit.com/images/210720--unmute-unstyled.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R42cla44--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.stackbit.com/images/210720--unmute-unstyled.png" alt="Where the heck are our styles??"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s because we didn’t tell Eleventy where our assets are. To do this, we must &lt;a href="https://www.11ty.dev/docs/config/"&gt;add an Eleventy config file&lt;/a&gt; at &lt;code&gt;.eleventy.js&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="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eleventyConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Copy static assets over to _site directory.&lt;/span&gt;
  &lt;span class="nx"&gt;eleventyConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addPassthroughCopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;eleventyConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addPassthroughCopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;images&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;eleventyConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addPassthroughCopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Return configuration object.&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give the browser a refresh and everything should look good again!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jBmgszeK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.stackbit.com/images/210720--unmute-styled.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jBmgszeK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.stackbit.com/images/210720--unmute-styled.png" alt="Phew! That’s better."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: Throughout this guide, when making changes, you may have to clear the cache on your browser. Most browsers have an option to reload while clearing the cache for that site.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: The Default Layout
&lt;/h2&gt;

&lt;p&gt;In Eleventy (and most static site generators), each page is wrapped in a &lt;em&gt;layout&lt;/em&gt;. A layout is just a fancy term for a template. We’re going to begin by creating a default layout. This will be code that every page uses. We do this because, as we’ll soon see, Eleventy supports nesting layouts within one another. So what we’re going to do here is define our base layout.&lt;/p&gt;

&lt;p&gt;In looking at &lt;code&gt;index.html&lt;/code&gt; and &lt;code&gt;content-page.html&lt;/code&gt;, the code that is consistent between the two is mostly contained within the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; tag, but also includes a few lines at the very bottom of the file. Here’s what it looks like:&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="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"apple-touch-icon"&lt;/span&gt; &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"180x180"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/images/favicon/apple-touch-icon.png"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"icon"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/png"&lt;/span&gt; &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"32x32"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/images/favicon/favicon-32x32.png"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"icon"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/png"&lt;/span&gt; &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"16x16"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/images/favicon/favicon-16x16.png"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"manifest"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/images/favicon/site.webmanifest"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"mask-icon"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/images/favicon/safari-pinned-tab.svg"&lt;/span&gt; &lt;span class="na"&gt;color=&lt;/span&gt;&lt;span class="s"&gt;"#5bbad5"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"msapplication-TileColor"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"#da532c"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"theme-color"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"#ffffff"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Stackbit Demo: Unmute&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/css/styles.css"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
      &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;onInit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;undefined&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="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onInit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"font-default"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- This was the unique code --&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/js/bundle.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put that code in a new file: &lt;code&gt;_includes/default.njk&lt;/code&gt;. And in the spot where the unique code existed, add the Nunjucks variable &lt;code&gt;content&lt;/code&gt;. (More on this in a moment.) Your resulting file should &lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/tree/7122194/03-templated-site/_includes/default.njk"&gt;look like this&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Three items are important to note in this new template:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; We changed the file extension from &lt;code&gt;.html&lt;/code&gt; to &lt;code&gt;.njk&lt;/code&gt;. This means we’re using &lt;a href="https://mozilla.github.io/nunjucks/"&gt;Nunjucks&lt;/a&gt; as our templating language. Eleventy supports &lt;a href="https://www.11ty.dev/docs/languages/"&gt;a number of&lt;/a&gt; languages. Nunjucks is nice for staying with our theme of using JavaScript. And it’s also fairly minimalistic, which is nice for our example.&lt;/li&gt;
&lt;li&gt; We added a single Nunjucks variable, &lt;code&gt;content&lt;/code&gt; and passed a &lt;code&gt;safe&lt;/code&gt; filter to it. &lt;code&gt;content&lt;/code&gt; tells Nunjucks to render the content of the each page in that area. That means all of our page content will fall where you see &lt;code&gt;{{ content | safe }}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; The &lt;code&gt;_includes&lt;/code&gt; directory has some special characteristics that make it easy to … well, &lt;em&gt;include&lt;/em&gt; files. &lt;a href="https://www.11ty.dev/docs/config/#directory-for-includes"&gt;Read more here&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Nothing has changed if we refresh the browser because we haven’t wired these up yet. When we go to the home page, we’re still just looking at &lt;code&gt;index.html&lt;/code&gt;, which isn’t using our new layout yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Wrap the home page in the new layout
&lt;/h2&gt;

&lt;p&gt;Let’s adjust our home page to use the new layout.&lt;/p&gt;

&lt;p&gt;Begin by changing &lt;code&gt;index.html&lt;/code&gt; to &lt;code&gt;index.njk&lt;/code&gt;. This is going to help us in the future when we want to use Nunjucks variables or tags in the page.&lt;/p&gt;

&lt;p&gt;Now, remove all the code you extracted to create the layout and refresh your browser.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What the heck?&lt;/em&gt; You’re back to a page without styles. What happened?&lt;/p&gt;

&lt;p&gt;What happened is that you removed your references to the CSS and JS files, which were in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; and near the bottom of the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;, but you didn’t tell Eleventy to use a layout. So let’s change that. Add the following at the top of your &lt;code&gt;index.njk&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now refresh the browser and you should see your styles come back!&lt;/p&gt;

&lt;p&gt;This style code — three hyphens, then some code, then three more hyphens — is called &lt;a href="https://www.seancdavis.com/blog/wtf-is-frontmatter/"&gt;&lt;em&gt;frontmatter&lt;/em&gt;&lt;/a&gt;. It’s like &lt;em&gt;code before the code&lt;/em&gt;. It provides us a space to place meta information about that file that won’t ultimately be written to your browser screen.&lt;/p&gt;

&lt;p&gt;The last thing to note here is that you will want to either remove or change the comments in your code. Nunjucks comments look a little different than HTML comments (although they will ultimately still be treated as comments, so you can ignore this if you’d like).&lt;/p&gt;

&lt;p&gt;Any code between &lt;code&gt;&amp;lt;!--&lt;/code&gt; and &lt;code&gt;--&amp;gt;&lt;/code&gt; is an HTML comment. Either remove these or change them to &lt;code&gt;{#&lt;/code&gt; and &lt;code&gt;#}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the end, &lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/tree/b0b554e/03-templated-site/index.njk"&gt;this is what your new &lt;code&gt;index.njk&lt;/code&gt; file should look like&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Content Pages
&lt;/h2&gt;

&lt;p&gt;Now let’s add a couple plain content pages. Our developer delivered a generic content page to us called &lt;code&gt;content-page.html&lt;/code&gt;. We want to use that to create multiple pages. In this case, maybe those are Terms &amp;amp; Conditions and Privacy Policy pages.&lt;/p&gt;

&lt;p&gt;To do this we first want to make our generic content page a layout. To do that rename &lt;code&gt;content-page.html&lt;/code&gt; to &lt;code&gt;content-page.njk&lt;/code&gt; and move it into the &lt;code&gt;_includes&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Now, here’s a really weird and super cool thing about Eleventy — we can nest layouts in other layouts. What that means is that because the content page has code that we’ve already used in &lt;code&gt;_includes/default.njk&lt;/code&gt;, we can reuse that with the content page layout.&lt;/p&gt;

&lt;p&gt;To do this, remove the shared code from &lt;code&gt;_includes/content-page.njk&lt;/code&gt; and specify the layout in the frontmatter at the top of the file.&lt;/p&gt;

&lt;p&gt;Now you can create two new files, &lt;code&gt;terms.njk&lt;/code&gt; (or &lt;code&gt;terms.html&lt;/code&gt; — we aren’t doing anything special at this point) and &lt;code&gt;privacy.njk&lt;/code&gt;. Add some content (I used &lt;a href="https://app.termsfeed.com/"&gt;a generator&lt;/a&gt; for the content and you can use what I have) and tell Eleventy to use the “content-page” layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content-page"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can go to localhost:8000/terms and localhost:8000/privacy and you should see your nicely-formatted content.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--c31bZbxu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.stackbit.com/images/210720--terms-page.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--c31bZbxu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.stackbit.com/images/210720--terms-page.png" alt="Terms page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are the links to these files at this stage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/tree/b709edb/03-templated-site/_includes/content-page.njk"&gt;&lt;code&gt;_includes/content-page.njk&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/tree/b709edb/03-templated-site/terms.njk"&gt;&lt;code&gt;terms.njk&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/tree/b709edb/03-templated-site/privacy.njk"&gt;&lt;code&gt;privacy.njk&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 7: Extracting Shared Code
&lt;/h2&gt;

&lt;p&gt;Now you’re in good shape and have a pattern to create new pages with a nice layout. Any new file you create with the “content-page” layout will now have a header and footer wrapping your content.&lt;/p&gt;

&lt;p&gt;But, we still have an opportunity for some improvement. While we are using a layout to create multiple pages, which means we’re sharing code, we do still have some duplicated code. Now the home page (&lt;code&gt;index.njk&lt;/code&gt;) and the content page layout (&lt;code&gt;_includes/content-page.njk&lt;/code&gt;) have separate footers. Even though they look the same, you’d have to edit the content in both if you wanted to make a global change.&lt;/p&gt;

&lt;p&gt;Let’s make that easier. Pull the footer content into its own file in the &lt;code&gt;_includes&lt;/code&gt; directory, &lt;code&gt;_includes/footer.njk&lt;/code&gt; (&lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/tree/f1af38f/03-templated-site/_includes/footer.njk"&gt;see here&lt;/a&gt;). Then remove that shared code from the home page and content page layout and replace it with &lt;a href="https://mozilla.github.io/nunjucks/templating.html#include"&gt;the &lt;code&gt;include&lt;/code&gt; Nunjucks tag&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{% include "footer.njk" %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(See the new &lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/tree/f1af38f/03-templated-site/index.njk"&gt;&lt;code&gt;index.njk&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/tree/f1af38f/03-templated-site/_includes/content-page.njk"&gt;&lt;code&gt;_includes/content-page.njk&lt;/code&gt;&lt;/a&gt; files.)&lt;/p&gt;

&lt;p&gt;Now you can make the change once and see it work everywhere. For example, you could &lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/commit/bf4025d"&gt;add links to the terms and privacy pages&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: You could have chosen to include the footer directly in the default layout, or even reference in in the default layout. This is entirely up to you and your project. In this case, I’m accounting for some future template that won’t want the footer. But if we’re sure every page wants the footer, maybe it makes sense to put it in the default layout.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Opportunities
&lt;/h2&gt;

&lt;p&gt;As your site grows, you’ll find other opportunities for improvement and abstraction (&lt;em&gt;abstraction&lt;/em&gt; being the process of cleaning up code to be shared).&lt;/p&gt;

&lt;h3&gt;
  
  
  Shared Images
&lt;/h3&gt;

&lt;p&gt;For example, many of the shapes you see on the pages are SVG elements. They are images, but represented with HTML code. And many of them are used more than once.&lt;/p&gt;

&lt;p&gt;You could remove the &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; elements and turn them into their own file in the &lt;code&gt;_includes&lt;/code&gt; directory. (You could even put them in their own subdirectory so they are grouped together.) Then you can reference them with a Nunjucks tag.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unique Titles
&lt;/h3&gt;

&lt;p&gt;You may also run into the opposite issue at some point, where you’ll have code or content in a shared space, but you want it to be unique to some particular template. For example, you’ll want the title of the page (contained in the &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; attribute) to be unique to each page. But that code is nestled up in the default layout.&lt;/p&gt;

&lt;p&gt;This is where Nunjucks variables and page frontmatter comes into play. Take the terms page, for example. You could add a &lt;code&gt;title&lt;/code&gt; attribute in its frontmatter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content-page"&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Terms&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Conditions"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in the &lt;code&gt;_includes/default.njk&lt;/code&gt; layout, you can use the &lt;code&gt;title&lt;/code&gt; variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;title&amp;gt;{{ title }}&amp;lt;/title&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could make the same change to the &lt;code&gt;_includes/content-page.njk&lt;/code&gt; body so that you don’t have to write the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; on every page. Something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;h1&amp;gt;{{ title }}&amp;lt;/h1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The world is your oyster when it comes to finding these efficiencies. It’s all about how you want to work and how you want the framework (Eleventy) to work for you.&lt;/p&gt;

&lt;p&gt;I bucketed a handful of these changes together and did them all at once. &lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/commit/6e9cb47"&gt;Here are the changes I made&lt;/a&gt; and &lt;a href="https://github.com/seancdavis/stackbit-jamstack-journey/tree/6e9cb47/03-templated-site"&gt;the files in the project at this point&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Phew! Take a break, give yourself a pat on the back. This process can be a harrowing one, especially the first time around. If you’ve made it through, you deserve a break and some ice cream.&lt;/p&gt;

&lt;p&gt;To quickly recap. What we started with was a jumbled mess of HTML files, handed to you by some developer. We took those files and turned them into layouts (or templates) that we could use to quickly create new pages with the same design.&lt;/p&gt;

&lt;p&gt;Where do we go from here?&lt;/p&gt;

&lt;p&gt;I’m glad you let me ask that question on your behalf, because we have a great next step! Instead of using &lt;code&gt;.html&lt;/code&gt; or &lt;code&gt;.njk&lt;/code&gt; files for our content, you can double-down on frontmatter and markdown to create a truly powerful editing experience that gets all the code out of your way and keeps you focused on only the content.&lt;/p&gt;

&lt;p&gt;And, lucky for you, &lt;a href="https://www.stackbit.com/blog/jamstack-journey-separate-content/"&gt;we have a guide&lt;/a&gt; to keep this thing going and to walk you through that process.&lt;/p&gt;

&lt;p&gt;But first, go get that ice cream.&lt;/p&gt;

</description>
      <category>jamstack</category>
      <category>html</category>
      <category>eleventy</category>
      <category>ssg</category>
    </item>
    <item>
      <title>Export Bear Notes to Markdown Files</title>
      <dc:creator>Sean C Davis</dc:creator>
      <pubDate>Wed, 16 Jun 2021 13:10:20 +0000</pubDate>
      <link>https://forem.com/seancdavis/export-bear-notes-to-markdown-files-5aal</link>
      <guid>https://forem.com/seancdavis/export-bear-notes-to-markdown-files-5aal</guid>
      <description>&lt;p&gt;I love &lt;a href="https://bear.app/"&gt;Bear&lt;/a&gt;. The only other writing experience I've had that rivaled it was &lt;a href="https://ulysses.app/"&gt;Ulysses&lt;/a&gt;. It's an absolute joy to use to author content with markdown.&lt;/p&gt;

&lt;p&gt;The biggest issue I have with Bear is that it's not easy to do anything with the content &lt;em&gt;outside&lt;/em&gt; of the application. But in the end, what you're authoring within Bear is just markdown (or it &lt;em&gt;can&lt;/em&gt; be), and that content should be portable to wherever you can make it most actionable.&lt;/p&gt;

&lt;p&gt;Here's a guide to get you started in building a workflow around your Bear notes by exporting them to markdown files.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Example
&lt;/h2&gt;

&lt;p&gt;We're going to write a single &lt;a href="https://dev.to/blog/wtf-is-node"&gt;Node.js&lt;/a&gt; script that will export notes from Bear into markdown files with frontmatter metadata elsewhere on your machine.&lt;/p&gt;

&lt;p&gt;In this specific example, we're going to target active notes (i.e. &lt;em&gt;not trashed&lt;/em&gt;). And in the frontmatter, we'll add five key-value pairs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt;: The ID of the note within the database.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;title&lt;/code&gt;: The title of the note, which comes from the note's main &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; tag.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;slug&lt;/code&gt;: A &lt;em&gt;slugified&lt;/em&gt; version of the title that we'll use to name the file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;updatedAt&lt;/code&gt;: The last time the note was updated in the Bear app.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tags&lt;/code&gt;: An array of tags used within the note.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can always adjust to store the data that you care about.&lt;/p&gt;

&lt;p&gt;We're going to keep this super simple. We'll find the database, make a copy, and query it directly with the &lt;a href="https://www.npmjs.com/package/sqlite3"&gt;sqlite3&lt;/a&gt; package. If I were going to build a real workflow that I wanted to last, I'd probably look at putting a more formal system together using an ORM like &lt;a href="https://sequelize.org/"&gt;Sequelize&lt;/a&gt; or &lt;a href="https://www.prisma.io/"&gt;Prisma&lt;/a&gt;. But I want to keep us focused here.&lt;/p&gt;

&lt;h3&gt;
  
  
  One Quick &lt;em&gt;Gotcha!&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Since Bear is setup to author in markdown, we're not actually going to change the content in any way, although I'll mention where you could do that work if you would like.&lt;/p&gt;

&lt;p&gt;However, the current version of Bear has its own style of markdown. Therefore, your mileage may vary if you're using that default markdown style. You may have to build a parser to convert it to actual markdown.&lt;/p&gt;

&lt;p&gt;An easy way around that is to use &lt;a href="https://bear.app/faq/Markup%20:%20Markdown/Markdown%20compatibility%20mode/"&gt;&lt;em&gt;markdown compatibility mode&lt;/em&gt;&lt;/a&gt;. Personally, I prefer to do this anyways because it has parity with authoring in other applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Locate and Examine the Database
&lt;/h2&gt;

&lt;p&gt;Before we write any code, we have to find the database. Fortunately, &lt;a href="https://bear.app/faq/Where%20are%20Bear's%20notes%20located/"&gt;someone had the same question&lt;/a&gt; and there's an answer that I've found success with so far.&lt;/p&gt;

&lt;p&gt;As part of this process we will copy the database each time before we run the command. But, before we begin, go ahead and manually copy the database to some more-easily accessible location. Then open it up to look at the content and schema. My favorite app for this is &lt;a href="https://tableplus.com/"&gt;TablePlus&lt;/a&gt;, but there's also the popular &lt;a href="https://sqlitebrowser.org/"&gt;DB Browser for SQLite&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You'll notice the database is littered with obscure table and column names. Here's what's important to know for this exercise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The notes are located in a table called &lt;code&gt;ZSFNOTE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Within that table, &lt;code&gt;ZTRASHED&lt;/code&gt; tells us if the note is active.&lt;/li&gt;
&lt;li&gt;We can grab the other attributes we need (except tags) directly:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Z_PK&lt;/code&gt; is the ID&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ZTITLE&lt;/code&gt; is the note title&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ZTEXT&lt;/code&gt; is the main body content&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ZMODIFICATIONDATE&lt;/code&gt; is the last updated date&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Tags are located in &lt;code&gt;ZSFNOTETAG&lt;/code&gt; where &lt;code&gt;Z_PK&lt;/code&gt; is its ID and &lt;code&gt;ZTITLE&lt;/code&gt; is its title.&lt;/li&gt;
&lt;li&gt;Tags can be joined to notes through the &lt;code&gt;Z_7TAGS&lt;/code&gt; table, where &lt;code&gt;Z_7NOTES&lt;/code&gt; is the note's ID and &lt;code&gt;Z_14TAGS&lt;/code&gt; is the tag's ID.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Phew! Are you already tired?&lt;/p&gt;

&lt;p&gt;Hopefully not, because we haven't even written any code yet. If you are, take a break and rest that brain. And then let's write some code!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Setup
&lt;/h2&gt;

&lt;p&gt;Let's begin by creating a new directory for your project and setting it up. &lt;a href="https://dev.to/blog/new-javascript-project-setup/"&gt;Here are the steps I take when adding a new JavaScript project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Follow steps 1-3. In Step 3, add the following to your &lt;code&gt;.gitignore&lt;/code&gt; file, in addition to &lt;code&gt;node_modules&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;database.sqlite
tmp/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Step 4, we're going to work with a few libraries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/sqlite3"&gt;sqlite3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/slugify"&gt;slugify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/js-yaml"&gt;js-yaml&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can install them with one command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm install sqlite3 slugify js-yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We'll deal with the &lt;code&gt;package.json&lt;/code&gt; scripts later on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Get Notes from the Database
&lt;/h2&gt;

&lt;p&gt;First thing we're going to do is add a script to copy the database. Put this in a &lt;code&gt;utils&lt;/code&gt; directory.&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="c1"&gt;// utils/copyDatabase.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&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="s2"&gt;fs&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;path&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="s2"&gt;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;HOME&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="s2"&gt;os&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;homedir&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;srcPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;HOME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/Library/Group Containers/9K33E3U3T4.net.shinyfrog.bear/Application Data/database.sqlite&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;destPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../database.sqlite&lt;/span&gt;&lt;span class="dl"&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;srcPath&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Could not find Bear database: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;srcPath&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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;copyFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;srcPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;destPath&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;`Copied Bear database: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;destPath&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will copy the database to the root of your project, or it will let you know that it couldn't find the database.&lt;/p&gt;

&lt;p&gt;Then let's add our main script at &lt;code&gt;index.js&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="c1"&gt;// index.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sqlite3&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="s2"&gt;sqlite3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;verbose&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;db&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;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./database.sqlite&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;promisify&lt;/span&gt; &lt;span class="p"&gt;}&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="s2"&gt;util&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;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;promisify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&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;getNotesQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
  SELECT
    Z_PK as id,
    ZTITLE as title,
    ZTEXT as body,
    ZSUBTITLE as subtitle,
    ZTRASHED as deleted,
    ZMODIFICATIONDATE as updatedAt
      FROM ZSFNOTE
      WHERE deleted = 0;`&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notes&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;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getNotesQuery&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;notes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;finally&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Done.&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="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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&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;message&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's not much going on here. We connect to a database then run the SQL query shown to grab all the active notes and log them to the console.&lt;/p&gt;

&lt;p&gt;There's some fanciness in here that protects us from madness later on. The SQLite library uses callbacks by default, which means it will run a function we give it after the query is complete.&lt;/p&gt;

&lt;p&gt;A more modern &lt;a href="https://dev.to/blog/wtf-is-javascript/"&gt;JavaScript&lt;/a&gt; pattern is to use promises through &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;await&lt;/code&gt;. That's what's going on here. We promisify the SQLite query and then run the main part of our code within an async function so that we can be sure each line is resolved before moving on to the next.&lt;/p&gt;

&lt;p&gt;Next, now we can add the scripts to &lt;code&gt;package.json&lt;/code&gt; so we can run these two commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"preexport"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node ./utils/copyDatabase.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"export"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node index.js"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While it looks a little funny, NPM has this handy method of &lt;a href="https://docs.npmjs.com/cli/v7/using-npm/scripts#pre--post-scripts"&gt;running scripts before and after other scripts&lt;/a&gt;. When we run the &lt;code&gt;export&lt;/code&gt; command, the &lt;code&gt;preexport&lt;/code&gt; will automatically be run first. Thus, we copy the database every time, and then run the main script.&lt;/p&gt;

&lt;p&gt;We could have combined this into a single script. But I like this because it feels like two very different actions — copy the database, then export markdown files from it.&lt;/p&gt;

&lt;p&gt;You can test it all out now by running the &lt;code&gt;export&lt;/code&gt; script:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm run export
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You should see content from your active notes logged to your console.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/seancdavis/seancdavis-com/tree/548636b855013885c53a038b54972398cdb70afe/examples/bear-to-markdown"&gt;Here is the code&lt;/a&gt; at this point in time if you'd like to look. There may be a few extra things in there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Add Tags
&lt;/h2&gt;

&lt;p&gt;Next, let's do the same thing (continue to log our notes to the console), but collect the tags.&lt;/p&gt;

&lt;p&gt;The SQL query gets much more complex here. It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="s1"&gt;'Note'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'Z_PK'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Note'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'ZTITLE'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Note'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'ZTEXT'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="s1"&gt;'body'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Note'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'ZTRASHED'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="s1"&gt;'deleted'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Note'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'ZMODIFICATIONDATE'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="s1"&gt;'updatedAt'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Tags'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'Z_PK'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="s1"&gt;'Tags.id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Tags'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'ZTITLE'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="s1"&gt;'Tags.title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Tags-&amp;gt;NoteTag'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'Z_7NOTES'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="s1"&gt;'Tags.NoteTag.NoteId'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Tags-&amp;gt;NoteTag'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'Z_14TAGS'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="s1"&gt;'Tags.NoteTag.TagId'&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="s1"&gt;'ZSFNOTE'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="s1"&gt;'Note'&lt;/span&gt;
    &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;OUTER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="s1"&gt;'Z_7TAGS'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="s1"&gt;'Tags-&amp;gt;NoteTag'&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="s1"&gt;'Note'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'Z_PK'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Tags-&amp;gt;NoteTag'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'Z_7NOTES'&lt;/span&gt;
    &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;OUTER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="s1"&gt;'ZSFNOTETAG'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="s1"&gt;'Tags'&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="s1"&gt;'Tags'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'Z_PK'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Tags-&amp;gt;NoteTag'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'Z_14TAGS'&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="s1"&gt;'Note'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'ZTRASHED'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is doing some fancy join stuff. It will ultimately provide a line representing every instance of every tag in every note. That means that some rows returned will be duplicate notes. So we have to collect notes in a bit of a different way.&lt;/p&gt;

&lt;p&gt;Our &lt;code&gt;main()&lt;/code&gt; function gets updated to this, with some comments for context:&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;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&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;// Reference to store note data.&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;notes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="c1"&gt;// Query the database for notes and their tag. There will be a row returned&lt;/span&gt;
  &lt;span class="c1"&gt;// for each tag that a note contains.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryResult&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;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getNotesQuery&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// Get a unique set of IDs for the notes returned, as more than one row may&lt;/span&gt;
  &lt;span class="c1"&gt;// contain the same note.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;noteIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="c1"&gt;// Collects all notes matching the passed ID and builds an object to represent&lt;/span&gt;
  &lt;span class="c1"&gt;// that note.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buildNoteObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;noteId&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;// Find all rows from the query result matching the passed ID.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;queryResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;noteId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// Return a null object if we were given a bad ID.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="c1"&gt;// Extract relevant attributes out of the first row. Each of these is&lt;/span&gt;
    &lt;span class="c1"&gt;// assumed to be the same value in any row. We're picking the first one&lt;/span&gt;
    &lt;span class="c1"&gt;// because we know there will always be a first one.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&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;deleted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updatedAt&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;// Collect the tag names. Each row in the query result has its own unique&lt;/span&gt;
    &lt;span class="c1"&gt;// tag name, assuming the tag was only used once in the document.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Tags.title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="c1"&gt;// Build the object and return it.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&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;deleted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updatedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// Loop through the notes and store the result in the notes object.&lt;/span&gt;
  &lt;span class="nx"&gt;noteIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&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;notes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buildNoteObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="c1"&gt;// Log our result.&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;notes&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;&lt;a href="https://github.com/seancdavis/seancdavis-com/blob/81ae1f1001afdad298c870fb300fe925ee1ea3da/examples/bear-to-markdown/index.js"&gt;Here is the file in its entirety at this point&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Write Notes to File
&lt;/h2&gt;

&lt;p&gt;Now we have &lt;em&gt;just about&lt;/em&gt; everything we need to write the notes to file.&lt;/p&gt;

&lt;p&gt;First, we need one more attribute to write the file — the filename! We'll get that by &lt;em&gt;slugifying&lt;/em&gt; the title. &lt;a href="https://github.com/seancdavis/seancdavis-com/commit/2c0664c"&gt;Here's the change to make that happen&lt;/a&gt;. And the resulting file &lt;a href="https://github.com/seancdavis/seancdavis-com/blob/2c0664c/examples/bear-to-markdown/index.js"&gt;looks like this&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now we're ready to write the note to file! Let's add another function inside our &lt;code&gt;main()&lt;/code&gt; function to export the note. This will take a prepared note object, build its fronmatter and body, and then write it to file.&lt;/p&gt;

&lt;p&gt;Then we can iterate over each note and run these functions.&lt;/p&gt;

&lt;p&gt;First, add the js-yaml dependency to the top of your file:&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;yaml&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="s2"&gt;js-yaml&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;Then add a few lines to create the export directory if it doesn't exist:&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;outputDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./tmp/export&lt;/span&gt;&lt;span class="dl"&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outputDir&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mkdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outputDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we're going to write these files to a &lt;code&gt;tmp/export&lt;/code&gt; directory within your current project.&lt;/p&gt;

&lt;p&gt;And add some new lines to the &lt;code&gt;main()&lt;/code&gt; function:&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;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&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;// ...&lt;/span&gt;
  &lt;span class="c1"&gt;// Builds frontmatter and then writes the note to file.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exportNote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;note&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outputDir&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;note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.md`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slug&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;tags&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;note&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frontmatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tags&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;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`---\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;---\n\n&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="s2"&gt;`&lt;/span&gt;
    &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&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="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// Loop through the notes and store the result in the notes object.&lt;/span&gt;
  &lt;span class="nx"&gt;noteIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;note&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buildNoteObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;exportNote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;note&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;`Wrote note to file: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filePath&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Altogether, the file should now look &lt;a href="https://github.com/seancdavis/seancdavis-com/blob/d279f54/examples/bear-to-markdown/index.js"&gt;like this&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Run the command again:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm run export
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And then check your &lt;code&gt;tmp/export&lt;/code&gt; directory for these new files.&lt;/p&gt;

&lt;p&gt;Now you should be able to take a note from Bear that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Hello World

#export-me

I'm so cool!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the resulting file, &lt;code&gt;tmp/export/hello-world.md&lt;/code&gt;, looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;203&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Hello World&lt;/span&gt;
&lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hello-world&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export-me&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Hello World&lt;/span&gt;

&lt;span class="gh"&gt;#export-me&lt;/span&gt;

I'm so cool!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty cool, right!?&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;That's a start to something that could be super powerful and enable you to bring Bear into a more efficient workflow for you personally.&lt;/p&gt;

&lt;p&gt;But on its own, it's not much. Maybe it's good enough for you. But you'll probably want to do more with it if it's really going to be effective. Here are some thoughts I have on what I might do next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If this workflow grows in complexity for you, it'd likely be easier to work with an ORM. It could help keep the code a little simpler and keep you away from nasty SQL statements.&lt;/li&gt;
&lt;li&gt;Instead of simply targeting notes that weren't trashed, you might want to target through some other means, like a specific tag or set of tags.&lt;/li&gt;
&lt;li&gt;This doesn't extract files from the content. Images that you've dropped inline are still hidden away on your machine. You may want to extract them.&lt;/li&gt;
&lt;li&gt;Syncing seems like a dangerous game to play here. But if you do proper backups of the database and know what you're doing, it might be something to explore.&lt;/li&gt;
&lt;li&gt;I'm not protecting against duplicate tags or filenames, both of which could happen.&lt;/li&gt;
&lt;li&gt;Write some tests if you're going to take this into a production-level workflow!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Closing Question
&lt;/h2&gt;

&lt;p&gt;I hope you got something out of this and it has helped you work more efficiently. I'll leave you with a question before I go:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Is there any value in this being a library of sorts?&lt;/em&gt; In other words, if instead of going through this entire process, you could have installed an NPM package and run something like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bearapp export
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I've considered building something like this, but I'm not sure of the usage it would get. I'd love to know what you think or what you are doing with your Bear exporter.&lt;/p&gt;

&lt;p&gt;Keep writing, keep exporting, keep being awesome!&lt;/p&gt;

</description>
      <category>bear</category>
      <category>markdown</category>
      <category>node</category>
    </item>
    <item>
      <title>Generate Dynamic JSON Pages with Next.js</title>
      <dc:creator>Sean C Davis</dc:creator>
      <pubDate>Fri, 11 Jun 2021 10:28:15 +0000</pubDate>
      <link>https://forem.com/grouparoo/generate-dynamic-json-pages-with-next-js-4jko</link>
      <guid>https://forem.com/grouparoo/generate-dynamic-json-pages-with-next-js-4jko</guid>
      <description>&lt;p&gt;&lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt; is a super powerful tool for building scalable websites and web applications. Building dynamic web pages is no big thing with Next.&lt;/p&gt;

&lt;p&gt;I had a scenario pop up in which I wanted to generate and deliver JSON pages. I wanted to retrieve the data from elsewhere and then output it to a file that didn't have to change between builds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations of Pages in Next.js
&lt;/h2&gt;

&lt;p&gt;Part of the reason Next is equal parts powerful and easy to use is a result of the opinions it brings along. One such opinion is the way in which pages are delivered.&lt;/p&gt;

&lt;p&gt;While there are options to &lt;a href="https://nextjs.org/docs/basic-features/data-fetching"&gt;fetch data prior to rendering a page&lt;/a&gt;, pages are rendered as React components. And they are wrapped in application-level components.&lt;/p&gt;

&lt;p&gt;That means there isn't an easy way for me to follow the Next.js pages pattern to generate statically dynamic JSON pages.&lt;/p&gt;

&lt;p&gt;Fortunately, I found two ways in which I could still accomplish what I wanted in two other ways.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;Before we walk through these two examples, I'm assuming you have a Next.js project ready to go. If you don't you can use &lt;code&gt;create-next-app&lt;/code&gt; to start with the default template.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npx create-next-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We're going to install a single dependency for this example, &lt;a href="https://www.npmjs.com/package/axios"&gt;axios&lt;/a&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm install axios
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once it seems you're ready to go, boot that development server. With the default template, that command is:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And the server runs at localhost:3000 in your browser.&lt;/p&gt;

&lt;p&gt;If you had an existing Next project, you may have a different command to start the dev server and a different port on which the front end runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method #1: API Routes
&lt;/h2&gt;

&lt;p&gt;Now that you're up and running, let's look at our first option for generating JSON pages: API routes.&lt;/p&gt;

&lt;p&gt;Okay, I lied. A little. Pages in next don't &lt;em&gt;have to be&lt;/em&gt; React components. Next also supports what they call &lt;a href="https://nextjs.org/docs/api-routes/introduction"&gt;API routes&lt;/a&gt;. These are methods that run on the server side and return data back to the user. That feels like a really good use case for our scenario.&lt;/p&gt;

&lt;p&gt;Let's pretend that we want to return a single random dad joke.&lt;/p&gt;

&lt;h3&gt;
  
  
  Starting Simple
&lt;/h3&gt;

&lt;p&gt;As a quick introduction to API routes, let's first create a page at &lt;code&gt;pages/api/joke.js&lt;/code&gt; with the following content:&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="c1"&gt;// pages/api/joke.js&lt;/span&gt;

&lt;span class="k"&gt;export&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;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="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;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;World&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;Now visit &lt;a href="http://localhost:3000/api/joke"&gt;http://localhost:3000/api/joke&lt;/a&gt; in your browser, or make a GET request to that same URL through an API client. You'll see on screen (or in your client) the object we sent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"world"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great!&lt;/p&gt;

&lt;h3&gt;
  
  
  Making it Dynamic
&lt;/h3&gt;

&lt;p&gt;Now let's make it dynamic by adding axios and querying &lt;a href="https://icanhazdadjoke.com/api"&gt;the icanhazdadjoke.com API&lt;/a&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="c1"&gt;// pages/api/joke.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&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;axios&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&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;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://icanhazdadjoke.com/&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="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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;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;200&lt;/span&gt;&lt;span class="p"&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;data&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's not much to that, really. We're asking the icanhazdadjoke.com API for a response and passing that response on to the user.&lt;/p&gt;

&lt;p&gt;Now refresh your browser or make a request through your API client again and you'll be sent something classically witty like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xXg3LZLZDd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"joke"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*Reversing the car* &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Ah, this takes me back&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note that if you were going to take this into production, you'd want to put some checks in place to guard against the icanhazdadjoke.com API being down or giving you something you didn't expect.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Method #2: Static File
&lt;/h2&gt;

&lt;p&gt;The first method is powerful and all, but it's also forcing you into a solution in which you have to run that method (i.e. do some work, like hit another API) every time you want the data in this file.&lt;/p&gt;

&lt;p&gt;Recall in the intro that I mentioned a nuance of not needing the file to change in between builds. Thus, the JSON file itself should be &lt;em&gt;generated dynamically&lt;/em&gt;, but could be &lt;em&gt;delivered statically&lt;/em&gt;. (This has all the makings of a &lt;a href="https://www.seancdavis.com/blog/lets-talk-about-static-apis/"&gt;static API&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;While we know we can't make pages as static JSON files, we could &lt;em&gt;generate&lt;/em&gt; a static JSON file prior to building the site and serve it &lt;a href="https://nextjs.org/docs/basic-features/static-file-serving"&gt;as a static asset&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate Script
&lt;/h3&gt;

&lt;p&gt;To do that, let's put together a little script at &lt;code&gt;scripts/getJoke.js&lt;/code&gt; which retrieves the dad joke and then writes it to a file at &lt;code&gt;public/joke.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: We're putting it in the &lt;code&gt;public&lt;/code&gt; directory because these files get copied over directly. A file at &lt;code&gt;public/joke.json&lt;/code&gt; would be available at &lt;code&gt;/joke.json&lt;/code&gt; on our website.&lt;/em&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="c1"&gt;// scripts/getJoke.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&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="s2"&gt;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;fs&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="s2"&gt;fs&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;axios&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="s2"&gt;axios&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;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../public/joke.json&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;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&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;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://icanhazdadjoke.com/&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="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&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;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;Done.&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;Notice this is very similar to our API function. But instead of returning the response, we write it to file.&lt;/p&gt;

&lt;p&gt;To see it in action, you can run the script like this from the root of your project:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ node ./scripts/getJoke
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Check your &lt;code&gt;public&lt;/code&gt; directory for a &lt;code&gt;joke.json&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xXg3LZLZDd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"joke"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*Reversing the car* &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Ah, this takes me back&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can verify that it will be available on your website by visiting localhost:3000/joke.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automate It!
&lt;/h3&gt;

&lt;p&gt;To automate this process, we can add &lt;a href="https://docs.npmjs.com/cli/v7/using-npm/scripts#pre--post-scripts"&gt;a pre script&lt;/a&gt; to hook into the appropriate script in our &lt;code&gt;package.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;For example, let's say we want this to run &lt;em&gt;before&lt;/em&gt; we run the &lt;code&gt;npm run dev&lt;/code&gt; script. To do that, add a &lt;code&gt;predev&lt;/code&gt; script to your &lt;code&gt;package.json&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;package.json&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"predev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node ./scripts/getJoke"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"next dev"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, whenever you run &lt;code&gt;npm run dev&lt;/code&gt;, your &lt;code&gt;getJoke&lt;/code&gt; script will run, producing a new static file.&lt;/p&gt;

&lt;p&gt;Try it out. Stop your server (if it's still running) and start it back up. Then visit localhost:3000/joke to see new content!&lt;/p&gt;




&lt;p&gt;There are two methods for taking dynamic data and rendering it as JSON in your Next.js application. One updates itself on every request, the other on every build. Choose the best path for you and keep building cool things!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>json</category>
      <category>api</category>
    </item>
    <item>
      <title>Simple JavaScript Pipeline with webpack</title>
      <dc:creator>Sean C Davis</dc:creator>
      <pubDate>Fri, 28 May 2021 15:17:32 +0000</pubDate>
      <link>https://forem.com/seancdavis/simple-javascript-pipeline-with-webpack-27b4</link>
      <guid>https://forem.com/seancdavis/simple-javascript-pipeline-with-webpack-27b4</guid>
      <description>&lt;h2&gt;
  
  
  webpack Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.seancdavis.com/blog/wtf-is-webpack"&gt;webpack&lt;/a&gt; has a reputation for being pretty gnarly. If you've dug through the code of an established project using webpack, it's likely mind-boggling at best. Shoot, take a look at the source code for &lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt; — they have &lt;a href="https://github.com/vercel/next.js/tree/5f3351dbb8de71bcdbc91d869c04bc862a25da5f/packages/next/bundles/webpack"&gt;an entire directory to manage webpack configuration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That complexity is due, in large part, to its power. webpack can do &lt;em&gt;a lot&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Fortunately, the fine folks building this free and open source tool have been working hard to make each version a little easier to use than the previous. And now, you can start very simply, with very little config. Thus, you can justify the power of webpack in the smallest and simplest of projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We're Going to Build
&lt;/h2&gt;

&lt;p&gt;Let's do that. Let's build a super simple build pipeline to bundle multiple &lt;a href="https://www.seancdavis.com/blog/wtf-is-javascript/"&gt;JavaScript&lt;/a&gt; modules together into a single file that we can load from any HTML page.&lt;/p&gt;

&lt;p&gt;You can take a look at &lt;a href="https://github.com/seancdavis/seancdavis-com/tree/0cc19cb/examples/webpack-es6-pipeline"&gt;the full code example&lt;/a&gt; at any point if you get stuck.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gotcha!
&lt;/h3&gt;

&lt;p&gt;There's one big gotcha we'll have to overcome along the way. The output bundle is obfuscated and anonymous. That means we can't access it by default. And even if we could, we likely wouldn't know how to navigate it.&lt;/p&gt;

&lt;p&gt;In our case, we want to access our bundled code from external places (like an HTML file), so we're going to load our main exports into an &lt;code&gt;App&lt;/code&gt; object that we can then access from that main HTML file.&lt;/p&gt;

&lt;p&gt;Specifically in this example, we want to be able to call &lt;code&gt;App.Logger.sayHi()&lt;/code&gt; and see it print &lt;code&gt;"Hi!"&lt;/code&gt; to the console.&lt;/p&gt;

&lt;p&gt;Let's go!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Setup
&lt;/h2&gt;

&lt;p&gt;If you have a project ready to go, great! If not, feel free to &lt;a href="https://www.seancdavis.com/blog/new-javascript-project-setup/"&gt;follow my steps to get started&lt;/a&gt;, with the following notes:&lt;/p&gt;

&lt;p&gt;These are the dependencies we're going to use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;http-server&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;webpack&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;webpack-cli&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And here are the scripts to add to &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 json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;package.json&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WEBPACK_ENV=production webpack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webpack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"serve"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http-server dist -p 8000"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Add JavaScript Files
&lt;/h2&gt;

&lt;p&gt;Now let's add a couple JavaScript files. First, our Logger at &lt;code&gt;src/modules/logger.js&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="c1"&gt;// src/modules/logger.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sayHi&lt;/span&gt; &lt;span class="o"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hi!&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="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sayHi&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And our main file (&lt;code&gt;src/main.js&lt;/code&gt;), which will be responsible for exporting the &lt;code&gt;Logger&lt;/code&gt; object.&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="c1"&gt;// src/main.js&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;Logger&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;./modules/logger&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Logger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: If this were a bigger web project where you have more files in your &lt;code&gt;src&lt;/code&gt; directory, you'd probably want to put these files in some other nested place, like a &lt;code&gt;js&lt;/code&gt; directory.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Add webpack Config
&lt;/h2&gt;

&lt;p&gt;Next, let's add our webpack config. This code example is commented so you can see what's going on:&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="c1"&gt;// webpack.config.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&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="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// Used to determine whether to watch the files or build.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;env&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;WEBPACK_ENV&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;development&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// The main file for the bundle.&lt;/span&gt;
  &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/main.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Name of the bundle file.&lt;/span&gt;
    &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bundle.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// Directory in which the bundle should be placed.&lt;/span&gt;
    &lt;span class="c1"&gt;// Here we're saying `dist/js/bundle.js` will be our bundled file.&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dist/js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;// These two library items tells webpack to make the code exported by main.js available as a variable called `App`.&lt;/span&gt;
    &lt;span class="na"&gt;libraryTarget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;var&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;library&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;mode&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="c1"&gt;// If we're in development mode, then watch for changes, otherwise just do a single build.&lt;/span&gt;
  &lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&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;To summarize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;main.js&lt;/code&gt; is the primary targeted file, which will be bundled to &lt;code&gt;dist/js/bundle.js&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The exports from &lt;code&gt;main.js&lt;/code&gt; will be available globally in an &lt;code&gt;App&lt;/code&gt; variable.&lt;/li&gt;
&lt;li&gt;When the &lt;code&gt;WEBPACK_ENV&lt;/code&gt; is set to something other than &lt;code&gt;production&lt;/code&gt;, webpack will watch for changes. When &lt;code&gt;WEBPACK_ENV&lt;/code&gt; is set to &lt;code&gt;production&lt;/code&gt;, it will build the bundle and then stop running. We're setting this variable automatically in the scripts added to &lt;code&gt;package.json&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 4: Add HTML
&lt;/h2&gt;

&lt;p&gt;Now let's add a simple &lt;code&gt;index.html&lt;/code&gt; file to the &lt;code&gt;dist&lt;/code&gt; directory, which is where the bundled JS file is going to be placed.&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="c"&gt;&amp;lt;!-- dist/index.html --&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"X-UA-Compatible"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"IE=edge"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Simple Webpack ES6 Pipeline&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Hello there.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/js/bundle.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script&amp;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;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sayHi&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In most real-world cases, you're probably going to have some sort of a build step that would place the file here, likely provided by the static site generator or other framework you're using.&lt;/p&gt;

&lt;p&gt;In this case, we're placing the file in here as though it was already built so we can stay focused and not worry about all that setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Run it!
&lt;/h2&gt;

&lt;p&gt;We actually have two commands we have to run to get this to work. First, build the JavaScript bundle:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then you can run the web server:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm run serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now visit localhost:8000, open your browser's console, and you should see the message &lt;code&gt;"Hi!"&lt;/code&gt; printed!&lt;/p&gt;

&lt;p&gt;If you want to to make changes to JavaScript and see them reflected without reloading your web server, you can use two terminal tabs. Run &lt;code&gt;npm run serve&lt;/code&gt; in one to run the web server, and &lt;code&gt;npm run dev&lt;/code&gt; in the other, which will watch for JavaScript changes and rebuild.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/seancdavis/seancdavis-com/tree/0cc19cb/examples/webpack-es6-pipeline"&gt;Full code example&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;That's all it really takes to get up and running with webpack. Starting with a simple foundation is the key to understanding and working with webpack. Now you can build on this base and create something truly awesome and unique to you.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webpack</category>
    </item>
    <item>
      <title>Use Netlify Functions to Send Email Notifications</title>
      <dc:creator>Sean C Davis</dc:creator>
      <pubDate>Thu, 20 May 2021 13:49:48 +0000</pubDate>
      <link>https://forem.com/seancdavis/use-netlify-functions-to-send-email-notifications-25ae</link>
      <guid>https://forem.com/seancdavis/use-netlify-functions-to-send-email-notifications-25ae</guid>
      <description>&lt;p&gt;While &lt;a href="https://www.seancdavis.com/blog/wtf-is-netlify"&gt;Netlify&lt;/a&gt; supports email notifications around triggers and features within its system (e.g. &lt;a href="https://docs.netlify.com/site-deploys/notifications/"&gt;deploy events&lt;/a&gt;, &lt;a href="https://docs.netlify.com/forms/notifications/"&gt;form submissions&lt;/a&gt;), you may want to add custom email notification triggered by actions from users on your site.&lt;/p&gt;

&lt;p&gt;Notifications sound tricky, though, don't they?&lt;/p&gt;

&lt;p&gt;Well, they don't have to be. We can use &lt;a href="https://www.netlify.com/products/functions/"&gt;Netlify Functions&lt;/a&gt;, along with some email-sending service, to make that process a walk in the park.&lt;/p&gt;

&lt;p&gt;To keep this example as simple as possible, we're going to use &lt;a href="https://nodemailer.com/about/"&gt;Nodemailer&lt;/a&gt; with &lt;a href="https://ethereal.email/"&gt;Ethereal&lt;/a&gt; as our email sending service. That means we will have to configure very little, but the emails will be &lt;em&gt;caught&lt;/em&gt;, not sent.&lt;/p&gt;

&lt;p&gt;Let's dive into a quick example! (You can view a full version of the example code &lt;a href="https://github.com/seancdavis/seancdavis-com/tree/50322c36844e2db007a9daa29d6f22895febc90c/examples/netlify-functions-send-email"&gt;here&lt;/a&gt; at any time.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Setup Project
&lt;/h2&gt;

&lt;p&gt;Before we get started, you'll want a new project. Create a directory for your project. My first steps in a new project are usually these:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm init -y
$ echo 'node_modules' &amp;gt;&amp;gt; .gitignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then you can install the only dependency we need, Nodemailer:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm install nodemailer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Step 2: Add the Netlify Function
&lt;/h2&gt;

&lt;p&gt;Let's add a Node-based Netlify function that will handle sending our email message.&lt;/p&gt;

&lt;p&gt;The function will expect a stringified JSON object as the body, containing two key-value pairs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;email&lt;/code&gt;: The email address to use to send the message.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;body&lt;/code&gt;: The message to use as the body of the email.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Place the following code in &lt;code&gt;netlify/functions/send-email.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: &lt;code&gt;netlify/functions&lt;/code&gt; is the default location for functions. If you have overridden this value for you site, be sure to place the file in the appropriate location.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Also note that we're having Nodemailer make use of &lt;a href="https://ethereal.email/"&gt;Ethereal&lt;/a&gt;, which is a mail-catching service. That means none of the messages are going to actually be delivered. I'll talk a little more about this when we get to &lt;em&gt;Next Steps&lt;/em&gt; at the end of this post.&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="c1"&gt;// netlify/functions/send-email.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&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="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="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&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;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&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 JSON text received.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&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;event&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="c1"&gt;// Build an HTML string to represent the body of the email to be sent.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;div style="margin: 20px auto;"&amp;gt;&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;body&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/div&amp;gt;`&lt;/span&gt;

  &lt;span class="c1"&gt;// Generate test SMTP service account from ethereal.email. Only needed if you&lt;/span&gt;
  &lt;span class="c1"&gt;// don't have a real mail account for testing&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;testAccount&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;nodemailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createTestAccount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// create reusable transporter object using the default SMTP transport&lt;/span&gt;
  &lt;span class="kd"&gt;let&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;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;smtp.ethereal.email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;587&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;secure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// true for 465, false for other ports&lt;/span&gt;
    &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;testAccount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// generated ethereal user&lt;/span&gt;
      &lt;span class="na"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;testAccount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pass&lt;/span&gt; &lt;span class="c1"&gt;// generated ethereal password&lt;/span&gt;
    &lt;span class="p"&gt;}&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="c1"&gt;// send mail with defined transport object&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;info&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;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="s1"&gt;"☁️ The Cloud ☁️" &amp;lt;thecloud@example.com&amp;gt;&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="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&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;New Form Submission&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;text&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;body&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;html&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="c1"&gt;// Log the result&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;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&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;stringify&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="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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Catch and log error.&lt;/span&gt;
    &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&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;This will log the result to the console, regardless of whether it is successful or not (&lt;code&gt;callback(error)&lt;/code&gt; will print feedback), so you can have an idea of what's going on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Wire up the Front End
&lt;/h2&gt;

&lt;p&gt;Next, let's build a simple HTML page that gives you the ability to set the &lt;code&gt;email&lt;/code&gt; and &lt;code&gt;body&lt;/code&gt; fields. Then we'll add just a little JavaScript to make it all work.&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="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"X-UA-Compatible"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"IE=edge"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Send Email Notifications with Netlify Functions&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;onsubmit=&lt;/span&gt;&lt;span class="s"&gt;"submitForm(event)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"margin-bottom: 1rem"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email Address&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
          &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
          &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
          &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
          &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Where should I send the message?"&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;
        &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"margin-bottom: 1rem"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"body"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Message&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"body"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"body"&lt;/span&gt; &lt;span class="na"&gt;cols=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="na"&gt;rows=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Send email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
      &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;submitForm&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="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Stop the browser's default behavior.&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;preventDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;// Retrieve data from the form.&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&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;FormData&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;target&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;request&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;XMLHttpRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;// Convert data to JSON object.&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&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;jsonData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="c1"&gt;// Send the data to the Netlify function.&lt;/span&gt;
        &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&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;/.netlify/functions/send-email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;request&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="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="c1"&gt;// Clear the form.&lt;/span&gt;
        &lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email request submitted!&lt;/span&gt;&lt;span class="dl"&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;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;&lt;strong&gt;WARNING!&lt;/strong&gt; It's not good practice to use an &lt;code&gt;onsubmit&lt;/code&gt; attribute on a form element to call a global function when submitting a message. This is just a very simple example for demonstration purposes.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Not Getting Emails?
&lt;/h3&gt;

&lt;p&gt;If you're not getting the email messages in the inbox you specified, it's because we're not &lt;em&gt;actually&lt;/em&gt; delivering them!&lt;/p&gt;

&lt;p&gt;WTF?&lt;/p&gt;

&lt;p&gt;Yes. Ethereal, which we're using as our email server, is a mail-catching service, which means that it catches the mail requests and lets you read them, but it doesn't actually send them.&lt;/p&gt;

&lt;p&gt;If you want to see the messages being caught, then instead of creating a test account in the function, go to &lt;a href="https://ethereal.email/"&gt;Ethereal&lt;/a&gt; and click &lt;em&gt;Create Ethereal Account&lt;/em&gt;. Then plug the username and password in. You can then visit the inbox for that account and see everything that was caught.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Being that this is just the beginning of something that you'd actually put into practice, here are some ideas on where you could take it from here to get it ready for production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instead of submitting through an HTML page and relying on user input to control the recipient and the message, you could submit through an API request platform, like &lt;a href="https://www.postman.com/"&gt;Postman&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Add a legit emailing service in place of Nodemailer and Ethereal, such as &lt;a href="https://www.mailgun.com/"&gt;Mailgun&lt;/a&gt; or &lt;a href="https://sendgrid.com/"&gt;SendGrid&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Adjust which values you accept and which you hard-code, like subject or from email.&lt;/li&gt;
&lt;li&gt;Add some validation or authentication so not just anyone can trigger the action.&lt;/li&gt;
&lt;li&gt;Be mindful of limitations or quotas on your accounts. Netlify Functions enables you to have a generous number of requests per day. Email services are generally not as generous, and you could run up a bill quickly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No matter where you go with it, I'd love to learn more about your approach or any questions you have. &lt;a href="https://twitter.com/seancdavis29"&gt;Let's chat&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/seancdavis/seancdavis-com/tree/50322c36844e2db007a9daa29d6f22895febc90c/examples/netlify-functions-send-email"&gt;Demo code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>netlify</category>
      <category>email</category>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>Testing sessionStorage and localStorage with Selenium (Node)</title>
      <dc:creator>Sean C Davis</dc:creator>
      <pubDate>Mon, 17 May 2021 18:05:56 +0000</pubDate>
      <link>https://forem.com/grouparoo/testing-sessionstorage-and-localstorage-with-selenium-node-2336</link>
      <guid>https://forem.com/grouparoo/testing-sessionstorage-and-localstorage-with-selenium-node-2336</guid>
      <description>&lt;p&gt;I recently added &lt;a href="https://dev.to/blog/getting-previous-path-nextjs"&gt;a feature to the Grouparoo site that is using &lt;code&gt;sessionStorage&lt;/code&gt;&lt;/a&gt; to send analytics data. Being that it's an important feature, we &lt;em&gt;should&lt;/em&gt; write test(s) to cover the use case(s), right?&lt;/p&gt;

&lt;p&gt;Okay, fine. Let's do it!&lt;/p&gt;

&lt;p&gt;This website is a &lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt; application that uses &lt;a href="https://jestjs.io/"&gt;Jest&lt;/a&gt; as the test runner and &lt;a href="https://www.selenium.dev/"&gt;Selenium WebDriver&lt;/a&gt; for integration test help.&lt;/p&gt;

&lt;p&gt;What I wanted to do with Jest and Selenium was to read from &lt;code&gt;sessionStorage&lt;/code&gt; after visiting a series of pages. After a bit of perusing, I finally uncovered a (goofy) way to achieve what I wanted.&lt;/p&gt;

&lt;p&gt;We can use the &lt;code&gt;executeScript&lt;/code&gt; method to run a JavaScript expression and capture the result. &lt;a href="https://github.com/grouparoo/www.grouparoo.com/blob/main/__tests__/integration/sessionStorage.ts"&gt;Our test looks like this&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getSessionItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executeScript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`return window.sessionStorage.getItem("&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&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;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stores page history in the session data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;`/docs/config`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;expect&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;getSessionItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prevPath&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;null&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;expect&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;getSessionItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;currentPath&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/docs/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;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;`/meet`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;expect&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;getSessionItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prevPath&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/docs/config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;expect&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;getSessionItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;currentPath&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/meet&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;Here are a few of the key items to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You must &lt;code&gt;return&lt;/code&gt; the JavaScript expression or you'll end up with &lt;code&gt;undefined&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It's a much cleaner approach to run tests as &lt;code&gt;async&lt;/code&gt; functions so you can use &lt;code&gt;await&lt;/code&gt; to retrieve the result of the script, rather than ending up in a nightmarish Promise chain.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;browser&lt;/code&gt; is often referred to as &lt;code&gt;driver&lt;/code&gt; in other documentation and implementations. This comes from &lt;a href="https://github.com/alexeyraspopov/jest-webdriver/tree/master/packages/jest-environment-webdriver"&gt;the library we're using to connect Jest and Selenium&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This now works like a charm! You could take a similar approach if you wanted to read from any other JavaScript object, including &lt;code&gt;localStorage&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>node</category>
      <category>storage</category>
      <category>testing</category>
    </item>
    <item>
      <title>Change the Primary Key Data Type with Sequelize</title>
      <dc:creator>Sean C Davis</dc:creator>
      <pubDate>Wed, 12 May 2021 15:45:43 +0000</pubDate>
      <link>https://forem.com/grouparoo/change-the-primary-key-data-type-with-sequelize-5e3g</link>
      <guid>https://forem.com/grouparoo/change-the-primary-key-data-type-with-sequelize-5e3g</guid>
      <description>&lt;p&gt;We recently adjusted how we handle primary keys. Previously they were &lt;a href="https://en.wikipedia.org/wiki/Universally_unique_identifier"&gt;UUIDs&lt;/a&gt; with a max length of &lt;code&gt;40&lt;/code&gt; characters. With our &lt;a href="https://dev.to/blog/declarative-data-sync"&gt;Declarative Sync&lt;/a&gt; feature, we allow developers to set primary key values from their configuration files. Thus, we needed to lengthen the maximum number of characters allowed on primary keys in our database.&lt;/p&gt;

&lt;p&gt;Seems simple, right?&lt;/p&gt;

&lt;p&gt;I thought so, too. We're using &lt;a href="https://sequelize.org/"&gt;Sequelize&lt;/a&gt; as our &lt;a href="https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping"&gt;ORM tool&lt;/a&gt;, and I found a handy &lt;a href="https://sequelize.org/master/manual/query-interface.html#changing-the-datatype-of-a-column"&gt;&lt;code&gt;changeColumn&lt;/code&gt; method&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So that's what I did. It looked like this:&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;await&lt;/span&gt; &lt;span class="nx"&gt;migration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;changeColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;columnName&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;DataTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;191&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;Note: In these examples, I'm accessing Sequelize methods through an object called &lt;code&gt;migration&lt;/code&gt;. This is because we use &lt;a href="https://github.com/actionhero/ah-sequelize-plugin#migrations"&gt;Actionhero to run our database migrations&lt;/a&gt;. Your objects will look different, but the methods on them should be the same.&lt;/p&gt;

&lt;p&gt;I first tested with SQLite and &lt;em&gt;voila!&lt;/em&gt; It did exactly as I expected. All the primary keys were changed and working just lovely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Changing PostgreSQL Primary Keys
&lt;/h2&gt;

&lt;p&gt;Since we support both Postgres and SQLite as our application database, I moved on to test in Postgres, and that's when, instead of the database being properly migrated, I was presented with this lovely message:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;column "id" is in a primary key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I thought: &lt;em&gt;Yes, true. That is correct. And ... ?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It turns out Sequelize doesn't handle this action well with Postgres. After going down a rabbit hole in &lt;a href="https://www.postgresql.org/docs/13/ddl-constraints.html"&gt;playing around with constraints&lt;/a&gt;, I ended up just writing the SQL statement directly. It looked something like this:&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;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`ALTER TABLE "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" ALTER COLUMN "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;columnName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" SET DATA TYPE varchar(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;maxIdLength&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;migration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sequelize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That worked!&lt;/p&gt;

&lt;h2&gt;
  
  
  Consistency is Awesome! (SQLite is Weird.)
&lt;/h2&gt;

&lt;p&gt;It made sense to me to try to use the same approach with both databases. So I tried my Postgres solution with SQLite.&lt;/p&gt;

&lt;p&gt;It didn't work. (Sensing a theme yet?)&lt;/p&gt;

&lt;p&gt;That seemed odd. But, of course, we already know that &lt;a href="https://dev.to/blog/7-awesome-sqlite-quirks"&gt;SQLite is weird&lt;/a&gt;. And it turns out &lt;a href="https://sqlite.org/lang_altertable.html"&gt;SQLite's &lt;code&gt;ALTER TABLE&lt;/code&gt; methods&lt;/a&gt; are extremely (and intentionally) limited.&lt;/p&gt;

&lt;p&gt;Which meant I was stuck with two solutions. And when that happens, we tend to look at the current dialect and execute the appropriate code. And that's why this is the weird function that alters the primary key column in both Postgres and SQLite:&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;changeColumn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;columnName&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="k"&gt;if&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="nx"&gt;sequelize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialect&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postgres&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`ALTER TABLE "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" ALTER COLUMN "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;columnName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" SET DATA TYPE varchar(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;maxIdLength&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;migration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sequelize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;migration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;changeColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;columnName&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;DataTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;191&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&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;You can see the complete set of changes that came along with this code in &lt;a href="https://github.com/grouparoo/grouparoo/pull/1764"&gt;this pull request&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>node</category>
      <category>sequelize</category>
      <category>sqlite</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Understanding Types with SQLite and Node.js</title>
      <dc:creator>Sean C Davis</dc:creator>
      <pubDate>Thu, 29 Apr 2021 19:27:11 +0000</pubDate>
      <link>https://forem.com/grouparoo/understanding-types-with-sqlite-and-node-js-4l84</link>
      <guid>https://forem.com/grouparoo/understanding-types-with-sqlite-and-node-js-4l84</guid>
      <description>&lt;p&gt;Two fun facts about &lt;a href="https://www.sqlite.org/index.html"&gt;SQLite&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The initial release was more than 20 years ago!&lt;/li&gt;
&lt;li&gt;It is the &lt;a href="https://www.sqlite.org/mostdeployed.html"&gt;most widely used database&lt;/a&gt; (and likely one of the most widely deployed pieces of software).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And here are a few of my opinions on SQLite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's super cool.&lt;/li&gt;
&lt;li&gt;We don't talk about it enough.&lt;/li&gt;
&lt;li&gt;It's actually really easy to use (which is likely why it's so widely used).&lt;/li&gt;
&lt;li&gt;It is a little quirky.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So let's talk about this super cool thing. Let's look at how easy it is to use with Node.js before spending some time uncovering its quirks, which mostly have to do with how it handles data types.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Quick SQLite Example with Node.js
&lt;/h2&gt;

&lt;p&gt;If you're able to run Node on your machine and install packages &lt;a href="https://www.npmjs.com/"&gt;via NPM&lt;/a&gt;, then you can very easily create and manipulate a SQLite database. Let's go through an example in which we do just that — create a database, users table, and a few users.&lt;/p&gt;

&lt;p&gt;(Note: You can also work with SQLite without Node or NPM. That's just what we're using for our examples here today.)&lt;/p&gt;

&lt;p&gt;To get started, create a new directory for your project:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mkdir my-sqlite-project
$ cd my-sqlite-project
$ npm init -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Pro Tip:&lt;/strong&gt; This is the point at which I usually drop a &lt;code&gt;.gitignore&lt;/code&gt; file that ignores the &lt;code&gt;node_modules&lt;/code&gt; directory. And then I run &lt;code&gt;git init&lt;/code&gt; so I can start tracking my changes.&lt;/p&gt;

&lt;p&gt;Now that you have an empty directory, let's install our dependencies:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm install sqlite3 faker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Notice this installs &lt;a href="https://github.com/marak/Faker.js"&gt;Faker.js&lt;/a&gt; in addition to the SQLite library. Faker is a nice and simple library that will help us add random data when we create new users.&lt;/p&gt;

&lt;p&gt;Next, create an &lt;code&gt;index.js&lt;/code&gt; file with the following code:&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;sqlite3&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="s2"&gt;sqlite3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;verbose&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;faker&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="s2"&gt;faker&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;path&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="s2"&gt;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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;promisify&lt;/span&gt; &lt;span class="p"&gt;}&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="s2"&gt;util&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Create a new database named mydb.sqlite in the root of this project.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dbFilePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mydb.sqlite&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;db&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;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dbFilePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Use the promise pattern for SQLite so we don't end up in callback hell.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;promisify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// SQL query for creating a users table if it doesn't already exist.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createTableQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
  CREATE TABLE IF NOT EXISTS users (
    "id" INTEGER PRIMARY KEY,
    "email" TEXT NOT NULL,
    "first_name" TEXT,
    "last_name" TEXT,
    "created_at" TEXT
  )
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Generate user attributes using faker.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;internet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Run an INSERT query on some given table and insert the given object.
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;object&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;,&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;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;v&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;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;,&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;res&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;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`INSERT INTO &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;table&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;keys&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) VALUES (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;values&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Read all records and all their columns from some given table.
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;read&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;table&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&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;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`SELECT * FROM &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;table&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * The main controller of this script. This is inside an async function so we
 * can use the promise pattern.
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&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;// Create users table if it doesn't exist.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createTableQuery&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Create a new user.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newUser&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// Read all the users.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&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;read&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// Print to the console.&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;users&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the script:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ node index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;After you do that, two things should happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;You should see some output in the console that is an array containing a single user with the values you just randomly generated. Something like:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dawson39@yahoo.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dorris&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;West&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1619034411275&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="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You should have a new file in your project called &lt;code&gt;mydb.sqlite&lt;/code&gt;. This is your database!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can open up your database file with a SQLite browser. I'm a big fan of &lt;a href="https://tableplus.com/"&gt;TablePlus&lt;/a&gt;, which has a free version. But you can also use the simple-but-popular &lt;a href="https://sqlitebrowser.org/"&gt;DB Browser for SQLite&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you open your users table, you should see the same record represented as was printed to the console.&lt;/p&gt;

&lt;p&gt;See how easy that was?&lt;/p&gt;

&lt;p&gt;Now that we have that basic example in place, let's keep it around while we dig into some of SQLite's quirks.&lt;/p&gt;

&lt;h2&gt;
  
  
  SQLite Type Quirks
&lt;/h2&gt;

&lt;p&gt;SQLite is weird. Well, it's not really &lt;em&gt;weird&lt;/em&gt;, it's just ... simple. So it &lt;em&gt;feels&lt;/em&gt; weird in comparison to other beefy (and structured) databases like &lt;a href="https://www.postgresql.org/"&gt;PostgreSQL&lt;/a&gt; or &lt;a href="https://www.mysql.com/"&gt;MySQL&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Most of the quirks with SQLite have to do with how it handles types. And that's in large part because of how SQLite stores its data (as a file on your file system). That's right. That &lt;code&gt;mydb.sqlite&lt;/code&gt; file you created in the simple example above is your entire database. Cool, right?&lt;/p&gt;

&lt;p&gt;Let's dig in to the basics of SQLite's data types before we look at how we can better work with them when writing a program with Node.js.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding SQLite Types
&lt;/h3&gt;

&lt;p&gt;SQLite only has &lt;a href="https://www.sqlite.org/datatype3.html"&gt;five data types&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NULL&lt;/code&gt;: The value is a NULL value.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;INTEGER&lt;/code&gt;: The value is a signed integer, stored in 1, 2, 3, 4, 6, or 8 bytes depending on the magnitude of the value.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;REAL&lt;/code&gt;: The value is a floating point value, stored as an 8-byte IEEE floating point number.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TEXT&lt;/code&gt;: The value is a text string, stored using the database encoding (UTF-8, UTF-16BE or UTF-16LE).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BLOB&lt;/code&gt;: The value is a blob of data, stored exactly as it was input.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What the heck does that mean? Reading between the lines, it means that the data in your database is either going to be (in JavaScript speak) a &lt;code&gt;string&lt;/code&gt; or a &lt;code&gt;number&lt;/code&gt;, at least for the majority of cases.&lt;/p&gt;

&lt;p&gt;That's right. That means there are no dates or booleans in SQLite. WTF?&lt;/p&gt;

&lt;h3&gt;
  
  
  SQLite does not store date objects
&lt;/h3&gt;

&lt;p&gt;SQLite has &lt;a href="https://www.sqlite.org/lang_datefunc.html"&gt;date functions&lt;/a&gt;, but it will ultimately store the actual value as either a string or number.&lt;/p&gt;

&lt;p&gt;For example, I could run this query against the users table from above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'now'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the value will be stored as &lt;code&gt;2021-04-21&lt;/code&gt;, as a string.&lt;/p&gt;

&lt;h3&gt;
  
  
  SQLite also doesn't store booleans
&lt;/h3&gt;

&lt;p&gt;Instead, booleans used in SQL statements are converted to either &lt;code&gt;0&lt;/code&gt; or &lt;code&gt;1&lt;/code&gt;. In other words, &lt;code&gt;true&lt;/code&gt; in an insert statement becomes &lt;code&gt;1&lt;/code&gt; when it's stored in the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Type Affinity Works in SQLite
&lt;/h2&gt;

&lt;p&gt;SQLite uses what is called &lt;em&gt;dynamic typing&lt;/em&gt;. Where most other databases set the data type of some given value by the column definition, SQLite looks more directly at the value itself.&lt;/p&gt;

&lt;p&gt;But, there is some magic happening through what SQLite calls &lt;em&gt;type affinity&lt;/em&gt; on columns in a database. You can set a type for a column, but it's really just a &lt;em&gt;recommended&lt;/em&gt; type. SQLite will do its best to convert the value to that type, but it may not always work as you'd expect.&lt;/p&gt;

&lt;p&gt;The goofy thing here is that aside from matching a type affinity name when setting the type of a column, the name is virtually meaningless. It can be anything. &lt;a href="https://www.sqlite.org/datatype3.html#affinity_name_examples"&gt;Here are a set of types that map to type affinities in SQLite&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's go through some specific examples to see what happens when we use the Node sqlite3 library to interact with a SQLite database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Type Affinity Examples with SQLite and Node.js
&lt;/h2&gt;

&lt;p&gt;Let's go through a few examples together to show how type affinity works with SQLite and how we can use JavaScript to gain more control over types by manually casting them. Then we'll close with a cool approach for getting around all this nonsense.&lt;/p&gt;

&lt;p&gt;Using the example from the beginning of the post, let's add a few columns to our users table. Edit the &lt;code&gt;createTableQuery&lt;/code&gt; in &lt;code&gt;index.js&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createTableQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
  CREATE TABLE IF NOT EXISTS users (
    "id" INTEGER PRIMARY KEY,
    "email" TEXT NOT NULL,
    "first_name" TEXT,
    "last_name" TEXT,
    "created_at" TEXT,
    "a" TEXT,
    "b" INTEGER,
    "c" CHEESE
  )
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we've recreated the &lt;code&gt;users&lt;/code&gt; table with three new columns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;a&lt;/code&gt; of type &lt;code&gt;TEXT&lt;/code&gt;. This matches the &lt;code&gt;TEXT&lt;/code&gt; affinity.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;b&lt;/code&gt; of type &lt;code&gt;INTEGER&lt;/code&gt;. This matches the &lt;code&gt;INTEGER&lt;/code&gt; affinity.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;c&lt;/code&gt; of type &lt;code&gt;CHEESE&lt;/code&gt;. This has no affinity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then, let's set &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt;, and &lt;code&gt;c&lt;/code&gt; on our &lt;code&gt;newUser&lt;/code&gt; object to be a random number.&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;newUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;internet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;datatype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;datatype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;datatype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;number&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;Before we load the content into the database, let's check the type. And when we retrieve it from the database, we'll check the types again.&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;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createTableQuery&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// Log the data types going into the database.&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;key&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&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;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&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;users&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;read&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// Log the types coming out of the database.&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;key&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;value&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&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;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delete the database &lt;code&gt;mydb.sqlite&lt;/code&gt; and run the script:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ node index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Notice what you see on the console. Before the data went into the database, each of our new values was a &lt;code&gt;number&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a: number
b: number
c: number
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's what we should have expected, considering we set those attributes as random numbers.&lt;/p&gt;

&lt;p&gt;For me, when they came out of the database, they looked different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a: '91057' (string)
b: 9807 (number)
c: 31711 (number)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that &lt;code&gt;a&lt;/code&gt; is now a &lt;code&gt;string&lt;/code&gt;. Why? Because we set the column's type affinity to a value that SQLite will use to try to convert to a string. Meanwhile, nothing happened with the &lt;code&gt;CHEESE&lt;/code&gt; column because it has no affinity, so it left it as a number.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try again, but with strings
&lt;/h3&gt;

&lt;p&gt;If we do that again, but change &lt;code&gt;faker.datatype.number()&lt;/code&gt; to &lt;code&gt;faker.datatype.string()&lt;/code&gt;, we will see something slightly different.&lt;/p&gt;

&lt;p&gt;The types going in are all strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a: string
b: string
c: string
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But coming out they are still all strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a: 'i_`kneb8|]' (string)
b: '/@adUCVEV3' (string)
c: '@8eMpbKoFk' (string)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason &lt;code&gt;b&lt;/code&gt; wasn't converted to a number is because SQLite doesn't know how to convert that random string to a number. There's no logical choice for what the number should be. So it leaves the original value as is.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using a number as a string
&lt;/h3&gt;

&lt;p&gt;Now, one last time. Let's use a number again, but let's convert it to a string. Change &lt;code&gt;faker.datatype.string()&lt;/code&gt; to &lt;code&gt;faker.datatype.number.toString()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, going in we still have all strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a: string
b: string
c: string
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But coming out, it looks a little different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a: '42995' (string)
b: 22072 (number)
c: 3466 (number)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whoa! Something weird happened this time. Now our affinities match for &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; as they did in the first example. This is example SQLite knows how to turn &lt;code&gt;"22072"&lt;/code&gt; into an integer.&lt;/p&gt;

&lt;p&gt;But &lt;code&gt;c&lt;/code&gt; &lt;em&gt;changed&lt;/em&gt; from being a string to a number. That's because SQLite is trying to do the best it can't with what it is given. And in this case it figured it could turn the string into a number, so it did that. The only way to have kept it as a string would have been to use an appropriate affinity mapping value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Typecasting SQLite Queries using Node.js
&lt;/h2&gt;

&lt;p&gt;This quirk is pretty interesting and clever, but it's dangerous in real-world scenarios when you don't have bounds around how it can behave. You don't want your database turning strings into numbers without knowing why that's happening.&lt;/p&gt;

&lt;p&gt;One approach is to write your own getters and setters that typecast values more strictly. That way your program has more control over what is going into and coming out of the database.&lt;/p&gt;

&lt;p&gt;For example, consider a function that takes user data retrieved from the database and parses a &lt;code&gt;created_at&lt;/code&gt; field to convert it from an integer into a JavaScript date object. That might look something like this:&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;normalizeUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&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="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="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;created_at&lt;/span&gt;&lt;span class="p"&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;The list goes on and on, but that could be a lot to manage as your application grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Or Use an ORM!
&lt;/h2&gt;

&lt;p&gt;As your application grows in size and complexity, it likely will make sense to make use of some other library that can handle this typecasting logic for you. These libraries are called ORMs, which stands for &lt;em&gt;object-relational mapping&lt;/em&gt;. That's a fancy term for a tool that makes it easier to move from database to database without needing to change the syntax used to manipulate data in the database.&lt;/p&gt;

&lt;p&gt;At Grouparoo, we use &lt;a href="https://sequelize.org/"&gt;Sequelize&lt;/a&gt; to interact with our application database.&lt;/p&gt;

&lt;p&gt;Here's a simple example that does essentially what we were doing in the introductory example using Sequelize:&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;faker&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="s2"&gt;faker&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;path&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="s2"&gt;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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Sequelize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DataTypes&lt;/span&gt; &lt;span class="p"&gt;}&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="s2"&gt;sequelize&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;sequelize&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;Sequelize&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;dialect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sqlite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mydb.sqlite&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userAttrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;internet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;User&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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&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="nx"&gt;DataTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;allowNull&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DataTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DataTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt;&lt;span class="p"&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;sequelize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;modelName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sequelize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sync&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;users&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;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataValues&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;key&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&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;value&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;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice here that I'm more explicit about the column types. Sequelize then handles the typecasting for me. For example, running this once, this was the object returned to me from the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;id (number): 1
email (string): Erling_Friesen50@gmail.com
firstName (string): Easton
lastName (string): Kub
createdAt (object): Tue Apr 20 2021 13:50:17 GMT-0400 (Eastern Daylight Time)
updatedAt (object): Tue Apr 20 2021 13:50:17 GMT-0400 (Eastern Daylight Time)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that it actually sent &lt;code&gt;date&lt;/code&gt; objects for &lt;code&gt;createdAt&lt;/code&gt; and &lt;code&gt;updatedAt&lt;/code&gt;. (Also notice that Sequelize handled setting those values when I created the record. I didn't have to do anything.)&lt;/p&gt;

&lt;p&gt;There are plenty of other ORMs out there. Sequelize is among the most popular for Node. Another I came across recently is &lt;a href="https://www.prisma.io/"&gt;Prisma&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;That's all for this exploration through how types work with SQLite when using a Node.js library. I hope you learned something!&lt;/p&gt;

</description>
      <category>node</category>
      <category>sqlite</category>
      <category>sequelize</category>
    </item>
  </channel>
</rss>
