<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Orim Dominic Adah - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Orim Dominic Adah - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 24 May 2026 22:23:49 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/orimdominic/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Create Dynamic Emails in Go with React Email  ]]>
                </title>
                <description>
                    <![CDATA[ Backend applications are required to send emails to users to deliver notifications and maintain communication outside the application interface. These emails usually contain information specific to ea ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-create-dynamic-emails-in-go-with-react-email/</link>
                <guid isPermaLink="false">69e689acc9501dd0102dc758</guid>
                
                    <category>
                        <![CDATA[ Go Language ]]>
                    </category>
                
                    <category>
                        <![CDATA[ golang ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Orim Dominic Adah ]]>
                </dc:creator>
                <pubDate>Mon, 20 Apr 2026 20:16:44 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/62917f79-c4d8-40e2-8eb7-87b63560e546.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Backend applications are required to send emails to users to deliver notifications and maintain communication outside the application interface. These emails usually contain information specific to each user, such as the user's name or address, making them dynamic.</p>
<p>This article walks you through building a dynamic email template with React Email, converting it to HTML, and injecting data into it using Go templates. It also contains an optional section that shows you how to send and test the email delivery with MailHog.</p>
<p>To follow along with this article, you need to have Go and Node.js installed on your computer. You should also have a basic understanding of React and some familiarity with Go templates, though these aren't strict requirements because you can pick them up as you practise along.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-is-react-email">What is React Email?</a></p>
</li>
<li><p><a href="#heading-go-templates">Go Templates</a></p>
<ul>
<li><a href="#heading-go-template-delimiters">Go Template Delimiters</a></li>
</ul>
</li>
<li><p><a href="#heading-create-dynamic-emails-in-go-with-react-email">Create Dynamic Emails in Go with React Email</a></p>
<ul>
<li><p><a href="#heading-set-up-the-project">Set Up the Project</a></p>
</li>
<li><p><a href="#heading-set-up-react-email">Set Up React Email</a></p>
</li>
<li><p><a href="#heading-create-a-react-email-template">Create a React Email Template</a></p>
</li>
<li><p><a href="#heading-set-up-go-templates-from-html-files">Set Up Go Templates from HTML Files</a></p>
</li>
<li><p><a href="#heading-render-the-dynamic-email-in-the-browser">Render the Dynamic Email in the Browser</a></p>
</li>
<li><p><a href="#heading-send-and-test-email-with-go-mail-and-mailhog">Send and Test Email with go-mail and MailHog</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-is-react-email">What is React Email?</h2>
<p><a href="https://react.email/">React Email</a> is a JavaScript library that helps you build dynamic email templates with React. If you already know basic React, React Email provides a better developer experience for building dynamic email templates. Here are some reasons why:</p>
<ul>
<li><p><strong>Familiar syntax with React:</strong> If you know React already, React Email eliminates the hassle in learning a separate templating language, using inefficient drag-and-drop UIs, or writing emaiil templates from scratch with HTML tables.</p>
</li>
<li><p><strong>Reusable built-in components</strong>: React Email provides ready-to-use UI components like <a href="https://react.email/components/buttons">Buttons</a> and <a href="https://react.email/components/footers">Footers</a> so you don't have to start from scratch, making email development seamless and fast.</p>
</li>
<li><p><strong>Consistency across email clients</strong>: React Email generates email templates that have been tested and work well across popular email clients. This helps eliminate worries over emails rendering inconsistently across email clients.</p>
</li>
<li><p><strong>Email development tooling</strong>: React Email has features for previewing and assessing emails built with it. Some of these features include:</p>
<ul>
<li><p>A local development server that lets you preview mobile and desktop views of your emails in your web browser as you develop the emails in real time</p>
</li>
<li><p>An email delivery feature that sends your email to a real email address for preview</p>
</li>
<li><p>A compatibility checker that shows you how well your email is supported across popular email clients</p>
</li>
<li><p>A spam scorer that analyses your email to determine if it's likely to be marked as spam</p>
</li>
</ul>
</li>
<li><p><strong>Tailwind integration</strong>: <a href="https://tailwindcss.com/">Tailwind</a> is a popular CSS framework that provides classes for styling HTML and making it responsive. React Email integrates with Tailwind easily for creating beautiful emails.</p>
</li>
</ul>
<p>All these features are free to use.</p>
<p>In this article, you'll learn how to generate an HTML file from a React Email template, convert it to a Go template, and inject data into the template for previewing.</p>
<h2 id="heading-go-templates">Go Templates</h2>
<p>The Go <a href="https://pkg.go.dev/html/template">html/template</a> package allows you to define reusable HTML templates that can be populated with dynamic data. These templates contain placeholders (called actions) that are evaluated by Go's templating engine and replaced with actual values during execution.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e28b713f978a0e2cd2b763/7ff18217-2ff1-43fc-96b5-01681fbd0ac5.png" alt="Golang HTML template parsing and execution" style="display:block;margin:0 auto" width="974" height="397" loading="lazy">

<p>First, you give the package HTML content that contains Go-specific annotations. It converts the HTML content to a Go HTML template and the Go-specific annotations to actions in the template. The template is then executed with data to produce HTML output that contains the data.</p>
<pre><code class="language-go">package main

import (
	"html/template"
	"os"
)

func main() {
	tmpl := template.New("hello")
	tmpl, _ = tmpl.Parse(`&lt;p&gt;Hello {{.}}&lt;/p&gt;`)
	tmpl.Execute(os.Stdout, "Gopher")
}

// Output: &lt;p&gt;Hello Gopher&lt;/p&gt;
// Playground: https://goplay.tools/snippet/KxbkWPIArz5
</code></pre>
<p>In the code snippet above:</p>
<ul>
<li><p><code>template.New</code> creates an empty template object with the name "hello"</p>
</li>
<li><p><code>tmpl.Parse(`&lt;p&gt;Hello {{.}}&lt;/p&gt;`)</code> parses the HTML string <code>&lt;p&gt;Hello {{.}}&lt;/p&gt;</code> to create a Go HTML template and saves it in <code>tmpl</code> . The <code>{{.}}</code> part of the HTML string is an action which acts as a placeholder for data. <code>{{</code> and <code>}}</code> are called delimiters and <code>.</code> is the data access identifier.</p>
</li>
<li><p><code>tmpl.Execute(os.Stdout, "Gopher")</code> populates the action with data - the "Gopher", string, and writes the resulting HTML output to the console.</p>
</li>
</ul>
<h3 id="heading-go-template-delimiters">Go Template Delimiters</h3>
<p>In the previous code snippet, you used double curly braces (<code>{{</code> and <code>}}</code>) as the delimiters in the Go template. Delimiters are symbols that Go uses to determine what parts of the input string represent an action – that is, a statement to be evaluated.</p>
<p>You can change the delimiters by using the <code>Delims</code> method on a template. An example is shown in the snippet below:</p>
<pre><code class="language-go">package main

import (
	"html/template"
	"os"
)

func main() {
	tmpl := template.New("hello")
	tmpl, _ = tmpl.Delims("((", "))").Parse(`&lt;p&gt;Hello ((.))&lt;/p&gt;`)
	tmpl.Execute(os.Stdout, "Gopher")
}

// Output: &lt;p&gt;Hello Gopher&lt;/p&gt;
// Playground: https://goplay.tools/snippet/00RuDzvZYwN
</code></pre>
<p>In the snippet above, <code>((</code> and <code>))</code> are used as the delimiters for the <code>hello</code> template.</p>
<p>This is important because you'll set your delimiters to prevent conflicts between Go's default delimiters and React's curly braces in React Email templates.</p>
<h2 id="heading-create-dynamic-emails-in-go-with-react-email">Create Dynamic Emails in Go with React Email</h2>
<p>The image below summarizes how the sample application you'll build in this article works:</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e28b713f978a0e2cd2b763/289f0ea5-1de7-46f7-b463-32f6c61fa750.png" alt="From React Email templates to Email HTML with Dynamic Data" style="display:block;margin:0 auto" width="1614" height="704" loading="lazy">

<p>You'll create a React Email template that contains Go template annotations. Next, you'll use Node.js to create HTML files from it. Go will parse the HTML file to create a Go template, execute it, and send it.</p>
<p>Optionally, you'll use go-mail to send the email and MailHog, a local SMTP server, to preview it in your browser.</p>
<h3 id="heading-set-up-the-project">Set Up the Project</h3>
<p>First, make sure that you have Go and Node.js installed on your computer already. Clone this <a href="https://github.com/orimdominic/freeCodeCamp-go-react-email">freeCodeCamp-go-react-email</a> repository and checkout the <code>01-setup</code> branch using <code>git checkout 01-setup</code>.</p>
<p>The project contains a <code>main.go</code> file in the <code>cmd</code> directory and a <code>go.mod</code> file. It also contains a <code>.gitignore</code> file to instruct Git to ignore all <code>node_modules</code> directories.</p>
<p>Run <code>go run cmd/main.go</code> in the terminal of the project. If you see "It works!" logged to the console, you have set it up properly and you can continue to the next section.</p>
<h3 id="heading-set-up-react-email">Set Up React Email</h3>
<p>In the project root directory, create a <code>mailer</code> directory which will serve as the mailer package. It will hold all functionality related to creating and sending mails.</p>
<p>In the <code>mailer</code> directory, you'll create the <code>emails</code> Node.js project that will handle React Email functionality. To create the project:</p>
<ul>
<li><p>Create a directory called <code>_emails</code> in the <code>mailer</code> directory. The name of the directory starts with an underscore because it should be ignored when the <code>go build</code> command is run. So it won't be included in the Go compiled executable file.</p>
</li>
<li><p>Run <code>npm init -y</code> in the root terminal of the <code>_emails</code> directory to initialise the Node.js project in it. This will create a <code>package.json</code> file in the directory.</p>
</li>
<li><p>Update the value of the name field in <code>package.json</code> to "emails" to make the package name more conventional. This step is not compulsory.</p>
</li>
</ul>
<p>Next, install the required React Email libraries by running the following commands in the root terminal of the <code>_emails</code> directory:</p>
<pre><code class="language-shell">npm install @react-email/ui @types/react -D -E
npm install react-email react react-dom -E
</code></pre>
<p>After the installation is complete, replace the <code>scripts</code> field of the <code>package.json</code> file with the code snippet below:</p>
<pre><code class="language-json">  "scripts": {
    "dev": "email dev --dir ./src",
    "export": "email export --pretty --dir ./src --outDir ../templates"
  },
</code></pre>
<p>The <code>dev</code> script starts and runs the server for previewing the React Email templates in the browser. You will write the template with React and store it in the <code>src</code> directory under <code>_emails</code>. The <code>export</code> script transpiles the template files in the <code>src</code> directory from JSX (or TSX) to HTML and stores them in a directory called <code>templates</code>, a direct child of the <code>mailer</code> directory – not a child of the <code>_emails</code> directory.</p>
<p>The <code>templates</code> directory is stored as a child directory of the <code>mailer</code> directory because the Go project needs only the HTML output stored in the <code>templates</code> directory and not the contents of <code>_emails</code>.</p>
<p>If you've completed all these steps, you have set up React Email in the <code>emails</code> Node.js project. To view the current status of the project at this point, visit <a href="https://github.com/orimdominic/freeCodeCamp-go-react-email/tree/02-setup-react-email">freeCodeCamp-go-react-email/02-setup-react-email</a>.</p>
<p>In the next section, you'll create a React Email template and preview it in the browser.</p>
<h3 id="heading-create-a-react-email-template">Create a React Email Template</h3>
<p>In this section, you'll create a React Email template and preview it in the browser. You'll also build and export the template to HTML files.</p>
<p>Create a directory called <code>src</code> inside the <code>_emails</code> directory. Inside the <code>src</code> directory, create a file called <code>welcome.tsx</code>. Copy and paste the content of the snippet below into <code>welcome.tsx</code>.</p>
<pre><code class="language-typescript">import {
  Body,
  Button,
  Container,
  Head,
  Heading,
  Html,
  Img,
  Preview,
  Section,
  Tailwind,
  Text,
} from "react-email";

interface WelcomeEmailProps {
  username?: string;
  company?: string;
  gophers?: string[];
}

const WelcomeEmail = ({
  username = "Nicole",
  company = "GoWorld",
  gophers = ["Tinky Winky", "Dipsy", "Laa-Laa", "Po"],
}: WelcomeEmailProps) =&gt; {
  const previewText = `Welcome to \({company}, \){username}!`;

  return (
    &lt;Html&gt;
      &lt;Head /&gt;
      &lt;Preview&gt;{previewText}&lt;/Preview&gt;
      &lt;Tailwind&gt;
        &lt;Body className="m-auto font-sans"&gt;
          &lt;Container className="mb-10 mx-auto p-5 max-w-[465px]"&gt;
            &lt;Section className="mt-10"&gt;
              &lt;Img
                src={`https://storage.googleapis.com/gopherizeme.appspot.com/gophers/69428e5ec867c34bb4a49d5a063fdbc2a6633aed.png`}
                width="80"
                height="80"
                alt="Logo"
                className="my-0 mx-auto"
              /&gt;
            &lt;/Section&gt;
            &lt;Heading className="text-2xl font-normal text-center p-0 my-8 mx-0"&gt;
              Welcome to &lt;strong&gt;{company}&lt;/strong&gt;, {username}!
            &lt;/Heading&gt;
            &lt;Text className="text-start text-base"&gt;Hello {username},&lt;/Text&gt;
            &lt;Text className="text-start text-base leading-relaxed"&gt;
              We're excited to have you onboard at &lt;strong&gt;{company}&lt;/strong&gt;.
              We hope you enjoy your journey with us. If you have any questions
              or need assistance, feel free to reach out to any of the following
              gophers:
            &lt;/Text&gt;
            &lt;div className="text-start text-base leading-relaxed"&gt;
              &lt;ul className="pl-3"&gt;
                {gophers.map((gopher) =&gt; (
                  &lt;li&gt;{gopher}&lt;/li&gt;
                ))}
              &lt;/ul&gt;
            &lt;/div&gt;
            &lt;Section className="text-center mt-[32px] mb-[32px]"&gt;
              &lt;Button
                className="py-2.5 px-5 bg-white rounded-md text-base font-semibold no-underline text-center bg-black text-white"
                href={`https://go.dev`}
              &gt;
                Get Started
              &lt;/Button&gt;
            &lt;/Section&gt;
            &lt;Text className="text-start text-base text-white"&gt;
              Cheers,
              &lt;br /&gt;
              The {company} Team
            &lt;/Text&gt;
          &lt;/Container&gt;
        &lt;/Body&gt;
      &lt;/Tailwind&gt;
    &lt;/Html&gt;
  );
};

export default WelcomeEmail;
</code></pre>
<p>The code snippet above is the React Email template that you'll use in this article. To preview it, navigate to the terminal of the <code>_emails</code> root directory and run <code>npm run dev</code> . Use your web browser to visit the preview URL displayed on the terminal. Click on the "welcome" link on the left sidebar and you should see a UI similar to the one in the screenshot below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e28b713f978a0e2cd2b763/9a1253d2-7502-40d5-9081-7974c7a83f36.png" alt="React Email preview UI" style="display:block;margin:0 auto" width="1062" height="633" loading="lazy">

<p>In the UI above, React Email renders the email with the default values supplied to the <code>welcome</code> email template.</p>
<p>Stop the server by clicking on the terminal that runs it by pressing <code>CTRL + C</code>. Build the HTML output of the <code>src</code> directory and export it by running <code>npm run export</code> in the terminal of the <code>_emails</code> root directory. This creates a <code>templates</code> directory within the <code>mailer</code> directory where the exported HTML files are stored. In the <code>templates</code> directory, you'll see a <code>welcome.html</code> file – the HTML output from <code>welcome.tsx</code>.</p>
<p>To see the current status of the project, visit <a href="https://github.com/orimdominic/freeCodeCamp-go-react-email/tree/03-create-react-email-template">freeCodeCamp-go-react-email/03-create-react-email-template</a>.</p>
<h3 id="heading-set-up-go-templates-from-html-files">Set Up Go Templates from HTML Files</h3>
<p>You have created a React Email template, previewed it, built it, and exported it as an HTML file. In this section, you'll update the React Email template to use the delimiters you set and not React's curly braces. You'll also create a Go template from the HTML file.</p>
<p>To get started, replace the content of <code>welcome.tsx</code> with the code snippet below to to use <code>((</code> and <code>))</code> as delimiters and remove TypeScript types:</p>
<pre><code class="language-typescript">import {
  Body,
  Button,
  Container,
  Head,
  Heading,
  Html,
  Img,
  Preview,
  Section,
  Tailwind,
  Text,
} from "react-email";

const WelcomeEmail = () =&gt; {
  const previewText = `Welcome to ((.Company)), ((.Username))!`;

  return (
    &lt;Html&gt;
      &lt;Head /&gt;
      &lt;Preview&gt;{previewText}&lt;/Preview&gt;
      &lt;Tailwind&gt;
        &lt;Body className="m-auto font-sans"&gt;
          &lt;Container className="mb-10 mx-auto p-5 max-w-[465px]"&gt;
            &lt;Section className="mt-10"&gt;
              &lt;Img
                src={`https://storage.googleapis.com/gopherizeme.appspot.com/gophers/69428e5ec867c34bb4a49d5a063fdbc2a6633aed.png`}
                width="80"
                height="80"
                alt="Gopher"
                className="my-0 mx-auto"
              /&gt;
            &lt;/Section&gt;
            &lt;Heading className="text-2xl font-normal text-center p-0 my-8 mx-0"&gt;
              Welcome to &lt;strong&gt;((.Company))&lt;/strong&gt;, ((.Username))!
            &lt;/Heading&gt;
            &lt;Text className="text-start text-base"&gt;Hello ((.Username)),&lt;/Text&gt;
            &lt;Text className="text-start text-base leading-relaxed"&gt;
              We're excited to have you onboard at &lt;strong&gt;((.Company))&lt;/strong&gt;
              . We hope you enjoy your journey with us. If you have any
              questions or need assistance, feel free to reach out to any of the
              following Gophers:
            &lt;/Text&gt;
            &lt;div className="text-start text-base leading-relaxed"&gt;
              &lt;ul className="pl-3"&gt;
                ((range .Gophers))
                &lt;li&gt;((.))&lt;/li&gt;
                ((end))
              &lt;/ul&gt;
            &lt;/div&gt;
            &lt;Section className="text-center mt-[32px] mb-[32px]"&gt;
              &lt;Button
                className="py-2.5 px-5 bg-white rounded-md border text-black text-base font-semibold no-underline text-center"
                href={`https://go.dev`}
              &gt;
                Get Started
              &lt;/Button&gt;
            &lt;/Section&gt;

            &lt;Text className="text-start text-base"&gt;
              Cheers,
              &lt;br /&gt;
              The ((.Company)), Team
            &lt;/Text&gt;
          &lt;/Container&gt;
        &lt;/Body&gt;
      &lt;/Tailwind&gt;
    &lt;/Html&gt;
  );
};

export default WelcomeEmail;
</code></pre>
<p>Run <code>npm run export</code> in the root terminal of the <code>_emails</code> directory to build and export this version of the React Email template to HTML. The HTML generated will contain Go template annotations that will become actions when parsed by Go to form a Go HTML template.</p>
<p>In the <code>mailer</code> directory, create a file named <code>fs.go</code>. The code in the file will be used to embed the files in the <code>templates</code> directory for use in the Go application. Copy and paste the content of the snippet below into <code>fs.go</code>:</p>
<pre><code class="language-go">package mailer

import (
	"embed"
	"io/fs"
)

//go:embed templates/*
var embedded embed.FS
var templateFS, _ = fs.Sub(embedded, "templates")
</code></pre>
<p><code>//go:embed templates/*</code> tells the Go compiler to embed files from the current directory (<code>mailer</code>) into the compiled binary of the Go application. You need this to access the HTML template files from the Go application. <code>templateFS</code> will be used to access the files in the <code>templates</code> subdirectory.</p>
<p>Create another file in the <code>mailer</code> directory and name it <code>mailer.go</code>. <code>mailer.go</code> will contain code used to parse HTML files to make Go HTML templates and also send emails. Copy the content of the code snippet below into <code>mailer.go</code>:</p>
<pre><code class="language-go">package mailer

import (
	"html/template"
	"io"
)

const (
	welcomeMailKey = "welcome_mail"
)

func setUpTemplates() (map[string]*template.Template, error) {
	templates := make(map[string]*template.Template)

	tmpl := template.New("welcome.html").Delims("((", "))")
	welcomeEmailTmpl, err := tmpl.ParseFS(templateFS, "welcome.html")
	if err != nil {
		return nil, err
	}

	templates[welcomeMailKey] = welcomeEmailTmpl

	return templates, nil
}

type Mailer struct {
	templates map[string]*template.Template
}

// NewMailer creates a new mailer
func NewMailer() (*Mailer, error) {
	tpls, err := setUpTemplates()
	if err != nil {
		return nil, err
	}

	return &amp;Mailer{
		templates: tpls,
	}, nil
}

type WelcomEmailData struct {
	Username string
	Company  string
	Gophers  []string
}

func (mailer *Mailer) WriteWelcomeMail(w io.Writer, data WelcomEmailData) error {
	tmpl := mailer.templates[welcomeMailKey]
	err := tmpl.Execute(w, data)

	return err
}
</code></pre>
<p>In the code snippet above:</p>
<ul>
<li><p><code>setUpTemplates</code> creates a template object, <code>tmpl</code>, and sets its delimiters. <code>tmpl</code> parses <code>welcome.html</code> to convert it to a Go template and stores the template with <code>welcomeEmailTmpl</code> as its identifier. After that, <code>welcomeEmailTmpl</code> is added to the <code>templates</code> map with <code>welcomeMailKey</code> as its key and <code>templates</code> is returned.</p>
</li>
<li><p><code>NewMailer</code> creates and returns a <code>Mailer</code> object which holds the templates map and methods to work with the mail templates.</p>
</li>
<li><p>WriteWelcomeMail is a method on <code>Mailer</code> that's used to execute the welcome email template with real data.</p>
</li>
</ul>
<p>To view the current status of the codebase at this point, visit <a href="https://github.com/orimdominic/freeCodeCamp-go-react-email/tree/04-create-golang-template">freeCodeCamp-go-react-email/04-create-golang-template</a>.</p>
<h3 id="heading-render-the-dynamic-email-in-the-browser">Render the Dynamic Email in the Browser</h3>
<p>In this section, you'll create a simple web server to view the rendered email template containing the dynamic values passed to it.</p>
<p>Replace the content of <code>main.go</code> with the code snippet below:</p>
<pre><code class="language-go">package main

import (
	"fmt"
	"net/http"
	"os"

	pkgMailer "github.com/orimdominic/freeCodeCamp-go-react-email/mailer"
)

func main() {
	mailer, err := pkgMailer.NewMailer()
	if err != nil {
		fmt.Fprint(os.Stderr, err)
		os.Exit(1)
	}

	http.HandleFunc("/mail", func(w http.ResponseWriter, r *http.Request) {
		username := r.URL.Query().Get("username")
		company := r.URL.Query().Get("company")
		gophers := []string{"Tinky Winky", "Dipsy", "Laa-Laa", "Po"}

		err := mailer.WriteWelcomeMail(w, pkgMailer.WelcomEmailData{
			Username: username,
			Company:  company,
			Gophers:  gophers,
		})
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	})

	port := ":8888"
	err = http.ListenAndServe(port, nil)
	if err != nil {
		fmt.Fprint(os.Stderr, err)
		os.Exit(1)
	}
}
</code></pre>
<p>The code snippet above first creates a mailer object using the <code>NewMailer</code> function from <code>mailer.go</code>. After the error handling, it creates a simple web server running on port <code>8888</code> with a <code>GET /mail</code> route.</p>
<p>The <code>GET /mail</code> route accepts two query parameters: <code>username</code> and <code>company</code>, which will be used as the dynamic data for the email. The result of executing the template with <code>WriteWelcomeMail</code> is written as an HTML response on the browser. You'll use this route to test the functionality of the <code>mailer</code> package.</p>
<p>Before you start the server, you should build and export the React Email templates so that your HTML files always have the most recent changes from React Email templates. Instead of navigating between different directories to build, export and run the server, you can use a Makefile.</p>
<p>Navigate to the terminal of the root directory of the project and create a file called <code>Makefile</code>. Copy and paste the content of the code snippet below into it:</p>
<pre><code class="language-plaintext">run: email-build
	go run cmd/main.go

email-build: mailer/_emails
	npm --prefix mailer/_emails run export
</code></pre>
<p>The <code>run</code> script of the Makefile above builds and exports the React Email templates as HTML to the <code>mailer/templates</code> directory and then starts the Go application. Ensure that <code>Makefile</code> uses hard tabs, not spaces for indentation.</p>
<p>Run <code>make run</code> in the terminal of the root directory of the project and visit <code>http://localhost:8888/mail?username=Nicole&amp;company=GoWorld</code> in the browser. You'll see the email rendered on the browser UI.</p>
<img alt="Go template executed in the browser" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>Replace the values of <code>username</code> and <code>company</code> in the URL to test the email with different values.</p>
<p>With this setup, you can integrate the result of executing the template with your mail client and the email recipient will see the email as it's displayed in the browser.</p>
<p>To view the current status of the codebase at this point, visit <a href="https://github.com/orimdominic/freeCodeCamp-go-react-email/tree/05-render-dynamic-email">freeCodeCamp-go-react-email/05-render-dynamic-email</a>.</p>
<h3 id="heading-send-and-test-email-with-go-mail-and-mailhog">Send and Test Email with go-mail and MailHog</h3>
<p>In the previous section, you supplied data to execute your template, but it was rendered in the browser, not an email client. In this section, you'll use go-mail to send the email and MailHog to intercept and view it.</p>
<p>This section is optional. If you don't have MailHog installed locally, you'll need Docker Compose to set it up for this project. Make sure Docker Compose is installed on your computer before proceeding.</p>
<p>In your terminal, navigate to the root directory of the project and run <code>go get github.com/wneessen/go-mail</code> to install go-mail. Create a <code>compose.yml</code> file in the root directory of the project and paste the contents of the code snippet below into it:</p>
<pre><code class="language-yaml">services:
  mailhog:
    image: mailhog/mailhog
    restart: no
    logging:
      driver: "none" # disable saving logs
    ports:
      - 1025:1025 # smtp server
      - 8025:8025 # web ui
</code></pre>
<p>In your terminal, navigate to the project's root directory and run <code>docker compose up</code> to pull and start the MailHog SMTP server. MailHog listens for emails on port <code>1025</code> and exposes a web UI at <code>http://localhost:8025</code> where you can view intercepted emails. Depending on your internet connection, the initial image pull may take a few minutes.</p>
<p>Replace <code>mailer.go</code> with the content of the code snippet below:</p>
<pre><code class="language-go">package mailer

import (
	"html/template"
	"io"

	"github.com/wneessen/go-mail"
)

const (
	welcomeMailKey = "welcome_mail"
    sender = "noreply@localhost.com"
)

func setUpTemplates() (map[string]*template.Template, error) {
	templates := make(map[string]*template.Template)

	tmpl := template.New("welcome.html").Delims("((", "))")
	welcomeEmailTmpl, err := tmpl.ParseFS(templateFS, "welcome.html")
	if err != nil {
		return nil, err
	}

	templates[welcomeMailKey] = welcomeEmailTmpl

	return templates, nil
}

type Mailer struct {
	client    *mail.Client
	templates map[string]*template.Template
}

// NewMailer creates a new mailer
func NewMailer() (*Mailer, error) {
	tpls, err := setUpTemplates()
	if err != nil {
		return nil, err
	}

	c, err := mail.NewClient(
		"localhost",
		mail.WithPort(1025),
		mail.WithTLSPolicy(mail.NoTLS),
	)

	if err != nil {
		return nil, err
	}

	return &amp;Mailer{
		client:    c,
		templates: tpls,
	}, nil
}

type WelcomEmailData struct {
	Username string
	Company  string
	Gophers  []string
}

func (mailer *Mailer) WriteWelcomeMail(w io.Writer, data WelcomEmailData) error {
	tmpl := mailer.templates[welcomeMailKey]
	err := tmpl.Execute(w, data)

	return err
}

func (mailer *Mailer) SendWelcomeMail(to string, data WelcomEmailData) error {
	m := mail.NewMsg()
	m.From(sender)
	m.To(to)
	m.Subject("Welcome to " + data.Company)
	m.SetBodyHTMLTemplate(mailer.templates[welcomeMailKey], data)

	err := mailer.client.DialAndSend(m)
	return err
}
</code></pre>
<p>The new changes to <code>mailer.go</code> include:</p>
<ul>
<li><p>An import of the go-mail</p>
</li>
<li><p>The creation of a <code>sender</code> constant which represents the email of the sender</p>
</li>
<li><p>The creation of a mail client with go-mail</p>
</li>
<li><p>The creation of a <code>SendWelcomeMail</code> method on the <code>mailer</code> struct which creates an email with <code>welcomeEmailTmpl</code>, executes it, and sends it to a receiver.</p>
</li>
</ul>
<p>In <code>main.go</code>, update the <code>GET /mail</code> route to use <code>SendWelcomeMail</code> instead of <code>WriteWelcomeMail</code>. You can use any email address you want. The snippet below uses <code>fcc@go.dev</code>:</p>
<pre><code class="language-go">		err := mailer.SendWelcomeMail("fcc@go.dev", pkgMailer.WelcomEmailData{
			Username: username,
			Company:  company,
			Gophers:  gophers,
		})
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		fmt.Fprint(w, "Email sent")
</code></pre>
<p>Ensure that the mail server is running by visiting <a href="http://localhost:8025">http://localhost:8025</a> in your web browser. In another terminal, from the root directory of the project, run <code>make run</code> to start the server. Test the functionality of the route by visiting <code>http://localhost:8888/mail?username=Nicole&amp;company=GoWorld</code> once again. Next, check the email server by visiting <a href="http://localhost:8025">http://localhost:8025</a>. You should see a UI similar to the one in the screenshot below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e28b713f978a0e2cd2b763/3cba1adf-f0e7-4539-8f65-931fef1aa73b.png" alt="MailHog UI for previewing mails" style="display:block;margin:0 auto" width="1005" height="486" loading="lazy">

<p>Click on "Welcome to Helix" to view the email.</p>
<p>To view the current status of the codebase at this point, visit <a href="https://github.com/orimdominic/freeCodeCamp-go-react-email/tree/06-send-email">freeCodeCamp-go-react-email/06-send-email</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>By following along with this tutorial, you have:</p>
<ul>
<li><p>Learned how to create Go email templates from React Email templates</p>
</li>
<li><p>Learned how to use Makefiles to run custom scripts</p>
</li>
<li><p>Previewed your email in the browser and tested it using MailHog</p>
</li>
</ul>
<p>You can now skip the hassle of writing raw HTML email tables or learning a new templating language. With React Email and Go templates, you have a cleaner, more developer-friendly way to build and send beautiful emails.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Voice-Powered AI Application with the Web Speech API ]]>
                </title>
                <description>
                    <![CDATA[ The Web Speech API is a web browser API that enables web applications to use sound as data in their operations. With the API, web apps can transcribe the speech in sound input and also synthesise spee ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-voice-powered-ai-application-with-the-web-speech-api/</link>
                <guid isPermaLink="false">69c5a2af10e664c5da34709b</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Orim Dominic Adah ]]>
                </dc:creator>
                <pubDate>Thu, 26 Mar 2026 21:18:39 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/d6c77704-8ad6-4852-8a10-6656c76a34f4.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API">Web Speech API</a> is a web browser API that enables web applications to use sound as data in their operations. With the API, web apps can transcribe the speech in sound input and also synthesise speech from text.</p>
<p>This guide shows you how to build a full-stack web application that:</p>
<ul>
<li><p>Accepts audio input and transcribes the speech in it</p>
</li>
<li><p>Prompts an AI agent with the transcription</p>
</li>
<li><p>Displays the AI response on the UI</p>
</li>
</ul>
<p>The application you'll build will be a simplified version of the <strong>Use Voice</strong> feature on AI chat applications highlighted in the image below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e28b713f978a0e2cd2b763/7adc60ff-cedb-48bc-a5c9-6e913dd3cc60.png" alt="Use voice feature of AI chat applications" style="display:block;margin:0 auto" width="700" height="390" loading="lazy">

<p>By practising along with this article, you'll learn how to:</p>
<ul>
<li><p>Build a frontend application that uses the <a href="https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition">SpeechRecognition</a> API to accept voice input and transcribe it</p>
</li>
<li><p>Build a backend app that prompts an AI assistant of your choice and sends a response back to clients</p>
</li>
<li><p>Connect both applications together to send the transcription to the backend as a prompt and display the AI response on the frontend</p>
</li>
</ul>
<p>Optionally, you'll also learn how to host the frontend with Firebase and the backend with Google Cloud Run.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-the-web-speech-api">The Web Speech API</a></p>
<ul>
<li><a href="#heading-how-to-use-the-web-speech-api-in-javascript-for-seo">How to Use the Web Speech API in JavaScript for SEO</a></li>
</ul>
</li>
<li><p><a href="#heading-how-the-application-works">How the Application Works</a></p>
</li>
<li><p><a href="#heading-how-to-build-the-application">How to Build the Application</a></p>
<ul>
<li><p><a href="#heading-create-the-backend-application-with-nodejs">Create the Backend Application with Node.js</a></p>
</li>
<li><p><a href="#heading-integrate-an-ai-assistant-into-the-nodejs-application">Integrate an AI Assistant into the Node.js Application</a></p>
</li>
<li><p><a href="#heading-create-the-frontend-application-with-vite">Create the Frontend Application with Vite</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-test-the-application-locally">Test the Application Locally</a></p>
</li>
<li><p><a href="#heading-deploy-the-backend-application-with-google-cloud-run">Deploy the Backend Application with Google Cloud Run</a></p>
</li>
<li><p><a href="#heading-deploy-the-frontend-application-with-firebase">Deploy the Frontend Application with Firebase</a></p>
</li>
<li><p><a href="#heading-connect-the-deployed-applications">Connect the Deployed Applications</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>This guide assumes that you have a working knowledge of HTML, CSS, and JavaScript in the browser. Basic familiarity with Node.js is beneficial but not essential.</p>
<p>In addition, you should have:</p>
<ul>
<li><p>Google Chrome (at least version 33 ) and a functional audio input device</p>
</li>
<li><p><a href="https://nodejs.org/">Node.js</a>&nbsp;and npm installed on your computer</p>
</li>
<li><p>An API key from any AI assistant of your choice</p>
</li>
<li><p>A Google Cloud account and a Firebase account if you intend to deploy the applications</p>
</li>
</ul>
<h2 id="heading-the-web-speech-api">The Web Speech API</h2>
<p>The Web Speech API enables applications to transcribe the speech in audio input and also synthesise audio from text. The API is made up of two components:</p>
<ul>
<li><p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition">SpeechRecognition</a> component which receives audio input, recognises speech in the input and transcribes it</p>
</li>
<li><p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis">SpeechSynthesis</a> component which synthesises speech from text</p>
</li>
</ul>
<p>You'll use the <code>SpeechRecognition</code> component in this guide.</p>
<h3 id="heading-how-to-use-the-web-speech-api-in-javascript-for-seo">How to Use the Web Speech API in JavaScript for SEO</h3>
<p>The <code>SpeechRecognition</code> component works through a JavaScript object instantiated in code.</p>
<pre><code class="language-javascript">const recognition = new SpeechRecognition();
</code></pre>
<p>The <code>recognition</code> instance exposes several event listeners that respond to audio input. For example, the <code>audiostart</code> event fires when sound is first detected, logging <code>"audio detected"</code> to the console as shown in the snippet below.</p>
<pre><code class="language-javascript">recognition.addEventListener("audiostart", function(event){
  console.log("audio detected")
}
</code></pre>
<p>The first time it recognises speech in a sound bite, the <code>speechstart</code> event is fired.</p>
<p>A <code>SpeechRecognition</code> instance also has the ability to configure how speech recognition should work. For example, it has a property called <code>lang</code> which sets the language that it should recognise. The default value of the <code>lang</code> property is the HTML <code>lang</code> attribute value, or the browser's language setting. It also has a boolean property called <code>interimResults</code>, which when set to true, enables the instance to return transcriptions incrementally rather than waiting for the audio input to end.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e28b713f978a0e2cd2b763/d3e7b39a-fcf2-4624-81ca-4667346c8269.png" alt="How speech is converted to transcripts via the Web Speech API" style="display:block;margin:0 auto" width="1600" height="124" loading="lazy">

<p>Audio captured by the microphone is processed by a recognition engine which could be in a remote server (for Google Chrome) or embedded in the browser (for Firefox).</p>
<p>After processing, the recognition engine returns a result, which is a list of words or phrases that have been recognised in the speech.</p>
<p>Each transcription in the list has two properties: <code>confidence</code>, a numerical estimate of its accuracy ranging from 0 (low) to 1 (high), and <code>transcript</code>, the recognised text for all or part of the speech.</p>
<h2 id="heading-how-the-application-works">How the Application Works</h2>
<p>In order for a <code>SpeechRecognition</code> instance to capture audio, it needs access to the microphone. The browser requests permission to use the microphone and, if granted, the application uses it to capture audio for the instance.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e28b713f978a0e2cd2b763/e2847268-dfca-4f81-b929-7cc8ebd57eee.png" alt="Architecture diagram showing how Web Speech API sends transcription to a Node.js backend" style="display:block;margin:0 auto" width="1279" height="514" loading="lazy">

<p>Speech captured by the instance goes through the recognition engine and produces results or transcriptions. Results with high confidence are combined and sent to the backend via an API request.</p>
<p>The backend uses the transcript it receives to prompt an AI assistant. The response from the AI assistant is sent back to the frontend and displayed on the UI as shown in the screenshot below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e28b713f978a0e2cd2b763/0cd3eb46-595b-4193-82a0-874e8b9f9652.png" alt="Sample image of the voice-powered application built in this guide" style="display:block;margin:0 auto" width="914" height="476" loading="lazy">

<h2 id="heading-how-to-build-the-application">How to Build the Application</h2>
<p>First, you'll build a Node.js backend application that:</p>
<ul>
<li><p>Receives a text prompt from the frontend</p>
</li>
<li><p>Sends the prompt to an AI assistant and receives a response</p>
</li>
<li><p>Returns the response of the AI assistant to the frontend</p>
</li>
</ul>
<p>Next, you'll build the frontend to:</p>
<ul>
<li><p>Accept your speech prompt, transcribe it, and display the transcription</p>
</li>
<li><p>Send the transcription result to the backend</p>
</li>
<li><p>Receive, format and display the response from the backend</p>
</li>
</ul>
<p>Optionally, you'll deploy the frontend to Firebase and the backend to Google Cloud Run, connecting them so the application is publicly accessible.</p>
<h3 id="heading-create-the-backend-application-with-nodejs">Create the Backend Application with Node.js</h3>
<p>The backend application you'll build in this section will receive text prompt from clients and use it to prompt an AI assistant. After receiving a response from the AI assistant, it will send the response back to the client.</p>
<p>We'll use Gemini in this guide, but you can use any AI assistant of your choice.</p>
<ol>
<li><p>Create a folder for the backend app and give it a name, for example, "server".</p>
</li>
<li><p>In terminal, navigate to the project folder, run the <code>npm init</code> command, and answer the follow-up questions to generate a <code>package.json</code> file</p>
</li>
<li><p>In the root of the project, create a file named <code>index.js</code>.</p>
</li>
</ol>
<p>Your project folder should have a structure like this:</p>
<pre><code class="language-plaintext">├── index.js
├── package.json
</code></pre>
<p>The <code>package.json</code> file should have the following values for <code>main</code> , <code>type</code> and <code>scripts.start</code>:</p>
<pre><code class="language-json"> { 
    "main": "index.js", 
    "type": "module", 
    "scripts": { 
       "start": "node index.js" 
    }, 
}  
</code></pre>
<ol>
<li>Copy and paste the code below into the <code>index.js</code> file to set up the server:</li>
</ol>
<pre><code class="language-javascript">import http from "node:http";

async function parseRequestBody(req) { 
    return new Promise((resolve, reject) =&gt; { 
        let data = ""; 
        req.on("data", (chunk) =&gt; (data += chunk)); 
        req.on("end", () =&gt; resolve(JSON.parse(data))); 
        req.on("error", reject); 
    }); 
}

const server = http.createServer(async function (req, res) { 
    switch (req.method) { 
        case "POST":
          return res.end("POST request received");
        default:
          return res.end("non-POST request received");
    }
})

const port = Number(process.env.PORT) || 8000; 
server.listen(port, function () { 
    console.log("server running on port", port); 
});
</code></pre>
<p>In the code snippet above, the <code>http</code> module is imported from Node.js. The <code>parseRequestBody</code> function converts the request body stream of a HTTP request to a JavaScript object.</p>
<p>It responds with <code>POST request received</code> for POST requests and <code>non-POST request received</code> for all others. By default, it listens on port 8000 unless a <code>PORT</code> environment variable is defined.</p>
<p>Run <code>npm run start</code> to start the server. To confirm it is running, execute the following command in the terminal:</p>
<pre><code class="language-shell"># For Linux/Mac, use:
curl -X POST -H "Content-Type: application/json" -d '{"prompt":"hello"}' http://localhost:8000

# For Windows, use:
curl.exe -X POST -H "Content-Type: application/json" -d '{"prompt":"hello"}' http://localhost:8000
</code></pre>
<p>You'll get the <code>POST request received</code> response from the server.</p>
<h3 id="heading-integrate-an-ai-assistant-into-the-nodejs-application">Integrate an AI Assistant into the Node.js Application</h3>
<p>In this section, you'll integrate the AI assistant into the backend application, prompt it with data sent from the frontend, and return its response to the client. Again, we'll use Gemini for this here.</p>
<p>Visit the npm page for your chosen AI assistant to learn how to install and set it up. Here are the npm pages for the most popular AI assistants:</p>
<ul>
<li><p><a href="https://www.npmjs.com/package/@anthropic-ai/sdk">Anthropic AI</a></p>
</li>
<li><p><a href="https://www.npmjs.com/package/@google/genai">Google Gemini</a></p>
</li>
<li><p><a href="https://www.npmjs.com/package/openai">Open AI</a></p>
</li>
</ul>
<p>Update the <code>index.js</code> file to include the setup for the AI assistant using the snippet below:</p>
<pre><code class="language-javascript">import http from "node:http";
import { GoogleGenAI } from "@google/genai"; 

const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });

async function parseRequestBody(req) { /* minimised code */ }

const server = http.createServer(async function (req, res) {
    res.setHeader("Access-Control-Allow-Origin", "*");

    switch (req.method) { 
        case "POST":
          const body = await parseRequestBody(req);
          const response = await ai.models.generateContent({
            model: "gemini-2.5-flash", // or whatever model you have
            contents: body.prompt,
         });

         return res.end(response.text);

        default:
          return res.end("non-POST request received");
    }
}
/* previous code minimised*/
</code></pre>
<p>The <code>GEMINI_API_KEY</code> is retrieved from the environment variables and passed as the <code>apiKey</code> to <code>GoogleGenAI</code>, which initialises the AI assistant.</p>
<p>The POST request body is parsed into a JavaScript object, and <code>body.prompt</code> is passed to <code>ai.models.generateContent</code> to prompt the AI assistant. The <code>text</code> property of the response which is in Markdown format, is then returned to the client.</p>
<p>Restart the server and test the current setup by making an API request to it with curl using the snippet below:</p>
<pre><code class="language-shell"># For Linux/Mac:

curl -X POST -H "Content-Type: application/json" -d '{"prompt":"hello"}' http://localhost:8000

# For Windows:

curl.exe -X POST -H "Content-Type: application/json" -d '{"prompt":"hello"}' http://localhost:8000
</code></pre>
<p>You'll get an AI text response in the form of Markdown.</p>
<h3 id="heading-create-the-frontend-application-with-vite">Create the Frontend Application with Vite</h3>
<p><a href="https://vite.dev/">Vite</a> is a build tool that provides a faster and more seamless development experience for developing applications. You'll use Vite to create the frontend application and connect it with the backend application from the previous section.</p>
<p>In another folder, create a project with Vite by running the <code>npm create vite@latest</code> command and answer the prompts:</p>
<pre><code class="language-shell">npm create vite@latest

Need to install the following packages:
create-vite@8.1.0
Ok to proceed? (y) y

&gt; npx create-vite

◇  Project name:
│  [name-of-your-frontend-app] e.g prompt-ai-with-speech-frontend
│
◇  Select a framework:
│  Vanilla
│
◇  Select a variant:
│  JavaScript
│
◇  Use rolldown-vite (Experimental)?:
│  No
│
◇  Install with npm and start now?
│  Yes
</code></pre>
<p>Open the project created in your code editor and make the following updates:</p>
<p>First, replace the content of <code>index.html</code> with the code snippet below:</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&gt;
    &lt;title&gt;Prompt AI with the Web Speech Recognition API&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;main id="app"&gt;
      &lt;section&gt;
        &lt;h1&gt;Prompt AI with the Web Speech Recognition API&lt;/h1&gt;
        &lt;ul id="ulist_chat"&gt;&lt;/ul&gt;
      &lt;/section&gt;
      &lt;div class="btn_container"&gt;
        &lt;button id="btn_record"&gt;Record prompt&lt;/button&gt;
      &lt;/div&gt;
    &lt;/main&gt;
    &lt;script type="module" src="/src/main.js"&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>Then replace the content of <code>src/style.css</code> with the code snippet below:</p>
<pre><code class="language-css">:root {
  font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
  line-height: 1.5;
  font-weight: 400;

  color-scheme: light dark;
  color: rgba(255, 255, 255, 0.87);
  background-color: #242424;

  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

button {
  border-radius: 8px;
  border: 1px solid transparent;
  padding: 0.6em 1.2em;
  font-size: 1em;
  font-weight: 500;
  font-family: inherit;
  background-color: #1a1a1a;
  cursor: pointer;
  transition: border-color 0.25s;
}
button:hover {
  border-color: #646cff;
}
button:focus,
button:focus-visible {
  outline: 4px auto -webkit-focus-ring-color;
}
.btn_container {
  padding: 16px 0px;
  display: flex;
  justify-content: center;
}

#ulist_chat {
  display: flex;
  flex-direction: column;
  width: 80%;
  margin: auto;
  padding: 0;
}

#ulist_chat .transcript {
  border: 1px solid tomato;
  background: #fce5e5af;
  border-radius: 4px;
  align-self: flex-end;
  list-style-type: none;
  margin: 8px;
  padding: 8px;
  max-width: 80%;
}

#ulist_chat .ai_response p { 
  margin: 2px; 
}

#ulist_chat .ai_response {
  border: 1px solid green;
  background: #e5fce8af;
  border-radius: 4px;
  align-self: flex-start;
  list-style-type: none;
  margin: 8px;
  padding: 8px;
  max-width: 80%;
}

@media (prefers-color-scheme: light) {
  :root {
    color: #000;
    background-color: #ffffff;
  }
  a:hover {
    color: #747bff;
  }
  button {
    background-color: #f9f9f9;
  }
}
</code></pre>
<p>Now replace the content of <code>src/main.js</code> with the code snippet below:</p>
<pre><code class="language-javascript">import "./style.css";
import { marked } from "marked";

const apiUrl = "http://localhost:8000";
const btnRecord = document.getElementById("btn_record");
const uListChat = document.getElementById("ulist_chat");

function ensureBrowserHasSpeechAPI() {
  if (
    !("webkitSpeechRecognition" in window) &amp;&amp;
    !("SpeechRecognition" in window)
  ) {
    btnRecord.style.display = "none";

    return alert(
      "This browser does not have the features required for this demo. Use Google Chrome &gt;= v33"
    );
  }

  start();
}

function toggleRecording(config, listener) {
  if (config.isListening) {
    config.isListening = false;
    btnRecord.innerText = "Start recording";
    return listener.stop();
  }

  config.isListening = true;
  btnRecord.innerText = "Stop recording";

  return listener.start();
}

/** @param {string} transcript  */
function appendTranscriptToChatList(transcript) {
  const li = document.createElement("li");
  li.innerText = transcript;
  li.classList.add("transcript");
  uListChat.appendChild(li);
}

/** @param {string} aiResponse  */
function appendAIResponseToChatList(aiResponse) {
  const li = document.createElement("li");
  li.innerHTML = marked.parse(aiResponse);
  li.classList.add("ai_response");
  uListChat.appendChild(li);
}

/** @param {string} prompt  */
async function promptAI(prompt) {
  try {
    const response = await fetch(apiUrl, {
      body: JSON.stringify({ prompt }),
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
    });

    if (!response.ok) {
      const err = await response.text();
      console.error(err);
      alert("An error occurred. Try again");
      return;
    }

    const text = await response.text();
    return text;
  } catch (error) {
    logError(error);
    alert("An error occurred. Try again");
    return ""
  }
}

function setUpSpeechRecognition() {
  const SpeechRecognition =
    window.SpeechRecognition || window.webkitSpeechRecognition;

  const listener = new SpeechRecognition();
  listener.continuous = true; // listen for long speech
  listener.maxAlternatives = 2; // only two transcription suggestions required
  let transcript = "";

  // automatic: onstart -&gt; onaudiostart -&gt; onsoundstart -&gt; onspeechstart
  // automatic: onspeechend -&gt; onsoundend -&gt; onaudioend -&gt; onresult -&gt; onend
  // click button: onaudioend -&gt; onresult -&gt; onend

  listener.onend = async function () {
    if (!transcript || !transcript.trim()) return;

    btnRecord.innerText = "Thinking...";
    btnRecord.disabled = true;
    appendTranscriptToChatList(transcript);
    promptAI(transcript)
      .then(function (res) {
        appendAIResponseToChatList(res);
      })
      .finally(function () {
        btnRecord.innerText = "Record prompt";
        btnRecord.disabled = false;
        transcript = "";
      });
  };

  listener.onerror = function (err) {
    logError(err);
    alert("Error occurred while capturing speech");
  };

  listener.onresult = function (event) {
    for (const alternatives of event.results) {
      const [bestAlternative] = Array.from(alternatives).toSorted(
        (altA, altB) =&gt; altB.confidence - altA.confidence
      );

      transcript += bestAlternative.transcript;
    }
  };

  return listener;
}

async function start() {
  const config = {
    isListening: false,
  };

  const listener = setUpSpeechRecognition();

  btnRecord.addEventListener("click", function () {
    toggleRecording(config, listener);
  });
}

ensureBrowserHasSpeechAPI();

function logError(...str) {
  for (const s of str) {
    console.error("error:", s);
  }
}
</code></pre>
<p><a href="https://www.npmjs.com/package/marked"><code>marked</code></a> is an npm package that helps convert Markdown text to HTML and it's a required dependency in the project. Install <code>marked</code> in the project by running the following command in the project's terminal:</p>
<pre><code class="language-shell">npm install marked
</code></pre>
<p>The <code>ensureBrowserHasSpeechAPI</code> function in <code>src/main.js</code> checks to see if the browser in use has the <code>WebSpeechAPI</code> feature. If it doesn't, it prevents the application from displaying the controls for the UI. That's why you'll need a Google Chrome browser with a version greater than or equal to 33 for this guide. Those versions have the <code>WebSpeechAPI</code> feature.</p>
<p>The <code>toggleRecording</code> function executes when the <strong>Record prompt</strong> button is clicked. On the first click, it requests microphone permission. It also enables/disables the activity of the <code>SpeechRecognition</code> instance.</p>
<p>The <code>setUpSpeechRecognition</code> function sets up the <code>SpeechRecognition</code> instance: <code>listener</code>, and its configuration. It also attaches functions to be run when the <code>end</code>, <code>error</code> and <code>result</code> events are triggered.</p>
<ul>
<li><p><code>error</code> is triggered when there is an error in capturing or processing audio</p>
</li>
<li><p><code>result</code> is triggered when the recognition engine returns transcription results</p>
</li>
<li><p><code>end</code> is triggered when the speech recognition service has disconnected from the application.</p>
</li>
</ul>
<p>The transcript is displayed on the UI after passing it as an argument to the <code>appendTranscriptToChatList</code> function.</p>
<p>The <code>promptAI</code> function executes when the <code>end</code> event fires, accepting the speech transcript as an argument and sending it to the backend via a POST request using <code>fetch</code>. On success, the AI response is returned as Markdown and passed to <code>appendAIResponseToChatList</code>, which converts it to HTML and displays it on the UI.</p>
<h2 id="heading-test-the-application-locally">Test the Application Locally</h2>
<p>Start the backend application by running <code>npm run start</code> in the backend project's terminal and start the frontend application by running <code>npm run dev</code> in the frontend project's terminal. Visit <code>http://localhost:5173</code> to view the UI of application. You should see a UI similar to the one in the image below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e28b713f978a0e2cd2b763/c5d6fc8b-d693-4b83-9611-8e0c493c9f7c.png" alt="Initial image of the voice-powered AI chat app UI built with the Web Speech API" style="display:block;margin:0 auto" width="1068" height="273" loading="lazy">

<p>Click the <strong>Record prompt</strong> button. A prompt will appear requesting microphone permission. Select "Allow while visiting the site" or "Allow this time" to grant access and begin recording. Click on the <strong>Stop recording</strong> button when you're done.</p>
<p>The UI will display the transcript of your speech and the application will send it to the backend as a prompt. After waiting for a short while, you'll see the response from the AI assistant displayed on the UI.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e28b713f978a0e2cd2b763/7a4ff532-6bd0-478c-ae3d-f6446c9d0a1f.png" alt="Final image of the voice-powered AI chat app UI built with the Web Speech API" style="display:block;margin:0 auto" width="914" height="476" loading="lazy">

<p>You have been able to use speech input to prompt an AI assistant, receive a response and display it. How do you make this application accessible to everyone? The next section guides you through deploying both applications.</p>
<h2 id="heading-deploy-the-backend-application-with-google-cloud-run">Deploy the Backend Application with Google Cloud Run</h2>
<p>In this section, you'll deploy the backend application with Google Cloud Run and get a URL which will be used as the <code>apiUrl</code> in the frontend application.</p>
<p>In order to host the backend application with Google Cloud Run, you need to have a:</p>
<ul>
<li><p>Google Cloud developer account</p>
</li>
<li><p>Google Cloud project</p>
</li>
</ul>
<p>Visit <a href="https://cloud.google.com/">Google Cloud</a> to create an account and create a project. You can name the project whatever you want but it's a good idea to give a descriptive name. Take note of the project's ID because you'll use it in the deployment process.</p>
<p>There are three ways to deploy applications on Google Cloud Run:</p>
<ul>
<li><p>Deploy a revision from an existing container image</p>
</li>
<li><p>Deploy from a repository such as GitHub or GitLab</p>
</li>
<li><p>Create a function using the inline editor</p>
</li>
</ul>
<p>You can see all three options if you visit the <a href="https://console.cloud.google.com/run/create">create Cloud Run service</a> page.</p>
<p>In this guide, you'll use the option to deploy from an existing container image. Follow the steps below to deploy the backend server from a container image or follow the Cloud Run documentation at <a href="https://docs.cloud.google.com/run/docs/quickstarts/build-and-deploy/deploy-nodejs-service">build and deploy Node.js service on Cloud Run</a>:</p>
<ul>
<li><p>Install the Google Cloud (gcloud) CLI on your computer by visiting the <a href="https://docs.cloud.google.com/sdk/docs/install">Install Google Cloud CLI</a> page and following the instructions on the page for your operating system</p>
</li>
<li><p>Initialise the gcloud CLI to connect it to your developer account by visiting the <a href="https://docs.cloud.google.com/sdk/docs/initializing">Initializing the gcloud CLI</a> page and following the instructions on the page</p>
</li>
<li><p>Set the project you want to deploy the backend server under by running the command below in your terminal:</p>
</li>
</ul>
<pre><code class="language-shell"># replace PROJECT_ID with your project ID

gcloud config set project PROJECT_ID
</code></pre>
<ul>
<li><p>Visit your project's <a href="https://console.cloud.google.com/iam-admin/iam">IAM Admin</a> page to enable the following roles on the service account created for this project:</p>
<ul>
<li><p><code>roles/run.sourceDeveloper</code></p>
</li>
<li><p><code>roles/iam.serviceAccountUser</code></p>
</li>
<li><p><code>roles/logging.viewer</code></p>
</li>
</ul>
</li>
</ul>
<p>These roles are required to enable the Cloud Run Admin API and Cloud Build APIs. Take note of the service account email address.</p>
<ul>
<li>Enable the Cloud Run Admin API and Cloud Build APIs by running the code snippet below in your terminal:</li>
</ul>
<pre><code class="language-shell">gcloud services enable run.googleapis.com cloudbuild.googleapis.com
</code></pre>
<ul>
<li>Grant the Cloud Build service account access to your project by running the code snippet below in your terminal:</li>
</ul>
<pre><code class="language-plaintext"># replace PROJECT_ID with your project ID and 
# SERVICE_ACCOUNT_EMAIL_ADDRESS with the service account's email address

gcloud projects add-iam-policy-binding PROJECT_ID \
--member=serviceAccount:SERVICE_ACCOUNT_EMAIL_ADDRESS \
--role=roles/run.builder
</code></pre>
<p>Update <code>index.js</code> in the backend project to restrict API requests to clients specified in the <code>ALLOWED_ORIGINS</code> environment variable, and update the AI assistant configuration to use the API key loaded from environment variables.</p>
<pre><code class="language-javascript">// Use the API key from the environment variable
const GEMINI_API_KEY = process.env.GEMINI_API_KEY; 
const ai = new GoogleGenAI({ apiKey: GEMINI_API_KEY });

// Replace res.setHeader("Access-Control-Allow-Origin", "*"); with
res.setHeader("Access-Control-Allow-Origin", process.env.ALLOWED_ORIGINS);
res.setHeader("Access-Control-Allow-Methods", "POST,OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
</code></pre>
<p>This ensures that the application will receive POST requests from only frontend URLs specified in the <code>ALLOWED_ORIGINS</code> environment variable. This setup prevents the backend from being loaded with requests from frontend clients that you don't know, and also prevents the excess use of your tokens. It also keeps you from deploying the application with the AI API key hardcoded in it.</p>
<p>To test that the new changes work, run the backend application with the command below:</p>
<pre><code class="language-shell"># replace YOUR_API_KEY with your Gemini API key

GEMINI_API_KEY=YOUR_API_KEY ALLOWED_ORIGINS="http://localhost:5173" npm run start
</code></pre>
<p>With the command in the code snippet above, the backend application will not respond to requests from frontend applications not hosted on <code>http://localhost:5173</code>. Try to send a prompt from the frontend application to test that it works.</p>
<p>To deploy the backend application to Cloud Run, run the command in the snippet below in the terminal of the backend project folder. The command sets the environment variables required for the application to run and also deploys it to Google Cloud Run.</p>
<pre><code class="language-plaintext"># replace &lt;api-key&gt; with your Gemini API key

gcloud run deploy --source . \
--set-env-vars "ALLOWED_ORIGINS=http://localhost:5173" \
--set-env-vars "GEMINI_API_KEY=&lt;api-key&gt;"
</code></pre>
<p>Once deployment is complete, you'll receive the URL of your hosted backend. Copy it and replace the value of <code>apiUrl</code> in your frontend application with it. Run the frontend, record a prompt, and confirm that everything works as expected.</p>
<h2 id="heading-deploy-the-frontend-application-with-firebase">Deploy the Frontend Application with Firebase</h2>
<p>In this section, you'll host the frontend application with Firebase. You need to have a Firebase account. Follow the steps below to host the frontend with Firebase:</p>
<ul>
<li><p>Create and set up a Firebase project</p>
</li>
<li><p>Install the Firebase CLI by visiting the <a href="https://firebase.google.com/docs/cli#install_the_firebase_cli">install Firebase CLI</a> page and follow the instructions for your operating system</p>
</li>
<li><p>In the terminal of the frontend project, run <code>firebase init hosting</code> to initialise the hosting configuration for the project. Follow the prompts and use <code>dist</code> as the public directory when prompted</p>
</li>
<li><p>Run <code>firebase deploy --only hosting</code> to host the application with Firebase</p>
</li>
</ul>
<p>Once deployment is complete, you will receive the URL of your hosted frontend application.</p>
<h2 id="heading-connect-the-deployed-applications">Connect the Deployed Applications</h2>
<p>Remember that the first time you deployed your backend application, you set <code>ALLOWED_ORIGINS</code> to <code>http://localhost:5173</code>. The deployed backend application doesn't know about the URL of the deployed frontend application so it won't accept requests from it.</p>
<p>In the terminal of the backend application, deploy the backend application again using the command in the snippet below:</p>
<pre><code class="language-shell"># replace &lt;frontend-url&gt; with your Firebase frontend URL and &lt;api-key&gt; 
# with your Gemini API key

gcloud run deploy --source . \
--set-env-vars "ALLOWED_ORIGINS=&lt;frontend-url&gt;" --set-env-vars "GEMINI_API_KEY=&lt;api-key&gt;"
</code></pre>
<p>Visit the deployed frontend application and test it. It should work without errors.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this guide, you built a frontend application that captures and transcribes speech, a Node.js backend application that prompts AI, and you connected both applications together to build a simplified version of the <strong>Use Voice</strong> feature in AI chat applications.</p>
<p>Can you add a feature to the application that will make it read out the response from the backend when it receives it? You can use the <a href="https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis"><code>SpeechSynthesis</code> API</a> to build it.</p>
<p>Feel free to <a href="https://www.linkedin.com/in/orimdominicadah/">connect with me on LinkedIn</a> if you have any questions. Thank you for reading this far and don’t hesitate to share this article if you found it insightful. Cheers!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create REST API Documentation in Node.js Using Scalar ]]>
                </title>
                <description>
                    <![CDATA[ A REST API documentation is a guide that explains how clients can make use of the REST APIs in an application. It details the available endpoints, how to send requests and what responses to expect. It ]]>
                </description>
                <link>https://www.freecodecamp.org/news/rest-api-documentation-with-scalar/</link>
                <guid isPermaLink="false">699f1d4e6049477bf67a9ad8</guid>
                
                    <category>
                        <![CDATA[ documentation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ APIs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ openai ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Orim Dominic Adah ]]>
                </dc:creator>
                <pubDate>Wed, 25 Feb 2026 16:03:26 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5fc16e412cae9c5b190b6cdd/ec5d63f1-d65f-4852-a297-9c517fc76e2f.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>A REST API documentation is a guide that explains how clients can make use of the REST APIs in an application. It details the available endpoints, how to send requests and what responses to expect. It may also contain explanations of concepts that are specific to the scope of the application.</p>
<p>Without API documentation, application development is considered incomplete because developers cannot build software to interact with it, rendering the application effectively useless.</p>
<p>In this article, you will learn how to create beautiful REST API documentation that also allows you to test the APIs for free using an OpenAPI specification and Scalar in Node.js projects. You will use <a href="https://github.com/asteasolutions/zod-to-openapi">asteasolutions/zod-to-openapi</a> to generate OpenAPI specification and use Scalar to create a web page from the specification.</p>
<p>To get the most out of this article, you should have experience developing REST APIs with Express or NestJS. You should also have experience with documenting REST APIs and using <a href="https://zod.dev/">zod</a>.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-rest-api-documentation-tools-for-nodejs">REST API Documentation Tools for Node.js</a></p>
<ul>
<li><p><a href="#heading-swagger">Swagger</a></p>
</li>
<li><p><a href="#heading-postman">Postman</a></p>
</li>
<li><p><a href="#heading-redoc">ReDoc</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-zod-to-openapi-and-scalar-for-rest-api-documentation">zod-to-openapi and Scalar for REST API Documentation</a></p>
<ul>
<li><a href="#heading-how-zod-to-openapi-works-with-scalar">How zod-to-openapi Works with Scalar</a></li>
</ul>
</li>
<li><p><a href="#heading-benefits-of-using-zod-to-openapi-and-scalar-to-create-rest-api-documentation">Benefits of Using zod-to-openapi and Scalar to Create REST API Documentation</a></p>
<ul>
<li><p><a href="#heading-openapi-specification-support">OpenAPI Specification Support</a></p>
</li>
<li><p><a href="#heading-open-source-and-free-to-use">Open Source and Free to Use</a></p>
</li>
<li><p><a href="#heading-better-documentation-experience">Better Documentation Experience</a></p>
</li>
<li><p><a href="#heading-developer-friendly-ui">Developer-friendly UI</a></p>
</li>
<li><p><a href="#heading-markdown-support">Markdown Support</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-to-create-the-api-documentation">How to Create the API Documentation</a></p>
<ul>
<li><p><a href="#heading-set-up-the-project">Set up the Project</a></p>
</li>
<li><p><a href="#heading-how-to-set-up-zod-to-openapi">How to Set Up zod-to-openapi</a></p>
</li>
<li><p><a href="#heading-how-to-generate-the-documentation-ui-with-scalar">How to Generate the Documentation UI with Scalar</a></p>
</li>
<li><p><a href="#heading-document-the-endpoints">Document the Endpoints</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-to-use-scalar-with-nestjs">How to Use Scalar with NestJS</a></p>
</li>
<li><p><a href="#heading-how-to-resolve-content-security-policy-csp-errors-when-used-with-helmet">How to Resolve Content Security Policy (CSP) Errors When Used with Helmet</a></p>
</li>
<li><p><a href="#heading-absence-of-asyncapi-documentation-feature">Absence of AsyncAPI Documentation Feature</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-rest-api-documentation-tools-for-nodejs">REST API Documentation Tools for Node.js</h2>
<p>A variety of tools already exist for documenting REST APIs and they have different strengths and weaknesses depending on their use case. While some of them are completely free to use, others operate a freemium model, and while some have an interface for testing APIs, others have a presentation-only user interface (UI).</p>
<p>Some of the most popular tools for documenting REST APIs in Node.js projects are listed below:</p>
<ul>
<li><p>Swagger</p>
</li>
<li><p>Postman</p>
</li>
<li><p>Redoc</p>
</li>
</ul>
<h3 id="heading-swagger">Swagger</h3>
<p>In order to document REST APIs in Express with Swagger, you need <a href="https://www.npmjs.com/package/swagger-jsdoc">swagger-jsdoc</a> and <a href="https://www.npmjs.com/package/swagger-ui-express">swagger-express-ui</a>. swagger-jsdoc collates and parses JSDoc-annotated documentation comments in the codebase and generates an OpenAPI specification document. swagger-ui-express uses the generated document to created a web page that renders the API documentation and test the APIs.</p>
<p>One of Swagger’s strengths lies in its support for the <a href="https://swagger.io/docs/specification/v3_0/about/">OpenAPI specification</a> which is an industry standard. Swagger is free to use, has a vibrant open source community and strong support for many programming languages and frameworks. It supports only REST APIs.</p>
<p>Its major drawback is the poor developer experience in manually writing JSDoc comments or YAML for the documentation. The process can be clumsy and developers can forget to include some annotations. Another drawback is that the JSDoc comments can interfere with reading functional code. Lastly, some developers have complained about its boring UI.</p>
<h3 id="heading-postman">Postman</h3>
<p><a href="https://www.postman.com/">Postman</a> is a cloud-based desktop API client application that allows developers and technical writers to write, test, collaborate on and publish API documentation. Unlike Swagger, it does not require deep programming experience to use most of its features. It is also not limited to REST API documentation — it can document APIs for GraphQL, websockets and gRPC.</p>
<p>Postman provides a UI to fill in details of an API documentation. The documentation process is manual and sometimes, its content can get out of sync with that of deployed applications. It is not free to use for teams and collaboration and hides the real behaviour of browser interaction with the APIs like CORS and streaming.</p>
<h3 id="heading-redoc">ReDoc</h3>
<p><a href="https://redocly.com/docs/redoc">ReDoc</a> is an open-source tool used to generate API documentation from an OpenAPI (Swagger) specification. It supports GraphQL, AsyncAPI and the OpenAPI specification and it renders a more beautiful documentation than Swagger. <a href="https://www.npmjs.com/package/redoc-express">redoc-express</a> is used to document Express REST APIs with Redoc.</p>
<p>Redoc’s major drawback is that its free community edition is presentation-only. It does not support testing the APIs. Similar to Swagger, a drawback it has is manually updating the application's OpenAPI document via a YAML specification file or JSDoc comments.</p>
<h2 id="heading-zod-to-openapi-and-scalar-for-rest-api-documentation">zod-to-openapi and Scalar for REST API Documentation</h2>
<p><a href="https://github.com/asteasolutions/zod-to-openapi">asteasolutions/zod-to-openapi</a> is a TypeScript library that generates OpenAPI specification from <a href="https://zod.dev/">zod</a> schemas. It provides typed methods which serve as guardrails for documenting API components instead of using code comments so that:</p>
<ul>
<li><p>The library methods serve as guardrails for what to document and how to document it</p>
</li>
<li><p>The documentation is consistent across the codebase</p>
</li>
<li><p>The documentation doesn't negatively affect code readability</p>
</li>
</ul>
<p>A sample snippet used to document a POST request for creating a user with zod-to-openapi is shown below:</p>
<pre><code class="language-typescript">// CreateUser and User are zod schema

registry.registerPath({
  method: "post",
  path: "/api/users",
  summary: "Create user",
  tags: ["users"],
  request: {
    body: {
      content: {
        "application/json": {
          schema: schema.CreateUser, 
        },
      },
      description: "Create user payload",
      required: true,
    },
  },
  responses: {
    201: {
      description: "User created",
      content: {
        "application/json": {
          schema: z.object({
            message: z.string(),
            data: schema.User,
          }),
        },
      },
    },
  },
});
</code></pre>
<p><a href="https://scalar.com/">Scalar</a> is a tool that generates beautiful, organized and searchable API documentation from OpenAPI documents. The documentation generated also supports testing the APIs and this makes Scalar effectively function as an API documentation generator and a lightweight API client. The image below shows a sample documentation generated by Scalar:</p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/66e28b713f978a0e2cd2b763/56ca6e73-23a8-4245-a8d3-88c1f67b16be.svg" alt="Sample Scalar documentation UI" style="display:block;margin:0 auto" width="1360" height="765" loading="lazy">

<h3 id="heading-how-zod-to-openapi-works-with-scalar">How zod-to-openapi Works with Scalar</h3>
<p>zod-to-openapi provides the functionality to generate an OpenAPI specification from code. Scalar uses the document generated to create a documentation web page that presents the information in the document in an organized and beautiful way that also allows for testing the APIs.</p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/66e28b713f978a0e2cd2b763/b327b21d-c80f-472c-ba6d-6c63d212faa8.png" alt="b327b21d-c80f-472c-ba6d-6c63d212faa8" style="display:block;margin:0 auto" width="923" height="470" loading="lazy">

<h2 id="heading-benefits-of-using-zod-to-openapi-and-scalar-to-create-rest-api-documentation">Benefits of Using zod-to-openapi and Scalar to Create REST API Documentation</h2>
<p>When you combine zod-to-openapi and Scalar to create REST API documentation for your Express applications, you get a myriad of benefits. Some of the benefits are explained below:</p>
<h3 id="heading-openapi-specification-support">OpenAPI Specification Support</h3>
<p>The OpenAPI specification is a format for describing REST APIs. It takes takes into consideration the important components of an API necessary for clients to use it effectively. These components include:</p>
<ul>
<li><p>Request paths, methods, headers, path parameters and query parameters,</p>
</li>
<li><p>Schemas of request and response payloads,</p>
</li>
<li><p>Authentication requirements,</p>
</li>
<li><p>Descriptions for information not accommodated by other components</p>
</li>
</ul>
<p>zod-to-openapi provides methods for all of these to be included in the documentation and it generates an OpenAPI specification-compliant document that Scalar uses to generate the documentation web page.</p>
<h3 id="heading-open-source-and-free-to-use">Open Source and Free to Use</h3>
<p>zod-to-openapi is open source and free to use. It has no plans to be unsupported or sunsetted soon because like Ruby on Rails and Laravel, the creators of the project use it in their day-to-day work.</p>
<p>Scalar is open source too. It has paid plans but the features in the paid plans are only really useful for enterprise applications. The free version supports the necessary features needed to create useful REST API documentation.</p>
<h3 id="heading-better-documentation-experience">Better Documentation Experience</h3>
<p>In terms of user experience, the union of zod-to-openapi and Scalar provides the following benefits when writing documentation with both tools:</p>
<h4 id="heading-guardrails-with-zod-to-openapi-methods">Guardrails with zod-to-openapi Methods</h4>
<p>The methods provided by zod-to-openapi serve as guardrails to ensure that developers don't omit or forget the documentation of important components of APIs. The methods also ensure that these components are documented in an OpenAPI specification-compliant manner through the typed nature of the methods' parameters.</p>
<h4 id="heading-avoid-the-clumsiness-of-comments-and-yaml-files">Avoid the Clumsiness of Comments and YAML Files</h4>
<p>With zod-to-openapi, you don't document the APIs using comments or a YAML file. You document APIs using methods from zod-to-openapi. This removes the cluttering of code with comments and the clumsiness around manually updating large YAML files of OpenAPI specification.</p>
<h4 id="heading-accuracy-and-auto-generation-of-documentation">Accuracy and Auto-generation of Documentation</h4>
<p>When you use zod-to-openapi and Scalar, your API documentation is generated automatically when the application runs. zod-to-openapi does the collation and compilation of the documented APIs, and Scalar creates a web page for it that can be hosted on an API route of the same application. You don't need to manually run CLI commands to generate the documentation.</p>
<p>Another benefit of accuracy and auto-generation is that the job of API documentation is not split between technical writer and backend developer. The documentation is in the code and this makes development faster and more seamless in terms of API documentation.</p>
<h3 id="heading-developer-friendly-ui">Developer-friendly UI</h3>
<p>While Swagger’s UI is functional, some developers consider its presentation somewhat minimal, particularly when displaying detailed endpoint descriptions. ReDoc improves on visual design but does not offer API testing features. Scalar, on the other hand, delivers a more refined and intuitive interface with greater customization options than ReDoc.</p>
<p>Beyond its design advantages, Scalar provides auto-generated code samples in multiple programming languages. This enables developers to integrate APIs more efficiently using examples tailored to their specific tech stack.</p>
<p>Scalar's UI provides a search feature that allows developers to quickly locate specific sections of the API documentation. It also includes an AI chat interface that enables users to understand how different API endpoints can help address their specific use cases. This approach is more efficient than manually reviewing the entire documentation.</p>
<p>Lastly, you can test the API endpoints from the UI. When you make authenticated API requests, the UI caches authentication tokens so that you don't have to type or paste them for subsequent requests.</p>
<h3 id="heading-markdown-support">Markdown Support</h3>
<p>zod-to-openapi and Scalar have <a href="https://scalar.com/products/api-references/markdown">Markdown support</a>. With Markdown, you can include conceptual documentation and more information about API endpoints that are not supported by the default documentation components like headers and the request body.</p>
<p>You can embed images, include tables and format text in the documentation. You can use Markdown to include notes that explain concepts related to the API.</p>
<h2 id="heading-how-to-create-the-api-documentation">How to Create the API Documentation</h2>
<p>In this section, you will create an Express CRUD API project that uses zod-to-openapi and Scalar to document its APIs. To practice along, clone the Express starter project from GitHub at <a href="https://github.com/orimdominic/freeCodeCamp-zod-to-openapi-scalar#">orimdominic/freeCodeCamp-zod-to-openapi-scalar</a>.</p>
<h3 id="heading-set-up-the-project">Set up the Project</h3>
<p>After cloning the project:</p>
<ul>
<li><p>install its dependencies using your preferred Node.js package manager</p>
</li>
<li><p>start the server using the <code>serve</code> script</p>
</li>
</ul>
<pre><code class="language-shell"># Install dependencies
npm install

# Start the application
npm run serve
</code></pre>
<p>You should see the following output on the terminal if the application runs successfully:</p>
<pre><code class="language-shell">&gt; freecodecamp-zod-to-openapi-scalar@1.0.0 serve
&gt; node --experimental-strip-types --watch src/index.ts

Listening on :3000
</code></pre>
<p>The project has two modules - Users and Pets.</p>
<p>The router configuration for each module is defined in the <code>router.ts</code> file, while the route controllers are located in the <code>controllers.ts</code> file within each module's folder under <code>src/modules</code>. The controllers do not contain business logic, they simply respond with JSON values generated by the <a href="https://fakerjs.dev/">Faker</a> library.</p>
<h3 id="heading-how-to-set-up-zod-to-openapi">How to Set Up zod-to-openapi</h3>
<p>Install <a href="https://github.com/asteasolutions/zod-to-openapi">asteasolutions/zod-to-openapi</a> using your preferred Node.js package manager. If you use npm, run the code snippet below in your terminal:</p>
<pre><code class="language-shell">npm install @asteasolutions/zod-to-openapi
</code></pre>
<p>After the installation, create a folder called <code>lib</code> (library) in the <code>src</code> folder. In the <code>lib</code> folder, create a file called <code>openapi.ts</code>. The file will house the code that sets up zod-to-openapi for collating the API documentation and generating the OpenAPI specification.</p>
<p>Copy and paste the code snippet below into <code>src/lib/openapi.ts</code>:</p>
<pre><code class="language-typescript">import z from "zod";
import {
  extendZodWithOpenApi,
  OpenApiGeneratorV31,
  OpenAPIRegistry,
} from "@asteasolutions/zod-to-openapi";

extendZodWithOpenApi(z);

export const registry = new OpenAPIRegistry();

export const bearerAuth = registry.registerComponent("securitySchemes", "bearerAuth", {
  type: "http",
  scheme: "bearer",
  bearerFormat: "JWT",
});

export function generateOpenAPIDocument() {
  const generator = new OpenApiGeneratorV3(registry.definitions);

  return generator.generateDocument({
    openapi: "3.1.0",
    info: {
      title: "Users API",
      version: "1.0.0",
      description: `Backend API documentation for users application.`,
    },
    tags: [
      {
        name: "users",
        description: "For operations carried out by admin users",
      },
    ],
    servers: [
      {
        url: "http://localhost:3000",
        description: "Local server",
      },
    ],
  });
}
</code></pre>
<blockquote>
<p>zod-to-openapi v8 requires zod v4. If you use zod v3, you should use v7.3.4 of zod-to-openapi.</p>
</blockquote>
<p><code>extendZodWithOpenApi</code> is a method provided by <code>zod-to-openapi</code> that enhances Zod schemas by adding an <code>openapi</code> method. The <code>openapi</code> method allows you to attach additional documentation to request payloads, responses, parameters, and their properties, which are then displayed in the API documentation rendered by Scalar.</p>
<p>It is important to call <code>extendZodWithOpenApi</code> before loading any files that use the <code>openapi</code> method, otherwise accessing <code>openapi</code> on Zod objects will result in errors.</p>
<p>An alternative is to use the <code>meta</code> method on zod v4 schemas for the additional documentation. For example, <code>schemaOne</code> and <code>schemaTwo</code> in the code snippet below are the same:</p>
<pre><code class="language-typescript">const schemaOne = z
  .string()
  .openapi({ description: 'Name of the user', example: 'Test' });

const schemaTwo = z
  .string()
  .meta({description: 'Name of the user', example: 'Test' });
</code></pre>
<p>The <code>meta</code> method supports all metadata information that you'd normally pass to <code>openapi</code> and will produce exactly the same results.</p>
<p>The <code>OpenAPIRegistry</code> is a utility that is used to collate API documentation which would later be passed to an OpenAPI specification generator. <code>registry</code> is created from <code>OpenAPIRegistry</code>, exported, and used to document API endpoints and components in modules where it is imported.</p>
<pre><code class="language-plaintext">export const registry = new OpenAPIRegistry();
</code></pre>
<p><code>bearerAuth</code> is a component created by the <code>registry</code> to represent JWT authentication. When <code>bearerAuth</code> is included in the documentation of an endpoint, the UI renders an input for submitting an authentication token for authenticated requests as shown in the image below.</p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/66e28b713f978a0e2cd2b763/2bfe72ca-a5ee-4ce5-ba5f-6ed6e1c52774.png" alt="Scalar input UI for submitting JWT" style="display:block;margin:0 auto" width="701" height="69" loading="lazy">

<p>In the <code>registerComponent</code> method, <code>"securitySchemes"</code> registers a security scheme component. <code>"bearerAuth"</code> , the first argument of <code>registerComponent</code>, is the name given to the component and it can be changed to a name that you prefer. It appears in the top right of the authentication token input, shown in the image above. The third input to <code>registerComponent</code> is an object that defines the component.</p>
<p>When <code>generateOpenAPIDocument</code> function is executed, it collates all the registry API definitions in the project, generates the OpenAPI specification through <code>generator.generateDocument</code>, and returns the specification as JSON.</p>
<p>The <code>tags</code> property in <code>generator.generateDocument</code> organizes API endpoints into sections on the documentation UI. For example, all API endpoints with the <code>Users</code> tag in their registry definition will be placed under the <code>Users</code> section of the UI. <code>description</code> can be written in Markdown within template literals.</p>
<p>The <code>servers</code> property is a collection of the servers connected to the application. If you have multiple servers, you have the option of selecting what server to use for the base URL in the documentation UI for making API requests from it.</p>
<p>With this setup in place, when endpoints are documented with the registry, <code>generateOpenAPIDocument</code> will have an OpenAPI specification to return.</p>
<h3 id="heading-how-to-generate-the-documentation-ui-with-scalar">How to Generate the Documentation UI with Scalar</h3>
<p>In this section, you will set up Scalar and connect it to the return value of <code>generateOpenAPIDocument</code>. You will also connect Scalar with an Express route, allowing the application to serve the documentation UI at that route.</p>
<p>Scalar has an <a href="https://scalar.com/products/api-references/integrations/express">Express API reference</a> library that makes it easier for you to connect it with the OpenAPI specification and Express. Install <code>scalar/express-api-reference</code> using your preferred Node.js package manager. If you use npm, use the snippet below:</p>
<pre><code class="language-shell">npm install @scalar/express-api-reference 
</code></pre>
<p>Copy and paste the code snippet below into <code>src/app.js</code>:</p>
<pre><code class="language-typescript">import express from "express";
import router from "./router.ts";
import { generateOpenAPIDocument } from "./lib/openapi.ts";
import { apiReference } from "@scalar/express-api-reference";

const app = express();
app.use(express.json(), express.urlencoded({ extended: true }));

app.get("/", function (req, res) {
  return res.send("OK");
});

app.use("/api", router);

const apiDocJsonContent = generateOpenAPIDocument();

app.use(
  "/docs", // documentation route
  apiReference({
    content: apiDocJsonContent,
    title: "Users API",
    pageTitle: "Users API",
  }),
);

export default app;
</code></pre>
<p>In the code snippet above, <code>generateOpenAPIDocument</code> is imported from <code>src/lib/openapi.ts</code>, and <code>apiReference</code> is imported from <code>@scalar/express-api-reference</code>. When executed, <code>generateOpenAPIDocument</code> returns the OpenAPI specification, which is stored in <code>apiDocJsonContent</code> for caching to improve perfoemance.</p>
<p>A <code>GET /docs</code> route is then created, with the Scalar <code>apiReference</code> function acting as the controller. It accepts <code>apiDocJsonContent</code> and returns a web page whenever the <code>GET /docs</code> route is accessed.</p>
<p>With this setup in place, run the application using <code>npm run serve</code> and visit the documentation page at <code>http://localhost:3000/docs</code> in your browser. You should see a user interface similar to the image below:</p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/66e28b713f978a0e2cd2b763/387698d3-ddcc-4263-8377-428399010892.png" alt="Scalar documentation starter UI" style="display:block;margin:0 auto" width="1906" height="971" loading="lazy">

<p>To view the codebase at this point, run <code>git checkout set-up-openapi-scalar</code> .</p>
<h3 id="heading-document-the-endpoints">Document the Endpoints</h3>
<p>You have set up zod-to-openapi and connected it with Scalar. You have also hooked it up with a route in the backend application. In this section, you will write code to document the endpoints in the application for generating the OpenAPI specification and rendering it on the documentation UI.</p>
<p>To document the route for creating users ( <code>POST /api/users</code> ), in <code>src/modules/users/router.ts</code> , import <code>registry</code>, the schemas and zod using the snippet below:</p>
<pre><code class="language-typescript">import z from "zod";
import { registry } from "../../lib/openapi.ts";
import { 
    UserSchema, 
    UserListItemSchema,
    UpdateUserSchema, 
    CreateUserSchema, 
} from "./types.ts";
</code></pre>
<p>Copy and paste the code below above the create user route to document the create user endpoint:</p>
<pre><code class="language-typescript">registry.registerPath({
  method: "post",
  path: "/api/users",
  summary: "Create user",
  tags: ["users"],
  request: {
    body: {
      content: {
        "application/json": {
          schema: CreateUserSchema,
        },
      },
      description: "Create user payload",
      required: true,
    },
  },
  responses: {
    201: {
      description: "User created",
      content: {
        "application/json": {
          schema: z.object({
            message: z.string().openapi({ example: "User created" }),
            data: UserSchema,
          }),
        },
      },
    },
  },
});
</code></pre>
<p>Visit the documentation page and you will find see a web page similar to the image below:</p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/66e28b713f978a0e2cd2b763/22e0f15f-a7ee-4806-ab86-cfe922a3feda.png" alt="Scalar documentation UI for the create user route" style="display:block;margin:0 auto" width="1914" height="962" loading="lazy">

<p>The UI result of some of the input fields of <code>registry.registerPath</code> have been labelled in the image above. The description in the API endpoint is italicised because its value is Markdown in a template string.</p>
<p>By registering the route path for creating users with <code>registry.registerPath</code> and filling its values, you added the documentation of the route to the registry definitions and that makes it included in the OpenAPI specification.</p>
<p>To test the endpoint from the documentation UI:</p>
<ul>
<li><p>click the <em>Test Request</em> button</p>
</li>
<li><p>fill in the payload in the dialog that appears and</p>
</li>
<li><p>click the <em>Send</em> button</p>
</li>
</ul>
<p>To document the <code>get</code> user by <code>id</code> route (<code>GET /api/users/:id</code> ), import <code>bearerAuth</code> from <code>src/lib/openapi.ts</code>, copy the code snippet below and paste it above the get user by id route definition.</p>
<pre><code class="language-typescript">registry.registerPath({
  method: "get",
  path: "/api/users/{userId}",
  summary: "Get user details by id",
  tags: ["Users"],
  security: [{ [bearerAuth.name]: [] }],
  request: {
    params: z.object({ userId: z.int() }),
  },
  responses: {
    200: {
      description: "User retrieved",
      content: { "application/json": { schema: UserSchema } },
    },
  },
});
</code></pre>
<p>When the <code>request.params</code> field is defined using a Zod object, it generates an input UI on the documentation web page that enables users to provide values for path parameters such as <code>userId</code>, highlighted in the image below:</p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/66e28b713f978a0e2cd2b763/ff4db093-580b-47eb-bd91-2216966566c0.png" alt="Request path parameter from registry definition on Scalar" style="display:block;margin:0 auto" width="701" height="216" loading="lazy">

<p>The complete code for the documentation of all endpoints in this section can be accessed when you check out the <code>complete-project</code> branch by running <code>git checkout complete-project</code> in your terminal. It contains documentation for the endpoint for uploading user photo, which demonstrates how to document endpoints that accept file uploads.</p>
<h2 id="heading-how-to-use-scalar-with-nestjs">How to Use Scalar with NestJS</h2>
<p>Scalar has a library that integrates with NestJS. You can use supply the Swagger document created by <a href="https://docs.nestjs.com/openapi/introduction">swagger/nestjs</a> to the <a href="https://scalar.com/products/api-references/integrations/nestjs">Scalar NestJS integration library</a> to generate the Scalar documentation UI.</p>
<p>In root folder of your NestJS project, install the Scalar NestJS integration library:</p>
<pre><code class="language-shell">npm install @scalar/nestjs-api-reference
</code></pre>
<p>Update the <code>main.ts</code> file of your NestJS project with the code snippet below:</p>
<pre><code class="language-typescript">import { NestFactory } from '@nestjs/core';
import { apiReference } from '@scalar/nestjs-api-reference';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const options = new DocumentBuilder()
    .setTitle('Cats example')
    .setDescription('The cats API description')
    .setVersion('1.0')
    .addTag('cats')
    .addBearerAuth()
    .build();

  const openApiSpecification = SwaggerModule.createDocument(app, options);
  
  // integrate the documentation with NestJS
  app.use(
    '/api/docs', // documentation route
    apiReference({
      content: openApiSpecification,
    }),
  )

  await app.listen(3000);
  console.log(`Application is running on: ${await app.getUrl()}`);
}

bootstrap();
</code></pre>
<p>With this setup in place, you can visit the <code>/api/docs</code> route in your browser to view the Scalar documentation for your NestJS application.</p>
<h2 id="heading-how-to-resolve-content-security-policy-csp-errors-when-used-with-helmet">How to Resolve Content Security Policy (CSP) Errors When Used with Helmet</h2>
<p>If you use <a href="https://www.npmjs.com/package/helmet">Helmet</a> in your Express or NestJS project, you will encounter <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP">CSP</a> errors when you try to render the Scalar documentation UI. To resolve the errors, update the Helmet CSP configuration in your code to have the value of the object in the code snippet below:</p>
<pre><code class="language-typescript">{
    directives: {
      defaultSrc: [`'self'`],
      styleSrc: [`'self'`, `'unsafe-inline'`],
      imgSrc: [`'self'`, 'data:', 'validator.swagger.io'],
      scriptSrc: ['self', 'https:', 'unsafe-inline'],
    },
  }
</code></pre>
<h2 id="heading-absence-of-asyncapi-documentation-feature">Absence of AsyncAPI Documentation Feature</h2>
<p>At the time of writing, Scalar does not fully support rendering AsyncAPI specifications for event-driven architecture APIs, although it is currently under development. You can track the progress of its development through the GitHub issue linked in the documentation to stay informed about its release.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You have learned about zod-to-openapi and how it makes it easier for you to generate an OpenAPI specification for your REST APIs than writing comments or large YAML files. You also learned how to use the specification document generated to render a beautiful API documentation UI which also functions as a lightweight API client. Endeavour to implement it in your projects that need a documentation uplift.</p>
<p>Feel free to <a href="https://www.linkedin.com/in/orimdominicadah/">connect with me on LinkedIn</a> for questions or clarifications. Thank you for reading this far and I hope this helps you achieve what you intended to achieve. Don’t hesitate to share this article if you feel that it would help someone else out there. Cheers!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build an In-Memory Rate Limiter in Next.js ]]>
                </title>
                <description>
                    <![CDATA[ An API rate limiter is a server-side component of a web service that limits the number of API requests a client can make to an endpoint within a period of time. For example, X (formerly known as Twitt ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-an-in-memory-rate-limiter-in-nextjs/</link>
                <guid isPermaLink="false">696155ea25d7491ccd74da74</guid>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ratelimit ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Orim Dominic Adah ]]>
                </dc:creator>
                <pubDate>Fri, 09 Jan 2026 19:24:26 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1767981990510/95306973-8c9a-435b-936e-ae5476f600de.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>An API rate limiter is a server-side component of a web service that limits the number of API requests a client can make to an endpoint within a period of time. For example, X (formerly known as Twitter) limits the number of tweets that a specific user can make to three hundred every three hours.</p>
<p>Rate limiters enforce the responsible use of APIs by blocking requests that exceed the set usage limits.</p>
<p>By following along with this article, you will:</p>
<ul>
<li><p>Learn how rate limiters work</p>
</li>
<li><p>Build an in-memory rate limiter for a Next.js pa router project</p>
</li>
<li><p>Use Artillery to load test the rate limiter for accuracy and resilience</p>
</li>
</ul>
<h3 id="heading-heres-what-well-cover">Here’s What We’ll Cover:</h3>
<ol>
<li><p><a href="#heading-benefits-of-rate-limiters">Benefits of Rate Limiters</a></p>
</li>
<li><p><a href="#heading-how-rate-limiters-work">How Rate Limiters Work</a></p>
</li>
<li><p><a href="#heading-rate-limiting-algorithms">Rate Limiting Algorithms</a></p>
</li>
<li><p><a href="#heading-how-to-build-an-in-memory-rate-limiter">How to Build an In-Memory Rate Limiter</a></p>
<ul>
<li><p><a href="#heading-how-the-in-memory-rate-limiter-works">How The In-Memory Rate Limiter Works</a></p>
</li>
<li><p><a href="#heading-the-request-handler">The Request Handler</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-the-front-end">The Front End</a></p>
</li>
<li><p><a href="#heading-how-to-load-test-the-rate-limiter-for-resilience-with-artillery">How to Load Test the Rate Limiter for Resilience with Artillery</a></p>
<ul>
<li><p><a href="#heading-the-load-test-configuration">The Load Test Configuration</a></p>
</li>
<li><p><a href="#heading-run-the-load-test">Run the Load Test</a></p>
</li>
<li><p><a href="#heading-review-the-results">Review the Results</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<p>To get the most out of this article, you should have experience in building APIs with Next.js pages router, Express, or any other Node.js backend framework that uses middlewares.</p>
<h2 id="heading-benefits-of-rate-limiters">Benefits of Rate Limiters</h2>
<p>Rate limiters control how many requests are allowed within a given time window. They have several benefits you should know about if you’re considering using them.</p>
<p>First, they help prevent the abuse of web servers. Rate limiters guard web servers from overuse that needlessly increases their load. They block excessive requests from Denial of Service (DoS) attacks from bots so that the web service doesn’t crash from unnecessary overload and can continue to be available to legitimate users.</p>
<p>They also help manage the cost of using external APIs. Some API endpoints make requests to external APIs to complete their operations – for example, API endpoints that send emails through an email service provider. When an endpoint relies on paid external APIs and user access of the endpoint is not restricted, excessive usage can lead to increased and expensive costs for the web service. Rate limiters block the excessive usage of endpoints like these, helping to keep costs to a reasonable minimum.</p>
<h2 id="heading-how-rate-limiters-work">How Rate Limiters Work</h2>
<p>Rate limiters work using a three-step mechanism. The process includes tracking requests from specific clients, monitoring their usage, and blocking extra requests once the threshold has been exceeded.</p>
<p>In more detail, rate limiters:</p>
<ul>
<li><p><strong>Track requests</strong>: Rate limiters take note of API clients that make requests and attributes that are specific to the clients (for example, an IP address or a userId). These specific attributes are references or keys that are used to identify clients.</p>
</li>
<li><p><strong>Monitor usage</strong>: Depending on the rate limiting mechanism, rate limiters increase or decrease the metric that is used to determine the threshold of use. For example, within a three-hour time period, Twitter can track and increase the number of times a user makes an API request to the <code>create tweet</code> endpoint.</p>
</li>
<li><p><strong>Ensure threshold compliance</strong>: Rate limiters check the threshold of use for every request made. If it has been exceeded, it blocks the request from accessing the functionality of the API endpoint and responds with a status code of 429.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767810794741/616acc5a-4df5-4314-ace2-d179b973874d.png" alt="Client-server interaction in a rate-limited endpoint" style="display:block;margin:0 auto" width="1751" height="853" loading="lazy"></li>
</ul>
<h2 id="heading-rate-limiting-algorithms">Rate Limiting Algorithms</h2>
<p>You can implement rate limiting using different algorithms based on the requirements of the rate limiter. Each rate limiting algorithm has its merits and demerits. Below are some popular rate limiting algorithms you can play around with.</p>
<h3 id="heading-fixed-window-algorithm">Fixed Window Algorithm</h3>
<p>In the fixed window rate limiting algorithm, the number of requests made within a fixed time period is tracked and every request increases the request count tracked. If the number requests within the time frame is exceeded, any extra request that comes in within the time frame is blocked. At the end of the time period, the request count is reset and increases for every request made.</p>
<p>Its mechanism is easy to understand and it’s memory-efficient. Its challenge is that spikes in traffic close to the start or the end of a time window can allow more requests than permitted.</p>
<h3 id="heading-sliding-window-algorithm">Sliding Window Algorithm</h3>
<p>The sliding window algorithm fixes the issue with the fixed window algorithm where spikes in traffic close to the start or end of a time window can allow more requests than permitted.</p>
<p>It works as follows:</p>
<ul>
<li><p>It keeps a track of the timestamps of requests made in a cache.</p>
</li>
<li><p>When there’s a new request, it removes all timestamps that are older than the start of the current time window and it appends the new request’s timestamp to the cache.</p>
</li>
<li><p>If the count of the requests in the cache is higher than the threshold, the request is blocked. Otherwise, it’s allowed.</p>
</li>
</ul>
<p>Although this algorithm is more accurate than the fixed window algorithm, it consumes more memory because of the storage of timestamps.</p>
<h3 id="heading-token-bucket-algorithm">Token Bucket Algorithm</h3>
<p>In the token bucket algorithm, a bucket that contains a predefined number of tokens is assigned to a user. Tokens are added to the bucket at a predefined rate, for example 2 tokens may be added every second.</p>
<p>Once the bucket is full, no more tokens are added. Each request consumes one or more tokens, and if the tokens are exhausted, requests are blocked until the bucket has tokens again.</p>
<p>The Token Bucket algorithm has the benefits of being memory efficient, easy to implement, and accurate enough to block extra requests even during a burst in traffic.</p>
<p>In this tutorial, we’ll use the fixed window algorithm to build a rate limiter. We’ll also battle-test it for resilience and accuracy using Artillery.</p>
<h2 id="heading-how-to-build-an-in-memory-rate-limiter">How to Build an In-Memory Rate Limiter</h2>
<p>If you’re a backend developer, you may have noticed that users sometimes abuse the reset password API endpoint in your Next.js application. This is a cause for concern because the API endpoint makes a request to your email service provider to send an email and you get charged for it.</p>
<p>Because of this, you may want to limit the requests that users make to this endpoint so that you can prevent the abuse of the API and save costs. And that’s where a rate limiter comes in.</p>
<p>You can get the <a href="https://github.com/orimdominic/nextjs-pages-router-rate-limiter">code for this tutorial here</a>is tutorial here. You can clone it, install the dependencies with <code>npm install</code>, and run it following the instructions in the <a href="https://github.com/orimdominic/nextjs-app-router-rate-limiter/blob/main/README.md">README file</a>. You’ll need it to follow along with the rest of this article.</p>
<p>I built the project using Next.js and it uses the pages router. I’ve also built the rate limiter and <a href="https://github.com/orimdominic/nextjs-app-router-rate-limiter/blob/main/src/lib/server/rate-limiter.ts">you can find it here</a>. You can see how to use it in the <a href="https://github.com/orimdominic/nextjs-app-router-rate-limiter/blob/main/src/pages/api/reset-password-init.ts">reset password API endpoint here</a>.</p>
<p>It has a user interface that you can use to test the rate limiter – but let’s dive into the code first.</p>
<h3 id="heading-how-the-in-memory-rate-limiter-works">How The In-Memory Rate Limiter Works</h3>
<p>To help you better understand the rate limiter, I've created this diagram. We'll walk through what's happening after:</p>
<img src="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/d453743c-5659-4ae1-870f-ea6117696cef.png" alt="A flow diagram for how the in-memory rate limiter works" style="display:block;margin:0 auto" width="1764" height="1652" loading="lazy">

<p>The <a href="https://github.com/orimdominic/nextjs-app-router-rate-limiter/blob/main/src/lib/server/rate-limiter.ts">src/lib/server/rate-limiter.ts</a> file exports a function called <code>applyRateLimiter</code> which accepts three parameters:</p>
<ul>
<li><p>the request object</p>
</li>
<li><p>the response object</p>
</li>
<li><p><code>getOptsFn</code></p>
</li>
</ul>
<p><code>getOptsFn</code> is a function that accepts the request object and, when executed, returns properties specific to the request for tracking, monitoring, and blocking by the rate limiter. <code>getOptsFn</code> is a function and not a static object so that the specific properties of a request can be dynamically created by the request handler for each request.</p>
<p><a href="https://github.com/orimdominic/nextjs-app-router-rate-limiter/blob/main/src/lib/server/rate-limiter.ts">src/lib/server/rate-limiter.ts</a> also has an in-memory map called <code>cache</code>. <code>cache</code> stores the key (or unique identifier) of a request and maps it to its usage. An interval runs every minute to remove keys with <code>expiredAt</code> values that have passed from the cache. This helps to manage the amount of memory used by the cache.</p>
<pre><code class="language-typescript">type GetOptionsFn = (req: NextApiRequest) =&gt; {
  key: string;
  maxTries: number;
  expiresAt: Date;
};

const cache = new Map&lt;string, Usage&gt;();

// clear stale keys from cache every minute
setInterval(() =&gt; {
  const currentDate = new Date();
  for (const [key, usage] of cache) {
    if (!usage) continue;

    if (currentDate &gt; usage.expiresAt) {
      cache.delete(key);
    }
  }
}, 60000);
</code></pre>
<p>When the rate limiter is executed, it uses the <code>getOptsFn</code> to generate the following from the request:</p>
<ul>
<li><p><code>key</code>: The unique identifier for the request that can be used to track its usage</p>
</li>
<li><p><code>maxTries</code>: The maximum number of times a request can be made within the specified time window</p>
</li>
<li><p><code>expiresAt</code>: The expiry time of a time window</p>
</li>
</ul>
<p>based on its content where it was created.</p>
<pre><code class="language-typescript">  const opts = getOptsFn(req);
  const usage = cache.get(opts.key);

  if (!usage) {
    cache.set(opts.key, {
      tries: 1,
      maxTries: opts.maxTries,
      expiresAt: opts.expiresAt,
    });

    return;
  }
</code></pre>
<p>The rate limiter then checks if the <code>key</code> of the request exists in the cache. If it doesn’t, it sets it in the cache, mapping it to the following values:</p>
<ul>
<li><p><code>tries</code> : The number of times that the request has been made without being blocked</p>
</li>
<li><p><code>maxTries</code>: The maximum number of times that the request should be allowed within the time window without blocking</p>
</li>
<li><p><code>expiresAt</code>: The expiry time of the time window</p>
</li>
</ul>
<p>It also allows the request to continue by exiting the rate limiter through the <code>return</code> statement. The values set in <code>cache</code> will be used to determine if and when consecutive requests with the same key should be blocked or not.</p>
<p>If the request’s key exists in <code>cache</code>, the rate limiter checks if the number of unblocked tries (<code>usage.tries</code>) from <code>cache</code> is less than the number of allowed usage tries (<code>usage.maxTries</code>). If it evaluates to <code>true</code>, it means that the request has not exceeded its maximum tries. It also checks if the expiry time of the time window stored in <code>cache</code> for the request has elapsed.</p>
<p>The request is not blocked if one of the following conditions evaluates to <code>true</code>:</p>
<ul>
<li><p>the request has not exceeded its maximum tries AND its time window has not elapsed</p>
</li>
<li><p>the current time window of the request usage in cache (<code>usage.expiresAt</code>) has elapsed</p>
</li>
</ul>
<pre><code class="language-typescript">  const currentDate = new Date();
  const retryAfter = usage.expiresAt.getTime() - currentDate.getTime();
  const timeWindowHasElapsed = retryAfter &lt; 0
  const canProceed = usage.tries &lt; opts.maxTries &amp;&amp; !timeWindowHasElapsed;

  if (canProceed) {
    cache.set(opts.key, {
      ...usage,
      tries: usage.tries + 1,
    });

    return;
  }

  if (timeWindowHasElapsed) { // if usage.expiresAt has elapsed
    cache.set(opts.key, {
      tries: 1,
      maxTries: opts.maxTries,
      expiresAt: opts.expiresAt,
    });

    return;
  }
</code></pre>
<p>If&nbsp;<code>canProceed</code>&nbsp;is truthy, the rate limiter increases the number of tries (<code>usage.tries</code>) that the request has in the cache and then allows the request to proceed by exiting the rate limiter using the <code>return</code> statement. If&nbsp;<code>timeWindowHasElapsed</code>&nbsp;is truthy, the rate limiter resets the usage of the request in the cache using values gotten from <code>getOptsFn</code> and then allows the request to proceed. If both conditions are falsy, the request is blocked with a 429 response status code.</p>
<pre><code class="language-typescript">  res.setHeader("Retry-After", retryAfter/1000);
  return res.status(429).json({
    error: { message: "Too many requests" },
  });
</code></pre>
<p>According to REST specifications, a 429 HTTP response may include a <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After">Retry-After</a> header to let clients know how long to wait before making a new request. The value of the <code>Retry-After</code> header had been calculated beforehand and is set on the response object using <code>res.setHeader</code>.</p>
<h3 id="heading-the-request-handler">The Request Handler</h3>
<p>You can find the reset password request handler in <a href="https://github.com/orimdominic/nextjs-app-router-rate-limiter/blob/main/src/pages/api/reset-password-init.ts">src/pages/api/reset-password-init.ts</a>. First, it performs validation checks on the request method and body to ensure that it is fit for its operations. The validation ensures that the request is a POST request and that the request body includes an <code>email</code> property. It ends the request with the appropriate response code if validation fails.</p>
<pre><code class="language-typescript">  if (req.method !== "POST") {
    return res.status(405).json({
      error: { message: "Not allowed" },
    });
  }

  if (!req.body.email || typeof req.body.email != "string") {
    return res.status(400).json({
      error: { message: "'email' is required" },
    });
  }
</code></pre>
<p><code>generateOptions</code> is the function that is eventually passed as <code>getOptsFn</code> to the rate limiter. The <code>generateOptions</code> function generates the specific properties of the request for the rate limiter. In the case of this endpoint, the properties are:</p>
<ul>
<li><p><code>key</code>: A string in the format <code>[method].[endpoint].[email]</code>. For an email value of “<a href="mailto:Hello@me.com">Hello@me.com</a>”, the key will be <code>post.reset-password.hello@me.com</code> which will be constant for every request for that email to this endpoint. This key value format makes it unique and specific to this request.</p>
</li>
<li><p><code>expiresAt</code>: The time when the time window expires. If the request is in cache, this value is ignored by the rate limiter and it uses the value in the cache instead</p>
</li>
<li><p><code>maxTries</code>: The maximum number of tries that should be allowed within the time window. If the request is in the rate limiter cache already, this value is ignored in preference of the value in cache.</p>
</li>
</ul>
<pre><code class="language-typescript">  const generateOptions = function (req: NextApiRequest) {
    const now = new Date();
    const inFiveSeconds = new Date(now.getTime() + 5000);

    return {
      expiresAt: inFiveSeconds,
      key: `post.reset-password.${req.body.email.toLowerCase()}`,
      maxTries: 1,
    };
  };
</code></pre>
<p>For the reset password handler, requests are rate limited to one every five seconds. You can tweak the <code>expiresAt</code> and <code>maxTries</code> values to test how it works. <code>applyRateLimiter</code> is executed with its arguments and if it does not block the request, the handler can go on to send the mail and respond to the client.</p>
<h2 id="heading-the-front-end">The Front End</h2>
<p>You can visit the user interface to test the rate limiter manually. Visit the URL shown (<a href="http://localhost:3000">http://localhost:3000</a> by default) after you ran <code>npm run dev</code>. You should see the user interface shown below to test the rate limiter.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767603425330/e7fd49a8-e8ce-4e76-b5f5-df094a5fa3f1.png" alt="User interface to test the rate limiter manually" style="display:block;margin:0 auto" width="625" height="472" loading="lazy">

<h2 id="heading-how-to-load-test-the-rate-limiter-for-resilience-with-artillery">How to Load Test the Rate Limiter for Resilience with Artillery</h2>
<p><a href="https://www.artillery.io/">Artillery</a> is a tool for testing and reporting how well web applications can perform under heavy load. In this section, you will use Artillery to test how efficient and accurate the rate limiter that you built is.</p>
<p>To use Artillery, install it globally via the <code>npm install -g artillery@latest</code> command so that the <code>artillery</code> command can be available for use via the CLI.</p>
<h3 id="heading-the-load-test-configuration">The Load Test Configuration</h3>
<p>In the <code>loadtest</code> folder located at the root of the project, you will find the <code>setup.yaml</code> file. It contains the instructions for Artillery to use to carry out the load test. The instructions tell Artillery to create virtual users that will make API requests to the application with the base URL identified by <code>target</code> in three phases:</p>
<ul>
<li><p><strong>Warm up</strong>: Make API requests for a duration of ten seconds, starting from one request per second and increase it to five requests per second.</p>
</li>
<li><p><strong>Ramp up</strong>: After warm up, make API requests for a duration of thirty seconds, starting from five requests per second and increase it to ten requests per second.</p>
</li>
<li><p><strong>Spike phase</strong>: After ramp up, make API requests for a duration of twenty seconds, starting from ten requests per second and increase it to thirty requests per second.</p>
</li>
</ul>
<p>This brings the total time of the load test to sixty seconds.</p>
<pre><code class="language-yaml">config:
  target: http://localhost:3000/api

  phases:
    - duration: 10
      arrivalRate: 1
      rampTo: 5
      name: Warm up

    - duration: 30
      arrivalRate: 5
      rampTo: 10
      name: Ramp up

    - duration: 20
      arrivalRate: 10
      rampTo: 30
      name: Spike phase
</code></pre>
<p>The <a href="https://www.artillery.io/docs/reference/extensions"><code>plugins</code></a> section contains instructions for extensions you can use to analyse the results from Artillery and get reports. For example, the <a href="https://www.artillery.io/docs/reference/extensions/ensure"><code>ensure</code></a> plugin contains setups that will report “OK” if at least 99% of the request responses have a latency of 100ms or less.</p>
<pre><code class="language-yaml">  plugins:
    ensure:
      thresholds:
        - http.response_time.p99: 100
        - http.response_time.p95: 75
</code></pre>
<p>The <a href="https://www.artillery.io/docs/reference/extensions/metrics-by-endpoint"><code>metrics-by-endpoint</code></a> plugin (not used in this project) is another Artillery plugin that is used to display response time metrics for each URL in the test.</p>
<p>A <a href="https://www.artillery.io/docs/reference/test-script#scenarios-section"><code>scenario</code></a> is a sequence of steps that describes a virtual user session in the app. Each virtual user created in <code>phases</code> will make an API request to the end endpoint in <code>flow</code> and the requests in the loop&nbsp;will happen or loop only once per virtual user (because the flow&nbsp;<code>count</code>&nbsp;has a value of 1).</p>
<pre><code class="language-yaml">scenarios:
  - flow:
      - loop:
          - post:
              url: "/reset-password-init"
              headers:
                Content-Type: "application/json"
              json:
                email: "j.doe@email.com"

        count: 1
</code></pre>
<h3 id="heading-run-the-load-test">Run the Load Test</h3>
<p>Make sure that the application is running and run the load test with the command <code>artillery run loadtest/setup.yaml --output loadtest/results.json</code> from the root folder of the project. This will run the load test on the rate-limited endpoint and save the output of the results in <code>loadtest/results.json</code>.</p>
<h3 id="heading-review-the-results">Review the Results</h3>
<p>Regardless of the of the number of requests made, the setup of our rate limiter allows only one request every five seconds. This means that the number of requests that should be allowed within a space of sixty seconds is twelve.</p>
<p>If you take a look at <code>loadtest/results.json</code>, you will see that only twelve requests had a status code of 200. If you increase the values of <code>arrivalRate</code> or <code>rampTo</code> in any or all of the phases to increase the number of requests made to the endpoint and you run the load test again, only twelve requests will still have a status code of 200. This means that our rate limiter is remaining effective and accurate even under high loads.</p>
<p>For latency, you should consider the report of the <code>ensure</code> plugin which is logged to the terminal at the end of the test. A result such as:</p>
<pre><code class="language-plaintext">Checks:
ok: http.response_time.p95 &lt; 75
ok: http.response_time.p99 &lt; 100
</code></pre>
<p>means that 95% of all requests made had a latency of less than 75 milliseconds and 99% of all requests made had a latency of less than 100 milliseconds. These are good results.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, you have learned about rate limiters, rate limiting algorithms, and how to build and use an in-memory rate limiter in Next.js.</p>
<p>You also got a brief introduction to load testing with Artillery. Be sure to apply what you have learned in one of your Next.js projects when you need it.</p>
<p>Feel free to <a href="https://www.linkedin.com/in/orimdominicadah/">connect with me on LinkedIn</a> for questions or clarifications. Thank you for reading this far and I hope this helps you achieve what you intended to achieve. Don’t hesitate to share this article if you feel that it would help someone else out there. Cheers!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How Does the Morgan Express Middleware Library Work? Explained with Code Examples ]]>
                </title>
                <description>
                    <![CDATA[ Morgan is an Express middleware library that examines HTTP requests and logs details of the request to an output. It is one of the most popular Express middleware libraries with over 8,000 GitHub star ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-does-the-morgan-library-work/</link>
                <guid isPermaLink="false">68dacc9e86b3b4616655000a</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Libraries ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Orim Dominic Adah ]]>
                </dc:creator>
                <pubDate>Mon, 29 Sep 2025 18:14:54 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759169256698/9cb4e87a-2bc3-49ac-b1a6-b9d02d410ea1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><a href="https://www.npmjs.com/package/morgan">Morgan</a> is an Express middleware library that examines HTTP requests and logs details of the request to an output. It is one of the most popular Express middleware libraries with over 8,000 GitHub stars and more than 9,000 npm libraries dependent on it. GitHub reports that Morgan is used by at least 3.6 million repositories.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>This guide explains the Morgan library’s code to help you understand how it works under the hood. This is helpful if you have experience with Express and you're interested in understanding the inner workings that produce Morgan log lines. An understanding of closures in JavaScript is helpful for this guide but not necessary.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-is-an-express-middleware">What is an Express Middleware?</a></p>
</li>
<li><p><a href="#heading-a-brief-overview-of-how-morgan-works">A Brief Overview of How Morgan Works</a></p>
</li>
<li><p><a href="#heading-what-is-a-morgan-token">What is a Morgan Token?</a></p>
</li>
<li><p><a href="#heading-what-happens-when-morgan-is-initialised">What Happens When Morgan is Initialised?</a></p>
</li>
<li><p><a href="#heading-what-happens-when-morgan-captures-a-request">What Happens When Morgan Captures a Request?</a></p>
</li>
<li><p><a href="#heading-next-steps">Next Steps</a></p>
</li>
</ul>
<h2 id="heading-what-is-an-express-middleware">What is an Express Middleware?</h2>
<p>According to <a href="https://expressjs.com/en/guide/writing-middleware.html">Express documentation</a>, a middleware is a function that has access to the request and response objects and the <code>next</code> function of an Express request cycle. They are generally used to intercept requests to execute side-effects before or after the request is handled by its route handler.</p>
<p>A middleware can be used to:</p>
<ul>
<li><p><strong>Make changes to the request and the response objects</strong>: It can make changes to the request and response objects by attaching properties like headers and cookies to them.</p>
</li>
<li><p><strong>Terminate the request-response cycle</strong>: It can terminate a request and send a response to the client before or after the request is handled by its route handler.</p>
</li>
<li><p><strong>Execute the next middleware in the stack</strong>: It can trigger the execution of the middleware after it via the <code>next</code> function argument.</p>
</li>
</ul>
<p>A function called <code>next</code> is usually the third argument of a middleware and it is used to pass the request to the next middleware. If the <code>next</code> function is not executed in a middleware and the request is not explicitly terminated by sending a response to the client, the request will be left hanging.</p>
<p>The interface of a middleware is shown in the code snippet below:</p>
<pre><code class="language-javascript">function middleware(request, response, next) {
    // operations to be performed when this middleware is executed
    next() // execute the next middleware
}
</code></pre>
<p>A middleware can intercept and handle cases where preceding middleware or route handlers throw unhandled errors. These middlewares are usually called error handler middlewares and accept four arguments as shown below:</p>
<pre><code class="language-javascript">function errorHandlerMiddleware(error, request, response, next) {}
</code></pre>
<p>The <code>error</code> argument represents the unhandled error.</p>
<p>Some middlewares like Morgan and <a href="https://www.npmjs.com/package/cors">cors</a> are higher-order functions. They accept configuration arguments when initialised and return a middleware function, executed by Express when hit by a request.</p>
<pre><code class="language-javascript">function initialise(...configArgs) {
    // make use of configArgs here
    return function middleware(request, response, next) {
        // can also make use of configArgs here
        // operations to perform when this middleware is hit by a request
        next() // execute the next middleware
    }
}
</code></pre>
<h2 id="heading-a-brief-overview-of-how-morgan-works">A Brief Overview of How Morgan Works</h2>
<pre><code class="language-javascript">import morgan from "morgan"
// morgan(format, [options])
morgan("tiny") // initialise morgan and return a middleware
// Sample output: GET /tiny 200 2 - 0.188 ms
</code></pre>
<p>Morgan is initialised by executing it with a required <code>format</code> argument and an optional <code>options</code> argument. The <code>format</code> argument may be:</p>
<ul>
<li><p>A predefined Morgan format name</p>
</li>
<li><p>A format string containing predefined tokens (a token set)</p>
</li>
<li><p>A custom format function that returns a log output in the form of a string</p>
</li>
</ul>
<p>The <code>options</code> argument is optional. It is an object with three properties:</p>
<ul>
<li><p><code>immediate</code> (boolean): If <code>true</code>, the log output will be created on receiving requests and not when a response is sent. It defaults to <code>false</code>.</p>
</li>
<li><p><code>skip</code> (function): The function accepts the request and response objects as arguments and returns a boolean value based on the logic in it. If the value returned is <code>true</code>, the log line for a request is not logged. <code>skip</code> defaults to <code>false</code>.</p>
</li>
<li><p><code>stream</code> (WritableStream): Output stream for writing log lines. It defaults to <code>process.stdout</code> but it could be a file.</p>
</li>
</ul>
<p>When Morgan is initialised, it stores its initialisation arguments in closure variables and returns a middleware function. The function is executed when a request hits it and it outputs a log line for the request. The format and where the log line is output to are determined by the initialisation arguments.</p>
<h2 id="heading-what-is-a-morgan-token">What is a Morgan Token?</h2>
<p>A Morgan token is a string prefixed by a colon, corresponding to property of the request or response objects or a user-generated value. For example, the request method’s token is <code>':method'</code> and the response status code’s token is <code>':status'</code>. A token can also accept an argument to customise its behaviour. For instance, in <code>':date[format]'</code>, <code>format</code> can be replaced with <code>clf</code>, <code>iso</code> or <code>web</code> to set the format of the date that would be in the log line. An understanding of Morgan tokens is crucial to understanding how Morgan works.</p>
<p>You can create new tokens using the <code>morgan.token</code> function. The code snippet below creates a new token called <code>':type'</code> which corresponds to the response <code>Content-Type</code> header:</p>
<pre><code class="language-javascript">morgan.token('type', function (req, res) {
    return res.headers['content-type']
})
</code></pre>
<p>Morgan has predefined named format (<code>tiny</code>, <code>dev</code>, <code>short</code>, <code>combined</code>, <code>common</code>) strings containing a set of tokens and each named format has its specific token set and configuration. The token set for tiny is <code>':method :url :status :res[content-length] - :response-time ms'</code>. Morgan can accept these named formats as the value of the <code>format</code> argument.</p>
<p>Aside from accepting named formats, Morgan can also accept a token set (for example <code>':method :url :status :res[content-length] - :response-time ms'</code>) as the <code>format</code> argument. A third argument type that Morgan accepts as the <code>format</code> argument is a format function. A format function accepts three arguments and returns a string that forms the log line for each request. For example, the format function described below:</p>
<pre><code class="language-javascript">morgan(function (tokens, req, res) {
    return `method: ${tokens.method(req, res)}
path: ${tokens.url(req, res)}
code: ${tokens.status(req, res)}`
})
</code></pre>
<p>This will produce a log line output like:</p>
<pre><code class="language-bash">method: GET
path: /
code: 200
</code></pre>
<p><code>tokens.method</code>, <code>tokens.url</code> and <code>tokens.status</code> are examples of functions on the <code>morgan</code> object that can generate values to be logged. To illustrate, the table below shows sample token methods, their token and sample output values:</p>
<table>
<thead>
<tr>
<th>Token method</th>
<th>Token</th>
<th>Sample output</th>
</tr>
</thead>
<tbody><tr>
<td>method</td>
<td><code>“:method”</code></td>
<td>GET</td>
</tr>
<tr>
<td>url</td>
<td><code>“:url”</code></td>
<td>/</td>
</tr>
<tr>
<td>status</td>
<td><code>”:status”</code></td>
<td>200</td>
</tr>
</tbody></table>
<p>The next sections of this article explain how Morgan works under the hood. To follow along, open up <a href="https://github.com/expressjs/morgan/blob/master/index.js">Morgan’s index.js file on GitHub</a>.</p>
<h2 id="heading-what-happens-when-morgan-is-initialised">What Happens When Morgan is Initialised?</h2>
<p>When Morgan is initialised, it makes a copy of the arguments provided to it. For arguments that were not provided, Morgan sets default values for them. For instance, if no <code>format</code> string argument was provided, Morgan uses the <code>'default'</code> named format and logs a deprecation notice afterwards with a suggested fix.</p>
<p>Morgan then sets up the <code>formatLine</code> function - the function that creates and returns the log line for a request when executed. How does it create the log line?</p>
<p>First, Morgan checks if <code>format</code> is a format function. If it is, the format function is assigned to <code>formatLine</code> and next, Morgan sets up the output stream. If <code>format</code> is not a function, it is passed as an argument to <code>getFormatFunction</code>. <code>getFormatFunction</code> accepts <code>format</code> and looks up Morgan’s object store to check if <code>format</code> is:</p>
<ul>
<li><p>One of Morgan’s named formats or a user-defined named format created via <code>morgan.format</code></p>
</li>
<li><p>A token set</p>
</li>
</ul>
<p>If it is neither of the two, Morgan uses the <code>default</code> named format.</p>
<pre><code class="language-javascript">function getFormatFunction (name) { // `name` is also `format`
  var fmt = morgan[name] || name || morgan.default

  return typeof fmt !== 'function'
    ? compile(fmt)
    : fmt
}
</code></pre>
<p>If the named format corresponds to a format function after the lookup, Morgan returns the format function, which is then assigned to <code>formatLine</code>, else, it corresponds to a token set. Morgan compiles the token set into a format function through the <code>compile</code> function - one of the most important functions in the Morgan package.</p>
<h3 id="heading-how-the-compile-function-works">How the <code>compile</code> Function Works</h3>
<p>The <code>compile</code> function accepts a token set and returns a function that has the interface of a format function. How does it do this?</p>
<p>With the JavaScript <code>replace</code> method, it uses a RegEx to search for all occurrences of a token in the token set and replaces each occurrence. If the token set is <code>':method :res[content-length] - :response-time ms'</code> , the RegEx <code>replace</code> method replaces the tokens as illustrated in the table below:</p>
<table>
<thead>
<tr>
<th>name</th>
<th>arg</th>
<th>replacement string</th>
</tr>
</thead>
<tbody><tr>
<td>‘method’</td>
<td><code>undefined</code></td>
<td>`(tokens["method"](req, res)</td>
</tr>
<tr>
<td>‘res’</td>
<td><code>’content-length’</code></td>
<td>`(tokens["res"](req, res, "content-length")</td>
</tr>
<tr>
<td>‘response-time’</td>
<td>undefined</td>
<td>`(tokens["response-time"](req, res)</td>
</tr>
</tbody></table>
<p>The result of the RegEx replace is prefixed with <code>"use strict"\n return ""</code> and ends up producing the string below:</p>
<pre><code class="language-plaintext">"use strict" 
    return "" +  
    (tokens["method"](req, res) || "-") + " " +   
    (tokens["res"](req, res, "content-length") || "-") + " - " + 
    (tokens["response-time"](req, res) || "-") + " ms"
</code></pre>
<p>The string above is used to create a format function using the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function">Function constructor</a> and returned as:</p>
<pre><code class="language-javascript">function (tokens, req, res) {
  "use strict"
  return "" +
    (tokens["method"](req, res) || "-") + " " +
    (tokens["res"](req, res, "content-length") || "-") + " - " +
    (tokens["response-time"](req, res) || "-") + " ms"
}
</code></pre>
<p>The format function is eventually stored in <code>formatLine</code>.</p>
<p>When <code>formatLine</code> is executed with <code>morgan</code> as the <code>tokens</code> argument, it will create a log line. In the case of the sample token set, it will create a log line that will look like GET 20 - 1.233 ms.</p>
<p>After creating the <code>formatLine</code> function, Morgan uses the <code>createBufferStream</code> function to set up the streaming of the log lines created to the preferred output if set by <code>options.stream</code>. If <code>options.stream</code> is not set, it uses <code>process.stdout</code>.</p>
<p>Morgan does all this setting up so that it can create log lines quickly on capturing a request. It will be inefficient to do all of these for each request.</p>
<h2 id="heading-what-happens-when-morgan-captures-a-request">What Happens When Morgan Captures a Request?</h2>
<p>When Morgan captures a request, it stores the IP address of the client using the <code>getip</code> function. Next, it stores the time that the request was triggered in the <code>startAt</code> property of the request object.</p>
<p>Then Morgan tries to generate the log line for the request and log it by executing the <code>logRequest</code> function. Morgan checks if the log line should be output on request, and if it should, Morgan executes <code>logRequest</code> and executes <code>next</code> thereafter to pass the request to the next middleware.</p>
<pre><code class="language-javascript">if (immediate) {
  logRequest()
} else {
  onHeaders(res, recordStartTime)
  onFinished(res, logRequest)
}

next()
</code></pre>
<p>If the log output should be created on response, Morgan registers two functions on the response object event listeners:</p>
<ul>
<li><p><strong>An function to be run when headers start to be written to the response object</strong>: When this listener is triggered, it records the time when headers start to be written to the response object as <code>_startAt</code> and <code>startTime</code>. These values are used to calculate the response time and the total time of the request.</p>
</li>
<li><p><strong>A function to be run when the request closes, finishes or errors</strong>: It executes <code>logRequest</code> when this event occurs.  </p>
<p>Just when Node.js starts sending a response to the client, a <code>_startAt</code> property – the time when the response starts getting sent – is attached to the response object. The absolute difference between <code>_startAt</code> on the request object and <code>_startAt</code> on the response object is the response time of the request and can be seen through ":response-time".  </p>
<p>":total-time" is the total time taken from when the request was received to when the response was completely sent. In practice, total-time will be equal to or slightly greater than response-time, depending on how long it takes to write the response body to the stream after the response has started.</p>
</li>
</ul>
<p>Within <code>logRequest</code>, Morgan checks the value of the <code>skip</code> option. If it is a function, it is executed and if it returns <code>true</code>, Morgan doesn’t create a log output for the request and it exits.</p>
<pre><code class="language-javascript">function logRequest () {
  if (skip !== false &amp;&amp; skip(req, res)) {
    debug('skip request')
    return
  }

  var line = formatLine(morgan, req, res)

  if (line == null) {
    debug('skip line')
    return
  }

  stream.write(line + '\n')
};
</code></pre>
<p>If <code>skip</code> is <code>false</code> or executing it evaluates to <code>false</code>, Morgan generates the log line for the request using <code>formatLine</code>. If the log line is <code>null</code>, Morgan exits, else it sends the log line to the output medium and exits.</p>
<h2 id="heading-next-steps">Next Steps</h2>
<p>You have learned how the Morgan Express middleware outputs logs. You now have foundational skills to pick up another middleware or Node.js library like helmet or cors that you use and study it to see how it works. Choose one, study it, write about it, and share it with others.</p>
<p>If you have any questions, you can connect with me on <a href="https://www.linkedin.com/in/orimdominicadah/">LinkedIn</a>. I’ll be happy to respond.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Postman Scripts to Simplify Your API Authentication Process ]]>
                </title>
                <description>
                    <![CDATA[ Postman is a platform used by developers, API testers, technical writers and DevOps teams for testing, documenting and collaborating on API development. It provides a user-friendly interface for making different types of API requests (HTTP, GraphQL, ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-postman-scripts/</link>
                <guid isPermaLink="false">68bee731d2147595571c5b44</guid>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ APIs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ automation ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Orim Dominic Adah ]]>
                </dc:creator>
                <pubDate>Mon, 08 Sep 2025 14:24:49 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1757341168209/dc77dc00-a0a6-40f7-b766-ce07d0d8a637.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Postman is a platform used by developers, API testers, technical writers and DevOps teams for testing, documenting and collaborating on API development. It provides a user-friendly interface for making different types of API requests (HTTP, GraphQL, gRPC), inspecting responses, and organizing API calls into collections for collaboration and automation.</p>
<p>Performing repetitive tasks while testing APIs is stressful and time-wasting. For example, the process of retrieving, copying and pasting new authentication tokens for use in Postman is repetitive. You can simplify this process by using Postman scripts to store auth tokens and then reuse them without repeating the copy and paste steps.</p>
<p>To practice along with this guide, you should have:</p>
<ul>
<li><p>The <a target="_blank" href="https://www.postman.com/downloads/">Postman API client</a> installed on your computer</p>
</li>
<li><p>Experience in making API requests with Postman</p>
</li>
<li><p>A backend application that uses JWT authentication and has its documentation in your Postman client</p>
</li>
</ul>
<p>If you don’t have a backend application setup, I created one that you can clone from GitHub at <a target="_blank" href="https://github.com/orimdominic/freeCodeCamp-postman-api-jwt">orimdominic/freeCodeCamp-postman-api-jwt</a>.</p>
<p>By the end of this article, you should be able to simplify the process of obtaining and reusing authentication tokens across your API requests. You should also have a practical understanding of some scripts necessary for use in other areas of software testing with Postman.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-table-of-contents">Table of Contents</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-are-postman-scripts">What are Postman Scripts?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-simplify-your-jwt-authentication-process">How to Simplify Your JWT Authentication Process</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-authenticate-to-get-the-token">Authenticate to Get the Token</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-save-the-token-in-a-variable-with-a-postman-script">How to Save the Token in a Variable with a Postman Script</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-use-the-variable-in-a-request">How to Use the Variable in a Request</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-next-steps">Next Steps</a></p>
</li>
</ul>
<h2 id="heading-what-are-postman-scripts">What are Postman Scripts?</h2>
<p><a target="_blank" href="https://learning.postman.com/docs/tests-and-scripts/tests-and-scripts/">Postman scripts</a> are blocks of JavaScript code that you can write and run within the Postman API client to automate and enhance API testing workflows. You can use Postman scripts to add code to run before and after API requests. These scripts can be used to:</p>
<ul>
<li><p>Add logic and process data from API requests</p>
</li>
<li><p>Write test assertions for API responses</p>
</li>
<li><p>Run automated tests on API endpoints</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756577771526/161bd327-fbf7-48cb-acab-317ab1cad4c5.jpeg" alt="The Postman scripts tab" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>You can find Postman scripts under the <strong>Scripts</strong> tab of an API request. Code written in the <strong>Pre-request</strong> tab runs before the request is made and code written in the <strong>Post-response</strong> tab runs after the response is made.</p>
<h2 id="heading-how-to-simplify-your-jwt-authentication-process">How to Simplify Your JWT Authentication Process</h2>
<p>In summary, you will carry out the following steps to achieve the objective of this tutorial:</p>
<ol>
<li><p>Authenticate to get the token</p>
</li>
<li><p>Save the token in a collection variable with Postman scripts</p>
</li>
<li><p>Use the variable in an API request</p>
</li>
</ol>
<h3 id="heading-authenticate-to-get-the-token">Authenticate to Get the Token</h3>
<p>To get started, carry out the following steps:</p>
<ol>
<li><p>Start your backend application and make sure it is running successfully.</p>
</li>
<li><p>Open up your Postman application and go to the API request for signing in to get a JWT.</p>
</li>
<li><p>Make an API request to the sign in endpoint and take note of the JSON response schema.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756573137191/b5aad14c-5094-4a84-8876-1bbbb869064c.jpeg" alt="Authentication request response" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>The highlighted part of the image above shows the JSON response from a successful sign in request. In the response schema, the auth token to be used for authorization is in the <code>data.token</code> field. You will use Postman scripts to store this token in a variable and then use the variable in the <code>Authorization</code> header of requests that require authorization.</p>
<h3 id="heading-how-to-save-the-token-in-a-variable-with-a-postman-script">How to Save the Token in a Variable with a Postman Script</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756575948975/2b43493d-2803-45cd-aefe-0ca5694f75e8.jpeg" alt="Add logic in Post-response Postman script" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>In Postman, click on the <strong>Scripts</strong> tab next to the <strong>Body</strong> tab. If the Postman application window is small, you may need to click a dropdown to see it. Next, click on the <strong>Post-response</strong> tab. In the text area to the right, you will write the script to capture the auth token from the response and store it in a Postman variable. Copy the JavaScript code below and paste it into the text area.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">if</span> (pm.response.code == <span class="hljs-number">200</span>) {
    <span class="hljs-keyword">const</span> token = pm.response.json().data.token
    pm.collectionVariables.set(<span class="hljs-string">"auth_token"</span>, token)
}
</code></pre>
<p>Postman scripts use the <a target="_blank" href="https://learning.postman.com/docs/tests-and-scripts/write-scripts/postman-sandbox-api-reference/"><code>pm</code> identifier</a> to access and modify information in the Postman environment. The script above uses <code>pm</code> to first ensure that the request was successful by checking if the response status code is <code>200</code>.</p>
<p>Inside the conditional statement, <code>pm.response.json().data.token</code> is used to get the authentication token from the JSON response and store it in a collection variable called <code>auth_token</code>. If <code>auth_token</code> doesn’t exist already, it is created and its value is set to the value of <code>token</code>. If it exists already, its value is replaced.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756581970294/bed1fe89-9c00-4b94-9f71-173ea3bf1cd1.png" alt="Postman collection variable set by a script" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>To confirm that <code>auth_token</code> has been set, click on the name of the collection (labelled 1 in the screenshot above) and then click on the <strong>Variables</strong> tab (labelled 2 in the screenshot above). Next, instead of repeatedly copying the token and pasting it in the <code>Authorization</code> header of your requests, you will use <code>auth_token</code> in the <code>Authorization</code> header of your requests.</p>
<h3 id="heading-how-to-use-the-variable-in-a-request">How to Use the Variable in a Request</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756583915681/d3bf0f56-c406-4d3e-b7f1-df4cbc2a3cfc.png" alt="Use the Variable in a Request" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Reference the collection variable in the <code>Authorization</code> header by surrounding it with double curly braces <code>{{auth_token}}</code>. When you make an API request, Postman will use the value referenced by <code>{{auth_token}}</code> as the <code>Authorization</code> header.</p>
<p>If another authentication request causes the value of <code>auth_token</code> to be updated, you no longer need to copy the new auth token. The script in the post-response tab will update the <code>auth_token</code> value and you can go on with making API requests smoothly. No need for repeatedly copying and pasting - <strong>Don’t Repeat Yourself (DRY)</strong>.</p>
<h2 id="heading-next-steps">Next Steps</h2>
<p>In this tutorial, you have learnt how to use Postman scripts to set environment variables in Postman. You have also learnt how to eliminate the process of repeatedly copying and pasting auth tokens for use in API requests.</p>
<p>For guides on writing assertion tests for your APIs, check out the <a target="_blank" href="https://learning.postman.com/docs/tests-and-scripts/test-apis/test-apis/">Test API Functionality and Performance in Postman</a> guide by Postman.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Common Open Source Contribution Myths – Debunked ]]>
                </title>
                <description>
                    <![CDATA[ Many developers shy away from contributing to open source, as it can be intimidating and hard to get started. Even though your contributions might seem inconsequential at first, they can potentially have a huge impact on your career. In this article,... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/common-open-source-contribution-myths-debunked/</link>
                <guid isPermaLink="false">68b703ede63728a780d3fc1f</guid>
                
                    <category>
                        <![CDATA[ Open Source ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ community ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Orim Dominic Adah ]]>
                </dc:creator>
                <pubDate>Tue, 02 Sep 2025 14:49:17 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1756821843592/e345ed9b-4cae-4273-b677-05e7047be8b7.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Many developers shy away from contributing to open source, as it can be intimidating and hard to get started. Even though your contributions might seem inconsequential at first, they can potentially have a huge impact on your career.</p>
<p>In this article, we’ll discuss some common misconceptions that might be holding you back from contributing to open source. I’ll show you what you’re missing out on, and give you some advice to help you begin.</p>
<h2 id="heading-table-of-contentsheading-table-of-contents"><a class="post-section-overview" href="#heading-table-of-contents">Table of Contents</a></h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-what-is-open-source">What is Open Source?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-key-factors-influencing-open-source-contributions">Key Factors Influencing Open Source Contributions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-people-hesitate-to-contribute">Why People Hesitate to Contribute</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-you-feel-like-an-impostor">You feel like an impostor</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-you-have-to-do-it-for-free">You have to do it for free</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-you-have-a-busy-schedule">You have a busy schedule</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-start-contributing-to-open-source-today">Start Contributing to Open Source Today</a></p>
</li>
</ol>
<h2 id="heading-what-is-open-source">What is Open Source?</h2>
<p>Open source refers to software that has its code publicly available for viewing, modification, and use. The code is usually hosted on platforms like GitHub where developers can contribute to the codebase and share their expertise with the project.</p>
<p>Although open source software code is publicly accessible, it doesn’t mean that the software has to be free. Creators of the software can make money by charging a fee for things like optional plugins, consultation about the software, and so on.</p>
<p><a target="_blank" href="https://github.com/nginx">Nginx</a> is an example of open source software that charges a fee for additional but optional plugins. <a target="_blank" href="https://github.com/nestjs">NestJS</a> is open source too, but has an official paid course (and its maintainers charge a consultation fee for more complex uses of the software).</p>
<h2 id="heading-key-factors-influencing-open-source-contributions">Key Factors Influencing Open Source Contributions</h2>
<p>In 2023, roughly <a target="_blank" href="https://opensource.googleblog.com/2024/08/2023-open-source-contribution-report.html">10% of Alphabet’s full-time workforce</a> actively contributed to open source projects. And according to <a target="_blank" href="https://opensourcesurvey.org/2024/">GitHub’s 2024 Open Source Survey</a>, respondents reported that the top five factors that influenced their contribution to open source projects, in order of importance, were:</p>
<ul>
<li><p>Whether the project had an open source license or not. Having an open source license was considered favourable.</p>
</li>
<li><p>The responsiveness of the project’s maintainers. Fast and positive responses are encouraging.</p>
</li>
<li><p>A welcoming community, indicating support from the maintainers and others contributing to the project.</p>
</li>
<li><p>The level of activity on the project – lots of activity signifies an actively maintained project (which is more rewarding and useful to work on).</p>
</li>
<li><p>Whether the project had a contribution guide that helps developers ease into contributing to the project.</p>
</li>
</ul>
<p><a target="_blank" href="https://opensourcesurvey.org/2024/"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753830717981/b753b4ae-36ec-4773-990d-dcb83a530c3f.png" alt="GitHub’s 2024 Open Source Survey - Key Factors Influencing Open Source Contributions" class="image--center mx-auto" width="600" height="400" loading="lazy"></a></p>
<p>But even though many projects are attractive, and their maintainers are welcoming, some people still hesitate to contribute for reasons not linked to the projects themselves.</p>
<h2 id="heading-why-people-hesitate-to-contribute">Why People Hesitate to Contribute</h2>
<p>After looking into responses from various discussions, I found that there are three primary barriers developers face when they’re considering contributing to open source:</p>
<ul>
<li><p>You feel like an impostor</p>
</li>
<li><p>You have to do it for free</p>
</li>
<li><p>You have a busy schedule</p>
</li>
</ul>
<p>The rest of this article will address (and hopefully break down) these barriers. I hope that by the end, you’ll be encouraged to contribute to open source.</p>
<h3 id="heading-you-feel-like-an-impostor">You feel like an impostor</h3>
<p>Many people think that you have to know a lot about a project to work on it, but this misconception is one of the biggest barriers that holds developers back from contributing to open source. Some people say to themselves “I don't have the experience”, “I have nothing to contribute”, “What if I break something?”, “I don’t know enough”.</p>
<p>Here’s some advice from <a target="_blank" href="https://www.freecodecamp.org/news/how-to-not-feel-like-an-imposter-3d41fdc91182/">How to Overcome Imposter Syndrome</a> that can help:</p>
<ul>
<li><p>Stop obsessing over not being good enough. It’s unproductive.</p>
</li>
<li><p>Everyone excels at different things. You likely excel at some things that others don’t.</p>
</li>
<li><p>Don’t compare yourself with others who are more experienced than you. In fact, stop comparing yourself with anyone else. You are unique.</p>
</li>
</ul>
<p>Try to contribute to projects that you have used. You already understand them better than projects that you haven’t used. As a beginner, working on issues labeled “good first issue”, “up-for-grabs”, “beginner-friendly” and so on is a great place to start. If you find an issue that interests you, read the discussions under it if there are any so you can understand it better. State that you are interested in working on it and an experienced contributor will likely respond to let you know if you can proceed. They can also provide you with more information if you need it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756458374283/cf14b8d9-b7e0-45b9-a7ca-a88e93df28c2.png" alt="Issues with labels that beginners can contribute to" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Put in the work to resolve it yourself first, and if you have any issues, then you can ask more questions. When you try to resolve it yourself before asking questions, it tells maintainers that you have put in the effort and they will be more willing to help. It also makes your question(s) sound thoughtful and direct.</p>
<blockquote>
<p><strong>“The three most powerful motives are curiosity, delight, and the desire to do something impressive. “</strong></p>
<p><strong>— How to Do Great Work by Paul Graham</strong></p>
</blockquote>
<p>I shared my experience in an article titled <a target="_blank" href="https://orimdominic.vercel.app/posts/you-dont-need-to-know-it-all-to-contribute/">You Don't Need to Know It All to Contribute</a> where I found out that the author of a popular JavaScript project was not the one that wrote the TypeScript part of the project and he was happy to receive my contribution.</p>
<p>If you still feel like you are not good enough, try to look at it as an opportunity to learn from a codebase built by developers with a wide range of experiences and expertise. If you dive into the code and try to improve it, you’ll learn things that you won’t find in online tutorials and blog posts.</p>
<p>Just keep in mind that you don’t always have to contribute code. Even though the majority of contributions are usually code-based, there are other areas that need attention, too, and are often overlooked. What happens if the documentation is nonexistent or outdated? What happens when issues aren’t triaged? You can help with these issues by:</p>
<ul>
<li><p>Discussing them with project maintainers and other contributors to clarify and improve docs and other resources</p>
</li>
<li><p>Giving feedback on pull requests</p>
</li>
<li><p>Adding labels for organising issues into proper categories</p>
</li>
</ul>
<p>By doing this, you provide value to these projects. What matters is your interest in solving problems and your ability to do the required research.</p>
<h3 id="heading-you-have-to-do-it-for-free">You have to do it for free</h3>
<p>Another misconception is that devs always do open source without getting paid. But this isn't true for all cases. Some open source repositories reward contributors via bounties, for example. With bounties, prize money is placed on an an issue and whoever solves it gets the prize money or reward. I earned money through this via <a target="_blank" href="https://orimdominic.vercel.app/posts/my-first-open-source-contribution-to-remotion/">my first contribution to Remotion</a>.</p>
<p>And there are other benefits of contributing to open source projects aside from getting paid. And often, people contribute primarily for these other reasons.</p>
<p>For example, for beginner developers, there aren’t many opportunities to get hands-on experience building “real” projects, or to learn what working on a tech team is really like.</p>
<p>But by contributing to an open source project and becoming part of that community, you get to practice teamwork, contribute to code that’s constantly changing and growing, and solve real-life problems. This gives you valuable experience that’ll help prepare you for the workforce (and that you can put on your résumé). The image below is a screenshot of two people who received a <a target="_blank" href="https://training.linuxfoundation.org/blog/500-promising-individuals-worldwide-receive-linux-foundation-it-training-certification-scholarships/">scholarship from the Linux Foundation</a> for their contributions to open source.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756482636970/e3fe1fad-7edb-4748-b760-00e054dd4e61.png" alt="Screenshot of open source scholarship winners" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>So as you can see, even if you don’t get monetary rewards, contributing to open source presents many other opportunities. You get to:</p>
<ul>
<li><p>Work with people from diverse cultures</p>
</li>
<li><p>Develop clear written technical communication skills</p>
</li>
<li><p>Improve your knowledge of various tools/frameworks</p>
</li>
<li><p>Prove yourself to be a self-motivated developer</p>
</li>
<li><p>Work asynchronously with people across multiple time zones</p>
</li>
</ul>
<p>You also get the opportunity to network with other developers and grow your reputation and credibility in the software development space. <a target="_blank" href="https://github.com/unicodeveloper">Prosper Otemuyiwa</a> and <a target="_blank" href="https://github.com/antfu">Anthony Fu</a> are great examples and beneficiaries of this type of career growth.</p>
<h3 id="heading-you-have-a-busy-schedule">You have a busy schedule</h3>
<p>Having other pressing commitments is a reasonable barrier to contributing to open source. We’re all human, and our resources (time and energy) are limited – so we have to manage them judiciously.</p>
<p>But have you considered what would happen if the open source project that you use had a serious bug that affects your performance or deliverables at work? What happens when the problem you are trying to solve at work requires expert knowledge of that open source project that you heavily depend on?</p>
<p>Contributing to open source projects in the little ways that you can will give you insight into how the projects work. Since you have to maintain a rapport with other developers on the project, you’ll form a professional bond with them and they’ll be glad to help you out if you have questions or require expert opinions on the project. If you are a major contributor, you may have the chance to influence the project’s roadmap.</p>
<p>Continuing to develop your skills is paramount to you as a developer. If you can make time to improve your skills, then you can make time to contribute to open source because it offers more rewards than just skill improvement. You can start small – try dedicating, for example, one hour a week before work, or a couple hours on the weekend. Then you can ramp up once you get into the rhythm and find more time. Contributing to open source doesn’t have to be overly demanding.</p>
<p>You’re already solving problems. Why not solve them in a visible way that helps others and builds your own credibility?</p>
<h2 id="heading-start-contributing-to-open-source-today">Start Contributing to Open Source Today</h2>
<p>We all should contribute to open source one way or another. If you feel that you don’t know enough, just keep in mind that other people feel (or felt, before starting) the same way. And know that nobody can claim to know it all – not even the founder of the project. So don’t let the feeling of impostor syndrome hold you back.</p>
<p>Remember also that Rome was not built in a day. It wasn’t also built by one person’s hand – but by countless laborers over many centuries and millennia. You could be part of building the next great open source project (or helping maintain the many great ones that are already out there and need your help).</p>
<p>So consider contributing today by visiting the issues tab of any of the open source projects that you use extensively and pick up something. You can find links to beginner-level issues from the following websites:</p>
<ul>
<li><p><a target="_blank" href="https://goodfirstissue.dev/">Good First Issue</a></p>
</li>
<li><p><a target="_blank" href="https://up-for-grabs.net/">Up For Grabs</a></p>
</li>
<li><p><a target="_blank" href="https://www.codetriage.com/">CodeTriage</a></p>
</li>
<li><p><a target="_blank" href="https://www.onlydust.com/">OnlyDust</a></p>
</li>
</ul>
<p>If you see an issue that you are interested in, take the following steps as a new contributor:</p>
<ul>
<li><p>Look for a <code>CONTRIBUTING.md</code> file and read it for guidance on how to contribute</p>
</li>
<li><p>Clone the repository and set it up locally</p>
</li>
<li><p>State your intention to work on the issue by making a comment on the issue</p>
</li>
</ul>
<p>Ask a question if an issue is unclear to you and you’ll get guidance.</p>
<p>Good luck on your open source journey!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Assign Unique IDs to Express API Requests for Tracing ]]>
                </title>
                <description>
                    <![CDATA[ The ability to track what happens with API requests is an important aspect of monitoring, tracing and debugging back-end applications. But how do you differentiate between the reports of two consecutive API requests to the same API endpoint? This art... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-assign-unique-ids-to-express-api-requests-for-tracing/</link>
                <guid isPermaLink="false">68a4b96a7c0721701837c266</guid>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Orim Dominic Adah ]]>
                </dc:creator>
                <pubDate>Tue, 19 Aug 2025 17:50:34 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755625819738/b5d45868-c770-4878-8c49-63011507ef56.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The ability to track what happens with API requests is an important aspect of monitoring, tracing and debugging back-end applications. But how do you differentiate between the reports of two consecutive API requests to the same API endpoint?</p>
<p>This article aims to show you how to:</p>
<ul>
<li><p>Properly assign a unique ID to API requests in your Express applications,</p>
</li>
<li><p>Store and access the ID using the <a target="_blank" href="https://nodejs.org/docs/latest-v18.x/api/async_context.html#class-asynclocalstorage">AsyncLocalStorage</a> API in Node.js, and</p>
</li>
<li><p>Use it in request logging.</p>
</li>
</ul>
<p>Experience in creating API endpoints and using middleware in Express will be helpful as you follow along with this guide. You can also apply ideas from this article to frameworks like NestJS and Koa.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-getting-started-with-the-starter-repository">Getting Started with the Starter Repository</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-set-up-logger-utilities">Set Up Logger Utilities</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-asynclocalstorage-and-why-is-it-important">What is AsyncLocalStorage and Why is it Important?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-store-the-request-id-in-asynclocalstorage">Store the Request ID in AsyncLocalStorage</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-the-request-id-in-the-logger-utilities">Use the Request ID in the Logger Utilities</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-set-header-to-have-x-request-id-optional-challenge">Set Header to have X-Request-Id (Optional Challenge)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-getting-started-with-the-starter-repository">Getting Started with the Starter Repository</h2>
<p>To make it easier to follow along, I’ve created a starter project and hosted it on GitHub. You can <a target="_blank" href="https://github.com/orimdominic/freecodecamp-express-request-ids">clone it from here</a>. To get it up and running on your local computer, install its dependencies using your preferred JavaScript package manager (npm, yarn, pnpm, bun). Then start the application by running the <code>npm start</code> command in the terminal of the project.</p>
<p>If the application starts successfully, it should log the snippet below on the terminal:</p>
<pre><code class="lang-bash">Listening on port 3333
</code></pre>
<p>The application has only one API endpoint currently – a <code>GET /</code> . When you make an API request to the endpoint using <code>curl</code> or a browser by visiting http://localhost:3333, you will get an “OK” string as the payload response:</p>
<pre><code class="lang-bash">$ curl -i http://localhost:3333

OK%
</code></pre>
<p>If the snippet above is what you see, then congratulations! You have set the project up correctly.</p>
<h2 id="heading-set-up-logger-utilities">Set Up Logger Utilities</h2>
<p>The first step is to set up custom loggers for logging messages to the terminal. The loggers will log the events that occur during the process of handling an API request and log the summary of the request.</p>
<p>To achieve this, you’ll need to install two Express middlewares – <a target="_blank" href="https://www.npmjs.com/package/morgan">morgan</a> and <a target="_blank" href="https://www.npmjs.com/package/winston">winston</a> – using your preferred package manager. If you use <code>npm</code>, you can run the command below in the folder terminal of the project:</p>
<pre><code class="lang-bash">$ npm install morgan winston
</code></pre>
<p>If the above command is successful, morgan and winston will be added to the <code>dependencies</code> object in <code>package.json</code>. Create a file named <code>logger.js</code> in the root folder of the project. <code>logger.js</code> will contain the code for the custom logger utilities.</p>
<p>The first logger utility you will create is <code>logger</code>, created from the winston package you installed earlier. <code>logger</code> is an object with two methods:</p>
<ul>
<li><p><code>info</code> for logging non-error messages to the terminal</p>
</li>
<li><p><code>error</code> for logging error messages to the terminal</p>
</li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-comment">// logger.js</span>

<span class="hljs-keyword">const</span> winston = <span class="hljs-built_in">require</span>(<span class="hljs-string">"winston"</span>);

<span class="hljs-keyword">const</span> { combine, errors, json, timestamp, colorize } = winston.format;

<span class="hljs-keyword">const</span> logHandler = winston.createLogger({
  <span class="hljs-attr">level</span>: <span class="hljs-string">"debug"</span>,
  <span class="hljs-attr">levels</span>: winston.config.npm.levels,
  <span class="hljs-attr">format</span>: combine(
    timestamp({ <span class="hljs-attr">format</span>: <span class="hljs-string">"YYYY-MM-DD hh:mm:ss.SSS A"</span> }), <span class="hljs-comment">// set the format of logged timestamps</span>
    errors({ <span class="hljs-attr">stack</span>: <span class="hljs-literal">true</span> }),
    json({ <span class="hljs-attr">space</span>: <span class="hljs-number">2</span> }),
    colorize({
      <span class="hljs-attr">all</span>: <span class="hljs-literal">true</span>,
      <span class="hljs-attr">colors</span>: {
        <span class="hljs-attr">info</span>: <span class="hljs-string">"gray"</span>, <span class="hljs-comment">// all info logs should be gray in colour</span>
        <span class="hljs-attr">error</span>: <span class="hljs-string">"red"</span>, <span class="hljs-comment">// all error logs should be red in colour     </span>
        },
    })
  ),
  <span class="hljs-attr">transports</span>: [<span class="hljs-keyword">new</span> winston.transports.Console()],
});

<span class="hljs-built_in">exports</span>.logger = {
  <span class="hljs-attr">info</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">message</span>) </span>{
    logHandler.child({}).info(message);
  },

  <span class="hljs-attr">error</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">message</span>) </span>{
    logHandler.child({}).error(message);
  },
};
</code></pre>
<p>In the code snippet above, <code>winston.createLogger</code> is used to create <code>logHandler</code>. <code>logger</code> is exported out of the <code>logger.js</code> module and <code>logger.info</code> and <code>logger.error</code> are functions that use <code>logHandler</code> to log messages to the terminal.</p>
<p>The second logger utility will be a middleware that will log information about the request just before the request response is sent to the client. It will log information such as how long it took to run the request and the status code of the request. It will be called <code>logRequestSummary</code> and will use the morgan package and the <code>http</code> method of <code>logHandler</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// logger.js</span>

<span class="hljs-keyword">const</span> winston = <span class="hljs-built_in">require</span>(<span class="hljs-string">"winston"</span>);
<span class="hljs-keyword">const</span> morgan = <span class="hljs-built_in">require</span>(<span class="hljs-string">"morgan"</span>);

<span class="hljs-keyword">const</span> { combine, errors, json, timestamp, colorize } = winston.format;

<span class="hljs-keyword">const</span> logHandler = winston.createLogger({
  <span class="hljs-comment">// ...</span>
  <span class="hljs-attr">format</span>: combine(
    <span class="hljs-comment">// ...</span>
    colorize({
      <span class="hljs-attr">all</span>: <span class="hljs-literal">true</span>,
      <span class="hljs-attr">colors</span>: {
        <span class="hljs-comment">// ...</span>
        <span class="hljs-attr">http</span>: <span class="hljs-string">"blue"</span>, <span class="hljs-comment">// 👈🏾 (new line) logs from logRequestSummary will be blue in colour</span>
      },
    })
  ),
  <span class="hljs-comment">// ...</span>
});

<span class="hljs-built_in">exports</span>.logger = {
    <span class="hljs-comment">// ...</span>
};

<span class="hljs-built_in">exports</span>.logRequestSummary = morgan(
  <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">tokens, req, res</span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">JSON</span>.stringify({
      <span class="hljs-attr">url</span>: tokens.url(req, res),
      <span class="hljs-attr">method</span>: tokens.method(req, res),
      <span class="hljs-attr">status_code</span>: <span class="hljs-built_in">Number</span>(tokens.status(req, res) || <span class="hljs-string">"500"</span>),
      <span class="hljs-attr">content_length</span>: tokens.res(req, res, <span class="hljs-string">"content-length"</span>) + <span class="hljs-string">" bytes"</span>,
      <span class="hljs-attr">response_time</span>: <span class="hljs-built_in">Number</span>(tokens[<span class="hljs-string">"response-time"</span>](req, res) || <span class="hljs-string">"0"</span>) + <span class="hljs-string">" ms"</span>,
    });
  },
  {
    <span class="hljs-attr">stream</span>: {
      <span class="hljs-comment">// use logHandler with the http severity</span>
      <span class="hljs-attr">write</span>: <span class="hljs-function">(<span class="hljs-params">message</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> httpLog = <span class="hljs-built_in">JSON</span>.parse(message);
        logHandler.http(httpLog);
      },
    },
  }
);
</code></pre>
<p>The JSON string returned by the first function when the <code>morgan</code> function is executed is received by the <code>write</code> function of the <code>stream</code> object in the second argument passed to the organ function. It’s then parsed to JSON and passed to <code>logHandler.http</code> to be logged with the <code>winston.npm</code> <code>http</code> severity level.</p>
<p>At this point, two objects are exported from <code>logger.js</code>: <code>logger</code> and <code>logRequestSummary</code>.</p>
<p>In <code>index.js</code>, create a new controller to handle <code>GET</code> requests to the <code>/hello</code> path. Also import and use the exported objects from <code>logger.js</code>. Use <code>logger</code> to log information when events occur in controllers and include <code>logRequestSummary</code> as a middleware for the application.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// index.js</span>
<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> { logRequestSummary, logger } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./logger"</span>);

<span class="hljs-keyword">const</span> app = express();

app.use(
  express.json(),
  express.urlencoded({ <span class="hljs-attr">extended</span>: <span class="hljs-literal">true</span> }),
  logRequestSummary <span class="hljs-comment">// logger middleware utility</span>
);

app.get(<span class="hljs-string">"/"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">req, res</span>) </span>{
  logger.info(<span class="hljs-string">`<span class="hljs-subst">${req.method}</span> request to <span class="hljs-subst">${req.url}</span>`</span>); <span class="hljs-comment">// logger utility for events</span>
  <span class="hljs-keyword">return</span> res.json({ <span class="hljs-attr">method</span>: req.method, <span class="hljs-attr">url</span>: req.url });
});

app.get(<span class="hljs-string">"/hello"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">req, res</span>) </span>{
  logger.info(<span class="hljs-string">`<span class="hljs-subst">${req.method}</span> request to <span class="hljs-subst">${req.url}</span>`</span>); <span class="hljs-comment">// logger utility for events</span>
  <span class="hljs-keyword">return</span> res.json({ <span class="hljs-attr">method</span>: req.method, <span class="hljs-attr">url</span>: req.url });
});

<span class="hljs-comment">// ...</span>
</code></pre>
<p>Stop the application (with <code>CTRL</code> + <code>C</code> or <code>OPTION</code> + <code>C</code>), and start it again with <code>npm start</code>. Make API requests to both API endpoints, you’ll see output similar to the snippet below in the terminal – an event log first and a log of the summary of the request after.</p>
<pre><code class="lang-bash">{
  <span class="hljs-string">"level"</span>: <span class="hljs-string">"info"</span>,
  <span class="hljs-string">"message"</span>: <span class="hljs-string">"GET request to /"</span>,
  <span class="hljs-string">"timestamp"</span>: <span class="hljs-string">"2025-08-16 10:35:06.831 PM"</span>
}
{
  <span class="hljs-string">"level"</span>: <span class="hljs-string">"http"</span>,
  <span class="hljs-string">"message"</span>: {
    <span class="hljs-string">"content_length"</span>: <span class="hljs-string">"26 bytes"</span>,
    <span class="hljs-string">"method"</span>: <span class="hljs-string">"GET"</span>,
    <span class="hljs-string">"response_time"</span>: <span class="hljs-string">"9.034 ms"</span>,
    <span class="hljs-string">"status_code"</span>: 200,
    <span class="hljs-string">"url"</span>: <span class="hljs-string">"/"</span>
  },
  <span class="hljs-string">"timestamp"</span>: <span class="hljs-string">"2025-08-16 10:35:06.844 PM"</span>
}
</code></pre>
<p>You can view the latest state of the code by switching to the <code>2-custom-logger-middleware</code> branch using <code>git checkout 2-custom-logger-middleware</code> or by visiting branch <a target="_blank" href="https://github.com/orimdominic/freecodecamp-express-request-ids/tree/2-custom-logger-middleware">2-custom-logger-middleware</a> of the repository.</p>
<p>Now that you’re able to log and view events that occur for each API request, how do you differentiate between two consecutive requests to the same endpoint? How do you figure out which API request logged a specific message? How do you specify the API request to trace when communicating with your teammates? By attaching a unique ID to each request, you’ll be able to answer all these questions.</p>
<h2 id="heading-what-is-asynclocalstorage-and-why-is-it-important">What is AsyncLocalStorage and Why is it Important?</h2>
<p>Before <a target="_blank" href="https://nodejs.org/docs/latest-v18.x/api/async_context.html#class-asynclocalstorage">AsyncLocalStorage</a>, users of Express stored request context information in the <code>res.locals</code> object. With AsyncLocalStorage, Node.js provides a native way to store information that’s necessary for executing asynchronous functions. According to its documentation, it’s a performant and memory-safe implementation that involves significant optimizations that would be difficult for you to implement by yourself.</p>
<p>When you use AsyncLocalStorage, you can store and access information in a similar manner to <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage">localStorage</a> in the browser. You pass the store value (usually an object, but it can be a primitive value, too) as the first argument and the asynchronous function that should access the store value as the second argument when you execute the <code>run</code> method.</p>
<p>James Snell, one of the leading contributors of Node.js explains it further in this video <a target="_blank" href="https://www.youtube.com/watch?v=ukefzxZ_G9U">Async Context Tracking in Node with Async Local Storage API</a>.</p>
<h2 id="heading-store-the-request-id-in-asynclocalstorage">Store the Request ID in AsyncLocalStorage</h2>
<p>In the project, create a file with the name <code>context-storage.js</code>. In this file, you’ll create an instance of AsyncLocalStorage (if it hasn’t been created yet) and export it. This instance of AsyncLocalStorage will be used in storing and retrieving the request IDs for the logger and any other context that needs the request ID.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// context-storage.js</span>
<span class="hljs-keyword">const</span> { AsyncLocalStorage } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"node:async_hooks"</span>);

<span class="hljs-keyword">let</span> store;

<span class="hljs-built_in">module</span>.exports.contextStorage = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">if</span> (!store) {
    store = <span class="hljs-keyword">new</span> AsyncLocalStorage();
  }

  <span class="hljs-keyword">return</span> store;
};
</code></pre>
<p>You’ll create another file called <code>set-request-id.js</code> which will create and export a middleware. The middleware will intercept API requests, generate a request ID, and store it in the instance of AsyncLocalStorage from <code>context-storage.js</code> if it doesn’t exist in it already.</p>
<p>You can use any ID-generating library you want, but here we’ll use <code>randomUUID</code> from the Node.js <code>crypto</code> package.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// set-request-id.js</span>
<span class="hljs-keyword">const</span> { randomUUID } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"node:crypto"</span>);
<span class="hljs-keyword">const</span> { contextStorage } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./context-storage"</span>);

<span class="hljs-comment">/**
 * Preferably your first middleware.
 *
 * It generates a unique ID and stores it in the AsyncLocalStorage
 * instance for the request context.
 */</span>
<span class="hljs-built_in">module</span>.exports.setRequestId = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">_req, _res, next</span>) </span>{
    requestId = randomUUID();
    <span class="hljs-keyword">const</span> store = contextStorage().getStore();

    <span class="hljs-keyword">if</span> (!store) {
      <span class="hljs-keyword">return</span> contextStorage().run({ requestId }, next);
    }

    <span class="hljs-keyword">if</span> (store &amp;&amp; !store.requestId) {
      store.requestId = requestId;
      <span class="hljs-keyword">return</span> next();
    }

    <span class="hljs-keyword">return</span> next();
  };
};
</code></pre>
<p>In the <code>setRequestId</code> function in the snippet above, the instance of AsyncLocalStorage created in <code>context-storage.js</code> is retrieved from the return value of executing <code>contextStorage</code> as <code>store</code>. If <code>store</code> is falsy, the <code>run</code> method executes the <code>next</code> Express callback, providing <code>requestId</code> in an object for access anywhere within <code>next</code> from <code>contextStorage</code>.</p>
<p>If <code>store</code> has a value but doesn’t have the <code>requestId</code> property, set the <code>requestId</code> property and its value on it and return the executed <code>next</code> function.</p>
<p>Lastly, place <code>setRequestId</code> as the first middleware of the Express application in <code>index.js</code> so that every request can have an ID before carrying out other operations.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// index.js</span>
<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> { logRequestSummary, logger } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./logger"</span>);
<span class="hljs-keyword">const</span> { setRequestId } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./set-request-id"</span>);

<span class="hljs-keyword">const</span> app = express();

app.use(
  setRequestId(), <span class="hljs-comment">// 👈🏾 set as first middleware</span>
  express.json(),
  express.urlencoded({ <span class="hljs-attr">extended</span>: <span class="hljs-literal">true</span> }),
  logRequestSummary
);

<span class="hljs-comment">// ...</span>
</code></pre>
<p>You can check the current state of this project if you run the <code>git checkout 3-async-local-storage-req-id</code> command on your terminal or by visiting <a target="_blank" href="https://github.com/orimdominic/freecodecamp-express-request-ids/tree/3-async-local-storage-req-id">3-async-local-storage-req-id</a> of the GitHub repository.</p>
<h2 id="heading-use-the-request-id-in-the-logger-utilities">Use the Request ID in the Logger Utilities</h2>
<p>Now that the <code>requestId</code> property has been set in the store, you can access it from anywhere within <code>next</code> using <code>contextStorage</code>. You’ll access it within the functions in <code>logger.js</code> and attach it to the logs so that when messages are logged to the terminal for a request, the request ID will appear with the logged message.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// logger.js</span>
<span class="hljs-keyword">const</span> winston = <span class="hljs-built_in">require</span>(<span class="hljs-string">"winston"</span>);
<span class="hljs-keyword">const</span> morgan = <span class="hljs-built_in">require</span>(<span class="hljs-string">"morgan"</span>);
<span class="hljs-keyword">const</span> { contextStorage } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./context-storage"</span>);

<span class="hljs-keyword">const</span> { combine, errors, json, timestamp, colorize } = winston.format;

<span class="hljs-keyword">const</span> logHandler = winston.createLogger({
  <span class="hljs-attr">level</span>: <span class="hljs-string">"debug"</span>,
  <span class="hljs-attr">levels</span>: winston.config.npm.levels,
  <span class="hljs-attr">format</span>: combine(

    <span class="hljs-comment">// 👇🏽 retrieve requestId from contextStorage and attach it to the logged message</span>
    winston.format(<span class="hljs-function">(<span class="hljs-params">info</span>) =&gt;</span> {
      info.request_id = contextStorage().getStore()?.requestId;
      <span class="hljs-keyword">return</span> info;
    })(),
    <span class="hljs-comment">// 👆🏽 retrieve requestId from contextStorage and attach it to the logged message</span>

    timestamp({ <span class="hljs-attr">format</span>: <span class="hljs-string">"YYYY-MM-DD hh:mm:ss.SSS A"</span> }),
    errors({ <span class="hljs-attr">stack</span>: <span class="hljs-literal">true</span> }),
    <span class="hljs-comment">// ...</span>
  ),
  <span class="hljs-attr">transports</span>: [<span class="hljs-keyword">new</span> winston.transports.Console()],
});

<span class="hljs-comment">// ...</span>
</code></pre>
<p>In the <code>combine</code> function from winston, you’ll include a function argument that accepts the message to be logged – <code>info</code> – as an argument and attach the <code>request_id</code> property to it. Its value is the value of <code>requestId</code> retrieved from <code>contextStorage</code>. With this modification, any message logged for a request will have the request ID for that request attached to it.</p>
<p>With this complete, stop the project from running if it’s running already and run it again with the <code>npm start</code> command. Make API requests to the two endpoints and you’ll see output similar to the snippet below on the terminal:</p>
<pre><code class="lang-bash">{
  <span class="hljs-string">"level"</span>: <span class="hljs-string">"info"</span>,
  <span class="hljs-string">"message"</span>: <span class="hljs-string">"GET request to /hello"</span>,
  <span class="hljs-string">"request_id"</span>: <span class="hljs-string">"c80e92d0-eafe-42c7-b093-e5ffce014819"</span>,
  <span class="hljs-string">"timestamp"</span>: <span class="hljs-string">"2025-08-17 07:58:13.571 PM"</span>
}
{
  <span class="hljs-string">"level"</span>: <span class="hljs-string">"http"</span>,
  <span class="hljs-string">"message"</span>: {
    <span class="hljs-string">"content_length"</span>: <span class="hljs-string">"31 bytes"</span>,
    <span class="hljs-string">"method"</span>: <span class="hljs-string">"GET"</span>,
    <span class="hljs-string">"response_time"</span>: <span class="hljs-string">"9.397 ms"</span>,
    <span class="hljs-string">"status_code"</span>: 200,
    <span class="hljs-string">"url"</span>: <span class="hljs-string">"/hello"</span>
  },
  <span class="hljs-string">"request_id"</span>: <span class="hljs-string">"c80e92d0-eafe-42c7-b093-e5ffce014819"</span>,
  <span class="hljs-string">"timestamp"</span>: <span class="hljs-string">"2025-08-17 07:58:13.584 PM"</span>
}
</code></pre>
<p>Unlike the previous log output, this one contains the request ID of each request. By using <code>AsyncLocalStorage</code> to efficiently store the value of the request ID and access it for use in the loggers, you can accurately trace logged messages to their API requests.</p>
<p>You can access the current state of the project if you run the <code>git checkout 4-use-context-in-logger</code> command on the terminal or by visiting <a target="_blank" href="https://github.com/orimdominic/freecodecamp-express-request-ids/tree/4-use-context-in-logger">4-use-context-in-logger</a> of the GitHub repository.</p>
<h2 id="heading-set-header-to-have-x-request-id-optional-challenge">Set Header to have X-Request-Id (Optional Challenge)</h2>
<p>You have been able to store, access, and attach a request’s ID to its logged message. Can you set the request ID as a header on the response? The challenge is to set a header, <code>X-Request-Id</code>, on the response so that every request response has the value of the request ID as the value of the <code>X-Request-Id</code> response header.</p>
<p>This is useful for communicating with the frontend when trying to debug requests.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>When API requests can be monitored, you can track performance metrics to discover areas that need improvement and attention, identify issues like failed requests and server errors and why they happened, and study patterns in request volume metrics for planning and scalability.</p>
<p>When you attach a unique identifier to an API request, you can use it to trace the events that occurred within the lifetime of the request and differentiate it from other requests of the same type.</p>
<p>Aside from using AsyncLocalStorage to store request IDs, you can also use it to store other request context information such as the authenticated user details. Using AsyncLocalStorage to store request context information is considered a best practice.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Write Tests Using the Node.js Test Runner and mongodb-memory-server ]]>
                </title>
                <description>
                    <![CDATA[ I recently migrated some tests from Jest to the Node.js test runner in two of my projects that use MongoDB. In one of those projects, test runtime was reduced from 107 seconds to 25 seconds (screenshot below). In the other project, test runtime was r... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-write-tests-using-the-nodejs-test-runner-and-mongodb-memory-server/</link>
                <guid isPermaLink="false">67ae1ed03cb7bcfa7a1a3530</guid>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MongoDB ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Orim Dominic Adah ]]>
                </dc:creator>
                <pubDate>Thu, 13 Feb 2025 16:33:20 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1739464298236/f5d6c959-4570-4813-bdd2-28c27dae4e1f.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>I recently migrated some tests from Jest to the Node.js test runner in two of my projects that use MongoDB. In one of those projects, test runtime was reduced from 107 seconds to 25 seconds (screenshot below). In the other project, test runtime was reduced by about 66%.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738830673460/1560f7a3-38c1-42f3-8944-df06b40d73e4.png" alt="76% reduction in time taken to run tests in Jest vs Node.js test runner" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>I decided to share with you how I was able to implement this. I think you’ll find it helpful, as it’s more cost-effective (in terms of reducing money spent on running tests in CI/CD), and it also improves your developer experience.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-nodejs-test-runner">The Node.js Test Runner</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-mongodb-in-memory-server">MongoDB In-Memory Server</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-write-the-tests">How to Write the Tests</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-1-set-up-the-project">1. Set Up the Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-set-up-mongoose-schema">2. Set up Mongoose Schema</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-3-set-up-services">3. Set Up Services</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-4-set-up-tests">4. Set Up Tests</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-5-write-tests">5. Write Tests</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-6-pass-tests">6. Pass Tests</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-7-use-typescript-optional">7. Use TypeScript (Optional)</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along with this guide, you should have experience working with Node.js, MongoDB, and Mongoose (or any other MongoDB object data mapper). You should also have Node.js (at least v20.18.2) and MongoDB installed on your computer.</p>
<h2 id="heading-the-nodejs-test-runner">The Node.js Test Runner</h2>
<p>The Node.js test runner was introduced as an experimental feature in version 18 of Node.js. It became fully available in version 20. It gives you the ability to:</p>
<ol>
<li><p>Run tests</p>
</li>
<li><p>Report test results</p>
</li>
<li><p>Report test coverage (still experimental at version 23)</p>
</li>
</ol>
<p>It’s a good idea to use the in-built test runner when writing tests in Node.js because it means that you have to use fewer external dependencies. You don’t need to install an external library (and its peer dependencies) to run tests.</p>
<p>The built-in best runner is also faster. Based on my experience using it on two projects (which formerly used Jest), I saw improvements of at least a 66% reduction in the time taken to run tests completely.</p>
<p>And unlike other testing frameworks or libraries, the Node.js test runner was built specifically for Node.js projects. It doesn’t try to accommodate the specifics of other programming environments like the browser. The specifics of Node.js are its main and only priority.</p>
<h2 id="heading-mongodb-in-memory-server">MongoDB In-Memory Server</h2>
<p>For tests that involve making requests to a database, some developers prefer to mock the requests to avoid making requests to a real database. They do this because making a request to a real database requires a lot of setting up which can cost time and resources.</p>
<p>Writing and fetching data using a real database is slower <a target="_blank" href="https://www.mongodb.com/resources/basics/databases/in-memory-database">compared to writing and fetching data from memory</a>. When running automated tests, using a real MongoDB server will be slower than using an in-memory database server, and that is where <a target="_blank" href="https://github.com/typegoose/mongodb-memory-server">mongodb-memory-server</a> becomes useful.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738832586702/62360547-70e8-4e74-854f-c7ad74d182ea.png" alt="Comparison between memory and database communication with CPU" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>According to its documentation, mongodb-memory-server creates and starts a real MongoDB server programmatically from within Node.js, but uses an in-memory database by default. It also allows you to connect to the database server it creates using your preferred object data mapper such as Mongoose, Prisma, or TypeORM. In this guide, we’ll use <a target="_blank" href="https://mongoosejs.com/">Mongoose</a> (v8.9.6).</p>
<p>Since the data stored by mongodb-memory-server resides in memory by default, it’s faster to read from and write to than when using a real database. mongodb-memory-server is also easier to set up. These benefits make it a good choice for using it as a database server for writing tests.</p>
<p>Note: Make sure to install v9.1.6 of mongodb-memory-server to follow this guide. v10 currently has issues with cleaning up resources after tests are done. See this issue titled <a target="_blank" href="https://github.com/typegoose/mongodb-memory-server/issues/912">Node forking will include any --import from the original command</a>.</p>
<p>The issue has been resolved at the time of writing this article, but the fix has not been merged for installs.</p>
<h2 id="heading-how-to-write-the-tests">How to Write the Tests</h2>
<p>Now I’ll take you through the following steps to get you started writing tests:</p>
<ol>
<li><p>Set up the project</p>
</li>
<li><p>Set up mongoose schema</p>
</li>
<li><p>Set up services</p>
</li>
<li><p>Set up tests</p>
</li>
<li><p>Write tests</p>
</li>
<li><p>Pass tests</p>
</li>
<li><p>Use TypeScript (Optional)</p>
</li>
</ol>
<h3 id="heading-1-set-up-the-project">1. Set Up the Project</h3>
<p>I created a GitHub repository to make it easier for you to follow this guide. Clone the repository at <a target="_blank" href="https://github.com/orimdominic/nodejs-test-runner-mongoose">nodejs-test-runner-mongoose</a> and checkout branch <code>01-setup</code>.</p>
<p>In <code>01-setup</code>, the dependencies for the project are in the <code>package.json</code> file. Install the dependencies using the <code>npm install</code> command to set up the project. To make sure that the setup is complete and correct, run the <code>node .</code> command in the terminal of your project. You should see your version of Node.js as an output on the terminal.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># install dependencies</span>
npm install
...
<span class="hljs-comment"># run the node command</span>
node .
<span class="hljs-comment"># the output</span>
You are running Node.js v22.13.1
</code></pre>
<h3 id="heading-2-set-up-mongoose-schema">2. Set up Mongoose Schema</h3>
<p>We’ll set up the schema for two collections (Task and User) in branch <a target="_blank" href="https://github.com/orimdominic/nodejs-test-runner-mongoose/tree/02-setup-schema"><code>02-setup-schema</code></a> using Mongoose. The <a target="_blank" href="https://github.com/orimdominic/nodejs-test-runner-mongoose/blob/02-setup-schema/task/model.mjs"><code>task/model.mjs</code></a> and <a target="_blank" href="https://github.com/orimdominic/nodejs-test-runner-mongoose/blob/02-setup-schema/user/model.mjs"><code>user/model.mjs</code></a> files contain the schema for the Task and the User collection, respectively. We’ll also set up a database connection in <code>index.mjs</code> to ensure that the schema setup works correctly.</p>
<p>I won’t go into detail about Mongoose models and schema in this article because they are outside its scope.</p>
<p>When you run the <code>node .</code> command after implementing the changes in <code>02-setup-schema</code>, you should see a similar result in the console as in the snippet below:</p>
<pre><code class="lang-bash">node .
You are running Node.js v22.13.1
Created user with id 679f1d7f73fbeaf23b2007df
Created task <span class="hljs-string">"Task title"</span> <span class="hljs-keyword">for</span> user with id <span class="hljs-string">"679f1d7f73fbeaf23b2007df"</span>
</code></pre>
<p>You can see the differences between <code>01-setup</code> and <code>02-setup-schema</code> via the <a target="_blank" href="https://github.com/orimdominic/nodejs-test-runner-mongoose/compare/01-setup...02-setup-schema">01-setup &lt;&gt; 02-setup-schema diff on GitHub</a>.</p>
<h3 id="heading-3-set-up-services">3. Set Up Services</h3>
<p>Next, we create service files (<a target="_blank" href="https://github.com/orimdominic/nodejs-test-runner-mongoose/blob/03-setup-services/task/service.mjs"><code>task/service.mjs</code></a> and <a target="_blank" href="https://github.com/orimdominic/nodejs-test-runner-mongoose/blob/03-setup-services/user/service.mjs"><code>user/service.mjs</code></a>) in branch <a target="_blank" href="https://github.com/orimdominic/nodejs-test-runner-mongoose/tree/03-setup-services"><code>03-setup-services</code></a>. Both files currently contain empty functions that we’ll write tests for later. These functions will contain business logic and also communicate with the database. We’re using <a target="_blank" href="https://jsdoc.app/">JSDoc</a> comments for typing parameters and return values.</p>
<p>Click <a target="_blank" href="https://github.com/orimdominic/nodejs-test-runner-mongoose/compare/01-setup...02-setup-schema">02-setup-schema &lt;&gt; 03-setup-services diff</a> to see the code changes between <code>02-setup-schema</code> and <code>03-setup-services</code>.</p>
<h3 id="heading-4-set-up-tests">4. Set Up Tests</h3>
<p>In branch <a target="_blank" href="https://github.com/orimdominic/nodejs-test-runner-mongoose/tree/04-set-up-tests"><code>04-set-up-tests</code></a>, we set up the codebase to run tests. We create <a target="_blank" href="https://github.com/orimdominic/nodejs-test-runner-mongoose/blob/04-set-up-tests/test.setup.mjs"><code>test.setup.mjs</code></a> which contains code that will be run before each test file is executed.</p>
<p>In <code>test.setup.mjs</code>, the <code>connect</code> function creates a MongoDB In-Memory server and connects to it with Mongoose for running the tests. The <code>closeDatabase</code> function closes the database connection and cleans up all resources to free memory.</p>
<p>The <code>connect</code> and <code>closeDatabase</code> functions get executed in the <a target="_blank" href="https://nodejs.org/api/test.html#beforefn-options"><code>t.before</code></a> hook and the <a target="_blank" href="https://nodejs.org/api/test.html#afterfn-options"><code>t.after</code></a> hook respectively. This ensures that, before a test file is run, a database connection is established through <code>t.before</code>. Then after tests for the file have been completely run, the database connection is dropped and the resources used are cleared up through <code>t.after</code>.</p>
<p>In <code>package.json</code>, we’ll update the npm <code>test</code> script to <code>node --test --import ./test.setup.mjs</code>. This command ensures that the <code>test.setup.mjs</code> ES Module is preloaded and executed through the <a target="_blank" href="https://nodejs.org/api/cli.html#--importmodule">--import</a> CLI command before each test file is run.</p>
<p>Then we’ll create the test files with empty tests in the <code>__tests__</code> folders for <code>user</code> and <code>task</code>. After implementing the <a target="_blank" href="https://github.com/orimdominic/nodejs-test-runner-mongoose/compare/03-setup-services...04-set-up-tests">new changes in 04-set-up-tests</a>, running the <code>test</code> script with <code>npm run test</code> should display output similar to the snippet below:</p>
<pre><code class="lang-bash">npm run <span class="hljs-built_in">test</span>

&gt; nodejs-test-runner-mongoose@1.0.0 <span class="hljs-built_in">test</span>
&gt; node --<span class="hljs-built_in">test</span> --import ./test.setup.mjs

...

ℹ tests 8
ℹ suites 5
ℹ pass 8
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 941.768873
</code></pre>
<p>All tests currently pass because there are no assertions that fail in them. We’ll write tests with assertions in the following section.</p>
<h3 id="heading-5-write-tests">5. Write Tests</h3>
<p>Now it’s time to write tests for the functions in the service files in the <a target="_blank" href="https://github.com/orimdominic/nodejs-test-runner-mongoose/blob/05-write-tests"><code>05-write-tests</code></a> branch. We’re using the <a target="_blank" href="https://nodejs.org/api/assert.html">Node.js assert library</a> to ensure that values returned from the functions are what we expect. You can view the tests we’ve written when you compare <a target="_blank" href="https://github.com/orimdominic/nodejs-test-runner-mongoose/compare/04-set-up-tests...05-write-tests">the differences between 04-set-up-tests and 05-write-tests</a></p>
<p>When the <code>tests</code> script is run, all tests fail because we haven’t written the functions in the service files yet. You should see output similar to the snippet below when you run the <code>test</code> script:</p>
<pre><code class="lang-bash">npm run <span class="hljs-built_in">test</span>

&gt; nodejs-test-runner-mongoose@1.0.0 <span class="hljs-built_in">test</span>
&gt; node --<span class="hljs-built_in">test</span> --import ./test.setup.mjs

...

ℹ tests 8
ℹ suites 5
ℹ pass 0
ℹ fail 8
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1202.031961
</code></pre>
<h3 id="heading-6-pass-tests">6. Pass Tests</h3>
<p>In <a target="_blank" href="https://github.com/orimdominic/nodejs-test-runner-mongoose/blob/06-pass-tests"><code>06-pass-tests</code></a>, we write the functions in the service files to pass the tests. Only 6 out of 7 tests pass when the <code>test</code> script is run because we skipped the test for the <code>getById</code> function in <code>user/service.mjs</code> has with <code>t.skip</code>. We haven’t finished the <code>getById</code> function in <code>user/service.mjs</code>. I figured we could leave it as an exercise.</p>
<p>When you run the <code>test</code> script, you should get a similar output in the terminal as below:</p>
<pre><code class="lang-bash">ℹ tests 7
ℹ suites 4
ℹ pass 6
ℹ fail 0
ℹ cancelled 0
ℹ skipped 1
ℹ todo 0
ℹ duration_ms 1287.564918
</code></pre>
<p>You can see the code we wrote to pass tests in the <a target="_blank" href="https://github.com/orimdominic/nodejs-test-runner-mongoose/compare/05-write-tests...06-pass-tests">code changes between 05-write-tests and 06-pass-tests</a>.</p>
<h3 id="heading-7-use-typescript-optional">7. Use TypeScript (Optional)</h3>
<p>If you intend to run tests with TypeScript, you can checkout branch <a target="_blank" href="https://github.com/orimdominic/nodejs-test-runner-mongoose/tree/07-with-typescript"><code>07-with-typescript</code></a>. You need to have Node.js <code>&gt;=v22.6.0</code> installed because we’re using the <code>--experimental-strip-types</code> option in the <code>test</code>. To set up tests to run with TypeScript, go through the following steps:</p>
<ol>
<li><p>Install TypeScript using the <code>npm install typescript --save-dev</code> command</p>
</li>
<li><p>Install tsx using the <code>npm install tsx</code> command</p>
</li>
<li><p>Create a default <code>tsconfig.json</code> file at the root of the project using the <code>npx tsc --init</code> command</p>
</li>
</ol>
<p>In <code>package.json</code>, update the <code>test</code> script to this:</p>
<pre><code class="lang-bash"><span class="hljs-string">"test"</span>: <span class="hljs-string">"node --test --experimental-strip-types --import tsx --import ./test.setup.mjs"</span>
</code></pre>
<ul>
<li><p><a target="_blank" href="https://nodejs.org/docs/latest-v22.x/api/cli.html#--experimental-strip-types"><code>--experimental-strip-types</code></a> helps strip out types before each test file is executed.</p>
</li>
<li><p>Preloading <code>tsx</code> with the <code>--import</code> helps execute the TypeScript file. Without it, the test runner will not be able to find files imported without the <code>.ts</code> extension. For example, <code>user/model.ts</code> imported with the code snippet below will not be found.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">import</span> { UserModel } <span class="hljs-keyword">from</span> <span class="hljs-string">"./model"</span>;
</code></pre>
</li>
</ul>
<p>The rest of the <a target="_blank" href="https://github.com/orimdominic/nodejs-test-runner-mongoose/compare/06-pass-tests...07-with-typescript">changes from 06-pass-tests to 07-with-typescript</a> involve updating types, changing file extensions from <code>.mjs</code> to <code>.ts</code> and updating import statements.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this guide, you have learned how to use the built-in Node.js test runner and why it’s often a better choice over other testing libraries and frameworks. You have also learned how to use mongodb-memory-server as a replacement for a real MongoDB server, as well as why it’s a good idea to use this instead of a real MongoDB server for tests.</p>
<p>Most importantly, you have learned how to set up and run tests in Node.js using the Node.js test runner and mongodb-memory-server. You should now know how to set up your projects to run the tests if you use TypeScript.</p>
<p>If you find the <a target="_blank" href="https://github.com/orimdominic/nodejs-test-runner-mongoose">nodejs-test-runner-mongoose</a> repository useful, kindly give it a star. It encourages me. Thank you.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Write Code That's Easy to Read – Tips for Developers ]]>
                </title>
                <description>
                    <![CDATA[ Programs are meant to be read by humans and only incidentally for computers to execute. - Donald Knuth Have you ever heard that programmers spend more time reading code than writing it? Well, I’ve found that this is often true: as a developer, you’l... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-write-code-thats-easy-to-read/</link>
                <guid isPermaLink="false">675098030c45d94ee71d4e04</guid>
                
                    <category>
                        <![CDATA[ clean code ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Orim Dominic Adah ]]>
                </dc:creator>
                <pubDate>Wed, 04 Dec 2024 17:57:23 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733334801650/9c73c253-246a-4678-8d65-6679ff7f35f2.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <blockquote>
<p>Programs are meant to be read by humans and only incidentally for computers to execute. - Donald Knuth</p>
</blockquote>
<p>Have you ever heard that programmers spend more time reading code than writing it? Well, I’ve found that this is often true: as a developer, you’ll often spend more time reading and thinking about code than actually writing code.</p>
<p>This means that, however optimally you intend to make your code run, it’s also important that it’s delightful and easy to read.</p>
<p>In this article, we’ll look at an example function: <code>createOrUpdateUserOnLogin</code>. It lives in a JavaScript codebase far away and it’s begging to be made delightful to read. We will look at <code>createOrUpdateUserOnLogin</code>, highlight what makes it hard to read and why, and eventually refactor it to make it easier to read and understand.</p>
<p>The function is written in JavaScript and uses <a target="_blank" href="https://jsdoc.app/">JSDoc</a> to document its parameters. Knowledge of JavaScript is not necessarily important because the logic in the function will be explained in detail. JSDoc is only used to document what the function’s parameters represent.</p>
<h2 id="heading-the-problematic-function">The Problematic Function</h2>
<p>This function is not made-up. It is a real function in the codebase of an application with more than a thousand users. Here it is:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * <span class="hljs-doctag">@param <span class="hljs-type">{Object}</span> <span class="hljs-variable">dto</span></span>
 * <span class="hljs-doctag">@param <span class="hljs-type">{string}</span> </span>dto.email
 * <span class="hljs-doctag">@param <span class="hljs-type">{string}</span> </span>dto.firstName
 * <span class="hljs-doctag">@param <span class="hljs-type">{string}</span> </span>dto.lastName
 * <span class="hljs-doctag">@param <span class="hljs-type">{string}</span> </span>[dto.photoUrl]
 * <span class="hljs-doctag">@param <span class="hljs-type">{'apple' | 'google'}</span> </span>[dto.loginProvider]
 * <span class="hljs-doctag">@param <span class="hljs-type">{string}</span> </span>[dto.appleId]
 * <span class="hljs-doctag">@returns <span class="hljs-type">{string}</span> <span class="hljs-variable">token</span></span> - access token
 */</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createOrUpdateUserOnLogin</span>(<span class="hljs-params">dto</span>) </span>{
  <span class="hljs-keyword">let</span> user;

  <span class="hljs-keyword">if</span> (dto.loginProvider == <span class="hljs-string">"apple"</span>) {
    user = <span class="hljs-keyword">await</span> findOneByAppleId(dto.appleId);
    <span class="hljs-keyword">if</span> (user?.isDisabled) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Unable to login"</span>);
    }

    <span class="hljs-keyword">if</span> (user &amp;&amp; !user.verified) {
      user = <span class="hljs-keyword">await</span> setUserAsVerified(user.email);
    }

    <span class="hljs-keyword">if</span> (!user) {
      user = <span class="hljs-keyword">await</span> findOneByEmail(dto.email);

      <span class="hljs-keyword">if</span> (user &amp;&amp; dto.appleId) {
        user = <span class="hljs-keyword">await</span> updateUserAppleId(user, dto.appleId);
      }

      <span class="hljs-keyword">if</span> (user &amp;&amp; !user.verified) {
        user = <span class="hljs-keyword">await</span> setUserAsVerified(user.email);
      }
    }
  } <span class="hljs-keyword">else</span> {
    user = <span class="hljs-keyword">await</span> findOneByEmail(dto.email);
    <span class="hljs-keyword">if</span> (user?.isDisabled) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Unable to login"</span>);
    }

    <span class="hljs-keyword">if</span> (user &amp;&amp; !user.photoUrl &amp;&amp; dto.photoUrl) {
      user.photoUrl = dto.photoUrl;
      user = <span class="hljs-keyword">await</span> updateUserDetails(user._id, user);
    }

    <span class="hljs-keyword">if</span> (user &amp;&amp; !user.verified) {
      user = <span class="hljs-keyword">await</span> setUserAsVerified(user.email);
    }
  }

  <span class="hljs-keyword">if</span> (!user) {
    user = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.usersService.create(loginProviderDto);
  }

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.createToken(user);
}
</code></pre>
<p>Perhaps you can tell by reading through and studying the code that it is quite hard to follow. If you leave your computer for a break right after reading this function, there is a good chance you won’t remember what exactly it does when you get back.</p>
<p>But this isn’t, and shouldn’t be, the case when you read a good story, regardless of its length. You can follow it easily and remember the basic details after hearing it.</p>
<p>The function is executed when a user attempts to sign in or sign up. Users can be authenticated using their Google account or their Apple account and the function has to return an access token on a successful attempt.</p>
<p>Some users have disabled their account. Those users are not allowed to authenticate successfully. The logic of the function also includes operations for updating the data of already-registered users based on some conditions.</p>
<p>The function does one of two things:</p>
<ol>
<li><p>It creates an authentication token for an existing account and returns it after updating the account details or,</p>
</li>
<li><p>It creates an account if none exists and returns an authentication token.</p>
</li>
</ol>
<p>This violates the Single Responsibility Principle – but fixing that is a challenge for another article.</p>
<p>The goal here is to refactor this function so that it reads so well that even a non-programmer can read it and understand what it does. Even better, we also want them to be able to remember it after they’ve been away from it for a while.</p>
<p>The function is well-tested, so there are no worries about breaking any functionality while refactoring. Tests will report any breaking changes.</p>
<h3 id="heading-what-makes-this-code-hard-to-read">What Makes This Code Hard to Read?</h3>
<p>A number of factors make this code harder to read. Here are the main ones:</p>
<ol>
<li><p><strong>Deep nesting</strong> (<code>if</code> statements within <code>if</code> statements) makes it hard to keep track of the changes that occur through the execution of the code. In the case of <code>createOrUpdateUserOnLogin</code>, it is nested conditionals. Other cases can include logic like an <code>if</code> statement inside a <code>while</code> loop which is nested inside another <code>if</code> statement.</p>
<p> Deep nesting increases the complexity of reading and understanding code. Its flow isn’t pleasant to the eyes and it makes writing tests more complicated because you have to cater for the operations inside the nested code blocks.</p>
</li>
<li><p><strong>Complex conditionals</strong> like <code>user &amp;&amp; !user.photoUrl &amp;&amp; dto.photoUrl</code> hold a lot of logic which has to be kept in your short-term memory and remembered as you read on.</p>
</li>
<li><p><strong>Haphazard flow</strong> which makes it hard for you to tell at a glance what the function is doing. The function seems to be doing a lot, but it really is not. Two operations are repeated: preventing disabled users from logging in (twice) and updating users’ verification status (three times). Finding a user by email is also repeated twice.</p>
</li>
</ol>
<h2 id="heading-how-to-refactor-the-code-for-easier-more-delightful-reading">How to Refactor the Code for Easier, More Delightful Reading</h2>
<p>After examining the function for issues that make it difficult to read, here are a couple of changes you’ll want to implement:</p>
<p><strong>Handle failure cases first</strong>: Consider failure cases first and get them out of the story so that the function can focus on the success cases for a smooth narration of the logic of the code.</p>
<p>This involves the use of <code>return</code> statements or throwing errors early in the function for operations that prevent the goal of the function from being achieved.</p>
<p><strong>Rearrange the flow</strong>: If it’s possible that some operations can occur before others and it will make the flow of the code memorable and enjoyable to read, while still achieving the purpose of the function, then you should rearrange it accordingly.</p>
<p><strong>Use everyday grammar</strong>: This involves updating identifiers and compressing complex conditionals into memorable identifier names. Everyday grammar is easy to read because it is familiar and relatable.</p>
<p><strong>Avoid nested code blocks</strong>: When debugging code mentally or trying to understand it, changes in the value of identifiers in nested code blocks are hard to keep track of. This is because with each nested conditional, there is at least a 2x increase in the number of paths that the logic execution can take to update the value of one identifier – and that gets worse if there is more than one identifier that is updated.</p>
<p>This means that your mind has to keep track of those paths which can escalate quickly into a mental memory overload, potentially resulting in mental grief and bugs when updating the code.</p>
<p>The visual effect of nested code is also not pleasant to the eyes and it makes writing tests more complex than it should be.</p>
<p>After refactoring the code using the guidelines above, we have the following snippet (I’ve numbered different parts of the code for reference in the explanations below):</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateUserOnLogin</span>(<span class="hljs-params">dto</span>) </span>{
  <span class="hljs-keyword">let</span> user = <span class="hljs-keyword">await</span> findUserByEmail(dto.email); <span class="hljs-comment">// 1</span>
  <span class="hljs-keyword">if</span> (!user) {
    user = <span class="hljs-keyword">await</span> createUser(dto);
  }

  <span class="hljs-keyword">if</span> (user.isDisabled) { <span class="hljs-comment">// 2a</span>
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Unable to login"</span>); <span class="hljs-comment">// 2b</span>
  }

  <span class="hljs-keyword">const</span> userIsNotVerified = <span class="hljs-built_in">Boolean</span>(user.isVerified) == <span class="hljs-literal">false</span> <span class="hljs-comment">// 3a</span>
  <span class="hljs-keyword">if</span> (userIsNotVerified) { <span class="hljs-comment">// 3b</span>
    <span class="hljs-keyword">await</span> setUserAsVerified(user.email);
  }

  <span class="hljs-keyword">const</span> shouldUpdateAppleId = dto.loginProvider == <span class="hljs-string">"apple"</span> &amp;&amp; dto.appleId <span class="hljs-comment">// 4a</span>
  <span class="hljs-keyword">if</span> (shouldUpdateAppleId) { <span class="hljs-comment">// 4b</span>
    <span class="hljs-keyword">await</span> setUserAppleId(user.email, dto.appleId);
  }

  <span class="hljs-keyword">const</span> shouldUpdatePhotoUrl = !user.photoUrl &amp;&amp; dto.photoUrl <span class="hljs-comment">// 5a</span>
  <span class="hljs-keyword">if</span> (shouldUpdatePhotoUrl) { <span class="hljs-comment">// 5b</span>
    <span class="hljs-keyword">await</span> updateUserDetails(user._id, { <span class="hljs-attr">photoUrl</span>: dto.photoUrl });
  }

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.createToken(user);
}
</code></pre>
<p>Alright, now let’s see what exactly we’ve done here to make the code more enjoyable to read.</p>
<h3 id="heading-1-rearranging-the-flow">1. Rearranging the Flow</h3>
<p>Judging by the JSDoc comment above the function, <code>email</code> is a required argument field. Existing accounts have an email address irrespective of their login provider. We can fetch an account by <code>email</code> first and decide to create a new one if none exists (code section 1). By doing this, failure cases are handled early.</p>
<p>Choosing to throw an error if the account is disabled at the beginning (section 2b) is also an attempt to handle failure cases early. This does not affect new accounts because new accounts are not disabled by default.</p>
<p>Handling failure cases early helps us understand the code easier because we’re free to consider only what is going to happen without keeping track of error cases from before (like remembering if the <code>user</code> object has a value or not (section 5)) as we read on.</p>
<p>The refactored code has also eliminated nested conditionals and still works as expected.</p>
<h3 id="heading-2-using-everyday-grammar">2. Using Everyday Grammar</h3>
<p>In trying to make the code read like everyday grammar, we’ve used clear and relatable variable names (see sections 2a, 3, 4, 5). Written this way, even non-programmers like product managers can read the code and understand what is happening.</p>
<p>Everyday grammar reads like pseudocode - “If user is not verified then set user to verified” and “If should update apple id then update appleId”.</p>
<p>Using everyday grammar is key to making code read like a story.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Code that is delightful to read promotes maintainability and thus, longevity of software. Contributors can read it, understand it, and eventually update it with ease. Like reading a well-written story, reading code can be an enjoyable activity.</p>
<p>Image credit: <a target="_blank" href="https://storyset.com/work">Work illustrations by Storyset</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Handle MongoDB Migrations with ts-migrate-mongoose ]]>
                </title>
                <description>
                    <![CDATA[ Database migrations are modifications made to a database. These modifications may include changing the schema of a table, updating the data in a set of records, seeding data or deleting a range of records. Database migrations are usually run before a... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/handle-mongodb-migrations-with-ts-migrate-mongoose/</link>
                <guid isPermaLink="false">6747274cb7666002fd1cb1a6</guid>
                
                    <category>
                        <![CDATA[ MongoDB ]]>
                    </category>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Orim Dominic Adah ]]>
                </dc:creator>
                <pubDate>Wed, 27 Nov 2024 14:06:04 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732615343335/0a1b3e5e-bfa7-4f57-81a7-7f4bac9e8b0a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Database migrations are modifications made to a database. These modifications may include changing the schema of a table, updating the data in a set of records, seeding data or deleting a range of records.</p>
<p>Database migrations are usually run before an application starts and do not run successfully more than once for the same database. Database migration tools save a history of migrations that have run in a database so that they can be tracked for future purposes.</p>
<p>In this article, you’ll learn how to set up and run database migrations in a minimal Node.js API application. We will use <a target="_blank" href="https://www.npmjs.com/package/ts-migrate-mongoose">ts-migrate-mongoose</a> and an npm script to create a migration and seed data into a MongoDB database. ts-migrate-mongoose supports running migration scripts from TypeScript code as well as CommonJS code.</p>
<p>ts-migrate-mongoose is a migration framework for Node.js projects that use <a target="_blank" href="https://www.npmjs.com/package/mongoose">mongoose</a> as the object-data mapper. It provides a template for writing migration scripts. It also provides a configuration to run the scripts programmatically and from the CLI.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-the-project">How to Set Up the Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-configure-ts-migrate-mongoose-for-the-project">How to Configure ts-migrate-mongoose for the Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-seed-user-data-with-ts-migrate-mongoose">How to Seed User Data with ts-migrate-mongoose</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-an-api-endpoint-to-fetch-seeded-data">How to Build an API Endpoint to Fetch Seeded Data</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-how-to-set-up-the-project">How to Set Up the Project</h2>
<p>To use ts-migrate-mongoose for database migrations, you need to have the following:</p>
<ol>
<li><p>A Node.js project with mongoose installed as a dependency.</p>
</li>
<li><p>A MongoDB database connected to the project.</p>
</li>
<li><p>MongoDB Compass (Optional – to enable us view the changes in the database).</p>
</li>
</ol>
<p>A starter repository which can be cloned from <a target="_blank" href="https://github.com/orimdominic/ts-migrate-mongoose-starter-repo">ts-migrate-mongoose-starter-repo</a> has been created for ease. Clone the repository, fill the environment variables and start the application by running the <code>npm start</code> command.</p>
<p>Visit <a target="_blank" href="http://localhost:8000">http://localhost:8000</a> with a browser or an API client such as Postman and the server will return a "Hello there!" text to show that the starter application runs as expected.</p>
<h2 id="heading-how-to-configure-ts-migrate-mongoose-for-the-project">How to Configure ts-migrate-mongoose for the Project</h2>
<p>To configure ts-migrate-mongoose for the project, install ts-migrate-mongoose with this command:</p>
<pre><code class="lang-bash">npm install ts-migrate-mongoose
</code></pre>
<p>ts-migrate-mongoose allows configuration with a JSON file, a TypeScript file, a <code>.env</code> file or via the CLI. It is advisable to use a <code>.env</code> file because the content of the configuration may contain a database password and it is not proper to have that exposed to the public. <code>.env</code> files are usually hidden via <code>.gitignore</code> files so they are more secure to use. This project will use a <code>.env</code> file for the ts-migrate-mongoose configuration.</p>
<p>The file should contain the following keys and their values:</p>
<ul>
<li><p><code>MIGRATE_MONGO_URI</code> - the URI of the Mongo database. It is the same as the database URL.</p>
</li>
<li><p><code>MIGRATE_MONGO_COLLECTION</code> - the name of the collection (or table) which migrations should be saved in. The default value is migrations which is what is used in this project. ts-migrate-mongoose saves migrations to MongoDB.</p>
</li>
<li><p><code>MIGRATE_MIGRATIONS_PATH</code> - the path to the folder for storing and reading migration scripts. The default value is <code>./migrations</code> which is what is used in this project.</p>
</li>
</ul>
<h2 id="heading-how-to-seed-user-data-with-ts-migrate-mongoose">How to Seed User Data with ts-migrate-mongoose</h2>
<p>We have been able to create a project and connect it successfully to a Mongo database. At this point, we want to seed user data into the database. We need to:</p>
<ol>
<li><p>Create a users collection (or table)</p>
</li>
<li><p>Use ts-migrate-mongoose to create a migration script to seed data</p>
</li>
<li><p>Use ts-migrate-mongoose to run the migration to seed the user data into the database before the application starts</p>
</li>
</ol>
<h3 id="heading-1-create-a-users-collection-using-mongoose">1. Create a users Collection using Mongoose</h3>
<p>Mongoose schema can be used to create a user collection (or table). User documents (or records) will have the following fields (or columns): <code>email</code>, <code>favouriteEmoji</code> and <code>yearOfBirth</code>.</p>
<p>To create a Mongoose schema for the user collection, create a <code>user.model.js</code> file in the root of the project containing the following code snippet:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">"mongoose"</span>);

<span class="hljs-keyword">const</span> userSchema = <span class="hljs-keyword">new</span> mongoose.Schema(
  {
    <span class="hljs-attr">email</span>: {
      <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
      <span class="hljs-attr">lowercase</span>: <span class="hljs-literal">true</span>,
      <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,
    },
    <span class="hljs-attr">favouriteEmoji</span>: {
      <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
      <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,
    },
    <span class="hljs-attr">yearOfBirth</span>: {
      <span class="hljs-attr">type</span>: <span class="hljs-built_in">Number</span>,
      <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,
    },
  },
  {
    <span class="hljs-attr">timestamps</span>: <span class="hljs-literal">true</span>,
  }
);

<span class="hljs-built_in">module</span>.exports.UserModel = mongoose.model(<span class="hljs-string">"User"</span>, userSchema);
</code></pre>
<h3 id="heading-2-create-a-migration-script-with-ts-migrate-mongoose">2. Create a Migration Script with ts-migrate-mongoose</h3>
<p>ts-migrate-mongoose provides CLI commands which can be used to create migration scripts.</p>
<p>Running <code>npx migrate create &lt;name-of-script&gt;</code> in the root folder of the project will create a script in the <code>MIGRATE_MIGRATIONS_PATH</code> folder (<code>./migrations</code> in our case). <code>&lt;name-of-script&gt;</code> is the name we want the migration script file to have when it is created.</p>
<p>To create a migration script to seed user data, run:</p>
<pre><code class="lang-bash">npx migrate create seed-users
</code></pre>
<p>The command will create a file in the <code>./migrations</code> folder with a name in the form -<code>&lt;timestamp&gt;-seed-users.ts</code>. The file will have the following code snippet content:</p>
<pre><code class="lang-ts"><span class="hljs-comment">// Import your models here</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">up</span> (<span class="hljs-params"></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">void</span>&gt; </span>{
  <span class="hljs-comment">// Write migration here</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">down</span> (<span class="hljs-params"></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">void</span>&gt; </span>{
  <span class="hljs-comment">// Write migration here</span>
}
</code></pre>
<p>The <code>up</code> function is used to run the migration. The <code>down</code> function is used to reverse whatever the <code>up</code> function executes, if need be. In our case, we are trying to seed users into the database. The <code>up</code> function will contain code to seed users into the database and the <code>down</code> function will contain code to delete users created in the <code>up</code> function.</p>
<p>If the database is inspected with MongoDB Compass, the migrations collection will have a document that looks like this:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"_id"</span>: ObjectId(<span class="hljs-string">"6744740465519c3bd9c1a7d1"</span>),
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"seed-users"</span>,
  <span class="hljs-attr">"state"</span>: <span class="hljs-string">"down"</span>,
  <span class="hljs-attr">"createdAt"</span>: <span class="hljs-number">2024</span><span class="hljs-number">-11</span><span class="hljs-number">-25</span>T12:<span class="hljs-number">56</span>:<span class="hljs-number">36.316</span>+<span class="hljs-number">00</span>:<span class="hljs-number">00</span>,
  <span class="hljs-attr">"updatedAt"</span>: <span class="hljs-number">2024</span><span class="hljs-number">-11</span><span class="hljs-number">-25</span>T12:<span class="hljs-number">56</span>:<span class="hljs-number">36.316</span>+<span class="hljs-number">00</span>:<span class="hljs-number">00</span>,
  <span class="hljs-attr">"__v"</span>: <span class="hljs-number">0</span>
}
</code></pre>
<p>The <code>state</code> field of the migration document is set to <code>down</code>. After it runs successfully, it changes to <code>up</code>.</p>
<p>You can update the code in <code>./migrations/&lt;timestamp&gt;-seed-users.ts</code> to the one in the snippet below:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>).config() <span class="hljs-comment">// load env variables</span>
<span class="hljs-keyword">const</span> db = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../db.js"</span>)
<span class="hljs-keyword">const</span> { UserModel } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../user.model.js"</span>);

<span class="hljs-keyword">const</span> seedUsers = [
  { email: <span class="hljs-string">"john@email.com"</span>, favouriteEmoji: <span class="hljs-string">"🏃"</span>, yearOfBirth: <span class="hljs-number">1997</span> },
  { email: <span class="hljs-string">"jane@email.com"</span>, favouriteEmoji: <span class="hljs-string">"🍏"</span>, yearOfBirth: <span class="hljs-number">1998</span> },
];

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">up</span> (<span class="hljs-params"></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">void</span>&gt; </span>{
  <span class="hljs-keyword">await</span> db.connect(process.env.MONGO_URI)
  <span class="hljs-keyword">await</span> UserModel.create(seedUsers);}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">down</span> (<span class="hljs-params"></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">void</span>&gt; </span>{
  <span class="hljs-keyword">await</span> db.connect(process.env.MONGO_URI)
  <span class="hljs-keyword">await</span> UserModel.delete({
    email: {
      $in: seedUsers.map(<span class="hljs-function">(<span class="hljs-params">u</span>) =&gt;</span> u.email),
    },
  });
}
</code></pre>
<h3 id="heading-3-run-the-migration-before-the-application-starts">3. Run the Migration Before the Application Starts</h3>
<p>ts-migrate-mongoose provides us with CLI commands to run the <code>up</code> and <code>down</code> function of migration scripts.</p>
<p>With <code>npx migrate up &lt;name-of-script&gt;</code> we can run the <code>up</code> function of a specific script. With <code>npx migrate up</code> we can run the <code>up</code> function of all scripts in the <code>./migrations</code> folder with a <code>state</code> of <code>down</code> in the database.</p>
<p>To run the migration before the application starts, we make use of npm scripts. npm scripts with a prefix of <code>pre</code> will run before a script without the <code>pre</code> prefix. For example, if there is a <code>dev</code> script and a <code>predev</code> script, whenever the <code>dev</code> script is run with <code>npm run dev</code>, the <code>predev</code> script will automatically run before the <code>dev</code> script is run.</p>
<p>We will use this feature of npm scripts to place the ts-migrate-mongoose command in a <code>prestart</code> script so that the migration will run before the <code>start</code> script.</p>
<p>Update the <code>package.json</code> file to have a <code>prestart</code> script that runs the ts-migrate-mongoose command for running the <code>up</code> function of migration scripts in the project.</p>
<pre><code class="lang-json">  <span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"prestart"</span>: <span class="hljs-string">"npx migrate up"</span>,
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"node index.js"</span>
  },
</code></pre>
<p>With this setup, when <code>npm run start</code> is executed to start the application, the <code>prestart</code> script will run to execute the migration using ts-migrate-mongoose and seed the database before the application starts.</p>
<p>You should have something similar to the snippet below after running <code>npm run start</code>:</p>
<pre><code class="lang-bash">Synchronizing database with file system migrations...
MongoDB connection successful
up: 1732543529744-seed-users.ts 
All migrations finished successfully

&gt; ts-migrate-mongoose-starter-repo@1.0.0 start
&gt; node index.js

MongoDB connection successful                      
Server listening on port 8000
</code></pre>
<p>Check out the <a target="_blank" href="https://github.com/orimdominic/ts-migrate-mongoose-starter-repo/tree/seed-users">seed-users</a> branch of the repository to see the current status of the codebase at this point in the article.</p>
<h2 id="heading-how-to-build-an-api-endpoint-to-fetch-seeded-data">How to Build an API Endpoint to Fetch Seeded Data</h2>
<p>We can build an API endpoint to fetch the seeded users data in our database. In the <code>server.js</code> file, update the code to the one in the snippet below:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { UserModel } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./user.model.js"</span>)

<span class="hljs-built_in">module</span>.exports = <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">req, res</span>) </span>{
  <span class="hljs-keyword">const</span> users = <span class="hljs-keyword">await</span> UserModel.find({}) <span class="hljs-comment">// fetch all the users in the database</span>

  res.writeHead(<span class="hljs-number">200</span>, { <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span> });
  <span class="hljs-keyword">return</span> res.end(<span class="hljs-built_in">JSON</span>.stringify({ <span class="hljs-comment">// return a JSON representation of the fetched users data</span>
    <span class="hljs-attr">users</span>: users.map(<span class="hljs-function">(<span class="hljs-params">u</span>) =&gt;</span> ({
      <span class="hljs-attr">email</span>: u.email,
      <span class="hljs-attr">favouriteEmoji</span>: u.favouriteEmoji,
      <span class="hljs-attr">yearOfBirth</span>: u.yearOfBirth,
      <span class="hljs-attr">createdAt</span>: u.createdAt
    }))
  }, <span class="hljs-literal">null</span>, <span class="hljs-number">2</span>));
};
</code></pre>
<p>If we start the application and visit <a target="_blank" href="http://localhost:8000">http://localhost:8000</a> using Postman or a browser, we get a JSON response similar to the one below:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"users"</span>: [
    {
      <span class="hljs-attr">"email"</span>: <span class="hljs-string">"john@email.com"</span>,
      <span class="hljs-attr">"favouriteEmoji"</span>: <span class="hljs-string">"🏃"</span>,
      <span class="hljs-attr">"yearOfBirth"</span>: <span class="hljs-number">1997</span>,
      <span class="hljs-attr">"createdAt"</span>: <span class="hljs-string">"2024-11-25T14:18:55.416Z"</span>
    },
    {
      <span class="hljs-attr">"email"</span>: <span class="hljs-string">"jane@email.com"</span>,
      <span class="hljs-attr">"favouriteEmoji"</span>: <span class="hljs-string">"🍏"</span>,
      <span class="hljs-attr">"yearOfBirth"</span>: <span class="hljs-number">1998</span>,
      <span class="hljs-attr">"createdAt"</span>: <span class="hljs-string">"2024-11-25T14:18:55.416Z"</span>
    }
  ]
}
</code></pre>
<p>Notice that if the application is run again, the migration script does not run anymore because the <code>state</code> of the migration will now be <code>up</code> after it has run successfully.</p>
<p>Check out the <a target="_blank" href="https://github.com/orimdominic/ts-migrate-mongoose-starter-repo/tree/fetch-users">fetch-users</a> branch of the repository to see the current status of the codebase at this point in the article.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Migrations are useful when building applications and there is need to seed initial data for testing, seeding administrative users, updating database schema by adding or removing columns and updating the values of columns in many records at once.</p>
<p>ts-migrate-mongoose can help provide a framework for running migrations for your Node.js applications if you use Mongoose with MongoDB.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
