<?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[ Express - 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[ Express - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 21 May 2026 04:57:54 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/express/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Multi-Tenant SaaS Platform with Next.js, Express, and Prisma ]]>
                </title>
                <description>
                    <![CDATA[ Have you ever wondered how platforms like Webflow, Notion, or Hashnode serve thousands of users from a single codebase — each with their own unique URL? The answer is multi-tenancy: an architecture wh ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-multi-tenant-saas-platform-with-next-js-express-and-prisma/</link>
                <guid isPermaLink="false">69f213e46e0124c05e19e1af</guid>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SaaS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ prisma ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Michael Okolo ]]>
                </dc:creator>
                <pubDate>Wed, 29 Apr 2026 14:21:24 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/ef0c87aa-4455-4230-9669-bf2c13db9947.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Have you ever wondered how platforms like Webflow, Notion, or Hashnode serve thousands of users from a single codebase — each with their own unique URL?</p>
<p>The answer is multi-tenancy: an architecture where one application dynamically serves isolated experiences to many different users, often through subdomains.</p>
<p>In this tutorial, you'll build a multi-tenant portfolio SaaS platform from scratch using Next.js, Express, and Prisma. Each user who signs up gets their own portfolio site, served on their own subdomain — generated instantly, powered by a single backend, and stored in a single database.</p>
<p>Here's what you'll build:</p>
<ul>
<li><p>A landing page where users fill out a form to create their portfolio</p>
</li>
<li><p>An Express + Prisma backend that stores each user as a "tenant"</p>
</li>
<li><p>A Next.js middleware layer that detects subdomains and routes requests dynamically</p>
</li>
<li><p>A JSON-driven template system that controls which sections appear on each portfolio</p>
</li>
<li><p>A production-ready portfolio page served at <code>name.localhost:3000</code> in development and <code>name.yourdomain.com</code> in production</p>
</li>
</ul>
<p>You can find the complete source code in the GitHub repositories linked at the end of this tutorial.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-what-is-multi-tenancy">What is Multi-Tenancy?</a></p>
</li>
<li><p><a href="#heading-how-to-set-up-the-backend">How to Set Up the Backend</a></p>
<ul>
<li><p><a href="#heading-how-to-install-dependencies">How to Install Dependencies</a></p>
</li>
<li><p><a href="#heading-how-to-configure-typescript-for-esm">How to Configure TypeScript for ESM</a></p>
</li>
<li><p><a href="#heading-how-to-initialize-prisma">How to Initialize Prisma</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-to-define-the-prisma-schema">How to Define the Prisma Schema</a></p>
</li>
<li><p><a href="#heading-how-to-run-your-first-migration">How to Run Your First Migration</a></p>
</li>
<li><p><a href="#heading-how-to-generate-and-instantiate-the-prisma-client">How to Generate and Instantiate the Prisma Client</a></p>
<ul>
<li><p><a href="#heading-how-to-generate-the-client">How to Generate the Client</a></p>
</li>
<li><p><a href="#heading-how-to-instantiate-the-client">How to Instantiate the Client</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-to-seed-a-template">How to Seed a Template</a></p>
</li>
<li><p><a href="#heading-how-to-build-the-express-api">How to Build the Express API</a></p>
<ul>
<li><p><a href="#heading-how-to-install-express">How to Install Express</a></p>
</li>
<li><p><a href="#heading-how-to-create-the-server-entry-point">How to Create the Server Entry Point</a></p>
</li>
<li><p><a href="#heading-how-to-create-the-express-app">How to Create the Express App</a></p>
</li>
<li><p><a href="#heading-how-to-create-the-tenant-controller">How to Create the Tenant Controller</a></p>
</li>
<li><p><a href="#heading-how-to-create-the-tenant-routes">How to Create the Tenant Routes</a></p>
</li>
<li><p><a href="#heading-how-to-start-the-server">How to Start the Server</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-to-create-the-nextjs-frontend">How to Create the Next.js Frontend</a></p>
</li>
<li><p><a href="#heading-how-to-add-subdomain-routing-with-middleware">How to Add Subdomain Routing with Middleware</a></p>
</li>
<li><p><a href="#heading-how-to-build-the-landing-page">How to Build the Landing Page</a></p>
<ul>
<li><p><a href="#heading-how-to-update-the-layout">How to Update the Layout</a></p>
</li>
<li><p><a href="#heading-how-to-create-the-home-page">How to Create the Home Page</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-to-build-the-tenant-portfolio-page">How to Build the Tenant Portfolio Page</a></p>
</li>
<li><p><a href="#heading-how-to-test-the-full-flow">How to Test the Full Flow</a></p>
</li>
<li><p><a href="#heading-next-steps">Next Steps</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a href="#heading-source-code">Source Code</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you begin, make sure you have the following:</p>
<ul>
<li><p>Node.js (version 18 or higher) installed on your machine</p>
</li>
<li><p>A basic understanding of React, TypeScript, and REST APIs</p>
</li>
<li><p>Familiarity with Prisma ORM (you don't need to be an expert)</p>
</li>
<li><p>A code editor like VS Code</p>
</li>
</ul>
<p>You'll use Prisma Postgres as your database, so you won't need to set up a separate database server locally. Prisma handles the connection string and adapter configuration for you.</p>
<h2 id="heading-what-is-multi-tenancy">What is Multi-Tenancy?</h2>
<p>Multi-tenancy is an architectural pattern where a single application serves multiple users — called tenants — each with isolated data and often their own URL.</p>
<p>Here's how the flow works in this tutorial:</p>
<ol>
<li><p>A user visits your landing page and fills out a form with their name, bio, and skills.</p>
</li>
<li><p>Your Express backend creates a new tenant record in the database and generates a slug from their name.</p>
</li>
<li><p>The browser redirects the user to <code>their-name.localhost:3000</code>.</p>
</li>
<li><p>Your Next.js middleware detects the subdomain, extracts the slug, and rewrites the request to <code>/tenant/their-name</code> internally.</p>
</li>
<li><p>The tenant page fetches that user's data from the API and renders their portfolio.</p>
</li>
</ol>
<p>The key insight is that the URL in the browser never changes — the rewrite is invisible to the user. One Next.js app serves every tenant dynamically.</p>
<h2 id="heading-how-to-set-up-the-backend">How to Set Up the Backend</h2>
<p>Start by creating a project folder with separate directories for the backend and frontend:</p>
<pre><code class="language-bash">mkdir portfolio-saas &amp;&amp; cd portfolio-saas
mkdir portfolio-api portfolio-client
</code></pre>
<p>Navigate into the backend directory and initialize a new Node.js project:</p>
<pre><code class="language-bash">cd portfolio-api
npm init -y
</code></pre>
<h3 id="heading-how-to-install-dependencies">How to Install Dependencies</h3>
<p>Install TypeScript, Prisma, and the supporting packages:</p>
<pre><code class="language-bash">npm install typescript tsx @types/node --save-dev
npx tsc --init
npm install prisma @types/node @types/pg --save-dev
npm install @prisma/client @prisma/adapter-pg pg dotenv
</code></pre>
<h3 id="heading-how-to-configure-typescript-for-esm">How to Configure TypeScript for ESM</h3>
<p>Open <code>tsconfig.json</code> and replace its contents with:</p>
<pre><code class="language-json">{
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "bundler",
    "target": "ES2023",
    "strict": true,
    "esModuleInterop": true,
    "ignoreDeprecations": "6.0",
    "types": ["node"]
  }
}
</code></pre>
<p>Then open <code>package.json</code> and add <code>"type": "module"</code> to enable ESM:</p>
<pre><code class="language-json">{
  "type": "module"
}
</code></pre>
<h3 id="heading-how-to-initialize-prisma">How to Initialize Prisma</h3>
<p>Run the following command to initialize Prisma and generate your schema setup:</p>
<pre><code class="language-bash">npx prisma init --db --output ../generated/prisma
</code></pre>
<p>This command creates a <code>prisma/schema.prisma</code> file, a <code>.env</code> file with your database connection string, and a generated Prisma configuration folder.</p>
<h2 id="heading-how-to-define-the-prisma-schema">How to Define the Prisma Schema</h2>
<p>Open <code>prisma/schema.prisma</code> and replace its contents with the following:</p>
<pre><code class="language-prisma">generator client {
  provider = "prisma-client"
  output   = "../generated/prisma"
}

datasource db {
  provider = "postgresql"
}

model Tenant {
  id         String   @id @default(uuid())
  slug       String   @unique
  name       String
  bio        String
  skills     String[]
  templateId String
  createdAt  DateTime @default(now())
}

model Template {
  id     String @id @default(uuid())
  name   String
  config Json
}

model Post {
  id         String   @id @default(uuid())
  title      String
  content    String
  tenantSlug String
  createdAt  DateTime @default(now())
}
</code></pre>
<p>Let's look at what each model does.</p>
<p>The <code>Tenant</code> model represents a user who has signed up and created a portfolio. The <code>slug</code> field is generated from their name (for example, "John Doe" becomes <code>john-doe</code>) and is used as their subdomain. The <code>templateId</code> links each tenant to a template that controls their portfolio's layout.</p>
<p>The <code>Template</code> model stores layout configuration as JSON. Instead of hardcoding sections like "hero" or "skills" into your components, you store them in the database. This means you can add or remove sections for different templates without touching any component code.</p>
<p>The <code>Post</code> model is included for future extensibility — you can use it to let tenants publish blog posts on their portfolio.</p>
<h2 id="heading-how-to-run-your-first-migration">How to Run Your First Migration</h2>
<p>Run the following command to create your database tables based on the schema:</p>
<pre><code class="language-bash">npx prisma migrate dev --name init
</code></pre>
<p>This command creates the database tables, generates a migration file, and applies the migration to your database. After it runs, your Postgres database structure matches your Prisma schema exactly.</p>
<h2 id="heading-how-to-generate-and-instantiate-the-prisma-client">How to Generate and Instantiate the Prisma Client</h2>
<h3 id="heading-how-to-generate-the-client">How to Generate the Client</h3>
<p>Run this command to generate a fully type-safe Prisma Client based on your schema:</p>
<pre><code class="language-bash">npx prisma generate
</code></pre>
<p>You only need to run this once after each schema change. The generated client lives in the <code>../generated/prisma</code> folder you configured earlier.</p>
<h3 id="heading-how-to-instantiate-the-client">How to Instantiate the Client</h3>
<p>Create a new file at <code>lib/prisma.ts</code> and add the following:</p>
<pre><code class="language-typescript">import 'dotenv/config';
import { PrismaPg } from '@prisma/adapter-pg';
import { PrismaClient } from '../generated/prisma/client';

const connectionString = process.env.DATABASE_URL as string;

const adapter = new PrismaPg({ connectionString });
const prisma = new PrismaClient({ adapter });

export default prisma;
</code></pre>
<p>This file creates a single shared Prisma Client instance that your entire backend will import. The <code>PrismaPg</code> adapter connects Prisma to your Postgres database using the connection string from your <code>.env</code> file.</p>
<h2 id="heading-how-to-seed-a-template">How to Seed a Template</h2>
<p>Your platform needs at least one template in the database before any tenant can sign up. Instead of hardcoding layout decisions into your components, you'll store the template configuration as JSON and read it at runtime.</p>
<p>Create a new file at <code>prisma/seed.ts</code> and add the following:</p>
<pre><code class="language-typescript">import prisma from '../lib/prisma';

async function main() {
  await prisma.template.create({
    data: {
      name: 'minimal',
      config: {
        theme: {
          primaryColor: '#6366f1',
          background: 'dark',
        },
        sections: {
          hero: true,
          about: true,
          skills: true,
          projects: true,
          blog: true,
          contact: true,
        },
      },
    },
  });

  console.log('Template seeded successfully.');
}

main()
  .catch((e) =&gt; {
    console.error(e);
    process.exit(1);
  })
  .finally(async () =&gt; {
    await prisma.$disconnect();
  });
</code></pre>
<p>The <code>config</code> field is stored as JSON in Postgres. When a tenant's portfolio loads, your frontend reads this JSON to decide which sections to show. Setting <code>hero: false</code> on a template would hide the hero section for every tenant using that template — no code changes needed.</p>
<p>Now add a seed script to your <code>package.json</code>:</p>
<pre><code class="language-json">{
  "scripts": {
    "seed": "tsx prisma/seed.ts"
  }
}
</code></pre>
<p>Run it:</p>
<pre><code class="language-bash">npm run seed
</code></pre>
<p>Your database now has a default template ready to attach to new tenants.</p>
<h2 id="heading-how-to-build-the-express-api">How to Build the Express API</h2>
<p>Now you'll build the backend API that creates tenants and retrieves their data.</p>
<h3 id="heading-how-to-install-express">How to Install Express</h3>
<pre><code class="language-bash">npm install express cors
npm install -D @types/express @types/cors
</code></pre>
<h3 id="heading-how-to-create-the-server-entry-point">How to Create the Server Entry Point</h3>
<p>Create <code>src/index.ts</code>:</p>
<pre><code class="language-typescript">import app from './app';

const PORT = 8080;

app.listen(PORT, () =&gt; {
  console.log('Server is running on port 8080');
});
</code></pre>
<h3 id="heading-how-to-create-the-express-app">How to Create the Express App</h3>
<p>Create <code>src/app.ts</code>:</p>
<pre><code class="language-typescript">import express from 'express';
import cors from 'cors';
import tenantRoutes from './routes/tenant.routes';

const app = express();

app.use(cors());
app.use(express.json());
app.use('/api', tenantRoutes);

export default app;
</code></pre>
<p>This file sets up CORS so your Next.js frontend can communicate with the API, parses JSON request bodies, and mounts all tenant routes under the <code>/api</code> prefix.</p>
<h3 id="heading-how-to-create-the-tenant-controller">How to Create the Tenant Controller</h3>
<p>Create <code>src/controllers/tenant.controller.ts</code>:</p>
<pre><code class="language-typescript">import { Request, Response } from 'express';
import prisma from '../../lib/prisma';

export async function createTenant(req: Request, res: Response) {
  const { name, bio, skills } = req.body;

  if (!name || !bio || !skills) {
    return res.status(400).json({ error: 'Missing required fields' });
  }

  const slug = name.toLowerCase().replace(/\s+/g, '-');

  const template = await prisma.template.findFirst();
  if (!template) {
    return res.status(500).json({ error: 'No template found' });
  }

  const tenant = await prisma.tenant.create({
    data: {
      slug,
      name,
      bio,
      skills,
      templateId: template.id,
    },
  });

  res.json({ slug: tenant.slug });
}

export async function getTenant(req: Request, res: Response) {
  const slug = req.params.slug;

  if (!slug || typeof slug !== 'string') {
    return res.status(400).json({ error: 'Invalid slug parameter' });
  }

  const tenant = await prisma.tenant.findUnique({
    where: { slug },
  });

  if (!tenant) {
    return res.status(404).json({ error: 'Tenant not found' });
  }

  const template = await prisma.template.findUnique({
    where: { id: tenant.templateId },
  });

  res.json({ tenant, template });
}
</code></pre>
<p>Let's break down what this controller does.</p>
<p><code>createTenant</code> takes the user's name, bio, and skills from the request body. It generates a slug by lowercasing the name and replacing spaces with hyphens — so "Jane Smith" becomes <code>jane-smith</code>. It then finds the first available template and creates the tenant record in the database, linking the template to the new tenant via <code>templateId</code>.</p>
<p><code>getTenant</code> looks up a tenant by their slug and also fetches the template attached to them. Both pieces of data are returned together so the frontend can render the portfolio and apply the correct layout configuration in a single API call.</p>
<h3 id="heading-how-to-create-the-tenant-routes">How to Create the Tenant Routes</h3>
<p>Create <code>src/routes/tenant.routes.ts</code>:</p>
<pre><code class="language-typescript">import { Router } from 'express';
import { createTenant, getTenant } from '../controllers/tenant.controller';

const router = Router();

router.post('/tenants', createTenant);
router.get('/tenants/:slug', getTenant);

export default router;
</code></pre>
<p>Your API now exposes two endpoints:</p>
<pre><code class="language-plaintext">POST   /api/tenants        — creates a new tenant
GET    /api/tenants/:slug  — retrieves a tenant and their template
</code></pre>
<h3 id="heading-how-to-start-the-server">How to Start the Server</h3>
<p>Add the dev script to your <code>package.json</code>:</p>
<pre><code class="language-json">{
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "seed": "tsx prisma/seed.ts"
  }
}
</code></pre>
<p>Run it:</p>
<pre><code class="language-bash">npm run dev
</code></pre>
<p>You should see <code>Server is running on port 8080</code> in your terminal. Your backend is ready.</p>
<h2 id="heading-how-to-create-the-nextjs-frontend">How to Create the Next.js Frontend</h2>
<p>Navigate to the <code>portfolio-client</code> directory and create a new Next.js project. Make sure to select <strong>Yes</strong> when the installer asks if you want to use Tailwind CSS:</p>
<pre><code class="language-bash">cd ../portfolio-client
npx create-next-app@latest . --typescript --app --tailwind --eslint
npm install
</code></pre>
<p>Since <code>create-next-app</code> sets up Tailwind for you automatically, no extra configuration is needed. The <code>tailwind.config.ts</code> and the <code>@tailwind</code> directives in <code>globals.css</code> are already in place.</p>
<h2 id="heading-how-to-add-subdomain-routing-with-middleware">How to Add Subdomain Routing with Middleware</h2>
<p>This is the heart of the multi-tenant architecture. You need a piece of code that runs before every request, reads the subdomain from the URL, and rewrites the request to the correct internal route — all without the user ever seeing the URL change.</p>
<p>Create a file called <code>proxy.ts</code> in the root directory:</p>
<pre><code class="language-typescript">import { NextRequest, NextResponse } from 'next/server';

export function proxy(request: NextRequest) {
  const { pathname } = request.nextUrl;
  const host = request.headers.get('host') ?? '';

  const hostname = host.split(':')[0];
  const parts = hostname.split('.');

  // Local development: john.localhost:3000
  if (hostname.endsWith('localhost')) {
    const subdomain = parts[0];

    // Root localhost — load the landing page normally
    if (subdomain === 'localhost') {
      return NextResponse.next();
    }

    // Already rewritten — don't rewrite again
    if (pathname.startsWith('/tenant')) {
      return NextResponse.next();
    }

    return NextResponse.rewrite(new URL(`/tenant/${subdomain}`, request.url));
  }

  // Production: john.yourdomain.com
  if (parts.length &gt; 2) {
    const subdomain = parts[0];

    if (subdomain !== 'www') {
      if (pathname.startsWith('/tenant')) {
        return NextResponse.next();
      }

      return NextResponse.rewrite(new URL(`/tenant/${subdomain}`, request.url));
    }
  }

  return NextResponse.next();
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml).*)',
  ],
};
</code></pre>
<p>Here's exactly what this proxy does, step by step.</p>
<p>It reads the <code>host</code> header from every incoming request and splits it on <code>.</code> to extract the subdomain. For a request to <code>john.localhost:3000</code>, the subdomain is <code>john</code>. For a request directly to <code>localhost:3000</code>, the subdomain is <code>localhost</code> itself — in which case the proxy lets the request through unchanged so the landing page loads normally.</p>
<p>When a subdomain is detected, the proxy rewrites the request URL from <code>/</code> to <code>/tenant/john</code> internally. This rewrite is invisible to the browser — the user still sees <code>john.localhost:3000</code> in their address bar, but Next.js routes the request to your <code>/tenant/[slug]</code> page.</p>
<p>The <code>if (pathname.startsWith('/tenant'))</code> guard prevents infinite rewrite loops. Without it, the already-rewritten request would be rewritten again on the next pass through the middleware.</p>
<h2 id="heading-how-to-build-the-landing-page">How to Build the Landing Page</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6792df3bde63bedd84d043e5/5b879d6d-6307-42ce-9a6e-d1f47a789380.png" alt="The PortfolioSaaS landing page showing an empty form with fields for name, bio, and skills on a dark background" style="display:block;margin:0 auto" width="1606" height="874" loading="lazy">

<h3 id="heading-how-to-update-the-layout">How to Update the Layout</h3>
<p>Open <code>app/layout.tsx</code> and update it:</p>
<pre><code class="language-typescript">import type { Metadata } from 'next';
import { Geist, Geist_Mono } from 'next/font/google';
import './globals.css';

const geistSans = Geist({
  variable: '--font-geist-sans',
  subsets: ['latin'],
});

const geistMono = Geist_Mono({
  variable: '--font-geist-mono',
  subsets: ['latin'],
});

export const metadata: Metadata = {
  title: 'Portfolio SaaS App',
  description: 'Create and host your portfolio with subdomains',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    &lt;html lang="en"&gt;
      &lt;body className={`\({geistSans.variable} \){geistMono.variable} antialiased`}&gt;
        {children}
      &lt;/body&gt;
    &lt;/html&gt;
  );
}
</code></pre>
<h3 id="heading-how-to-create-the-home-page">How to Create the Home Page</h3>
<p>Create <code>app/page.tsx</code>:</p>
<pre><code class="language-typescript">'use client';

import { useState } from 'react';

export default function Home() {
  const [name, setName] = useState('');
  const [bio, setBio] = useState('');
  const [skills, setSkills] = useState('');

  const handleSubmit = async () =&gt; {
    const res = await fetch('http://localhost:8080/api/tenants', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        name,
        bio,
        skills: skills.split(',').map((s) =&gt; s.trim()),
      }),
    });

    const data = await res.json();
    window.location.href = `http://${data.slug}.localhost:3000`;
  };

  return (
    &lt;div className="min-h-screen bg-gradient-to-br from-slate-900 to-slate-800 text-white flex flex-col"&gt;
      {/* Header */}
      &lt;header className="flex items-center justify-between px-10 py-5"&gt;
        &lt;span className="text-xl font-semibold tracking-wide"&gt;PortfolioSaaS&lt;/span&gt;
        &lt;nav className="flex gap-6 text-sm text-slate-400"&gt;
          &lt;a href="#features" className="hover:text-white transition-colors"&gt;Features&lt;/a&gt;
          &lt;a href="#pricing" className="hover:text-white transition-colors"&gt;Pricing&lt;/a&gt;
          &lt;a href="#docs" className="hover:text-white transition-colors"&gt;Docs&lt;/a&gt;
        &lt;/nav&gt;
      &lt;/header&gt;

      {/* Main */}
      &lt;main className="flex flex-1 items-center justify-center px-6 py-10"&gt;
        &lt;div className="w-full max-w-md bg-slate-800 rounded-2xl p-10 shadow-2xl"&gt;
          &lt;h1 className="text-3xl font-bold mb-3"&gt;Create Your Portfolio&lt;/h1&gt;
          &lt;p className="text-slate-400 text-sm mb-8"&gt;
            Launch your personal portfolio instantly with your own subdomain.
          &lt;/p&gt;

          &lt;input
            type="text"
            placeholder="Your Name"
            onChange={(e) =&gt; setName(e.target.value)}
            className="w-full bg-slate-900 border border-slate-700 rounded-lg px-4 py-3 text-sm text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 mb-4"
          /&gt;

          &lt;textarea
            placeholder="Short Bio"
            rows={4}
            onChange={(e) =&gt; setBio(e.target.value)}
            className="w-full bg-slate-900 border border-slate-700 rounded-lg px-4 py-3 text-sm text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 mb-4 resize-none"
          /&gt;

          &lt;input
            type="text"
            placeholder="Skills (comma separated)"
            onChange={(e) =&gt; setSkills(e.target.value)}
            className="w-full bg-slate-900 border border-slate-700 rounded-lg px-4 py-3 text-sm text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 mb-6"
          /&gt;

          &lt;button
            onClick={handleSubmit}
            className="w-full bg-indigo-600 hover:bg-indigo-500 text-white font-semibold py-3 rounded-lg transition-colors"
          &gt;
            Create Portfolio
          &lt;/button&gt;
        &lt;/div&gt;
      &lt;/main&gt;

      {/* Footer */}
      &lt;footer className="text-center text-xs text-slate-500 py-5 border-t border-slate-800"&gt;
        © {new Date().getFullYear()} PortfolioSaaS. All rights reserved.
      &lt;/footer&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>When a user submits the form, three things happen in sequence. First, a <code>POST</code> request creates a new tenant in your database. Second, the API returns the generated slug. Third, the browser redirects the user to their subdomain — <code>their-name.localhost:3000</code> — where the middleware takes over and renders their portfolio.</p>
<h2 id="heading-how-to-build-the-tenant-portfolio-page">How to Build the Tenant Portfolio Page</h2>
<p>Create <code>app/tenant/[slug]/page.tsx</code>:</p>
<pre><code class="language-typescript">import type { Metadata } from 'next';

async function getTenant(slug: string) {
  const res = await fetch(`http://localhost:8080/api/tenants/${slug}`, {
    cache: 'no-store',
  });
  return res.json();
}

export async function generateMetadata({
  params,
}: {
  params: Promise&lt;{ slug: string }&gt;;
}): Promise&lt;Metadata&gt; {
  const { slug } = await params;
  const { tenant } = await getTenant(slug);

  if (!tenant) {
    return {
      title: 'Portfolio Not Found',
      description: 'This portfolio does not exist.',
      robots: { index: false, follow: false },
    };
  }

  return {
    title: tenant.name,
    description:
      tenant.bio?.slice(0, 160) ||
      `Explore ${tenant.name}'s professional portfolio.`,
    openGraph: {
      title: tenant.name,
      description: tenant.bio,
      type: 'website',
    },
  };
}

function initials(name: string) {
  return name
    .split(' ')
    .filter(Boolean)
    .slice(0, 2)
    .map((n) =&gt; n[0]?.toUpperCase())
    .join('');
}

export default async function TenantPage({
  params,
}: {
  params: Promise&lt;{ slug: string }&gt;;
}) {
  const { slug } = await params;
  const data = await getTenant(slug);

  const tenant = data?.tenant;
  const template = data?.template;

  if (!tenant) {
    return (
      &lt;div className="min-h-screen bg-slate-900 text-white flex items-center justify-center"&gt;
        &lt;h1 className="text-2xl font-bold text-slate-400"&gt;Portfolio not found&lt;/h1&gt;
      &lt;/div&gt;
    );
  }

  const primary = template?.config?.theme?.primaryColor || '#6366f1';

  // Template-driven section toggles with safe defaults
  const sections = {
    hero: true,
    about: true,
    skills: true,
    projects: true,
    blog: true,
    contact: true,
    ...(template?.config?.sections ?? {}),
  };

  const avatarUrl = tenant.avatarUrl as string | undefined;

  return (
    &lt;div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 text-slate-100"&gt;

      {/* Header */}
      &lt;header className="sticky top-0 z-20 backdrop-blur-md bg-slate-900/60 border-b border-white/5"&gt;
        &lt;div className="max-w-5xl mx-auto px-5 py-4 flex items-center justify-between gap-4"&gt;
          &lt;div className="flex items-center gap-3 min-w-0"&gt;
            &lt;span
              className="w-2.5 h-2.5 rounded-full shrink-0"
              style={{ backgroundColor: primary }}
            /&gt;
            &lt;span className="font-semibold truncate"&gt;{tenant.name}&lt;/span&gt;
          &lt;/div&gt;

          &lt;nav className="hidden md:flex items-center gap-5 text-sm text-slate-400"&gt;
            {(sections.hero || sections.about) &amp;&amp; (
              &lt;a href="#about" className="hover:text-white transition-colors"&gt;About&lt;/a&gt;
            )}
            {sections.skills &amp;&amp; (
              &lt;a href="#skills" className="hover:text-white transition-colors"&gt;Skills&lt;/a&gt;
            )}
            {sections.projects &amp;&amp; (
              &lt;a href="#projects" className="hover:text-white transition-colors"&gt;Projects&lt;/a&gt;
            )}
            {sections.blog &amp;&amp; (
              &lt;a href="#blog" className="hover:text-white transition-colors"&gt;Blog&lt;/a&gt;
            )}
            {sections.contact &amp;&amp; (
              &lt;a href="#contact" className="hover:text-white transition-colors"&gt;Contact&lt;/a&gt;
            )}
          &lt;/nav&gt;

          {sections.contact &amp;&amp; (
            &lt;a
              href="#contact"
              className="text-sm font-semibold px-4 py-2 rounded-full transition-transform hover:-translate-y-px"
              style={{ backgroundColor: primary, color: '#0b1020' }}
            &gt;
              Hire me
            &lt;/a&gt;
          )}
        &lt;/div&gt;
      &lt;/header&gt;

      {/* Hero / About */}
      {(sections.hero || sections.about) &amp;&amp; (
        &lt;section className="px-5 pt-20 pb-14" id="about"&gt;
          &lt;div className="max-w-5xl mx-auto bg-white/[0.04] border border-white/[0.08] rounded-2xl p-7 shadow-2xl grid grid-cols-[110px_1fr] gap-6 items-center"&gt;

            {/* Avatar */}
            &lt;div className="w-[110px] h-[110px] rounded-full overflow-hidden border border-white/10 bg-white/5 flex items-center justify-center shrink-0"&gt;
              {avatarUrl ? (
                // eslint-disable-next-line @next/next/no-img-element
                &lt;img src={avatarUrl} alt={`${tenant.name} avatar`} className="w-full h-full object-cover" /&gt;
              ) : (
                &lt;span className="text-2xl font-extrabold text-slate-200 tracking-tight"&gt;
                  {initials(tenant.name)}
                &lt;/span&gt;
              )}
            &lt;/div&gt;

            {/* Text */}
            &lt;div className="min-w-0"&gt;
              &lt;h1
                className="text-5xl font-extrabold tracking-tight leading-tight mb-3"
                style={{ color: primary }}
              &gt;
                {tenant.name}
              &lt;/h1&gt;
              &lt;p className="text-slate-400 text-base leading-relaxed max-w-2xl"&gt;
                {tenant.bio}
              &lt;/p&gt;
              &lt;div className="flex flex-wrap gap-3 mt-5"&gt;
                {sections.contact &amp;&amp; (
                  &lt;a
                    href="#contact"
                    className="inline-flex items-center px-5 py-2.5 rounded-full text-sm font-semibold transition-transform hover:-translate-y-px"
                    style={{ backgroundColor: primary, color: '#0b1020' }}
                  &gt;
                    Let&amp;apos;s connect
                  &lt;/a&gt;
                )}
                {sections.skills &amp;&amp; (
                  &lt;a
                    href="#skills"
                    className="inline-flex items-center px-5 py-2.5 rounded-full text-sm font-semibold text-slate-200 border border-white/10 bg-white/5 hover:border-white/20 transition-all hover:-translate-y-px"
                  &gt;
                    View skills
                  &lt;/a&gt;
                )}
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/section&gt;
      )}

      {/* Skills */}
      {sections.skills &amp;&amp; (
        &lt;section className="px-5 py-14 max-w-5xl mx-auto" id="skills"&gt;
          &lt;h2 className="text-2xl font-bold text-center tracking-tight mb-7"&gt;Skills&lt;/h2&gt;
          &lt;div className="flex flex-wrap gap-3 justify-center"&gt;
            {tenant.skills.map((skill: string) =&gt; (
              &lt;span
                key={skill}
                className="px-4 py-2 rounded-full text-sm font-semibold bg-white/5 backdrop-blur-sm hover:-translate-y-0.5 hover:shadow-lg transition-all"
                style={{ border: `1px solid ${primary}` }}
              &gt;
                {skill}
              &lt;/span&gt;
            ))}
          &lt;/div&gt;
        &lt;/section&gt;
      )}

      {/* Projects */}
      {sections.projects &amp;&amp; (
        &lt;section className="px-5 py-14 max-w-5xl mx-auto" id="projects"&gt;
          &lt;h2 className="text-2xl font-bold text-center tracking-tight mb-7"&gt;Projects&lt;/h2&gt;
          &lt;div className="flex flex-wrap gap-3 justify-center"&gt;
            {['Portfolio SaaS', 'Multi-tenant Routing', 'Template Builder'].map((p) =&gt; (
              &lt;span
                key={p}
                className="px-4 py-2 rounded-full text-sm font-semibold bg-white/5 backdrop-blur-sm hover:-translate-y-0.5 hover:shadow-lg transition-all"
                style={{ border: `1px solid ${primary}` }}
              &gt;
                {p}
              &lt;/span&gt;
            ))}
          &lt;/div&gt;
        &lt;/section&gt;
      )}

      {/* Blog */}
      {sections.blog &amp;&amp; (
        &lt;section className="px-5 py-14 max-w-5xl mx-auto" id="blog"&gt;
          &lt;h2 className="text-2xl font-bold text-center tracking-tight mb-7"&gt;Blog&lt;/h2&gt;
          &lt;div className="flex flex-wrap gap-3 justify-center"&gt;
            {[
              'How I built this portfolio',
              'Next.js Middleware Tips',
              'Designing Templates',
            ].map((post) =&gt; (
              &lt;span
                key={post}
                className="px-4 py-2 rounded-full text-sm font-semibold bg-white/5 backdrop-blur-sm hover:-translate-y-0.5 hover:shadow-lg transition-all"
                style={{ border: `1px solid ${primary}` }}
              &gt;
                {post}
              &lt;/span&gt;
            ))}
          &lt;/div&gt;
        &lt;/section&gt;
      )}

      {/* Contact */}
      {sections.contact &amp;&amp; (
        &lt;section className="px-5 pt-2 pb-16" id="contact"&gt;
          &lt;div className="max-w-3xl mx-auto bg-white/[0.04] border border-white/[0.08] rounded-2xl p-7"&gt;
            &lt;h2 className="text-xl font-bold mb-2"&gt;Contact&lt;/h2&gt;
            &lt;p className="text-slate-400 leading-relaxed mb-5"&gt;
              Want to work together? Send a message and I&amp;apos;ll reply quickly.
            &lt;/p&gt;
            &lt;div className="flex flex-wrap gap-3"&gt;
              &lt;a
                href={`mailto:hello@${tenant.slug}.com`}
                className="inline-flex items-center px-5 py-2.5 rounded-full text-sm font-semibold transition-transform hover:-translate-y-px"
                style={{ backgroundColor: primary, color: '#0b1020' }}
              &gt;
                Email me
              &lt;/a&gt;
              &lt;a
                href="#about"
                className="inline-flex items-center px-5 py-2.5 rounded-full text-sm font-semibold text-slate-200 border border-white/10 bg-white/5 hover:border-white/20 transition-all hover:-translate-y-px"
              &gt;
                Back to top
              &lt;/a&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/section&gt;
      )}

      {/* Footer */}
      &lt;footer className="text-center text-xs text-slate-500 py-5 border-t border-white/5"&gt;
        © {new Date().getFullYear()} {tenant.name}
      &lt;/footer&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>There are a few important details in this page worth calling out.</p>
<p>The <code>generateMetadata</code> function runs server-side before the page renders and sets the page title, description, and Open Graph tags for each tenant individually. This means every portfolio gets its own SEO metadata — important for a real SaaS product.</p>
<p>The <code>sections</code> object merges safe defaults (<code>hero: true</code>, <code>skills: true</code>, and so on) with whatever the tenant's template specifies. This means even if the template JSON is missing a key, the page won't break — the section will simply fall back to being shown.</p>
<p>The <code>initials</code> helper generates a two-letter avatar placeholder from the tenant's name when no profile image is available. "Jane Smith" produces "JS" — a small detail that makes the portfolio look polished even before a user adds a photo.</p>
<p>Notice that the primary color from the template JSON (<code>theme.primaryColor</code>) is applied using the <code>style</code> prop rather than a Tailwind class. This is intentional. Tailwind generates class names at build time and cannot know the dynamic color value stored in your database. Inline styles are the correct approach whenever a CSS value is truly dynamic.</p>
<h2 id="heading-how-to-test-the-full-flow">How to Test the Full Flow</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6792df3bde63bedd84d043e5/ed282eea-60a0-456c-a6b5-ba4463bae338.png" alt="The PortfolioSaaS form filled in with Alex Morgan's name, bio, and a comma-separated list of skills ready to submit" style="display:block;margin:0 auto" width="1612" height="871" loading="lazy">

<p>Start both servers in separate terminal windows:</p>
<pre><code class="language-bash"># Terminal 1 — Backend
cd portfolio-api
npm run dev

# Terminal 2 — Frontend
cd portfolio-client
npm run dev
</code></pre>
<p>Now test the complete flow:</p>
<ol>
<li><p>Visit <code>http://localhost:3000</code> and fill out the form with your name, a short bio, and a comma-separated list of skills.</p>
</li>
<li><p>Click <strong>Create Portfolio</strong>. The form submits to your Express API, which creates the tenant record and returns the slug.</p>
</li>
<li><p>Your browser redirects to <code>http://your-name.localhost:3000</code>.</p>
</li>
<li><p>The Next.js middleware detects the subdomain, rewrites the request to <code>/tenant/your-name</code>, and your portfolio page fetches and renders your data.</p>
</li>
</ol>
<p>You should see a fully rendered portfolio page with your name, bio, skills, and the placeholder projects and blog sections — all styled with Tailwind utility classes.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6792df3bde63bedd84d043e5/6bb311aa-e55b-4444-9b5e-2b778daccc5e.png" alt="Alex Morgan's generated portfolio page showing the hero section with initials avatar, bio, skills badges, and projects section" style="display:block;margin:0 auto" width="1600" height="903" loading="lazy">

<h2 id="heading-next-steps">Next Steps</h2>
<p>You now have a working multi-tenant SaaS foundation. Here are some extensions worth considering for a production build:</p>
<p>You could add authentication with NextAuth.js so tenants can log in and update their portfolio without losing their data between sessions.</p>
<p>You could also add custom domain support so tenants can point their own domain (for example, <code>janedoe.com</code>) to their portfolio by adding a CNAME record. You would need to handle wildcard SSL certificates on your hosting provider.</p>
<p>You could add image uploads for avatars using Cloudinary or AWS S3, then store the URL in the tenant record and replace the initials fallback with a real photo.</p>
<p>You could add real blog post management using the <code>Post</code> model already defined in your schema. Tenants could write and publish posts that appear on their portfolio.</p>
<p>And you could add Stripe subscriptions so tenants pay a monthly fee to keep their portfolio live. The architecture from the Stripe Connect tutorial maps directly onto this.</p>
<p>Finally, you could deploy the backend to Railway or Render and the frontend to Vercel. Just make sure to update your API URLs from <code>localhost:8080</code> to your production URL before deploying.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, you built a complete multi-tenant SaaS platform where users can sign up, get their own subdomain, and have a portfolio site generated instantly — all from a single codebase.</p>
<p>You learned how to use Next.js middleware to detect subdomains and rewrite requests dynamically, model multi-tenant data in Prisma with a slug-based routing system, build a JSON-driven template system that controls page layout without code changes, and style a production-ready Next.js frontend entirely with Tailwind CSS utility classes.</p>
<p>The core insight is that multi-tenancy isn't magic. It's subdomain detection plus dynamic routing plus isolated data. Once you understand those three moving parts, you can apply this pattern to any SaaS product you build.</p>
<p>If you found this tutorial helpful, share it with someone who is learning to build full-stack applications. Happy coding!</p>
<h2 id="heading-source-code">Source Code</h2>
<p>You can find the complete source code for both parts of this project on GitHub:</p>
<ul>
<li><strong>Frontend (Next.js multi-tenant app) + Backend (Express + Prisma API):</strong> <a href="https://github.com/michaelokolo/portfolio-saas-v1">https://github.com/michaelokolo/portfolio-saas-v1</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build Real-Time Update Systems with MQTT and Express.js  ]]>
                </title>
                <description>
                    <![CDATA[ Real-time updates are everywhere – like live sports scores, stock tickers, chat applications, and IoT dashboards. If you want to build systems that push data to users the moment it changes, you need t ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-real-time-update-systems-with-mosquitto-and-expressjs/</link>
                <guid isPermaLink="false">69b04145abc0d950017e4629</guid>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mqtt ]]>
                    </category>
                
                    <category>
                        <![CDATA[ backend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ web application ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ David Aniebo ]]>
                </dc:creator>
                <pubDate>Tue, 10 Mar 2026 16:05:25 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/f39f54d7-d39b-46aa-9046-9a88315369d4.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Real-time updates are everywhere – like live sports scores, stock tickers, chat applications, and IoT dashboards. If you want to build systems that push data to users the moment it changes, you need the right tools.</p>
<p>Message Queuing Telemetry Transport (MQTT) is a lightweight messaging protocol that excels at this. Combined with a broker like Mosquitto and a web framework like Express, you can build a production-ready real-time system in a single afternoon.</p>
<p>In this tutorial, you'll build a complete real-time football (soccer) sports update system from scratch. You'll create an admin interface for uploading scores and match details, a viewer interface for watching live updates, and a backend that uses MQTT to broadcast changes instantly to every connected client.</p>
<p>By the end of this guide, you'll understand how to integrate MQTT with Express, set up the Mosquitto broker, and deliver real-time data to web browsers using Server-Sent Events. You will have a working system that you can extend for production use.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a href="#heading-what-you-will-learn">What You Will Learn</a></p>
</li>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-understanding-the-architecture">Understanding the Architecture</a></p>
</li>
<li><p><a href="#heading-what-is-mqtt-and-why-use-it">What is MQTT and Why Use It?</a></p>
<ul>
<li><p><a href="#heading-mqtt-topic-design">MQTT Topic Design</a></p>
</li>
<li><p><a href="#heading-why-serversent-events-instead-of-websockets">Why Server-Sent Events Instead of WebSockets?</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-project-setup">Project Setup</a></p>
</li>
<li><p><a href="#heading-how-to-set-up-the-mqtt-broker">How to Set Up the MQTT Broker</a></p>
<ul>
<li><p><a href="#heading-option-1-docker-recommended">Option 1: Docker (Recommended)</a></p>
</li>
<li><p><a href="#heading-option-2-local-install">Option 2: Local Install</a></p>
</li>
<li><p><a href="#heading-option-3-public-test-broker">Option 3: Public Test Broker</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-to-build-the-express-server">How to Build the Express Server</a></p>
</li>
<li><p><a href="#heading-how-to-implement-the-match-routes">How to Implement the Match Routes</a></p>
</li>
<li><p><a href="#heading-how-to-bridge-mqtt-to-the-browser-with-serversent-events">How to Bridge MQTT to the Browser with Server-Sent Events</a></p>
</li>
<li><p><a href="#heading-how-to-build-the-admin-upload-interface">How to Build the Admin Upload Interface</a></p>
<ul>
<li><p><a href="#heading-admin-html-structure-and-styles">Admin HTML Structure and Styles</a></p>
</li>
<li><p><a href="#heading-admin-javascript-logic">Admin JavaScript Logic</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-to-build-the-live-viewer-interface">How to Build the Live Viewer Interface</a></p>
<ul>
<li><a href="#heading-viewer-javascript-logic">Viewer JavaScript Logic</a></li>
</ul>
</li>
<li><p><a href="#heading-how-to-build-the-home-page">How to Build the Home Page</a></p>
</li>
<li><p><a href="#heading-how-to-run-and-test-the-system">How to Run and Test the System</a></p>
</li>
<li><p><a href="#heading-how-to-extend-the-system-for-production">How to Extend the System for Production</a></p>
</li>
<li><p><a href="#heading-api-reference">API Reference</a></p>
</li>
<li><p><a href="#heading-troubleshooting">Troubleshooting</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-what-you-will-learn">What You Will Learn</h2>
<p>During this tutorial, you'll learn how to:</p>
<ul>
<li><p>Connect an Express server to an MQTT broker using the MQTT.js library</p>
</li>
<li><p>Publish and subscribe to MQTT topics for real-time messaging</p>
</li>
<li><p>Use Server-Sent Events to push MQTT messages to web browsers</p>
</li>
<li><p>Build a REST application programming interface (API) for match and score management</p>
</li>
<li><p>Create a simple admin interface for uploading match data</p>
</li>
<li><p>Create a viewer interface that updates in real time without page refreshes</p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you start, you should have:</p>
<ul>
<li><p>Node.js version 18 or later installed on your machine</p>
</li>
<li><p>Basic familiarity with JavaScript, Express, and HTML</p>
</li>
<li><p>A terminal or command line for running commands</p>
</li>
<li><p>Docker installed (optional, for running Mosquitto in a container)</p>
</li>
</ul>
<p>If you don't have Node.js installed, you can download it from the official Node.js website.</p>
<h2 id="heading-understanding-the-architecture">Understanding the Architecture</h2>
<p>The system has three main parts:</p>
<ol>
<li><p><strong>Admin interface</strong> – A web page where you create matches, update scores, and add events such as goals and cards.</p>
</li>
<li><p><strong>Express server</strong> – Receives HyperText Transfer Protocol (HTTP) requests from the admin, publishes data to MQTT, subscribes to MQTT topics, and streams updates to viewers via Server-Sent Events.</p>
</li>
<li><p><strong>Viewer interface</strong> – A web page that connects to the server and displays live scores and events as they arrive.</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/6904c2dbd42ef6b1f9e61c3e/e6d0b007-764d-41bf-8d05-9e37971bd78e.png" alt="Architecture-diagram" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><strong>How the flow works</strong><br>When you submit a score update in the admin panel, the Express server publishes a message to the MQTT broker. The server also subscribes to those same topics. When a message arrives, the server forwards it to all connected viewers through Server-Sent Events. The viewers update their display without refreshing the page.</p>
<h2 id="heading-what-is-mqtt-and-why-use-it">What is MQTT and Why Use It?</h2>
<p>MQTT stands for Message Queuing Telemetry Transport. It's a lightweight, publish-subscribe messaging protocol designed for low bandwidth and unreliable networks. It's widely used in Internet of Things (IoT) applications, but it works well for any real-time system where you need to broadcast updates to many subscribers.</p>
<p>Here are some reasons to use MQTT for a sports update system:</p>
<ul>
<li><p><strong>Low overhead</strong>: Messages are small and efficient, which helps when you have many clients.</p>
</li>
<li><p><strong>Built-in Quality of Service (QoS)</strong>: You can choose how many times a message is delivered (at most once, at least once, or exactly once).</p>
</li>
<li><p><strong>Topic-based routing</strong>: You organize messages by topic (for example, <code>sports/football/match/123</code>) so subscribers receive only what they need.</p>
</li>
<li><p><strong>Broker-based</strong>: A central broker (Mosquitto) handles all message distribution, so your application logic stays simple.</p>
</li>
</ul>
<p>Mosquitto is a popular, open-source MQTT broker that's easy to install and configure.</p>
<h3 id="heading-mqtt-topic-design">MQTT Topic Design</h3>
<p>MQTT uses a hierarchical topic structure. For this project, the topics are:</p>
<ul>
<li><p><code>sports/football/match/{id}</code>: One topic per match. When you publish the full match object here, any subscriber receives the complete state. This makes it easy to add new match fields later without changing the topic structure.</p>
</li>
<li><p><code>sports/football/scores</code>: A single topic for score-related notifications. Messages include a <code>type</code> field (<code>match_created</code> or <code>score_update</code>) so subscribers can handle them differently.</p>
</li>
<li><p><code>sports/football/events</code>: A topic for match events such as goals and cards. Subscribers receive <code>{ type: 'match_event', matchId, event }</code>.</p>
</li>
</ul>
<p>The <code>#</code> wildcard in a subscription means "match this level and all levels below." So <code>sports/football/#</code> subscribes to every topic under <code>sports/football</code>, including <code>sports/football/match/abc123</code> and <code>sports/football/scores</code>. The <code>+</code> wildcard matches exactly one level. For example, <code>sports/+/match/#</code> would match any sport, not just football.</p>
<h3 id="heading-why-server-sent-events-instead-of-websockets">Why Server-Sent Events Instead of WebSockets?</h3>
<p>You might wonder why this tutorial uses Server-Sent Events (SSE) instead of WebSockets. Both can push data to the browser. The main difference:</p>
<ul>
<li><p><strong>Server-Sent Events</strong>: One-way (server to client). Built on HTTP. Automatic reconnection in the browser. Simpler to implement. No extra libraries.</p>
</li>
<li><p><strong>WebSockets</strong>: Two-way. Requires a different protocol. More flexible but more complex.</p>
</li>
</ul>
<p>For a sports score viewer, you only need server-to-client updates. The viewer never sends messages back over the same channel. Server-Sent Events is a better fit. If you later need the client to send commands (for example, to filter by league), you can add a separate HTTP API or switch to WebSockets.</p>
<h2 id="heading-project-setup">Project Setup</h2>
<p>Start by creating a new folder for your project and initialize it with npm. The <code>mkdir</code> command creates the directory, <code>cd</code> moves into it, and <code>npm init -y</code> creates a <code>package.json</code> file with default values without prompting you for input.</p>
<pre><code class="language-shell">mkdir mqtt-football-scores
cd mqtt-football-scores
npm init -y
</code></pre>
<p>Install the required dependencies. Each package serves a specific role in the application. Run this command in the project root directory.</p>
<pre><code class="language-shell">npm install express cors mqtt uuid
</code></pre>
<ul>
<li><p><code>express</code>: Web framework for the HTTP server and API. It provides routing, middleware, and static file serving.</p>
</li>
<li><p><code>cors</code>: Enables Cross-Origin Resource Sharing so your frontend can call the API from a different origin (for example, if you serve the HTML from a different port or domain).</p>
</li>
<li><p><code>mqtt</code>: MQTT client for Node.js. It handles connection, publish, subscribe, reconnection, and Quality of Service (QoS) flows.</p>
</li>
<li><p><code>uuid</code>: Generates unique identifiers (Universally Unique Identifiers) for matches and events. Each ID is practically unique across all systems.</p>
</li>
</ul>
<p>Next, create the following folder structure. The <code>server</code> folder holds the Node.js backend code. The <code>public</code> folder holds the HTML, CSS, and client-side JavaScript that the browser loads. The <code>routes</code> subfolder keeps the match-related route handlers separate from the main server file.</p>
<pre><code class="language-plaintext">mqtt-football-scores/
├── server/
│   ├── index.js
│   ├── sse.js
│   └── routes/
│       └── matches.js
├── public/
│   ├── index.html
│   ├── admin.html
│   └── viewer.html
├── mosquitto.conf
├── docker-compose.yml
└── package.json
</code></pre>
<p>Add <code>"type": "module"</code> to your <code>package.json</code> so you can use JavaScript modules (import and export). The <code>type</code> field tells Node.js to treat <code>.js</code> files as ES modules, which allows you to use <code>import</code> and <code>export</code> syntax instead of CommonJS <code>require</code> and <code>module.exports</code>.</p>
<pre><code class="language-json">{
  "name": "mqtt-football-scores",
  "version": "1.0.0",
  "type": "module",
  "main": "server/index.js"
}
</code></pre>
<h2 id="heading-how-to-set-up-the-mqtt-broker">How to Set Up the MQTT Broker</h2>
<p>You need an MQTT broker running before the server can connect. You have three options.</p>
<h3 id="heading-option-1-docker-recommended">Option 1: Docker (Recommended)</h3>
<p>Create a <code>docker-compose.yml</code> file. This file defines a single service named <code>mosquitto</code> that runs the Eclipse Mosquitto 2 image. The <code>ports</code> directive maps port 1883 on your host to port 1883 in the container so your Express server can connect. The <code>volumes</code> directive mounts your local <code>mosquitto.conf</code> into the container so the broker uses your configuration. The <code>restart: unless-stopped</code> option ensures the container restarts automatically if it crashes or if you reboot your machine.</p>
<pre><code class="language-yaml">version: "3.8"

services:
  mosquitto:
    image: eclipse-mosquitto:2
    container_name: mqtt-football-mosquitto
    ports:
      - "1883:1883"
    volumes:
      - ./mosquitto.conf:/mosquitto/config/mosquitto.conf
    restart: unless-stopped
</code></pre>
<p>Create a <code>mosquitto.conf</code> file. The <code>listener 1883</code> directive tells Mosquitto to listen on port 1883 for MQTT connections. The <code>protocol mqtt</code> specifies the standard MQTT protocol (as opposed to WebSocket). The <code>allow_anonymous true</code> setting permits connections without a username and password, which is fine for local development but should be disabled in production. The <code>log_dest stdout</code> and <code>log_type all</code> directives send all log output to the console so you can debug connection issues.</p>
<pre><code class="language-plaintext">listener 1883
protocol mqtt
allow_anonymous true
log_dest stdout
log_type all
</code></pre>
<p>Start the broker:</p>
<pre><code class="language-bash">docker-compose up -d
</code></pre>
<h3 id="heading-option-2-local-install">Option 2: Local Install</h3>
<p>On macOS with Homebrew:</p>
<pre><code class="language-bash">brew install mosquitto
mosquitto -c mosquitto.conf
</code></pre>
<p>On Ubuntu or Debian:</p>
<pre><code class="language-bash">sudo apt install mosquitto mosquitto-clients
sudo systemctl start mosquitto
</code></pre>
<h3 id="heading-option-3-public-test-broker">Option 3: Public Test Broker</h3>
<p>You can use the public test broker at <code>test.mosquitto.org</code> without installing anything. Set the environment variable when you start the server:</p>
<pre><code class="language-bash">MQTT_BROKER=mqtt://test.mosquitto.org npm start
</code></pre>
<p>Note: The public broker is shared and not suitable for production. Use it only for development and testing.</p>
<h2 id="heading-how-to-build-the-express-server">How to Build the Express Server</h2>
<p>In this step, you'll create the main server that powers the real-time application. The Express server handles HTTP requests, serves static files like HTML and JavaScript, and acts as the bridge between the browser and the MQTT broker. It also provides endpoints that allow data to be sent and received in real time. Essentially, this server is the backbone of the application, enabling communication between all components.</p>
<p>Begin by creating the main server file at <code>server/index.js</code>:</p>
<pre><code class="language-typescript">import express from 'express';
import cors from 'cors';
import mqtt from 'mqtt';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { v4 as uuidv4 } from 'uuid';

import { matchRoutes } from './routes/matches.js';
import { setupSSE, addSSEClient } from './sse.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const MQTT_BROKER = process.env.MQTT_BROKER || 'mqtt://localhost:1883';
const PORT = process.env.PORT || 3000;

const app = express();
app.use(cors());
app.use(express.json());
app.use(express.static(join(__dirname, '../public')));

let mqttClient = null;

function connectMQTT() {
  mqttClient = mqtt.connect(MQTT_BROKER, {
    clientId: `football-scores-${uuidv4().slice(0, 8)}`,
    reconnectPeriod: 3000,
    connectTimeout: 10000,
  });

  mqttClient.on('connect', () =&gt; {
    console.log('Connected to MQTT broker at', MQTT_BROKER);
    mqttClient.subscribe('sports/football/#', { qos: 1 }, (err) =&gt; {
      if (err) console.error('Subscribe error:', err);
    });
  });

  mqttClient.on('error', (err) =&gt; {
    console.error('MQTT error:', err.message);
  });

  mqttClient.on('close', () =&gt; {
    console.log('MQTT connection closed');
  });

  mqttClient.on('reconnect', () =&gt; {
    console.log('MQTT reconnecting...');
  });

  return mqttClient;
}

const mqttClientInstance = connectMQTT();
const { publishMatch, publishScoreUpdate, publishEvent, getMatches } = matchRoutes(mqttClientInstance);
setupSSE(mqttClientInstance);

app.get('/api/events', (req, res) =&gt; addSSEClient(res));
app.post('/api/matches', publishMatch);
app.patch('/api/matches/:id/score', publishScoreUpdate);
app.post('/api/matches/:id/events', publishEvent);
app.get('/api/matches', getMatches);

app.listen(PORT, () =&gt; {
  console.log(`Football Scores Server running at http://localhost:${PORT}`);
  console.log(`Admin (upload):  http://localhost:${PORT}/admin.html`);
  console.log(`Viewer:          http://localhost:${PORT}/viewer.html`);
});
</code></pre>
<p>Here is what each part does:</p>
<ul>
<li><p><strong>Imports</strong>: The <code>fileURLToPath</code> and <code>dirname</code> utilities replicate the <code>__dirname</code> variable that CommonJS provides, since ES modules don't have it. You need <code>__dirname</code> to build the path to the <code>public</code> folder.</p>
</li>
<li><p><strong>Environment variables</strong>: <code>MQTT_BROKER</code> defaults to <code>mqtt://localhost:1883</code> so the server connects to a local Mosquitto instance. You can override it for Docker or a remote broker. <code>PORT</code> defaults to 3000.</p>
</li>
<li><p><strong>Middleware</strong>: <code>cors()</code> allows requests from any origin, which is useful during development. <code>express.json()</code> parses JSON request bodies. <code>express.static()</code> serves files from <code>public</code> so <code>/admin.html</code> and <code>/viewer.html</code> are available.</p>
</li>
<li><p><strong>connectMQTT</strong>: Creates an MQTT client with a unique client ID (required by the broker), a 3-second reconnect interval, and a 10-second connection timeout. On connect, it subscribes to <code>sports/football/#</code> with QoS 1. The <code>#</code> wildcard means "all topics under sports/football."</p>
</li>
<li><p><strong>matchRoutes</strong>: Returns the route handlers that create matches, update scores, and add events. Each handler publishes to MQTT and responds with JSON.</p>
</li>
<li><p><strong>setupSSE</strong>: Registers a listener on the MQTT client's <code>message</code> event. When a message arrives, it forwards the payload to all connected Server-Sent Events clients.</p>
</li>
<li><p><strong>addSSEClient</strong>: Called when a viewer opens <code>/api/events</code>. It sets the response headers for Server-Sent Events, flushes the headers so the connection stays open, and adds the response object to a set of active clients.</p>
</li>
<li><p><strong>Routes</strong>: The GET <code>/api/events</code> route establishes the Server-Sent Events stream. The POST, PATCH, and GET routes for matches delegate to the handlers from <code>matchRoutes</code>.</p>
</li>
</ul>
<p>The server serves static files from the <code>public</code> folder, so your HTML pages are available at the root.</p>
<h2 id="heading-how-to-implement-the-match-routes">How to Implement the Match Routes</h2>
<p>In this step, you'll create the route handlers that manage matches, scores, and events. These routes allow the server to receive requests from the admin interface, update match data, and publish real-time updates to MQTT. They also store match information in memory so it can be retrieved and updated during the session.</p>
<p>Begin by creating the file at <code>server/routes/matches.js</code>:</p>
<pre><code class="language-javascript">import { v4 as uuidv4 } from 'uuid';

const TOPIC_MATCH = 'sports/football/match';
const TOPIC_SCORES = 'sports/football/scores';
const TOPIC_EVENTS = 'sports/football/events';

const matches = new Map();

function publish(client, topic, payload, qos = 1) {
  if (!client?.connected) {
    console.warn('MQTT not connected, message not published');
    return false;
  }
  client.publish(topic, JSON.stringify(payload), { qos, retain: false });
  return true;
}

export function matchRoutes(mqttClient) {
  return {
    publishMatch: (req, res) =&gt; {
      const { homeTeam, awayTeam, league, venue, kickoff } = req.body;
      if (!homeTeam || !awayTeam) {
        return res.status(400).json({ error: 'homeTeam and awayTeam are required' });
      }

      const match = {
        id: uuidv4(),
        homeTeam,
        awayTeam,
        homeScore: 0,
        awayScore: 0,
        league: league || 'Premier League',
        venue: venue || 'TBD',
        kickoff: kickoff || new Date().toISOString(),
        status: 'scheduled',
        minute: 0,
        events: [],
        createdAt: new Date().toISOString(),
      };

      matches.set(match.id, match);

      const topic = `\({TOPIC_MATCH}/\){match.id}`;
      publish(mqttClient, topic, match);
      publish(mqttClient, TOPIC_SCORES, { type: 'match_created', match });

      res.status(201).json(match);
    },

    publishScoreUpdate: (req, res) =&gt; {
      const { id } = req.params;
      const { homeScore, awayScore, minute, status } = req.body;

      const match = matches.get(id);
      if (!match) {
        return res.status(404).json({ error: 'Match not found' });
      }

      if (homeScore !== undefined) match.homeScore = homeScore;
      if (awayScore !== undefined) match.awayScore = awayScore;
      if (minute !== undefined) match.minute = minute;
      if (status !== undefined) match.status = status;

      const topic = `\({TOPIC_MATCH}/\){id}`;
      publish(mqttClient, topic, match);
      publish(mqttClient, TOPIC_SCORES, {
        type: 'score_update',
        matchId: id,
        homeScore: match.homeScore,
        awayScore: match.awayScore,
        minute: match.minute,
        status: match.status,
      });

      res.json(match);
    },

    publishEvent: (req, res) =&gt; {
      const { id } = req.params;
      const { type, team, player, minute, description } = req.body;

      const match = matches.get(id);
      if (!match) {
        return res.status(404).json({ error: 'Match not found' });
      }

      const event = {
        id: uuidv4().slice(0, 8),
        type: type || 'goal',
        team,
        player: player || 'Unknown',
        minute: minute ?? match.minute,
        description: description || `\({type}: \){player}`,
        timestamp: new Date().toISOString(),
      };

      match.events.push(event);
      if (type === 'goal') {
        if (team === match.homeTeam) match.homeScore++;
        else if (team === match.awayTeam) match.awayScore++;
      }

      const topic = `\({TOPIC_MATCH}/\){id}`;
      publish(mqttClient, topic, match);
      publish(mqttClient, TOPIC_EVENTS, { type: 'match_event', matchId: id, event });

      res.status(201).json({ match, event });
    },

    getMatches: (req, res) =&gt; {
      const list = Array.from(matches.values()).sort(
        (a, b) =&gt; new Date(b.createdAt) - new Date(a.createdAt)
      );
      res.json(list);
    },
  };
}
</code></pre>
<p>The routes use an in-memory <code>Map</code> to store matches. In production, you would replace this with a database such as PostgreSQL or MongoDB.</p>
<p>Explanation of the key logic:</p>
<ul>
<li><p><strong>publish</strong>: A helper that checks if the MQTT client is connected before publishing. If the broker is down, it logs a warning instead of throwing. The <code>retain: false</code> option means the broker doesn't store the last message for new subscribers.</p>
</li>
<li><p><strong>publishMatch</strong>: Validates that <code>homeTeam</code> and <code>awayTeam</code> are present. Creates a match object with default values for league, venue, kickoff, status, and events. Stores it in the Map, publishes to the match-specific topic and the scores topic, and returns the match with status 201 (Created).</p>
</li>
<li><p><strong>publishScoreUpdate</strong>: Looks up the match by ID. If not found, returns 404. Updates only the fields that are provided (using <code>!== undefined</code> so you can set a score to 0). Publishes the full match and a score_update notification.</p>
</li>
<li><p><strong>publishEvent</strong>: Creates an event object with a short unique ID. Pushes it to the match's events array. If the event type is <code>goal</code>, increments the home or away score based on the team. Publishes the updated match and an event notification.</p>
</li>
<li><p><strong>getMatches</strong>: Converts the Map to an array, sorts by <code>createdAt</code> descending (newest first), and returns the list as JSON.</p>
</li>
</ul>
<p>MQTT topics used:</p>
<ul>
<li><p><code>sports/football/match/{id}</code>: Full match state. Used when a match is created or updated.</p>
</li>
<li><p><code>sports/football/scores</code>: Score change notifications. Used for match creation and score updates.</p>
</li>
<li><p><code>sports/football/events</code>: Match events such as goals and cards.</p>
</li>
</ul>
<p>The <code>publish</code> function sends JavaScript Object Notation (JSON) payloads with QoS 1 (at least once delivery). MQTT defines three QoS levels: 0 (at most once), 1 (at least once), and 2 (exactly once). Level 1 ensures the broker will retry until the subscriber acknowledges the message, which reduces the chance of losing score updates if the connection drops briefly.</p>
<h2 id="heading-how-to-bridge-mqtt-to-the-browser-with-server-sent-events">How to Bridge MQTT to the Browser with Server-Sent Events</h2>
<p>In this step, you'll create the Server-Sent Events (SSE) module that bridges MQTT messages to the browser. MQTT works over TCP and browsers cannot connect to it directly, so we use SSE as a lightweight HTTP-based streaming channel. SSE keeps an open connection and allows the server to push updates to connected browsers in real time. This is what enables viewers to see match updates instantly without refreshing.</p>
<p>Now create the file at <code>server/sse.js</code>:</p>
<pre><code class="language-javascript">const clients = new Set();

export function setupSSE(mqttClient) {
  if (!mqttClient) return;

  mqttClient.on('message', (topic, message) =&gt; {
    try {
      const payload = JSON.parse(message.toString());
      const data = JSON.stringify({ topic, ...payload });
      clients.forEach((res) =&gt; {
        try {
          res.write(`data: ${data}\n\n`);
        } catch (e) {
          clients.delete(res);
        }
      });
    } catch (e) {
      console.error('SSE parse error:', e.message);
    }
  });
}

export function addSSEClient(res) {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  res.setHeader('X-Accel-Buffering', 'no');
  res.flushHeaders();

  clients.add(res);

  res.on('close', () =&gt; {
    clients.delete(res);
  });
}
</code></pre>
<p>Explanation of each part:</p>
<ul>
<li><p><strong>clients Set</strong>: A Set holds all active Server-Sent Events response objects. Using a Set makes it easy to add and remove clients without duplicates.</p>
</li>
<li><p><strong>setupSSE</strong>: Attaches a <code>message</code> listener to the MQTT client. When any message arrives on a subscribed topic, the callback runs. It parses the message payload (which is JSON), merges the topic into the payload with <code>{ topic, ...payload }</code>, and sends the result to every client. The Server-Sent Events format requires each message to be <code>data: {content}\n\n</code> (two newlines). The <code>forEach</code> loop catches write errors (for example, if a client disconnected) and removes the client from the set.</p>
</li>
<li><p><strong>addSSEClient</strong>: Sets the <code>Content-Type</code> header to <code>text/event-stream</code> so the browser treats the response as an event stream. The <code>Cache-Control: no-cache</code> and <code>Connection: keep-alive</code> headers prevent the browser or proxy from caching or closing the connection. The <code>X-Accel-Buffering: no</code> header disables buffering in Nginx, which can delay or block Server-Sent Events. The <code>flushHeaders</code> call sends the headers immediately so the connection is established. The <code>close</code> event handler removes the client when the client disconnects (closes the tab or navigates away).</p>
</li>
</ul>
<p>Server-Sent Events is one-way (server to client). For this use case, that is enough because viewers only need to receive updates, not send messages back over the same channel.</p>
<h2 id="heading-how-to-build-the-admin-upload-interface">How to Build the Admin Upload Interface</h2>
<p>The admin interface is a single HTML page where match creators can create new matches, update scores, and add events such as goals or cards. It uses standard HTML forms so data can be submitted to the server, and JavaScript will later handle form submissions and dynamic updates. All the markup, styles, and script live in <code>public/admin.html</code>, which the Express server serves as a static page.</p>
<h3 id="heading-admin-html-structure-and-styles">Admin HTML Structure and Styles</h3>
<p>The document starts with the standard HTML5 boilerplate. The <code>charset</code> and <code>viewport</code> meta tags ensure proper character encoding and responsive layout on mobile devices. The page loads the Outfit font from Google Fonts for a clean, modern look.</p>
<p>The Cascading Style Sheets (CSS) uses custom properties (variables) in the <code>:root</code> block so you can change the color scheme in one place. The <code>--bg</code> variable holds the dark background color, <code>--surface</code> for card backgrounds, <code>--accent</code> for the green accent color, and <code>--text-muted</code> for secondary text. The <code>.grid</code> class creates a two-column layout for form fields that collapses to one column on screens under 600 pixels wide. The <code>.toast</code> class positions the notification at the bottom-right and uses <code>transform</code> and <code>opacity</code> for a slide-in animation when the <code>.show</code> class is added.</p>
<p>Now create the file at <code>public/admin.html</code>:</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;Admin - Football Scores Upload&lt;/title&gt;
  &lt;link rel="preconnect" href="https://fonts.googleapis.com"&gt;
  &lt;link rel="preconnect" href="https://fonts.gstatic.com" crossorigin&gt;
  &lt;link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&amp;display=swap" rel="stylesheet"&gt;
  &lt;style&gt;
    :root {
      --bg: #0f1419;
      --surface: #1a2332;
      --surface-hover: #243044;
      --accent: #00d26a;
      --accent-dim: #00a854;
      --text: #e8edf2;
      --text-muted: #8b9aab;
      --border: #2d3a4d;
      --danger: #ff4757;
    }
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body {
      font-family: 'Outfit', sans-serif;
      background: var(--bg);
      color: var(--text);
      min-height: 100vh;
      padding: 2rem;
    }
    .container { max-width: 900px; margin: 0 auto; }
    header {
      display: flex;
      align-items: center;
      gap: 1rem;
      margin-bottom: 2rem;
      padding-bottom: 1rem;
      border-bottom: 1px solid var(--border);
    }
    section {
      background: var(--surface);
      border-radius: 12px;
      padding: 1.5rem;
      margin-bottom: 1.5rem;
      border: 1px solid var(--border);
    }
    .grid {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 1rem;
    }
    @media (max-width: 600px) { .grid { grid-template-columns: 1fr; } }
    .form-group { display: flex; flex-direction: column; gap: 0.4rem; }
    .form-group.full { grid-column: 1 / -1; }
    input, select {
      padding: 0.65rem 1rem;
      border: 1px solid var(--border);
      border-radius: 8px;
      background: var(--bg);
      color: var(--text);
      font-family: inherit;
    }
    button {
      padding: 0.75rem 1.5rem;
      border: none;
      border-radius: 8px;
      font-family: inherit;
      font-weight: 600;
      cursor: pointer;
    }
    .btn-primary { background: var(--accent); color: var(--bg); }
    .btn-secondary { background: var(--surface-hover); color: var(--text); border: 1px solid var(--border); }
    .actions { display: flex; gap: 0.75rem; flex-wrap: wrap; margin-top: 1rem; }
    .badge { background: var(--accent); color: var(--bg); font-size: 0.75rem; padding: 0.25rem 0.6rem; border-radius: 999px; font-weight: 600; }
    .viewer-link { margin-left: auto; color: var(--accent); text-decoration: none; font-weight: 500; }
    section h2 { font-size: 1rem; font-weight: 600; margin-bottom: 1rem; color: var(--text-muted); }
    label { font-size: 0.85rem; font-weight: 500; color: var(--text-muted); }
    .toast {
      position: fixed;
      bottom: 2rem;
      right: 2rem;
      padding: 1rem 1.5rem;
      border-radius: 8px;
      font-weight: 500;
      color: var(--bg);
      background: var(--accent);
      transform: translateY(100px);
      opacity: 0;
      transition: transform 0.3s, opacity 0.3s;
      z-index: 100;
    }
    .toast.show { transform: translateY(0); opacity: 1; }
    .toast.error { background: var(--danger); }
    .match-card {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 1rem;
      background: var(--bg);
      border-radius: 8px;
      margin-bottom: 0.5rem;
      border: 1px solid var(--border);
    }
    .match-score { font-size: 1.5rem; font-weight: 700; color: var(--accent); margin: 0 1rem; }
    .match-info { flex: 1; }
    .match-teams { font-weight: 600; font-size: 1rem; }
    .match-meta { font-size: 0.8rem; color: var(--text-muted); margin-top: 0.25rem; }
    .match-actions { display: flex; gap: 0.5rem; }
    .match-actions button { padding: 0.5rem 1rem; font-size: 0.85rem; }
    .match-list { margin-top: 1rem; }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div class="container"&gt;
    &lt;header&gt;
      &lt;h1&gt;⚽ Football Scores&lt;/h1&gt;
      &lt;span class="badge"&gt;Admin&lt;/span&gt;
      &lt;a href="/viewer.html" class="viewer-link"&gt;→ Open Viewer&lt;/a&gt;
    &lt;/header&gt;

    &lt;section&gt;
      &lt;h2&gt;Create New Match&lt;/h2&gt;
      &lt;form id="createMatch"&gt;
        &lt;div class="grid"&gt;
          &lt;div class="form-group"&gt;
            &lt;label for="homeTeam"&gt;Home Team&lt;/label&gt;
            &lt;input type="text" id="homeTeam" placeholder="e.g. Manchester United" required&gt;
          &lt;/div&gt;
          &lt;div class="form-group"&gt;
            &lt;label for="awayTeam"&gt;Away Team&lt;/label&gt;
            &lt;input type="text" id="awayTeam" placeholder="e.g. Liverpool" required&gt;
          &lt;/div&gt;
          &lt;div class="form-group"&gt;
            &lt;label for="league"&gt;League&lt;/label&gt;
            &lt;input type="text" id="league" placeholder="e.g. Premier League" value="Premier League"&gt;
          &lt;/div&gt;
          &lt;div class="form-group"&gt;
            &lt;label for="venue"&gt;Venue&lt;/label&gt;
            &lt;input type="text" id="venue" placeholder="e.g. Old Trafford"&gt;
          &lt;/div&gt;
          &lt;div class="form-group full"&gt;
            &lt;label for="kickoff"&gt;Kickoff (ISO)&lt;/label&gt;
            &lt;input type="datetime-local" id="kickoff"&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class="actions"&gt;
          &lt;button type="submit" class="btn-primary"&gt;Create Match&lt;/button&gt;
        &lt;/div&gt;
      &lt;/form&gt;
    &lt;/section&gt;

    &lt;section&gt;
      &lt;h2&gt;Update Score&lt;/h2&gt;
      &lt;form id="updateScore"&gt;
        &lt;div class="grid"&gt;
          &lt;div class="form-group full"&gt;
            &lt;label for="scoreMatchId"&gt;Select Match&lt;/label&gt;
            &lt;select id="scoreMatchId" required&gt;
              &lt;option value=""&gt;-- Select match --&lt;/option&gt;
            &lt;/select&gt;
          &lt;/div&gt;
          &lt;div class="form-group"&gt;
            &lt;label for="homeScore"&gt;Home Score&lt;/label&gt;
            &lt;input type="number" id="homeScore" min="0" value="0"&gt;
          &lt;/div&gt;
          &lt;div class="form-group"&gt;
            &lt;label for="awayScore"&gt;Away Score&lt;/label&gt;
            &lt;input type="number" id="awayScore" min="0" value="0"&gt;
          &lt;/div&gt;
          &lt;div class="form-group"&gt;
            &lt;label for="minute"&gt;Minute&lt;/label&gt;
            &lt;input type="number" id="minute" min="0" placeholder="e.g. 67"&gt;
          &lt;/div&gt;
          &lt;div class="form-group"&gt;
            &lt;label for="status"&gt;Status&lt;/label&gt;
            &lt;select id="status"&gt;
              &lt;option value="scheduled"&gt;Scheduled&lt;/option&gt;
              &lt;option value="live"&gt;Live&lt;/option&gt;
              &lt;option value="halftime"&gt;Halftime&lt;/option&gt;
              &lt;option value="finished"&gt;Finished&lt;/option&gt;
            &lt;/select&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class="actions"&gt;
          &lt;button type="submit" class="btn-primary"&gt;Update Score&lt;/button&gt;
        &lt;/div&gt;
      &lt;/form&gt;
    &lt;/section&gt;

    &lt;section&gt;
      &lt;h2&gt;Add Match Event (Goal, Card, etc.)&lt;/h2&gt;
      &lt;form id="addEvent"&gt;
        &lt;div class="grid"&gt;
          &lt;div class="form-group full"&gt;
            &lt;label for="eventMatchId"&gt;Select Match&lt;/label&gt;
            &lt;select id="eventMatchId" required&gt;
              &lt;option value=""&gt;-- Select match --&lt;/option&gt;
            &lt;/select&gt;
          &lt;/div&gt;
          &lt;div class="form-group"&gt;
            &lt;label for="eventType"&gt;Event Type&lt;/label&gt;
            &lt;select id="eventType"&gt;
              &lt;option value="goal"&gt;Goal&lt;/option&gt;
              &lt;option value="yellow_card"&gt;Yellow Card&lt;/option&gt;
              &lt;option value="red_card"&gt;Red Card&lt;/option&gt;
              &lt;option value="substitution"&gt;Substitution&lt;/option&gt;
              &lt;option value="penalty"&gt;Penalty&lt;/option&gt;
            &lt;/select&gt;
          &lt;/div&gt;
          &lt;div class="form-group"&gt;
            &lt;label for="eventTeam"&gt;Team&lt;/label&gt;
            &lt;input type="text" id="eventTeam" placeholder="e.g. Manchester United"&gt;
          &lt;/div&gt;
          &lt;div class="form-group"&gt;
            &lt;label for="eventPlayer"&gt;Player&lt;/label&gt;
            &lt;input type="text" id="eventPlayer" placeholder="e.g. Marcus Rashford"&gt;
          &lt;/div&gt;
          &lt;div class="form-group"&gt;
            &lt;label for="eventMinute"&gt;Minute&lt;/label&gt;
            &lt;input type="number" id="eventMinute" min="0" placeholder="e.g. 23"&gt;
          &lt;/div&gt;
          &lt;div class="form-group full"&gt;
            &lt;label for="eventDesc"&gt;Description&lt;/label&gt;
            &lt;input type="text" id="eventDesc" placeholder="Optional description"&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class="actions"&gt;
          &lt;button type="submit" class="btn-primary"&gt;Add Event&lt;/button&gt;
        &lt;/div&gt;
      &lt;/form&gt;
    &lt;/section&gt;

    &lt;section&gt;
      &lt;h2&gt;Active Matches&lt;/h2&gt;
      &lt;div class="match-list" id="matchList"&gt;&lt;/div&gt;
    &lt;/section&gt;
  &lt;/div&gt;

  &lt;div class="toast" id="toast"&gt;&lt;/div&gt;
</code></pre>
<p>The three forms use <code>id</code> attributes (<code>createMatch</code>, <code>updateScore</code>, <code>addEvent</code>) so the JavaScript can attach submit handlers. The match dropdowns (<code>scoreMatchId</code> and <code>eventMatchId</code>) are populated dynamically when the page loads. The <code>matchList</code> div is the container for the list of active matches. The toast element sits outside the main container so it can be fixed to the viewport.</p>
<h3 id="heading-admin-javascript-logic">Admin JavaScript Logic</h3>
<p>The script block handles all interaction between the admin page and the server. It defines helper functions for showing notifications, fetching matches, updating the UI, and submitting data. When the page loads or after any action (create, update, or event submission), the UI refreshes so the latest match data is always visible.</p>
<pre><code class="language-javascript">  &lt;script&gt;
    const API = '/api';
    const toast = document.getElementById('toast');

    function showToast(msg, isError = false) {
      toast.textContent = msg;
      toast.className = 'toast show' + (isError ? ' error' : '');
      setTimeout(() =&gt; toast.classList.remove('show'), 3000);
    }

    async function fetchMatches() {
      const res = await fetch(`${API}/matches`);
      return res.json();
    }

    function populateSelects(matches) {
      const opts = matches.map(m =&gt; `&lt;option value="\({m.id}"&gt;\){m.homeTeam} vs ${m.awayTeam}&lt;/option&gt;`).join('');
      const html = '&lt;option value=""&gt;-- Select match --&lt;/option&gt;' + opts;
      document.getElementById('scoreMatchId').innerHTML = html;
      document.getElementById('eventMatchId').innerHTML = html;
    }

    function renderMatchList(matches) {
      const list = document.getElementById('matchList');
      if (!matches.length) {
        list.innerHTML = '&lt;p style="color: var(--text-muted);"&gt;No matches yet. Create one above.&lt;/p&gt;';
        return;
      }
      list.innerHTML = matches.map(m =&gt; `
        &lt;div class="match-card" data-id="${m.id}"&gt;
          &lt;div class="match-info"&gt;
            &lt;div class="match-teams"&gt;\({m.homeTeam} vs \){m.awayTeam}&lt;/div&gt;
            &lt;div class="match-meta"&gt;\({m.league} • \){m.status} • ${m.minute}'&lt;/div&gt;
          &lt;/div&gt;
          &lt;div class="match-score"&gt;\({m.homeScore} - \){m.awayScore}&lt;/div&gt;
          &lt;div class="match-actions"&gt;
            &lt;button class="btn-secondary" onclick="quickScore('\({m.id}', \){m.homeScore}, ${m.awayScore})"&gt;Update&lt;/button&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      `).join('');
    }

    function quickScore(id, h, a) {
      document.getElementById('scoreMatchId').value = id;
      document.getElementById('homeScore').value = h;
      document.getElementById('awayScore').value = a;
    }

    async function loadMatches() {
      const matches = await fetchMatches();
      populateSelects(matches);
      renderMatchList(matches);
    }

    document.getElementById('createMatch').onsubmit = async (e) =&gt; {
      e.preventDefault();
      const body = {
        homeTeam: document.getElementById('homeTeam').value.trim(),
        awayTeam: document.getElementById('awayTeam').value.trim(),
        league: document.getElementById('league').value.trim() || 'Premier League',
        venue: document.getElementById('venue').value.trim() || 'TBD',
        kickoff: document.getElementById('kickoff').value
          ? new Date(document.getElementById('kickoff').value).toISOString()
          : new Date().toISOString(),
      };
      try {
        const res = await fetch(`${API}/matches`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(body),
        });
        const data = await res.json();
        if (res.ok) {
          showToast('Match created!');
          document.getElementById('createMatch').reset();
          loadMatches();
        } else showToast(data.error || 'Failed', true);
      } catch (err) {
        showToast('Network error', true);
      }
    };

    document.getElementById('updateScore').onsubmit = async (e) =&gt; {
      e.preventDefault();
      const id = document.getElementById('scoreMatchId').value;
      const body = {
        homeScore: parseInt(document.getElementById('homeScore').value, 10),
        awayScore: parseInt(document.getElementById('awayScore').value, 10),
        minute: parseInt(document.getElementById('minute').value, 10) || undefined,
        status: document.getElementById('status').value,
      };
      try {
        const res = await fetch(`\({API}/matches/\){id}/score`, {
          method: 'PATCH',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(body),
        });
        const data = await res.json();
        if (res.ok) {
          showToast('Score updated!');
          loadMatches();
        } else showToast(data.error || 'Failed', true);
      } catch (err) {
        showToast('Network error', true);
      }
    };

    document.getElementById('addEvent').onsubmit = async (e) =&gt; {
      e.preventDefault();
      const id = document.getElementById('eventMatchId').value;
      const body = {
        type: document.getElementById('eventType').value,
        team: document.getElementById('eventTeam').value.trim(),
        player: document.getElementById('eventPlayer').value.trim(),
        minute: parseInt(document.getElementById('eventMinute').value, 10) || undefined,
        description: document.getElementById('eventDesc').value.trim() || undefined,
      };
      try {
        const res = await fetch(`\({API}/matches/\){id}/events`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(body),
        });
        const data = await res.json();
        if (res.ok) {
          showToast('Event added!');
          loadMatches();
        } else showToast(data.error || 'Failed', true);
      } catch (err) {
        showToast('Network error', true);
      }
    };

    loadMatches();
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>Explanation of each part:</p>
<ul>
<li><p><code>showToast</code>: updates the toast text and adds the show class to trigger the CSS animation. The isError parameter switches the toast to a red background for error messages. The setTimeout removes the show class after 3 seconds so the toast slides back out.</p>
</li>
<li><p><code>fetchMatches</code>: calls the GET /api/matches endpoint and returns the parsed JSON so the UI can display the latest data.</p>
</li>
<li><p><code>populateSelects</code>: builds option elements from the matches array and injects them into both dropdowns, so the same match list appears in the Update Score and Add Event forms.</p>
</li>
<li><p><code>renderMatchList</code>: either shows a placeholder when there are no matches or renders each match as a card with team names, scores, league, status, and an Update button.</p>
</li>
<li><p><code>quickScore</code>: pre-fills the Update Score form when you click the Update button on a match card, so you can adjust the score without re-selecting the match.</p>
</li>
<li><p><code>loadMatches</code>: fetches matches, populates the dropdowns, and renders the list. It runs on page load and after every successful create, update, or event submission.</p>
</li>
<li><p><code>onsubmit</code>: calls e.preventDefault() to stop the default form submission, builds a request body from the form values, sends a fetch request to the appropriate endpoint, and on success shows a toast and calls loadMatches to refresh the UI.</p>
</li>
</ul>
<h2 id="heading-how-to-build-the-live-viewer-interface">How to Build the Live Viewer Interface</h2>
<p>The viewer interface displays real-time match updates and connects to the Server-Sent Events endpoint so it can receive data the moment the server pushes it.</p>
<p>Unlike the admin page, the viewer is read-only: it shows live scores, match events, and status updates without requiring user input. The page uses a dark theme and a connection indicator so users can see whether the real-time stream is active.</p>
<p>Now create the file at <code>public/viewer.html</code>:</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;Live Football Scores&lt;/title&gt;
  &lt;link rel="preconnect" href="https://fonts.googleapis.com"&gt;
  &lt;link rel="preconnect" href="https://fonts.gstatic.com" crossorigin&gt;
  &lt;link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&amp;display=swap" rel="stylesheet"&gt;
  &lt;style&gt;
    :root {
      --bg: #0a0e14;
      --surface: #131a24;
      --accent: #00d26a;
      --accent-glow: rgba(0, 210, 106, 0.3);
      --text: #e8edf2;
      --text-muted: #8b9aab;
      --border: #2d3a4d;
      --live: #ff4757;
    }
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body {
      font-family: 'Outfit', sans-serif;
      background: var(--bg);
      color: var(--text);
      min-height: 100vh;
      padding: 2rem;
    }
    .container { max-width: 700px; margin: 0 auto; }
    header { text-align: center; margin-bottom: 2rem; }
    .status {
      display: inline-flex;
      align-items: center;
      gap: 0.5rem;
      font-size: 0.85rem;
      color: var(--text-muted);
    }
    .status-dot {
      width: 8px;
      height: 8px;
      border-radius: 50%;
      background: var(--text-muted);
      animation: pulse 2s infinite;
    }
    .status-dot.connected {
      background: var(--accent);
      box-shadow: 0 0 0 3px var(--accent-glow);
    }
    @keyframes pulse {
      0%, 100% { opacity: 1; }
      50% { opacity: 0.5; }
    }
    .match-card {
      background: var(--surface);
      border-radius: 16px;
      padding: 1.5rem;
      margin-bottom: 1rem;
      border: 1px solid var(--border);
      transition: border-color 0.2s, box-shadow 0.2s;
    }
    .match-card.live {
      border-color: var(--live);
      box-shadow: 0 0 0 1px rgba(255, 71, 87, 0.2);
    }
    .match-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 1rem;
    }
    .league { 
      font-size: 0.8rem; 
      color: var(--text-muted); 
      margin-bottom: 0.25rem; 
    }
    .match-teams {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 1rem;
      margin: 1rem 0;
    }
    .team { flex: 1; text-align: center; font-weight: 600; font-size: 1.1rem; }
    .team.home { text-align: left; }
    .team.away { text-align: right; }
    .score-box {
      display: flex;
      align-items: center;
      justify-content: center;
      min-width: 80px;
      gap: 0.5rem;
    }
    .score { 
      font-size: 2rem; 
      font-weight: 700; 
      color: var(--accent); 
     }
    .status-badge { 
      font-size: 0.7rem; 
      padding: 0.2rem 0.5rem; 
      border-radius: 4px; 
      font-weight: 600; 
     }
    .status-badge.live { background: var(--live); color: white; }
    .status-badge.finished { background: var(--border); color: var(--text-muted); }
    .status-badge.scheduled { background: var(--accent); color: var(--bg); }
    .match-meta { font-size: 0.85rem; color: var(--text-muted); margin-top: 0.5rem; }
    .events { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--border); }
    .events h4 { font-size: 0.8rem; color: var(--text-muted); margin-bottom: 0.5rem; }
    .event { display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; padding: 0.35rem 0; border-bottom: 1px solid var(--border); }
    .event:last-child { border-bottom: none; }
    .event-icon { width: 24px; text-align: center; font-size: 1rem; }
    .event.goal .event-icon { color: var(--accent); }
    .event.yellow_card .event-icon { color: #ffd93d; }
    .event.red_card .event-icon { color: var(--live); }
    .feed { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid var(--border); }
    .feed h3 { font-size: 1rem; margin-bottom: 1rem; color: var(--text-muted); }
    .feed-item { font-size: 0.85rem; padding: 0.5rem 0; color: var(--text-muted); border-bottom: 1px solid var(--border); }
    .feed-item:last-child { border-bottom: none; }
    .feed-item strong { color: var(--text); }
    .empty { text-align: center; padding: 3rem 2rem; color: var(--text-muted); }
    .empty-icon { font-size: 3rem; margin-bottom: 1rem; opacity: 0.5; }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div class="container"&gt;
    &lt;header&gt;
      &lt;h1&gt;⚽ Live Football Scores&lt;/h1&gt;
      &lt;div class="status" id="status"&gt;
        &lt;span class="status-dot" id="statusDot"&gt;&lt;/span&gt;
        &lt;span id="statusText"&gt;Connecting...&lt;/span&gt;
      &lt;/div&gt;
    &lt;/header&gt;

    &lt;div id="matches"&gt;&lt;/div&gt;

    &lt;div class="feed" id="feedSection"&gt;
      &lt;h3&gt;Live Feed&lt;/h3&gt;
      &lt;div id="feed"&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
</code></pre>
<p>Explanation of each part:</p>
<ul>
<li><p><code>header</code>: displays the page title and a connection indicator so users know whether the real-time stream is active.</p>
</li>
<li><p><code>status dot</code>: a small circle that pulses while disconnected and turns green with a glow when the SSE connection is established.</p>
</li>
<li><p><code>matches container</code>: the div with id="matches" is where match cards will be rendered dynamically by JavaScript as data arrives.</p>
</li>
<li><p><code>feed section</code>: shows a chronological list of live updates so users can see recent events at a glance.</p>
</li>
<li><p><code>CSS theme</code>: uses dark colors and custom properties so the look can be adjusted in one place. The live badge and border styles highlight matches that are in progress.</p>
</li>
<li><p><code>Server-Sent Events integration</code>: JavaScript (added in the next step) will connect to /api/events and update this page whenever new data arrives.</p>
</li>
</ul>
<p>The header shows the title and a connection status indicator. The <code>matches</code> div is the container for match cards, populated by JavaScript. The <code>feed</code> div displays the live feed of recent updates.</p>
<h3 id="heading-viewer-javascript-logic">Viewer JavaScript Logic</h3>
<p>The script powers the live viewer interface by maintaining an in-memory collection of matches and a feed of recent updates. It connects to the Server-Sent Events endpoint so data flows from the server to the browser in real time. When messages arrive, the page updates automatically to show the latest scores, events, and match details.</p>
<pre><code class="language-javascript">  &lt;script&gt;
    const API = '/api';
    const matches = new Map();
    const feed = [];
    const MAX_FEED = 20;

    const statusDot = document.getElementById('statusDot');
    const statusText = document.getElementById('statusText');
    const matchesEl = document.getElementById('matches');
    const feedEl = document.getElementById('feed');

    function setStatus(connected) {
      statusDot.classList.toggle('connected', connected);
      statusText.textContent = connected ? 'Live' : 'Reconnecting...';
    }

    function renderMatches() {
      const list = Array.from(matches.values()).sort(
        (a, b) =&gt; new Date(b.createdAt) - new Date(a.createdAt)
      );
      if (!list.length) {
        matchesEl.innerHTML = `
          &lt;div class="empty"&gt;
            &lt;div class="empty-icon"&gt;⚽&lt;/div&gt;
            &lt;p&gt;No matches yet. Updates will appear here in real-time.&lt;/p&gt;
          &lt;/div&gt;
        `;
        return;
      }
      matchesEl.innerHTML = list.map(m =&gt; `
        &lt;div class="match-card \({m.status === 'live' ? 'live' : ''}" data-id="\){m.id}"&gt;
          &lt;div class="match-header"&gt;
            &lt;div&gt;
              &lt;div class="league"&gt;${m.league}&lt;/div&gt;
              &lt;div class="match-meta"&gt;\({m.venue} • \){m.kickoff ? new Date(m.kickoff).toLocaleString() : ''}&lt;/div&gt;
            &lt;/div&gt;
            &lt;span class="status-badge \({m.status}"&gt;\){m.status}&lt;/span&gt;
          &lt;/div&gt;
          &lt;div class="match-teams"&gt;
            &lt;div class="team home"&gt;${m.homeTeam}&lt;/div&gt;
            &lt;div class="score-box"&gt;
              &lt;span class="score"&gt;${m.homeScore}&lt;/span&gt;
              &lt;span&gt;-&lt;/span&gt;
              &lt;span class="score"&gt;${m.awayScore}&lt;/span&gt;
            &lt;/div&gt;
            &lt;div class="team away"&gt;${m.awayTeam}&lt;/div&gt;
          &lt;/div&gt;
          \({m.minute ? `&lt;div class="match-meta"&gt;\){m.minute}'&lt;/div&gt;` : ''}
          ${m.events?.length ? `
            &lt;div class="events"&gt;
              &lt;h4&gt;Events&lt;/h4&gt;
              ${m.events.map(e =&gt; `
                &lt;div class="event ${e.type}"&gt;
                  &lt;span class="event-icon"&gt;${getEventIcon(e.type)}&lt;/span&gt;
                  &lt;span&gt;\({e.minute}' \){e.player} (\({e.team}) - \){e.description || e.type}&lt;/span&gt;
                &lt;/div&gt;
              `).join('')}
            &lt;/div&gt;
          ` : ''}
        &lt;/div&gt;
      `).join('');
    }

    function getEventIcon(type) {
      const icons = { goal: '⚽', yellow_card: '🟨', red_card: '🟥', substitution: '🔄', penalty: '⚽' };
      return icons[type] || '•';
    }

    function renderFeed() {
      const items = feed.slice(-MAX_FEED).reverse();
      feedEl.innerHTML = items.length
        ? items.map(f =&gt; `&lt;div class="feed-item"&gt;${f}&lt;/div&gt;`).join('')
        : '&lt;div class="feed-item"&gt;Waiting for updates...&lt;/div&gt;';
    }

    function addFeedItem(type, msg) {
      const time = new Date().toLocaleTimeString();
      feed.push(`&lt;strong&gt;\({time}&lt;/strong&gt; | \){msg}`);
      if (feed.length &gt; MAX_FEED) feed.shift();
      renderFeed();
    }

    function handleMessage(data) {
      if (data.match) {
        matches.set(data.match.id, data.match);
        renderMatches();
      }
      if (data.id &amp;&amp; data.homeTeam &amp;&amp; data.awayTeam) {
        matches.set(data.id, data);
        renderMatches();
      }
      if (data.type === 'match_created' &amp;&amp; data.match) {
        addFeedItem('match', `New match: \({data.match.homeTeam} vs \){data.match.awayTeam}`);
      }
      if (data.type === 'score_update') {
        const m = matches.get(data.matchId);
        if (m) {
          m.homeScore = data.homeScore;
          m.awayScore = data.awayScore;
          m.minute = data.minute;
          m.status = data.status;
          matches.set(data.matchId, m);
          addFeedItem('score', `\({m.homeTeam} \){data.homeScore}-\({data.awayScore} \){m.awayTeam} (${data.minute || '?'}')`);
          renderMatches();
        }
      }
      if (data.type === 'match_event' &amp;&amp; data.event) {
        const m = matches.get(data.matchId);
        if (m) {
          m.events = m.events || [];
          m.events.push(data.event);
          if (data.event.type === 'goal') {
            if (data.event.team === m.homeTeam) m.homeScore++;
            else if (data.event.team === m.awayTeam) m.awayScore++;
          }
          matches.set(data.matchId, m);
          addFeedItem('event', `\({data.event.type}: \){data.event.player} (\({data.event.team}) - \){data.event.minute}'`);
          renderMatches();
        }
      }
    }

    function handleSSEMessage(msg) {
      try {
        const data = JSON.parse(msg);
        if (data.topic &amp;&amp; /^sports\/football\/match\/([^/]+)$/.test(data.topic) &amp;&amp; data.id) {
          matches.set(data.id, data);
        }
        handleMessage(data);
      } catch (e) {}
    }

    function connectSSE() {
      const es = new EventSource(`${API}/events`);
      es.onopen = () =&gt; setStatus(true);
      es.onerror = () =&gt; {
        setStatus(false);
        es.close();
        setTimeout(connectSSE, 3000);
      };
      es.onmessage = (e) =&gt; handleSSEMessage(e.data);
    }

    async function loadInitial() {
      try {
        const res = await fetch(`${API}/matches`);
        const list = await res.json();
        list.forEach(m =&gt; matches.set(m.id, m));
        renderMatches();
      } catch (e) {
        matchesEl.innerHTML = '&lt;div class="empty"&gt;&lt;p&gt;Could not load matches. Is the server running?&lt;/p&gt;&lt;/div&gt;';
      }
    }

    loadInitial();
    connectSSE();
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>Explanation of each part:</p>
<ul>
<li><p>The <code>matches Map</code>: stores match objects keyed by ID so updates can be applied efficiently without searching arrays.</p>
</li>
<li><p>The <code>feed array</code>: keeps a small history of recent events (limited to MAX_FEED) so the live feed remains lightweight.</p>
</li>
<li><p><code>setStatus</code>: toggles the connected class on the status dot and updates the status text to “Live” or “Reconnecting…” so users know connection status.</p>
</li>
<li><p><code>renderMatches</code>: converts the Map to a sorted array (newest first). If there are no matches, it displays an empty state. Otherwise it renders cards showing league, venue, teams, scores, status badge, minute, and events.</p>
</li>
<li><p><code>getEventIcon</code>: returns an emoji for each event type so events are visually identifiable (goal, card, substitution, and so on).</p>
</li>
<li><p><code>renderFeed</code>: displays the live feed items or a placeholder message when no updates exist.</p>
</li>
<li><p><code>addFeedItem</code>: appends a timestamped message to the feed, keeps only the latest items, and re-renders the feed.</p>
</li>
<li><p><code>handleMessage</code>: processes incoming data. It updates the matches Map for full match objects, score updates, and match events. For score updates and goals it adjusts scores and adds feed items so the viewer reflects real-time changes.</p>
</li>
<li><p><code>handleSSEMessage</code>: parses the Server-Sent Events payload and forwards it to handleMessage. If the message contains a match topic and full match data, it stores it in the Map.</p>
</li>
<li><p><code>connectSSE</code>: creates an EventSource connection to /api/events. On open it marks the connection as live. On error it closes and retries after three seconds so transient network issues do not break the stream.</p>
</li>
<li><p><code>loadInitial</code>: fetches existing matches on page load so the viewer displays data even before real-time updates arrive.</p>
</li>
</ul>
<h2 id="heading-how-to-build-the-home-page">How to Build the Home Page</h2>
<p>The home page (<code>public/index.html</code>) is a simple landing page that links to the viewer and admin interfaces. It uses a centered card layout with two buttons. The primary button (green) goes to the viewer, and the secondary button (outlined) goes to the admin. The page uses the same dark theme and Outfit font for consistency. There is no JavaScript – it's purely static HTML and CSS.</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;Football Scores - MQTT Real-Time&lt;/title&gt;
  &lt;link rel="preconnect" href="https://fonts.googleapis.com"&gt;
  &lt;link rel="preconnect" href="https://fonts.gstatic.com" crossorigin&gt;
  &lt;link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&amp;display=swap" rel="stylesheet"&gt;
  &lt;style&gt;
    :root {
      --bg: #0a0e14;
      --surface: #131a24;
      --accent: #00d26a;
      --text: #e8edf2;
      --text-muted: #8b9aab;
    }
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body {
      font-family: 'Outfit', sans-serif;
      background: var(--bg);
      color: var(--text);
      min-height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 2rem;
    }
    .card {
      background: var(--surface);
      border-radius: 16px;
      padding: 2rem;
      max-width: 400px;
      text-align: center;
      border: 1px solid rgba(255,255,255,0.06);
    }
    h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
    p { color: var(--text-muted); font-size: 0.95rem; margin-bottom: 1.5rem; }
    .links { display: flex; flex-direction: column; gap: 0.75rem; }
    a {
      display: block;
      padding: 1rem 1.5rem;
      background: var(--accent);
      color: var(--bg);
      text-decoration: none;
      font-weight: 600;
      border-radius: 10px;
      transition: opacity 0.2s;
    }
    a:hover { opacity: 0.9; }
    a.secondary {
      background: transparent;
      color: var(--text);
      border: 1px solid rgba(255,255,255,0.15);
    }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div class="card"&gt;
    &lt;h1&gt;⚽ Football Scores&lt;/h1&gt;
    &lt;p&gt;Real-time updates via MQTT &amp; Mosquitto&lt;/p&gt;
    &lt;div class="links"&gt;
      &lt;a href="/viewer.html"&gt;View Live Scores&lt;/a&gt;
      &lt;a href="/admin.html" class="secondary"&gt;Admin - Upload Scores&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>The Express server serves <code>index.html</code> when you visit the root URL (<code>http://localhost:3000/</code>) because the <code>express.static</code> middleware serves files from the <code>public</code> folder, and Express automatically serves <code>index.html</code> when the request path is <code>/</code>.</p>
<h2 id="heading-how-to-run-and-test-the-system">How to Run and Test the System</h2>
<p>Start the MQTT broker (Docker or local install).</p>
<p>Then start the Express server:</p>
<pre><code class="language-bash">npm start
</code></pre>
<p>Next, open <code>http://localhost:3000/admin.html</code> in the admin panel. Create a new match (for example, Manchester United vs Liverpool).</p>
<p>Open <code>http://localhost:3000/viewer.html</code> in another tab or window. Then update the score or add an event in the admin panel. The viewer should update within a second without refreshing.</p>
<h2 id="heading-how-to-extend-the-system-for-production">How to Extend the System for Production</h2>
<p>The current implementation uses in-memory storage. For production, you should:</p>
<ul>
<li><p><strong>Add a database</strong>: Store matches in PostgreSQL, MongoDB, or another database. Load matches on startup and persist every create, update, and event.</p>
</li>
<li><p><strong>Add authentication</strong>: Protect the admin routes with JSON Web Tokens (JWT) or session-based auth so only authorized users can upload scores.</p>
</li>
<li><p><strong>Add validation</strong>: Validate request bodies with a library such as Joi or Zod to prevent invalid data.</p>
</li>
<li><p><strong>Enable TLS</strong>: Use HTTPS for the Express server and secure WebSockets or MQTTS for the broker in production.</p>
</li>
<li><p><strong>Scale horizontally</strong>: If you run multiple server instances, each will have its own MQTT connection and SSE clients. The MQTT broker will deliver messages to all subscribers, so each instance will receive updates and forward them to its connected viewers.</p>
</li>
</ul>
<h2 id="heading-api-reference">API Reference</h2>
<p>For quick reference, here are the endpoints your server exposes:</p>
<table>
<thead>
<tr>
<th>Method</th>
<th>Endpoint</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr>
<td>GET</td>
<td><code>/api/matches</code></td>
<td>Returns all matches as a JSON array</td>
</tr>
<tr>
<td>POST</td>
<td><code>/api/matches</code></td>
<td>Creates a new match. Body: <code>{ homeTeam, awayTeam, league?, venue?, kickoff? }</code></td>
</tr>
<tr>
<td>PATCH</td>
<td><code>/api/matches/:id/score</code></td>
<td>Updates a match score. Body: <code>{ homeScore?, awayScore?, minute?, status? }</code></td>
</tr>
<tr>
<td>POST</td>
<td><code>/api/matches/:id/events</code></td>
<td>Adds an event. Body: <code>{ type?, team, player?, minute?, description? }</code></td>
</tr>
<tr>
<td>GET</td>
<td><code>/api/events</code></td>
<td>Server-Sent Events stream for real-time updates</td>
</tr>
</tbody></table>
<p>The <code>status</code> field can be <code>scheduled</code>, <code>live</code>, <code>halftime</code>, or <code>finished</code>. The <code>type</code> field for events can be <code>goal</code>, <code>yellow_card</code>, <code>red_card</code>, <code>substitution</code>, or <code>penalty</code>.</p>
<h2 id="heading-troubleshooting">Troubleshooting</h2>
<p>You might come across various issues as you build this. Here are some common ones:</p>
<p><strong>The server cannot connect to the MQTT broker.</strong> Check that Mosquitto is running. If you use Docker, run <code>docker ps</code> to verify the container is up. If you use the public broker, ensure you have internet connectivity and that your firewall allows outbound connections on port 1883.</p>
<p><strong>The viewer does not update when you change scores.</strong> Open the browser developer tools and check the Network tab. The <code>/api/events</code> request should show a pending state (it stays open). If it fails or closes, check the server logs for errors. Ensure you are not behind a proxy that buffers or closes long-lived connections.</p>
<p><strong>Matches disappear when you restart the server.</strong> The current implementation stores matches in memory. Restarting the server clears the data. Add a database as described in the production section to persist matches across restarts.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, you built a real-time football sports update system using MQTT, Mosquitto, and Express. You've learned how to:</p>
<ul>
<li><p>Connect an Express server to an MQTT broker</p>
</li>
<li><p>Publish match and score updates to MQTT topics</p>
</li>
<li><p>Subscribe to topics and forward messages to browsers via Server-Sent Events</p>
</li>
<li><p>Build an admin interface for creating matches and updating scores</p>
</li>
<li><p>Build a viewer interface that displays live updates without page refreshes</p>
</li>
</ul>
<p>The same pattern applies to other real-time systems: IoT dashboards, live notifications, collaborative editing, and more. MQTT gives you a reliable, scalable messaging layer, and Server-Sent Events gives you a simple way to push updates to web clients.</p>
<p>The code in this tutorial gives you a solid foundation. Try adding features such as match filtering by league, historical event logs, or push notifications to make the system your own.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Payroll System with Express and Monnify Using Background Jobs ]]>
                </title>
                <description>
                    <![CDATA[ Processing payroll payments is an important operation for any business. When you need to pay employees simultaneously, you can't afford to have your server hang, get blocking errors, or timeout while waiting for each payment to complete. Building a p... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-payroll-system-with-express-and-monnify-using-background-jobs/</link>
                <guid isPermaLink="false">69680d9ead82a9267c20097d</guid>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PostgreSQL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ David Aniebo ]]>
                </dc:creator>
                <pubDate>Wed, 14 Jan 2026 21:41:50 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1768414407566/4384def7-fdc2-4274-888d-d5bd5bd5549b.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Processing payroll payments is an important operation for any business. When you need to pay employees simultaneously, you can't afford to have your server hang, get blocking errors, or timeout while waiting for each payment to complete.</p>
<p>Building a payroll system is an excellent way to practice real-world backend development skills. Unlike simple CRUD applications, payroll systems require you to think about:</p>
<ul>
<li><p><strong>Asynchronous processing</strong>: When you need to pay hundreds of employees, processing payments synchronously can cause your server to timeout. Background jobs with Bull and Redis allow you to handle long-running operations without blocking your API.</p>
</li>
<li><p><strong>Payment gateway integration</strong>: Working with payment APIs like Monnify teaches you how to handle external service integrations, authentication flows, webhook verification, and error handling in production systems.</p>
</li>
<li><p><strong>Data consistency</strong>: Payroll systems need to maintain accurate records. You'll learn about transaction reconciliation, idempotency, and how to handle partial failures gracefully.</p>
</li>
<li><p><strong>Production-ready patterns</strong>: This tutorial covers patterns you'll use in real applications: job queues, webhook handlers, database migrations, and proper error handling.</p>
</li>
</ul>
<p>Whether you're building a fintech application, an HR system, or just want to understand how payment processing works, the concepts in this tutorial will serve you well. The combination of Express, TypeScript, background jobs, and payment APIs represents a common stack in modern backend development.</p>
<p>In this tutorial, you’ll learn how to build a production-grade payroll engine using Express.js, TypeScript, and Monnify's payment API. You'll implement background job processing with <code>Bull</code> and <code>Redis</code> to handle bulk disbursements efficiently.</p>
<p>By the end, you will have a fully functional payroll system that can:</p>
<ul>
<li><p>Manage employee records with bank account details</p>
</li>
<li><p>Create and process payroll batches</p>
</li>
<li><p>Process bulk payments using Monnify's disbursement API</p>
</li>
<li><p>Handle payment status updates via webhooks</p>
</li>
<li><p>Reconcile transactions to ensure data consistency</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-project-architecture-overview">Project Architecture Overview</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-setting-up-the-project">Setting Up the Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-configuring-docker-for-postgresql-and-redis">Configuring Docker for PostgreSQL and Redis</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-setting-up-the-database">Setting Up the Database</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-creating-database-models">Creating Database Models</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-employee-model">Employee Model</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-employee-data-structure-employee-interface">Employee Data Structure (Employee Interface)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-employee-model-class">Employee Model Class</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-auto-generating-employee-ids-generateemployeeid">Auto-Generating Employee IDs (generateEmployeeId)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-creating-an-employee-create">Creating an Employee (create)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-retrieving-all-active-employees-findall">Retrieving All Active Employees (findAll)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-retrieving-an-employee-by-database-id-findbyid">Retrieving an Employee by Database ID (findById)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-retrieving-an-employee-by-employee-identifier-findbyemployeeid">Retrieving an Employee by Employee Identifier (findByEmployeeId)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-updating-an-employee-update">Updating an Employee (update)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-soft-deleting-an-employee-delete">Soft-Deleting an Employee (delete)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-payroll-model">Payroll Model</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-payroll-status-lifecycle">Payroll Status Lifecycle</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-payroll-entity">Payroll Entity</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-payroll-item-entity">Payroll Item Entity</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-creating-a-payroll-payrollmodelcreate">Creating a Payroll (PayrollModel.create)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-fetching-payroll-records">Fetching Payroll Records</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-updating-payroll-status-payrollmodelupdatestatus">Updating Payroll Status (PayrollModel.updateStatus)</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-payrollitemmodel">PayrollItemModel</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-fetching-payroll-items-payrollitemmodelfindbypayrollid">Fetching Payroll Items (PayrollItemModel.findByPayrollId)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-fetching-a-single-payroll-item-payrollitemmodelfindbyid">Fetching a Single Payroll Item (PayrollItemModel.findById)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-updating-payroll-item-status-payrollitemmodelupdatestatus">Updating Payroll Item Status (PayrollItemModel.updateStatus)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-overall-payroll-flow">Overall Payroll Flow</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-building-the-monnify-client">Building the Monnify Client</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-configuration-and-environment-setup">Configuration and Environment Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-create-the-monnifyclient-class">Create the MonnifyClient Class</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-axios-client-and-request-interceptor">Axios Client and Request Interceptor</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-authenticate-with-monnify">Authenticate with Monnify</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-automatic-token-refresh-ensureauthenticated">Automatic Token Refresh (ensureAuthenticated)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-initiating-bulk-transfers">Initiating Bulk Transfers</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-authorizing-bulk-transfers-otp-validation">Authorizing Bulk Transfers (OTP Validation)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-transaction-status-lookup">Transaction Status Lookup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-batch-details-retrieval">Batch Details Retrieval</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-wallet-balance-check">Wallet Balance Check</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-implementing-background-job-processing">Implementing Background Job Processing</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-set-up-the-payroll-processing-queue">Set Up the Payroll Processing Queue</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-queue-processor-registration">Queue Processor Registration</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-bulk-payroll-processing-flow-processbulkpayroll">Bulk Payroll Processing Flow (processBulkPayroll)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-building-the-bulk-transfer-payload">Building the Bulk Transfer Payload</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-initiating-bulk-disbursement-via-monnify">Initiating Bulk Disbursement via Monnify</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-storing-transaction-references">Storing Transaction References</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-payroll-statistics-reconciliation-updatepayrollstats">Payroll Statistics Reconciliation (updatePayrollStats)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-queue-entry-point-processpayrollitems">Queue Entry Point (processPayrollItems)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-role-in-the-overall-payroll-architecture">Role in the Overall Payroll Architecture</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-creating-the-api-controllers">Creating the API Controllers</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-controller-responsibilities">Controller Responsibilities</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-creating-an-employee-createemployee">Creating an Employee (createEmployee)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-fetching-all-employees-getallemployees">Fetching All Employees (getAllEmployees)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-fetching-a-single-employee-getemployeebyid">Fetching a Single Employee (getEmployeeById)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-updating-an-employee-updateemployee">Updating an Employee (updateEmployee)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-deleting-an-employee-deleteemployee">Deleting an Employee (deleteEmployee)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-error-handling-strategy">Error Handling Strategy</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-role-in-the-overall-payroll-system">Role in the Overall Payroll System</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-payroll-controller">Payroll Controller</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-controller-responsibilities-1">Controller Responsibilities</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-creating-a-payroll-createpayroll">Creating a Payroll (createPayroll)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-fetching-all-payrolls-getallpayrolls">Fetching All Payrolls (getAllPayrolls)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-fetching-a-payroll-with-items-getpayrollbyid">Fetching a Payroll with Items (getPayrollById)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-processing-a-payroll-processpayroll">Processing a Payroll (processPayroll)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-reconciling-payroll-payments-reconcilepayroll">Reconciling Payroll Payments (reconcilePayroll)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-payroll-statistics-update-internal-helper">Payroll Statistics Update (Internal Helper)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-fetching-payroll-status-summary-getpayrollstatus">Fetching Payroll Status Summary (getPayrollStatus)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-authorizing-bulk-transfers-authorizebulktransfer">Authorizing Bulk Transfers (authorizeBulkTransfer)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-checking-transaction-status-checktransactionstatus">Checking Transaction Status (checkTransactionStatus)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-checking-wallet-balance-getaccountbalance">Checking Wallet Balance (getAccountBalance)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-error-handling-and-resilience">Error Handling and Resilience</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-role-in-the-overall-payroll-architecture-1">Role in the Overall Payroll Architecture</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-setting-up-webhook-handlers">Setting Up Webhook Handlers</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-wiring-up-routes">Wiring Up Routes</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-employee-routes">Employee Routes</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-payroll-routes">Payroll Routes</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-main-application-entry-point">Main Application Entry Point</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-testing-the-system">Testing the System</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-setting-up-webhooks-for-production">Setting Up Webhooks for Production</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-key-takeaways">Key Takeaways</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-references">References:</a></p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you begin, make sure you have the following:</p>
<ul>
<li><p>Node.js (v18 or higher)</p>
</li>
<li><p>Docker and Docker Compose installed</p>
</li>
<li><p>A Monnify merchant account with API credentials</p>
</li>
<li><p>Basic knowledge of TypeScript and Express.js</p>
</li>
<li><p>Familiarity with REST APIs</p>
</li>
</ul>
<p>You'll also need to obtain these credentials from your Monnify dashboard:</p>
<ul>
<li><p>API Key</p>
</li>
<li><p>Secret Key</p>
</li>
<li><p>Contract Code</p>
</li>
<li><p>Webhook Secret (for verifying webhook signatures)</p>
</li>
</ul>
<h2 id="heading-project-architecture-overview">Project Architecture Overview</h2>
<p>Here's how the payroll system works:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766393228193/8626c139-776c-491b-b060-2f95a760f32b.png" alt="Payroll system working principle" class="image--center mx-auto" width="2952" height="1494" loading="lazy"></p>
<p><strong>Key components:</strong></p>
<ol>
<li><p><strong>Express API</strong>: A minimal and flexible Node.js web framework that handles HTTP requests for managing employees and payrolls. Express provides routing, middleware support, and makes it easy to build RESTful APIs.</p>
</li>
<li><p><strong>Bull Queue</strong>: A Redis-based queue library for Node.js that processes payroll jobs asynchronously in the background. Bull handles job retries, scheduling, and provides a reliable way to process long-running tasks without blocking your main application thread.</p>
</li>
<li><p><strong>Redis</strong>: An in-memory data structure store that serves as the backend for Bull queues. Redis stores job data, manages job states (pending, active, completed, failed), and enables distributed job processing across multiple workers.</p>
</li>
<li><p><strong>PostgreSQL</strong>: A relational database that persists employee records, payrolls, and payment items. PostgreSQL's ACID compliance ensures data integrity, and its support for complex queries makes it ideal for financial applications.</p>
</li>
<li><p><strong>Monnify API</strong>: A payment gateway service that handles actual money transfers to employee bank accounts. Monnify provides bulk disbursement capabilities, allowing you to process multiple payments in a single API call, which is essential for payroll systems.</p>
</li>
<li><p><strong>Webhooks</strong>: HTTP callbacks that receive real-time payment status updates from Monnify. When a payment completes or fails, Monnify sends a webhook to your server, allowing you to update your database immediately without polling.</p>
</li>
</ol>
<h2 id="heading-setting-up-the-project">Setting Up the Project</h2>
<p>In this section, we'll initialize a new Node.js project with TypeScript and install all the necessary dependencies. We'll configure TypeScript for type safety and set up the project structure that will support our payroll system.</p>
<p>First, create a new directory and initialize your project:</p>
<pre><code class="lang-bash">mkdir monnify-payroll-system
<span class="hljs-built_in">cd</span> monnify-payroll-system
npm init -y
</code></pre>
<p>Next, install the required dependencies:</p>
<pre><code class="lang-bash">npm install express cors helmet dotenv axios bull ioredis pg swagger-jsdoc swagger-ui-express express-validator
</code></pre>
<p>Then install the development dependencies:</p>
<pre><code class="lang-bash">npm install -D typescript ts-node-dev @types/node @types/express @types/cors @types/pg @types/bull
</code></pre>
<p>Create a <code>tsconfig.json</code> file:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"compilerOptions"</span>: {
    <span class="hljs-attr">"target"</span>: <span class="hljs-string">"ES2020"</span>,
    <span class="hljs-attr">"module"</span>: <span class="hljs-string">"commonjs"</span>,
    <span class="hljs-attr">"lib"</span>: [<span class="hljs-string">"ES2020"</span>],
    <span class="hljs-attr">"outDir"</span>: <span class="hljs-string">"./dist"</span>,
    <span class="hljs-attr">"rootDir"</span>: <span class="hljs-string">"./src"</span>,
    <span class="hljs-attr">"strict"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"esModuleInterop"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"skipLibCheck"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"forceConsistentCasingInFileNames"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"resolveJsonModule"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"declaration"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"declarationMap"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"sourceMap"</span>: <span class="hljs-literal">true</span>
  },
  <span class="hljs-attr">"include"</span>: [<span class="hljs-string">"src/**/*"</span>, <span class="hljs-string">"scripts/**/*"</span>],
  <span class="hljs-attr">"exclude"</span>: [<span class="hljs-string">"node_modules"</span>, <span class="hljs-string">"dist"</span>]
}
</code></pre>
<p>And update your <code>package.json</code> scripts:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"build"</span>: <span class="hljs-string">"tsc"</span>,
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"node dist/index.js"</span>,
    <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"ts-node-dev --respawn --transpile-only src/index.ts"</span>,
    <span class="hljs-attr">"migrate"</span>: <span class="hljs-string">"ts-node scripts/run-migrations.ts"</span>
  }
}
</code></pre>
<p>Now, create a <code>.env</code> file for your environment variables. All the Monnify env details can be gotten in this <a target="_blank" href="https://app.monnify.com/developer">route</a>:</p>
<pre><code class="lang-plaintext"># Server
PORT=3008
NODE_ENV=development

# Database
DB_HOST=localhost
DB_PORT=5433
DB_NAME=payroll_db
DB_USER=payroll_user
DB_PASSWORD=payroll_password

# Redis
REDIS_HOST=localhost
REDIS_PORT=6379

# Monnify
MONNIFY_API_KEY=your_api_key
MONNIFY_SECRET_KEY=your_secret_key
MONNIFY_BASE_URL=https://sandbox.monnify.com
MONNIFY_CONTRACT_CODE=your_contract_code
MONNIFY_WEBHOOK_SECRET=your_webhook_secret
</code></pre>
<h2 id="heading-configuring-docker-for-postgresql-and-redis">Configuring Docker for PostgreSQL and Redis</h2>
<p>Before we can start building our application, we need to set up the infrastructure services: PostgreSQL for data persistence and Redis for job queue management. Using Docker Compose makes it easy to run these services locally with a single command. This approach ensures consistency across development environments and simplifies deployment.</p>
<p>Create a <code>docker-compose.yml</code> file to set up PostgreSQL and Redis:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">postgres:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:15-alpine</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">monnify-payroll-db</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">POSTGRES_USER:</span> <span class="hljs-string">payroll_user</span>
      <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">payroll_password</span>
      <span class="hljs-attr">POSTGRES_DB:</span> <span class="hljs-string">payroll_db</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'5433:5432'</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">postgres_data:/var/lib/postgresql/data</span>
    <span class="hljs-attr">healthcheck:</span>
      <span class="hljs-attr">test:</span> [<span class="hljs-string">'CMD-SHELL'</span>, <span class="hljs-string">'pg_isready -U payroll_user'</span>]
      <span class="hljs-attr">interval:</span> <span class="hljs-string">10s</span>
      <span class="hljs-attr">timeout:</span> <span class="hljs-string">5s</span>
      <span class="hljs-attr">retries:</span> <span class="hljs-number">5</span>

  <span class="hljs-attr">redis:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">redis:7-alpine</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">monnify-payroll-redis</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'6379:6379'</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">redis_data:/data</span>
    <span class="hljs-attr">healthcheck:</span>
      <span class="hljs-attr">test:</span> [<span class="hljs-string">'CMD'</span>, <span class="hljs-string">'redis-cli'</span>, <span class="hljs-string">'ping'</span>]
      <span class="hljs-attr">interval:</span> <span class="hljs-string">10s</span>
      <span class="hljs-attr">timeout:</span> <span class="hljs-string">5s</span>
      <span class="hljs-attr">retries:</span> <span class="hljs-number">5</span>

<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">postgres_data:</span>
  <span class="hljs-attr">redis_data:</span>
</code></pre>
<p>Start the services:</p>
<pre><code class="lang-bash">docker-compose up -d
</code></pre>
<p>And verify that both services are running:</p>
<pre><code class="lang-bash">docker-compose ps
</code></pre>
<h2 id="heading-setting-up-the-database">Setting Up the Database</h2>
<p>Now we'll configure the database connection and create the necessary tables. We'll use a connection pool for efficient database access and create migration files to set up our schema. This approach ensures our database structure is version-controlled and can be easily reproduced.</p>
<p>Create the <code>src/config/database.ts</code> file to configure the PostgreSQL connection:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Pool, PoolConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'pg'</span>;
<span class="hljs-keyword">import</span> dotenv <span class="hljs-keyword">from</span> <span class="hljs-string">'dotenv'</span>;

dotenv.config();

<span class="hljs-keyword">const</span> dbName = (process.env.DB_NAME || <span class="hljs-string">'payroll_db'</span>).trim();
<span class="hljs-keyword">if</span> (!dbName) {
  <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Database name (DB_NAME) must be set and cannot be empty'</span>);
}

<span class="hljs-keyword">const</span> config: PoolConfig = {
  host: process.env.DB_HOST || <span class="hljs-string">'localhost'</span>,
  port: <span class="hljs-built_in">parseInt</span>(process.env.DB_PORT || <span class="hljs-string">'5433'</span>),
  database: dbName,
  user: process.env.DB_USER || <span class="hljs-string">'payroll_user'</span>,
  password: process.env.DB_PASSWORD || <span class="hljs-string">'payroll_password'</span>,
  max: <span class="hljs-number">20</span>,
  idleTimeoutMillis: <span class="hljs-number">30000</span>,
  connectionTimeoutMillis: <span class="hljs-number">2000</span>,
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> pool = <span class="hljs-keyword">new</span> Pool(config);

pool.on(<span class="hljs-string">'error'</span>, <span class="hljs-function">(<span class="hljs-params">err: <span class="hljs-built_in">Error</span></span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Unexpected error on idle client'</span>, err);
  process.exit(<span class="hljs-number">-1</span>);
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> query = <span class="hljs-keyword">async</span> (text: <span class="hljs-built_in">string</span>, params?: <span class="hljs-built_in">any</span>[]) =&gt; {
  <span class="hljs-keyword">const</span> start = <span class="hljs-built_in">Date</span>.now();
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> pool.query(text, params);
    <span class="hljs-keyword">return</span> res;
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Database query error'</span>, error);
    <span class="hljs-keyword">throw</span> error;
  }
};
</code></pre>
<p>Now create the migration files. First, create a <code>migrations</code> folder:</p>
<pre><code class="lang-bash">mkdir migrations
</code></pre>
<p>Then create <code>migrations/001_create_employees_table.sql</code>:</p>
<pre><code class="lang-sql"><span class="hljs-comment">-- Create employees table</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> employees (
  <span class="hljs-keyword">id</span> <span class="hljs-built_in">SERIAL</span> PRIMARY <span class="hljs-keyword">KEY</span>,
  <span class="hljs-keyword">name</span> <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">255</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  email <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">255</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> <span class="hljs-keyword">UNIQUE</span>,
  employee_id <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">100</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> <span class="hljs-keyword">UNIQUE</span>,
  salary <span class="hljs-built_in">DECIMAL</span>(<span class="hljs-number">15</span>, <span class="hljs-number">2</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  account_number <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">50</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  bank_code <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">20</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  bank_name <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">255</span>),
  is_active <span class="hljs-built_in">BOOLEAN</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-literal">true</span>,
  created_at <span class="hljs-built_in">TIMESTAMP</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">CURRENT_TIMESTAMP</span>,
  updated_at <span class="hljs-built_in">TIMESTAMP</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">CURRENT_TIMESTAMP</span>
);

<span class="hljs-comment">-- Create indexes for faster lookups</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> idx_employees_employee_id <span class="hljs-keyword">ON</span> employees(employee_id);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> idx_employees_is_active <span class="hljs-keyword">ON</span> employees(is_active);
</code></pre>
<p>Now, create <code>migrations/002_create_payrolls_table.sql</code>:</p>
<pre><code class="lang-sql"><span class="hljs-comment">-- Create payrolls table</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> payrolls (
  <span class="hljs-keyword">id</span> <span class="hljs-built_in">SERIAL</span> PRIMARY <span class="hljs-keyword">KEY</span>,
  payroll_period <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">100</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  total_amount <span class="hljs-built_in">DECIMAL</span>(<span class="hljs-number">15</span>, <span class="hljs-number">2</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  total_employees <span class="hljs-built_in">INTEGER</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  <span class="hljs-keyword">status</span> <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">50</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-string">'pending'</span>,
  processed_count <span class="hljs-built_in">INTEGER</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-number">0</span>,
  failed_count <span class="hljs-built_in">INTEGER</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-number">0</span>,
  created_at <span class="hljs-built_in">TIMESTAMP</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">CURRENT_TIMESTAMP</span>,
  updated_at <span class="hljs-built_in">TIMESTAMP</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">CURRENT_TIMESTAMP</span>,
  processed_at <span class="hljs-built_in">TIMESTAMP</span>
);

<span class="hljs-comment">-- Create indexes for faster queries</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> idx_payrolls_status <span class="hljs-keyword">ON</span> payrolls(<span class="hljs-keyword">status</span>);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> idx_payrolls_period <span class="hljs-keyword">ON</span> payrolls(payroll_period);
</code></pre>
<p>And next, create <code>migrations/003_create_payroll_items_table.sql</code>:</p>
<pre><code class="lang-sql"><span class="hljs-comment">-- Create payroll_items table</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> payroll_items (
  <span class="hljs-keyword">id</span> <span class="hljs-built_in">SERIAL</span> PRIMARY <span class="hljs-keyword">KEY</span>,
  payroll_id <span class="hljs-built_in">INTEGER</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> <span class="hljs-keyword">REFERENCES</span> payrolls(<span class="hljs-keyword">id</span>) <span class="hljs-keyword">ON</span> <span class="hljs-keyword">DELETE</span> <span class="hljs-keyword">CASCADE</span>,
  employee_id <span class="hljs-built_in">INTEGER</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> <span class="hljs-keyword">REFERENCES</span> employees(<span class="hljs-keyword">id</span>) <span class="hljs-keyword">ON</span> <span class="hljs-keyword">DELETE</span> <span class="hljs-keyword">CASCADE</span>,
  amount <span class="hljs-built_in">DECIMAL</span>(<span class="hljs-number">15</span>, <span class="hljs-number">2</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  <span class="hljs-keyword">status</span> <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">50</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-string">'pending'</span>,
  transaction_reference <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">255</span>),
  error_message <span class="hljs-built_in">TEXT</span>,
  processed_at <span class="hljs-built_in">TIMESTAMP</span>,
  created_at <span class="hljs-built_in">TIMESTAMP</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">CURRENT_TIMESTAMP</span>,
  updated_at <span class="hljs-built_in">TIMESTAMP</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">CURRENT_TIMESTAMP</span>
);

<span class="hljs-comment">-- Create indexes for faster queries</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> idx_payroll_items_payroll_id <span class="hljs-keyword">ON</span> payroll_items(payroll_id);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> idx_payroll_items_employee_id <span class="hljs-keyword">ON</span> payroll_items(employee_id);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> idx_payroll_items_status <span class="hljs-keyword">ON</span> payroll_items(<span class="hljs-keyword">status</span>);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> idx_payroll_items_transaction_ref <span class="hljs-keyword">ON</span> payroll_items(transaction_reference);
</code></pre>
<p>Then create a migration runner script at <code>scripts/run-migrations.ts</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> fs <span class="hljs-keyword">from</span> <span class="hljs-string">'fs'</span>;
<span class="hljs-keyword">import</span> path <span class="hljs-keyword">from</span> <span class="hljs-string">'path'</span>;
<span class="hljs-keyword">import</span> { pool } <span class="hljs-keyword">from</span> <span class="hljs-string">'../src/config/database'</span>;

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">runMigrations</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> migrationsDir = path.join(__dirname, <span class="hljs-string">'../migrations'</span>);
  <span class="hljs-keyword">const</span> files = fs.readdirSync(migrationsDir).sort();

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> file <span class="hljs-keyword">of</span> files) {
    <span class="hljs-keyword">if</span> (file.endsWith(<span class="hljs-string">'.sql'</span>)) {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Running migration: <span class="hljs-subst">${file}</span>`</span>);
      <span class="hljs-keyword">const</span> sql = fs.readFileSync(path.join(migrationsDir, file), <span class="hljs-string">'utf-8'</span>);
      <span class="hljs-keyword">await</span> pool.query(sql);
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Completed: <span class="hljs-subst">${file}</span>`</span>);
    }
  }

  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'All migrations completed'</span>);
  <span class="hljs-keyword">await</span> pool.end();
}

runMigrations().catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Migration failed:'</span>, err);
  process.exit(<span class="hljs-number">1</span>);
});
</code></pre>
<p>Run the migrations:</p>
<pre><code class="lang-bash">npm run migrate
</code></pre>
<h2 id="heading-creating-database-models">Creating Database Models</h2>
<p>In this section, we'll create the data access layer for our payroll system. Models encapsulate all database operations, providing a clean interface for the rest of the application. We'll build two main models: one for managing employees and another for handling payrolls and payroll items.</p>
<p>For each model, I’ll first explain its purpose and key methods, then show you the complete code implementation. This approach helps you understand what each model does before you implement it.</p>
<h3 id="heading-employee-model">Employee Model</h3>
<p>The <code>EmployeeModel</code> serves as the data-access layer for employee records. It handles creating, reading, updating, and soft-deleting employees. The model includes automatic employee ID generation (format: <code>EMP001</code>, <code>EMP002</code>, and so on) and ensures that each employee has the banking details required for payroll disbursement.</p>
<p>Start by creating a new file at <code>src/models/employee.ts</code> where we’ll implement all employee-related database logic.</p>
<p>After creating the file, import a shared database query helper to execute parameterized SQL safely.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { query } <span class="hljs-keyword">from</span> <span class="hljs-string">'../config/database'</span>;
</code></pre>
<p>This keeps raw SQL isolated from controllers and ensures protection against SQL injection.</p>
<h3 id="heading-employee-data-structure-employee-interface">Employee Data Structure (<code>Employee</code> Interface)</h3>
<p>Next, we’ll define the employee interface.</p>
<p>The <code>Employee</code> interface represents a row in the <code>employees</code> database table and captures both operational and audit fields. It includes identifying fields (`id`, <code>employee_id</code>), personal fields (`name`, <code>email</code>), payroll fields (`salary`), banking details (`account_number`, <code>bank_code</code>, <code>bank_name</code>), operational state (`is_active`), and timestamps (`created_at`, <code>updated_at</code>). The <code>is_active</code> flag is used to support soft deletion and employee deactivation without permanently removing historical payroll relationships.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> Employee {
  id: <span class="hljs-built_in">number</span>;
  name: <span class="hljs-built_in">string</span>;
  email: <span class="hljs-built_in">string</span>;
  employee_id: <span class="hljs-built_in">string</span>;
  salary: <span class="hljs-built_in">number</span>;
  account_number: <span class="hljs-built_in">string</span>;
  bank_code: <span class="hljs-built_in">string</span>;
  bank_name: <span class="hljs-built_in">string</span>;
  is_active: <span class="hljs-built_in">boolean</span>;
  created_at: <span class="hljs-built_in">Date</span>;
  updated_at: <span class="hljs-built_in">Date</span>;
}
</code></pre>
<p>Now, we’ll define the <code>CreateEmployeeInput</code> interface which represent the expected payload for creating an employee. It includes required fields such as name, email, salary, and bank details.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> CreateEmployeeInput {
  name: <span class="hljs-built_in">string</span>;
  email: <span class="hljs-built_in">string</span>;
  employee_id?: <span class="hljs-built_in">string</span>;
  salary: <span class="hljs-built_in">number</span>;
  account_number: <span class="hljs-built_in">string</span>;
  bank_code: <span class="hljs-built_in">string</span>;
  bank_name: <span class="hljs-built_in">string</span>;
}
</code></pre>
<p>The <code>employee_id</code> field is optional, allowing the system to auto-generate a unique identifier if one is not provided. This flexibility supports both automated workflows and manual HR data imports.</p>
<h3 id="heading-employee-model-class">Employee Model Class</h3>
<p>Next, we’ll define the <code>EmployeeModel</code> class.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> EmployeeModel {
  <span class="hljs-comment">// Class methods will go here</span>
}
</code></pre>
<p>This class encapsulates all database operations related to employee records. It centralizes logic for creating, retrieving, updating, and deleting employees, as well as generating unique sequential employee IDs.</p>
<h3 id="heading-auto-generating-employee-ids-generateemployeeid">Auto-Generating Employee IDs (<code>generateEmployeeId</code>)</h3>
<p>We start by creating the <code>generateEmployeeId</code> method inside the <code>EmployeeModel</code> class.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> generateEmployeeId(): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">string</span>&gt; {
    <span class="hljs-comment">// Get the highest existing employee_id number that matches EMP### pattern</span>
    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> query(
      <span class="hljs-string">`SELECT employee_id FROM employees
       WHERE employee_id LIKE 'EMP%'
       AND LENGTH(employee_id) &gt;= 4
       AND SUBSTRING(employee_id FROM 4) ~ '^[0-9]+$'
       ORDER BY CAST(SUBSTRING(employee_id FROM 4) AS INTEGER) DESC
       LIMIT 1`</span>
    );

    <span class="hljs-keyword">if</span> (result.rows.length === <span class="hljs-number">0</span>) {
      <span class="hljs-keyword">return</span> <span class="hljs-string">'EMP001'</span>;
    }

    <span class="hljs-keyword">const</span> lastId = result.rows[<span class="hljs-number">0</span>].employee_id;
    <span class="hljs-keyword">const</span> numberPart = lastId.substring(<span class="hljs-number">3</span>);
    <span class="hljs-keyword">const</span> lastNumber = <span class="hljs-built_in">parseInt</span>(numberPart, <span class="hljs-number">10</span>);

    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">isNaN</span>(lastNumber)) {
      <span class="hljs-keyword">return</span> <span class="hljs-string">'EMP001'</span>;
    }

    <span class="hljs-keyword">const</span> nextNumber = lastNumber + <span class="hljs-number">1</span>;
    <span class="hljs-comment">// Format as EMP001, EMP002, etc. (3 digits minimum)</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">`EMP<span class="hljs-subst">${nextNumber.toString().padStart(<span class="hljs-number">3</span>, <span class="hljs-string">'0'</span>)}</span>`</span>;
  }
</code></pre>
<p>The private <code>generateEmployeeId</code> method generates a unique employee identifier in a readable sequential format such as <code>EMP001</code>, <code>EMP002</code>, and so on. It queries the database for the highest existing employee ID that matches the expected pattern (<code>EMP</code> prefix followed by numeric digits), orders by the numeric suffix in descending order, and increments the latest number to produce the next ID.</p>
<p>If no matching record exists, it starts from <code>EMP001</code>. The method also protects against malformed data by returning <code>EMP001</code> if parsing fails.</p>
<p>Finally, it ensures formatting consistency by padding the number portion to at least three digits using <code>padStart(3, '0')</code>, which keeps IDs aligned and easy to sort visually.</p>
<h3 id="heading-creating-an-employee-create">Creating an Employee (<code>create</code>)</h3>
<p>Next, we’ll define the <code>create</code> method, which inserts a new employee record into the database. If the caller does not supply an <code>employee_id</code>, the method generates one automatically using <code>generateEmployeeId</code>.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> create(data: CreateEmployeeInput): <span class="hljs-built_in">Promise</span>&lt;Employee&gt; {
    <span class="hljs-comment">// Auto-generate employee_id if not provided</span>
    <span class="hljs-keyword">let</span> employeeId = data.employee_id;
    <span class="hljs-keyword">if</span> (!employeeId) {
      employeeId = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.generateEmployeeId();
    }

    <span class="hljs-comment">// Check if employee_id already exists (if manually provided)</span>
    <span class="hljs-keyword">if</span> (data.employee_id) {
      <span class="hljs-keyword">const</span> existing = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.findByEmployeeId(data.employee_id);
      <span class="hljs-keyword">if</span> (existing) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Employee ID already exists'</span>);
      }
    }

    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> query(
      <span class="hljs-string">`INSERT INTO employees (name, email, employee_id, salary, account_number, bank_code, bank_name)
       VALUES ($1, $2, $3, $4, $5, $6, $7)
       RETURNING *`</span>,
      [
        data.name,
        data.email,
        employeeId,
        data.salary,
        data.account_number,
        data.bank_code,
        data.bank_name,
      ]
    );
    <span class="hljs-keyword">return</span> result.rows[<span class="hljs-number">0</span>];
  }
</code></pre>
<p>Here’s what’s happening in the code<strong>:</strong></p>
<p>If an <code>employee_id</code> is manually provided, it validates uniqueness by checking if that ID already exists among active employees, preventing collisions and ensuring each employee has a distinct identifier. After validations, the employee is inserted into the <code>employees</code> table and the new record is returned. This method ensures every employee created has complete banking details required for payroll disbursement.</p>
<h3 id="heading-retrieving-all-active-employees-findall">Retrieving All Active Employees (<code>findAll</code>)</h3>
<p>The <code>findAll</code> method fetches all active employees from the database.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> findAll(): <span class="hljs-built_in">Promise</span>&lt;Employee[]&gt; {
  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> query(
    <span class="hljs-string">'SELECT * FROM employees WHERE is_active = true ORDER BY created_at DESC'</span>
  );
  <span class="hljs-keyword">return</span> result.rows;
}
</code></pre>
<p>The <code>findAll</code> method returns all active employees (<code>is_active = true</code>) ordered by most recent creation date. This behavior supports common UI patterns such as HR dashboards and payroll selection screens, where only active employees should be visible by default.</p>
<h3 id="heading-retrieving-an-employee-by-database-id-findbyid">Retrieving an Employee by Database ID (<code>findById</code>)</h3>
<p>The <code>findById</code> method retrieves a single employee by the internal numeric primary key (<code>id</code>).</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> findById(id: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">Promise</span>&lt;Employee | <span class="hljs-literal">null</span>&gt; {
  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> query(<span class="hljs-string">'SELECT * FROM employees WHERE id = $1'</span>, [id]);
  <span class="hljs-keyword">return</span> result.rows[<span class="hljs-number">0</span>] || <span class="hljs-literal">null</span>;
}
</code></pre>
<p>If the employee does not exist, it returns <code>null</code>. This is typically used for internal operations such as payroll processing, updates, or admin detail views.</p>
<h3 id="heading-retrieving-an-employee-by-employee-identifier-findbyemployeeid">Retrieving an Employee by Employee Identifier (<code>findByEmployeeId</code>)</h3>
<p>The <code>findByEmployeeId</code> method retrieves an active employee using the business-friendly <code>employee_id</code> (for example, <code>EMP014</code>).</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> findByEmployeeId(employeeId: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">Promise</span>&lt;Employee | <span class="hljs-literal">null</span>&gt; {
    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> query(
      <span class="hljs-string">'SELECT * FROM employees WHERE employee_id = $1 AND is_active = true'</span>,
      [employeeId]
    );
    <span class="hljs-keyword">return</span> result.rows[<span class="hljs-number">0</span>] || <span class="hljs-literal">null</span>;
}
</code></pre>
<p>The method filters by <code>is_active = true</code> to prevent selecting deactivated employees during operations like payroll runs or HR searches.</p>
<h3 id="heading-updating-an-employee-update">Updating an Employee (<code>update</code>)</h3>
<p>The <code>update</code> method supports partial updates by dynamically building the SQL <code>SET</code> clause based on the fields present in the update payload. It iterates through the provided properties, includes only those with defined values, and constructs a parameterized query to prevent SQL injection and preserve correctness.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> update(
    id: <span class="hljs-built_in">number</span>,
    data: Partial&lt;CreateEmployeeInput&gt;
  ): <span class="hljs-built_in">Promise</span>&lt;Employee&gt; {
    <span class="hljs-keyword">const</span> fields: <span class="hljs-built_in">string</span>[] = [];
    <span class="hljs-keyword">const</span> values: <span class="hljs-built_in">any</span>[] = [];
    <span class="hljs-keyword">let</span> paramCount = <span class="hljs-number">1</span>;

    <span class="hljs-comment">// Build dynamic update query based on provided fields</span>
    <span class="hljs-built_in">Object</span>.entries(data).forEach(<span class="hljs-function">(<span class="hljs-params">[key, value]</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (value !== <span class="hljs-literal">undefined</span>) {
        fields.push(<span class="hljs-string">`<span class="hljs-subst">${key}</span> = $<span class="hljs-subst">${paramCount}</span>`</span>);
        values.push(value);
        paramCount++;
      }
    });

    <span class="hljs-keyword">if</span> (fields.length === <span class="hljs-number">0</span>) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'No fields to update'</span>);
    }

    <span class="hljs-comment">// Always update the updated_at timestamp</span>
    fields.push(<span class="hljs-string">`updated_at = $<span class="hljs-subst">${paramCount}</span>`</span>);
    values.push(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>());
    values.push(id);

    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> query(
      <span class="hljs-string">`UPDATE employees SET <span class="hljs-subst">${fields.join(<span class="hljs-string">', '</span>)}</span> WHERE id = $<span class="hljs-subst">${
        paramCount + <span class="hljs-number">1</span>
      }</span> RETURNING *`</span>,
      values
    );
    <span class="hljs-keyword">return</span> result.rows[<span class="hljs-number">0</span>];
  }
</code></pre>
<p>Here’s what’s happening in the code:</p>
<p>If no fields are provided, it throws an error to avoid performing a meaningless update. It also explicitly updates the <code>updated_at</code> timestamp to ensure accurate audit tracking. Finally, it returns the updated database record, making it easy for controllers to respond with the latest employee state.</p>
<h3 id="heading-soft-deleting-an-employee-delete">Soft-Deleting an Employee (<code>delete</code>)</h3>
<p>Finally, instead of permanently removing the employee record, the <code>delete</code> method performs a soft delete by setting <code>is_active = false</code> and updating the <code>updated_at</code> timestamp.</p>
<p>This approach preserves historical payroll references and audit trails while excluding inactive employees from standard queries like <code>findAll</code>. It’s especially important in payroll systems where historical payment records must remain valid and traceable even after an employee leaves the organization.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">delete</span>(id: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
  <span class="hljs-keyword">await</span> query(
    <span class="hljs-string">'UPDATE employees SET is_active = false, updated_at = NOW() WHERE id = $1'</span>,
    [id]
  );
}
</code></pre>
<p>Key features of the employee model:</p>
<ul>
<li><p>Auto-generates sequential employee IDs if not provided</p>
</li>
<li><p>Validates employee ID uniqueness</p>
</li>
<li><p>Supports soft deletion to preserve historical payroll records</p>
</li>
<li><p>Provides methods for finding employees by database ID or employee identifier</p>
</li>
</ul>
<h3 id="heading-payroll-model">Payroll Model</h3>
<p>The <code>PayrollModel</code> manages payroll batches and individual payroll items. A payroll represents a single payment cycle (for example, "December 2024"), while payroll items represent individual employee payments within that cycle. This separation allows us to track the status of each payment independently.</p>
<p>Key features:</p>
<ul>
<li><p>Creates payroll batches with automatic calculation of totals</p>
</li>
<li><p>Supports filtering employees for selective payroll runs</p>
</li>
<li><p>Tracks status at both batch and item levels</p>
</li>
<li><p>Provides methods for reconciliation and status updates</p>
</li>
</ul>
<p>Let's implement the Payroll Model.</p>
<p>We’ll begin by creating a new file at <code>src/models/payroll.ts</code>, where we’ll implement the payroll models that encapsulate payroll batch creation, employee payment tracking, and payroll status management.</p>
<p>First, import a shared database query helper to execute parameterized SQL safely.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { query } <span class="hljs-keyword">from</span> <span class="hljs-string">'../config/database'</span>;
</code></pre>
<p>This keeps raw SQL isolated from controllers and ensures protection against SQL injection.</p>
<h3 id="heading-payroll-status-lifecycle">Payroll Status Lifecycle</h3>
<p>Next, we’ll define the <code>PayrollStatus</code> enum.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-built_in">enum</span> PayrollStatus {
 PENDING = <span class="hljs-string">'pending'</span>,
 PROCESSING = <span class="hljs-string">'processing'</span>,
 COMPLETED = <span class="hljs-string">'completed'</span>,
 FAILED = <span class="hljs-string">'failed'</span>,
 PARTIALLY_COMPLETED = <span class="hljs-string">'partially_completed'</span>,
}
</code></pre>
<p>The <code>PayrollStatus</code> enum defines all possible states for both payroll batches and individual payroll items:</p>
<ul>
<li><p><strong>PENDING</strong> – Created but not yet processed</p>
</li>
<li><p><strong>PROCESSING</strong> – Currently being processed by background workers</p>
</li>
<li><p><strong>COMPLETED</strong> – Successfully processed</p>
</li>
<li><p><strong>FAILED</strong> – Processing failed</p>
</li>
<li><p><strong>PARTIALLY_COMPLETED</strong> – Some items succeeded while others failed</p>
</li>
</ul>
<h3 id="heading-payroll-entity">Payroll Entity</h3>
<p>With the payroll status lifecycle defined, we can now define the payroll entity.</p>
<p>The <code>Payroll</code> interface represents a single payroll run, such as a monthly salary payout. It stores aggregate and audit information including the payroll period, total salary amount, total number of employees, processing status, counts of successful and failed payments, and timestamps for creation, updates, and completion.</p>
<p>Add the following interface to <code>src/models/payroll.ts</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> Payroll {
 id: <span class="hljs-built_in">number</span>;
 payroll_period: <span class="hljs-built_in">string</span>;
 total_amount: <span class="hljs-built_in">number</span>;
 total_employees: <span class="hljs-built_in">number</span>;
 status: PayrollStatus;
 processed_count: <span class="hljs-built_in">number</span>;
 failed_count: <span class="hljs-built_in">number</span>;
 created_at: <span class="hljs-built_in">Date</span>;
 updated_at: <span class="hljs-built_in">Date</span>;
 processed_at?: <span class="hljs-built_in">Date</span>;
}
</code></pre>
<p>This entity acts as the parent record for all employee payments within a payroll cycle and is used to track overall payroll progress and outcomes.</p>
<h3 id="heading-payroll-item-entity">Payroll Item Entity</h3>
<p>Next, we’ll define the payroll item entity, which represents an individual employee payment within a payroll.</p>
<p>The <code>PayrollItem</code> tracks the employee being paid, the payment amount, its processing status, any transaction reference returned by the payment provider, error messages in case of failure, and relevant timestamps.</p>
<p>Add the following interface just below the <code>Payroll</code> interface:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> PayrollItem {
  id: <span class="hljs-built_in">number</span>;
  payroll_id: <span class="hljs-built_in">number</span>;
  employee_id: <span class="hljs-built_in">number</span>;
  amount: <span class="hljs-built_in">number</span>;
  status: PayrollStatus;
  transaction_reference?: <span class="hljs-built_in">string</span>;
  error_message?: <span class="hljs-built_in">string</span>;
  processed_at?: <span class="hljs-built_in">Date</span>;
  created_at: <span class="hljs-built_in">Date</span>;
  updated_at: <span class="hljs-built_in">Date</span>;
}
</code></pre>
<p>This structure allows individual employee payments to be retried, audited, or reconciled independently without affecting the rest of the payroll batch.</p>
<h3 id="heading-creating-a-payroll-payrollmodelcreate">Creating a Payroll (<code>PayrollModel.create</code>)</h3>
<p>Now that we’ve defined the <code>Payroll</code> and <code>PayrollItem</code> entities, we can move on to creating a payroll batch.</p>
<p>To keep our business logic organized, we’ll introduce a <code>PayrollModel</code> class. This class will be responsible for creating payroll records, calculating aggregates, and generating individual payroll items for each employee.</p>
<p>Before writing the model itself, let’s define the input required to create a payroll.</p>
<p>Add the following interface below the <code>PayrollItem</code> interface:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> CreatePayrollInput {
  payroll_period: <span class="hljs-built_in">string</span>;
  employee_ids?: <span class="hljs-built_in">number</span>[];
}
</code></pre>
<ul>
<li><p><code>payroll_period</code> identifies the payroll run (for example, <code>2025-01</code>)</p>
</li>
<li><p><code>employee_ids</code> is optional and allows us to run payroll for a subset of employees, enabling selective payouts or retries</p>
</li>
</ul>
<p>Next, create the <code>PayrollModel</code> class. This class will encapsulate all payroll-related database operations.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> PayrollModel {
<span class="hljs-comment">// Payroll model class methods will go here</span>
}
</code></pre>
<p>We’ll start by implementing the <code>create</code> method, which is responsible for creating a new payroll batch.</p>
<p>The method performs the following steps:</p>
<ol>
<li><p>Optionally filters employees if specific employee IDs are provided</p>
</li>
<li><p>Calculates aggregate payroll statistics from the employees table</p>
</li>
<li><p>Creates a payroll record with a <code>PENDING</code> status</p>
</li>
<li><p>Creates a payroll item for each eligible employee</p>
</li>
</ol>
<p>Here’s the implementation:</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> create(data: CreatePayrollInput): <span class="hljs-built_in">Promise</span>&lt;Payroll&gt; {
    <span class="hljs-keyword">let</span> employeeFilter = <span class="hljs-string">''</span>;
    <span class="hljs-keyword">let</span> queryParams: <span class="hljs-built_in">any</span>[] = [];

    <span class="hljs-comment">// Build filter for selective employee payrolls</span>
    <span class="hljs-keyword">if</span> (data.employee_ids &amp;&amp; data.employee_ids.length &gt; <span class="hljs-number">0</span>) {
      employeeFilter = <span class="hljs-string">`AND id = ANY($1::int[])`</span>;
      queryParams = [data.employee_ids];
    }

    <span class="hljs-comment">// Calculate aggregate statistics from employees table</span>
    <span class="hljs-keyword">const</span> employeeStats = <span class="hljs-keyword">await</span> query(
      <span class="hljs-string">`SELECT COUNT(*) as count, COALESCE(SUM(salary), 0) as total
       FROM employees
       WHERE is_active = true <span class="hljs-subst">${employeeFilter}</span>`</span>,
      queryParams
    );

    <span class="hljs-keyword">const</span> totalEmployees = <span class="hljs-built_in">parseInt</span>(employeeStats.rows[<span class="hljs-number">0</span>].count);
    <span class="hljs-keyword">const</span> totalAmount = <span class="hljs-built_in">parseFloat</span>(employeeStats.rows[<span class="hljs-number">0</span>].total);

    <span class="hljs-comment">// Create the payroll record</span>
    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> query(
      <span class="hljs-string">`INSERT INTO payrolls (payroll_period, total_amount, total_employees, status)
       VALUES ($1, $2, $3, $4)
       RETURNING *`</span>,
      [data.payroll_period, totalAmount, totalEmployees, PayrollStatus.PENDING]
    );

    <span class="hljs-keyword">const</span> payroll = result.rows[<span class="hljs-number">0</span>];

    <span class="hljs-comment">// Create payroll items for each employee</span>
    <span class="hljs-comment">// Each item starts with PENDING status and will be processed asynchronously</span>
    <span class="hljs-keyword">const</span> employees = <span class="hljs-keyword">await</span> query(
      <span class="hljs-string">`SELECT id, salary FROM employees WHERE is_active = true <span class="hljs-subst">${employeeFilter}</span>`</span>,
      queryParams
    );

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> employee <span class="hljs-keyword">of</span> employees.rows) {
      <span class="hljs-keyword">await</span> query(
        <span class="hljs-string">`INSERT INTO payroll_items (payroll_id, employee_id, amount, status)
         VALUES ($1, $2, $3, $4)`</span>,
        [payroll.id, employee.id, employee.salary, PayrollStatus.PENDING]
      );
    }

    <span class="hljs-keyword">return</span> payroll;
  }
</code></pre>
<p>The payroll creation process begins by determining which employees should be included. If specific employee IDs are provided, only those employees are selected – otherwise, all active employees are included. This allows the system to support both full payroll runs and selective payouts.</p>
<p>Next, the system calculates aggregate payroll statistics directly from the employees table by counting eligible employees and summing their salaries. These values are stored in a new payroll record created with a <code>PENDING</code> status.</p>
<p>Finally, a payroll item is generated for each eligible employee, with each item also initialized in a <code>PENDING</code> state. This design separates payroll setup from payment execution, allowing employee payments to be processed asynchronously and in parallel in later stages of the system.</p>
<h3 id="heading-fetching-payroll-records">Fetching Payroll Records</h3>
<p>After creating payrolls, we often need to retrieve them for administrative dashboards, reporting, and audit trails.</p>
<p>The <code>PayrollModel</code> provides two simple methods:</p>
<ol>
<li><p><code>findById</code> – Retrieves a single payroll by its unique identifier</p>
</li>
<li><p><code>findAll</code> – Retrieves all payroll records, ordered by creation date (newest first)</p>
</li>
</ol>
<p>These methods should be added <strong>below</strong> the <code>create</code> method in the <code>PayrollModel</code> class:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> findById(id: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">Promise</span>&lt;Payroll | <span class="hljs-literal">null</span>&gt; {
  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> query(<span class="hljs-string">'SELECT * FROM payrolls WHERE id = $1'</span>, [id]);
  <span class="hljs-keyword">return</span> result.rows[<span class="hljs-number">0</span>] || <span class="hljs-literal">null</span>;
}

<span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> findAll(): <span class="hljs-built_in">Promise</span>&lt;Payroll[]&gt; {
  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> query(
    <span class="hljs-string">'SELECT * FROM payrolls ORDER BY created_at DESC'</span>
  );
  <span class="hljs-keyword">return</span> result.rows;
}
</code></pre>
<p>The <code>findById</code> method retrieves a single payroll by its identifier, while <code>findAll</code> returns all payroll records ordered by creation date.</p>
<h3 id="heading-updating-payroll-status-payrollmodelupdatestatus">Updating Payroll Status (<code>PayrollModel.updateStatus</code>)</h3>
<p>Once payroll processing begins, we need a way to track the overall status of a payroll batch. The <code>updateStatus</code> method updates the payroll record with:</p>
<ul>
<li><p>The current status (<code>PENDING</code>, <code>PROCESSING</code>, <code>COMPLETED</code>, and so on)</p>
</li>
<li><p>Optional counts of processed and failed payments</p>
</li>
<li><p>A <code>processed_at</code> timestamp automatically set for terminal states (<code>COMPLETED</code> or <code>PARTIALLY_COMPLETED</code>)</p>
</li>
</ul>
<p>Add the following method below the fetch methods in your <code>PayrollModel</code> class:</p>
<pre><code class="lang-typescript">
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> updateStatus(
    id: <span class="hljs-built_in">number</span>,
    status: PayrollStatus,
    processedCount?: <span class="hljs-built_in">number</span>,
    failedCount?: <span class="hljs-built_in">number</span>
  ): <span class="hljs-built_in">Promise</span>&lt;Payroll&gt; {
    <span class="hljs-keyword">const</span> updates: <span class="hljs-built_in">string</span>[] = [<span class="hljs-string">'status = $2'</span>, <span class="hljs-string">'updated_at = NOW()'</span>];
    <span class="hljs-keyword">const</span> values: <span class="hljs-built_in">any</span>[] = [id, status];

    <span class="hljs-comment">// Dynamically add processed_count if provided</span>
    <span class="hljs-keyword">if</span> (processedCount !== <span class="hljs-literal">undefined</span>) {
      updates.push(<span class="hljs-string">`processed_count = $<span class="hljs-subst">${values.length + <span class="hljs-number">1</span>}</span>`</span>);
      values.push(processedCount);
    }

    <span class="hljs-comment">// Dynamically add failed_count if provided</span>
    <span class="hljs-keyword">if</span> (failedCount !== <span class="hljs-literal">undefined</span>) {
      updates.push(<span class="hljs-string">`failed_count = $<span class="hljs-subst">${values.length + <span class="hljs-number">1</span>}</span>`</span>);
      values.push(failedCount);
    }

    <span class="hljs-comment">// Set processed_at timestamp for terminal states</span>
    <span class="hljs-keyword">if</span> (
      status === PayrollStatus.COMPLETED ||
      status === PayrollStatus.PARTIALLY_COMPLETED
    ) {
      updates.push(<span class="hljs-string">`processed_at = NOW()`</span>);
    }

    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> query(
      <span class="hljs-string">`UPDATE payrolls SET <span class="hljs-subst">${updates.join(<span class="hljs-string">', '</span>)}</span> WHERE id = $1 RETURNING *`</span>,
      values
    );
    <span class="hljs-keyword">return</span> result.rows[<span class="hljs-number">0</span>];
  }
}
</code></pre>
<p>As payroll processing progresses, this method updates the overall payroll status along with optional counts of processed and failed payments. When a payroll reaches a terminal state such as <code>COMPLETED</code> or <code>PARTIALLY_COMPLETED</code>, the system automatically records a completion timestamp. This ensures accurate tracking of payroll execution and supports reconciliation workflows.</p>
<h2 id="heading-payrollitemmodel">PayrollItemModel</h2>
<p>After handling payroll batches with <code>PayrollModel</code>, we need a way to manage individual employee payments. This is where the <code>PayrollItemModel</code> comes in. It encapsulates database operations related to payroll items, including fetching, and updating records with employee details.</p>
<p>Start by adding a new class <strong>below</strong> <code>PayrollModel</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> PayrollItemModel {
  <span class="hljs-comment">// Methods will go here</span>
}
</code></pre>
<h3 id="heading-fetching-payroll-items-payrollitemmodelfindbypayrollid">Fetching Payroll Items (<code>PayrollItemModel.findByPayrollId</code>)</h3>
<p>Often, we want to get all payroll items for a specific payroll batch. For example, to display them on a dashboard or process them in a background worker.</p>
<p>This <code>findByPayrollId</code> method does that exactly. It retrieves all payroll items associated with a specific payroll and enriches them with employee details such as name, bank account number, and bank information through a database join.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> findByPayrollId(payrollId: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">Promise</span>&lt;PayrollItem[]&gt; {
  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> query(
    <span class="hljs-string">`SELECT
       pi.id, pi.payroll_id, pi.employee_id, pi.amount, pi.status,
       pi.transaction_reference, pi.error_message, pi.processed_at,
       pi.created_at, pi.updated_at,
       e.name as employee_name, e.employee_id as employee_identifier,
       e.account_number, e.bank_code, e.bank_name
     FROM payroll_items pi
     JOIN employees e ON pi.employee_id = e.id
     WHERE pi.payroll_id = $1
     ORDER BY pi.created_at`</span>,
      [payrollId]
    );
    <span class="hljs-comment">// Normalize numeric fields from PostgreSQL (which returns them as strings)</span>
    <span class="hljs-keyword">return</span> result.rows.map(<span class="hljs-function">(<span class="hljs-params">row</span>) =&gt;</span> ({
      ...row,
      employee_id: <span class="hljs-built_in">parseInt</span>(row.employee_id, <span class="hljs-number">10</span>),
      id: <span class="hljs-built_in">parseInt</span>(row.id, <span class="hljs-number">10</span>),
      payroll_id: <span class="hljs-built_in">parseInt</span>(row.payroll_id, <span class="hljs-number">10</span>),
      amount: <span class="hljs-built_in">parseFloat</span>(row.amount),
    }));
  }
</code></pre>
<p>Here’s what’s happening in the code:</p>
<ol>
<li><p>We use a JOIN with the <code>employees</code> table so each payroll item includes the employee’s name, account number, and bank information.</p>
</li>
<li><p>Some numeric fields may come as strings, so we convert them to proper JavaScript numbers (<code>parseInt</code> / <code>parseFloat</code>) for accurate calculations and display.</p>
</li>
<li><p>The results are ordered by creation date, which helps when rendering items in a UI or processing them sequentially.</p>
</li>
</ol>
<p>This method makes it easy to work with all items in a payroll batch while keeping the data enriched and consistent.</p>
<h3 id="heading-fetching-a-single-payroll-item-payrollitemmodelfindbyid">Fetching a Single Payroll Item (<code>PayrollItemModel.findById</code>)</h3>
<p>Sometimes, you need to look at one specific employee’s payment (for example, to retry a failed transaction or investigate an issue). The <code>findById</code> method helps in fetching a single payroll item along with the employee’s details, so you have everything you need in one place.</p>
<p>Here’s how we implement it:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> findById(id: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">Promise</span>&lt;PayrollItem | <span class="hljs-literal">null</span>&gt; {
  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> query(
    <span class="hljs-string">`SELECT
       pi.id, pi.payroll_id, pi.employee_id, pi.amount, pi.status,
       pi.transaction_reference, pi.error_message, pi.processed_at,
       pi.created_at, pi.updated_at,
       e.name as employee_name, e.employee_id as employee_identifier,
       e.account_number, e.bank_code, e.bank_name
     FROM payroll_items pi
     JOIN employees e ON pi.employee_id = e.id
     WHERE pi.id = $1`</span>,
    [id]
  );

  <span class="hljs-keyword">if</span> (result.rows.length === <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;

  <span class="hljs-keyword">const</span> row = result.rows[<span class="hljs-number">0</span>];

  <span class="hljs-comment">// Convert numeric fields to proper JavaScript numbers for easier calculations and display</span>
  <span class="hljs-keyword">return</span> {
    ...row,
    employee_id: <span class="hljs-built_in">parseInt</span>(row.employee_id, <span class="hljs-number">10</span>),
    id: <span class="hljs-built_in">parseInt</span>(row.id, <span class="hljs-number">10</span>),
    payroll_id: <span class="hljs-built_in">parseInt</span>(row.payroll_id, <span class="hljs-number">10</span>),
    amount: <span class="hljs-built_in">parseFloat</span>(row.amount),
  };
}
</code></pre>
<p>Here’s what’s happening in the code:</p>
<ul>
<li><p>We use a JOIN with the <code>employees</code> table to include employee info such as name, account number, and bank details.</p>
</li>
<li><p>If the ID doesn’t exist, the method returns <code>null</code> so you can handle missing records gracefully.</p>
</li>
<li><p>Numeric fields are converted to JavaScript numbers, making it easy to calculate totals or display amounts in the UI.</p>
</li>
</ul>
<p>This method ensures that whenever you need a single payroll item, you get a complete, ready-to-use record.</p>
<h3 id="heading-updating-payroll-item-status-payrollitemmodelupdatestatus">Updating Payroll Item Status (<code>PayrollItemModel.updateStatus</code>)</h3>
<p>As individual employee payments are processed, this method updates the payroll item’s status, stores transaction references from external payment providers, captures error messages on failure, and timestamps completion or failure events. This fine-grained tracking enables reliable retries, audits, and reconciliation with external payment systems.</p>
<p>Here’s the implementation:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> updateStatus(
  id: <span class="hljs-built_in">number</span>,
  status: PayrollStatus,
  transactionReference?: <span class="hljs-built_in">string</span>,
  errorMessage?: <span class="hljs-built_in">string</span>
): <span class="hljs-built_in">Promise</span>&lt;PayrollItem&gt; {
  <span class="hljs-keyword">const</span> updates: <span class="hljs-built_in">string</span>[] = [<span class="hljs-string">'status = $2'</span>, <span class="hljs-string">'updated_at = NOW()'</span>];
  <span class="hljs-keyword">const</span> values: <span class="hljs-built_in">any</span>[] = [id, status];

  <span class="hljs-comment">// Add transaction reference if provided (from Monnify API response)</span>
  <span class="hljs-keyword">if</span> (transactionReference) {
    updates.push(<span class="hljs-string">`transaction_reference = $<span class="hljs-subst">${values.length + <span class="hljs-number">1</span>}</span>`</span>);
    values.push(transactionReference);
  }

  <span class="hljs-comment">// Add error message if provided (from failed payment)</span>
  <span class="hljs-keyword">if</span> (errorMessage) {
    updates.push(<span class="hljs-string">`error_message = $<span class="hljs-subst">${values.length + <span class="hljs-number">1</span>}</span>`</span>);
    values.push(errorMessage);
  }

  <span class="hljs-comment">// Set processed_at timestamp for terminal states</span>
  <span class="hljs-keyword">if</span> (status === PayrollStatus.COMPLETED || status === PayrollStatus.FAILED) {
    updates.push(<span class="hljs-string">`processed_at = NOW()`</span>);
  }

  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> query(
    <span class="hljs-string">`UPDATE payroll_items SET <span class="hljs-subst">${updates.join(
      <span class="hljs-string">', '</span>
     )}</span> WHERE id = $1 RETURNING *`</span>,
     values
   );
   <span class="hljs-keyword">return</span> result.rows[<span class="hljs-number">0</span>];
 }
}
</code></pre>
<p>Here’s what’s happening in the code:</p>
<ul>
<li><p>We build a dynamic SET clause to update only the fields provided – status is required, while transaction reference and error message are optional.</p>
</li>
<li><p>Terminal states (<code>COMPLETED</code> or <code>FAILED</code>) trigger an automatic timestamp on <code>processed_at</code>, so we always know when a payment finished.</p>
</li>
<li><p>The method returns the updated payroll item, ready for further processing, logging, or UI display.</p>
</li>
</ul>
<p>This ensures each payroll item is tracked accurately throughout its lifecycle, enabling reliable retries and complete audit trails.</p>
<h3 id="heading-overall-payroll-flow">Overall Payroll Flow</h3>
<p>In this payroll flow, an administrator creates a payroll batch, which generates individual payroll items for each employee. The payroll is then handed off to background workers that process each payroll item independently via an external payment service.</p>
<p>As each payment succeeds or fails, payroll items are updated accordingly. Once processing concludes, the payroll batch status is updated to reflect the overall outcome, whether fully successful, partially successful, or failed.</p>
<p>This architecture provides scalability, resilience, and strong auditability for real-world payroll systems.</p>
<h2 id="heading-building-the-monnify-client">Building the Monnify Client</h2>
<p>The Monnify client is the bridge between our application and Monnify's payment API. In this section, we'll build a reusable client that handles authentication, bulk transfers, and transaction tracking. The client automatically manages API tokens, retries failed requests, and provides a clean interface for the rest of our application.</p>
<p>This module implements a reusable Monnify API client responsible for handling authentication, bulk payroll disbursements, authorization, transaction tracking, and balance checks in a secure and production-ready manner. It abstracts all Monnify-specific logic behind a single class, making it easy to integrate into background jobs, payroll processors, or service layers.</p>
<p>We’ll begin by creating a new file at <code>src/config/monnify.ts</code> where we’ll implement the Monnify client.</p>
<h3 id="heading-configuration-and-environment-setup">Configuration and Environment Setup</h3>
<p>Start by loading the configuration from environment variables using <code>dotenv</code>, ensuring that sensitive credentials are never hardcoded. These include the Monnify API key, secret key, base URL, and contract code (wallet account number). This setup allows the same client to be safely used across development, staging, and production environments.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> axios, { AxiosInstance } <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;
<span class="hljs-keyword">import</span> dotenv <span class="hljs-keyword">from</span> <span class="hljs-string">'dotenv'</span>;

dotenv.config();

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> MonnifyConfig {
  apiKey: <span class="hljs-built_in">string</span>;
  secretKey: <span class="hljs-built_in">string</span>;
  baseUrl: <span class="hljs-built_in">string</span>;
  contractCode: <span class="hljs-built_in">string</span>;
}
</code></pre>
<h3 id="heading-create-the-monnifyclient-class">Create the <code>MonnifyClient</code> Class</h3>
<p>Next, you’ll define the <code>MonnifyClient</code> class. This class encapsulates all communication with the Monnify API. It internally manages API credentials, an Axios HTTP client, an access token, and token expiry tracking.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> MonnifyClient {
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> apiKey: <span class="hljs-built_in">string</span>;
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> secretKey: <span class="hljs-built_in">string</span>;
  <span class="hljs-keyword">private</span> baseUrl: <span class="hljs-built_in">string</span>;
  <span class="hljs-keyword">private</span> contractCode: <span class="hljs-built_in">string</span>;
  <span class="hljs-keyword">private</span> client: AxiosInstance;

  <span class="hljs-keyword">private</span> accessToken: <span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span> = <span class="hljs-literal">null</span>;
  <span class="hljs-keyword">private</span> tokenExpiry: <span class="hljs-built_in">number</span> = <span class="hljs-number">0</span>;
</code></pre>
<p>This design ensures authentication is handled transparently and automatically for every request.</p>
<h3 id="heading-axios-client-and-request-interceptor">Axios Client and Request Interceptor</h3>
<p>Inside the constructor, initialize the Monnify client with credentials from environment variables. The Axios instance is created with the Monnify base URL and JSON headers.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
    <span class="hljs-built_in">this</span>.apiKey = process.env.MONNIFY_API_KEY || <span class="hljs-string">''</span>;
    <span class="hljs-built_in">this</span>.secretKey = process.env.MONNIFY_SECRET_KEY || <span class="hljs-string">''</span>;
    <span class="hljs-built_in">this</span>.baseUrl = process.env.MONNIFY_BASE_URL || <span class="hljs-string">'https://api.monnify.com'</span>;
    <span class="hljs-built_in">this</span>.contractCode = process.env.MONNIFY_CONTRACT_CODE || <span class="hljs-string">''</span>;

    <span class="hljs-built_in">this</span>.client = axios.create({
      baseURL: <span class="hljs-built_in">this</span>.baseUrl,
      headers: {
        <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
      },
    });
</code></pre>
<p>We attach the request interceptor to this client to automatically inject a valid Bearer token into every outgoing request (except the authentication endpoint). Before each request, the interceptor ensures the client is authenticated, preventing unauthorized requests and eliminating token-related boilerplate across the codebase.</p>
<pre><code class="lang-typescript">    <span class="hljs-built_in">this</span>.client.interceptors.request.use(<span class="hljs-keyword">async</span> (config: <span class="hljs-built_in">any</span>) =&gt; {
      <span class="hljs-comment">// Skip auth for the login endpoint itself</span>
      <span class="hljs-keyword">if</span> (config.url?.includes(<span class="hljs-string">'/auth/login'</span>)) {
        <span class="hljs-keyword">return</span> config;
      }

      <span class="hljs-comment">// Ensure a valid token exists before every request</span>
      <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.ensureAuthenticated();

      <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.accessToken) {
        config.headers.Authorization = <span class="hljs-string">`Bearer <span class="hljs-subst">${<span class="hljs-built_in">this</span>.accessToken}</span>`</span>;
      }

      <span class="hljs-keyword">return</span> config;
    });
  }
</code></pre>
<h3 id="heading-authenticate-with-monnify">Authenticate with Monnify</h3>
<p>Authentication is handled using Monnify’s Basic Auth mechanism, where the API key and secret key are base64-encoded and sent to the <code>/auth/login</code> endpoint. Upon successful authentication, the client stores the returned access token and sets an internal expiry timestamp slightly below the official token lifetime to avoid edge-case expirations. Any authentication failure is logged and surfaced as a controlled error to prevent silent failures.</p>
<pre><code class="lang-typescript">
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> authenticate(): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Encode credentials as Base64 for Basic Auth</span>
      <span class="hljs-keyword">const</span> credentials = Buffer.from(
        <span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-built_in">this</span>.apiKey}</span>:<span class="hljs-subst">${<span class="hljs-built_in">this</span>.secretKey}</span>`</span>
      ).toString(<span class="hljs-string">'base64'</span>);

      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> axios.post(
        <span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-built_in">this</span>.baseUrl}</span>/api/v1/auth/login`</span>,
        {},
        {
          headers: {
            Authorization: <span class="hljs-string">`Basic <span class="hljs-subst">${credentials}</span>`</span>,
            <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
          },
        }
      );

      <span class="hljs-built_in">this</span>.accessToken = response.data.responseBody.accessToken;
      <span class="hljs-comment">// Set expiry to 23 hours (Monnify tokens typically last 24 hours)</span>
      <span class="hljs-comment">// This prevents edge cases where token expires mid-request</span>
      <span class="hljs-built_in">this</span>.tokenExpiry = <span class="hljs-built_in">Date</span>.now() + <span class="hljs-number">23</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>;
    } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
      <span class="hljs-built_in">console</span>.error(
        <span class="hljs-string">'Monnify authentication error:'</span>,
        error.response?.data || error.message
      );
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Failed to authenticate with Monnify'</span>);
    }
  }
</code></pre>
<h3 id="heading-automatic-token-refresh-ensureauthenticated">Automatic Token Refresh (<code>ensureAuthenticated</code>)</h3>
<p>Before any API call, the client verifies whether a valid access token exists or if the token has expired. If so, it transparently re-authenticates.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> ensureAuthenticated(): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.accessToken || <span class="hljs-built_in">Date</span>.now() &gt;= <span class="hljs-built_in">this</span>.tokenExpiry) {
      <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.authenticate();
    }
  }
</code></pre>
<p>This ensures that long-running processes such as payroll queues or background workers can safely make Monnify requests without manual token handling.</p>
<h3 id="heading-initiating-bulk-transfers">Initiating Bulk Transfers</h3>
<p>The <code>initiateBulkTransfer</code> method handles the creation of a bulk disbursement batch, typically used for payroll payments. It validates input transfers to ensure each payment has a valid amount, destination account number, and bank code.</p>
<p>A structured batch request is then constructed, including a unique batch reference, source account (contract code), narration, and a list of transactions. The request is logged for traceability and sent to Monnify’s batch disbursement endpoint. Any API error is normalized and returned with meaningful messaging to aid debugging and retries.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">async</span> initiateBulkTransfer(
    transfers: <span class="hljs-built_in">Array</span>&lt;{
      amount: <span class="hljs-built_in">number</span>;
      recipientAccountNumber: <span class="hljs-built_in">string</span>;
      recipientBankCode: <span class="hljs-built_in">string</span>;
      recipientName: <span class="hljs-built_in">string</span>;
      narration: <span class="hljs-built_in">string</span>;
      reference: <span class="hljs-built_in">string</span>;
    }&gt;
  ): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">any</span>&gt; {
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.ensureAuthenticated();
</code></pre>
<p>We validate inputs early to fail fast:</p>
<pre><code class="lang-typescript">    <span class="hljs-keyword">if</span> (!transfers || transfers.length === <span class="hljs-number">0</span>) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'No transfers provided'</span>);
    }

    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.contractCode) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Monnify contract code is not configured'</span>);
    }
</code></pre>
<p>Each transfer is validated individually:</p>
<pre><code class="lang-typescript">    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> transfer <span class="hljs-keyword">of</span> transfers) {
      <span class="hljs-keyword">if</span> (!transfer.amount || transfer.amount &lt;= <span class="hljs-number">0</span>) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Invalid amount for transfer: <span class="hljs-subst">${transfer.reference}</span>`</span>);
      }
      <span class="hljs-keyword">if</span> (!transfer.recipientAccountNumber) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Missing account number for transfer: <span class="hljs-subst">${transfer.reference}</span>`</span>);
      }
      <span class="hljs-keyword">if</span> (!transfer.recipientBankCode) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Missing bank code for transfer: <span class="hljs-subst">${transfer.reference}</span>`</span>);
      }
    }
</code></pre>
<p>We then construct the batch payload:</p>
<pre><code class="lang-typescript">    <span class="hljs-keyword">const</span> requestBody = {
      title: <span class="hljs-string">'Bulk Payroll Transfers'</span>,
      batchReference: <span class="hljs-string">`BATCH_<span class="hljs-subst">${<span class="hljs-built_in">Date</span>.now()}</span>`</span>,
      narration: <span class="hljs-string">'Payroll batch disbursement'</span>,
      sourceAccountNumber: <span class="hljs-built_in">this</span>.contractCode,
      onValidationFailure: <span class="hljs-string">'CONTINUE'</span>,
      notificationInterval: <span class="hljs-number">50</span>,
      transactionList: transfers.map(<span class="hljs-function">(<span class="hljs-params">t</span>) =&gt;</span> ({
        amount: t.amount,
        reference: t.reference,
        narration: t.narration,
        destinationBankCode: t.recipientBankCode,
        destinationAccountNumber: t.recipientAccountNumber,
        currency: <span class="hljs-string">'NGN'</span>,
      })),
    };
</code></pre>
<p>Finally, we send the request and normalize errors:</p>
<pre><code class="lang-typescript">    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.client.post(
        <span class="hljs-string">'/api/v2/disbursements/batch'</span>,
        requestBody
      );
      <span class="hljs-keyword">return</span> response.data;
    } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
      <span class="hljs-keyword">const</span> errorData = error.response?.data;
      <span class="hljs-keyword">const</span> message =
        errorData?.responseMessage ||
        errorData?.message ||
        <span class="hljs-string">`Monnify API error (<span class="hljs-subst">${error.response?.status}</span>)`</span>;
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(message);
    }
  }
</code></pre>
<h3 id="heading-authorizing-bulk-transfers-otp-validation">Authorizing Bulk Transfers (OTP Validation)</h3>
<p>Some bulk transfers require OTP authorization. The <code>authorizeBulkTransfer</code> method validates the presence of a batch reference and authorization code before submitting them to Monnify’s OTP validation endpoint. This step finalizes the batch disbursement and allows processing to continue. Errors are logged and surfaced clearly for operational visibility.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">async</span> authorizeBulkTransfer(
reference: <span class="hljs-built_in">string</span>,
authorizationCode: <span class="hljs-built_in">string</span>
): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">any</span>&gt; {
<span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.ensureAuthenticated();
    <span class="hljs-keyword">if</span> (!reference) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Batch reference is required'</span>);
    }

    <span class="hljs-keyword">if</span> (!authorizationCode) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Authorization code (OTP) is required'</span>);
    }

    <span class="hljs-keyword">const</span> requestBody = {
      reference,
      authorizationCode,
    };

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.client.post(
        <span class="hljs-string">'/api/v2/disbursements/batch/validate-otp'</span>,
        requestBody
      );

      <span class="hljs-keyword">return</span> response.data;
    } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
      <span class="hljs-keyword">const</span> errorDetails = error.response?.data || error.message;
      <span class="hljs-built_in">console</span>.error(
        <span class="hljs-string">'Monnify authorization error:'</span>,
        <span class="hljs-built_in">JSON</span>.stringify(errorDetails, <span class="hljs-literal">null</span>, <span class="hljs-number">2</span>)
      );

      <span class="hljs-keyword">if</span> (error.response) {
        <span class="hljs-keyword">const</span> errorData = error.response.data;
        <span class="hljs-keyword">const</span> errorMessage =
          errorData?.responseMessage ||
          errorData?.message ||
          <span class="hljs-string">`Monnify API error (<span class="hljs-subst">${error.response.status}</span>)`</span>;
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(errorMessage);
      }
      <span class="hljs-keyword">throw</span> error;
    }
}
</code></pre>
<h3 id="heading-transaction-status-lookup">Transaction Status Lookup</h3>
<p>The <code>getTransactionStatus</code> method retrieves the real-time status of an individual transaction using its reference.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">async</span> getTransactionStatus(transactionReference: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">any</span>&gt; {
<span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.ensureAuthenticated();
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.client.get(
        <span class="hljs-string">`/api/v2/disbursements/<span class="hljs-subst">${transactionReference}</span>/status`</span>
      );
      <span class="hljs-keyword">return</span> response.data;
    } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
      <span class="hljs-built_in">console</span>.error(
        <span class="hljs-string">'Monnify status check error:'</span>,
        error.response?.data || error.message
      );
      <span class="hljs-keyword">throw</span> error;
    }
}
</code></pre>
<p>This is useful for reconciliation, webhook fallbacks, or manual verification of disbursement outcomes.</p>
<h3 id="heading-batch-details-retrieval">Batch Details Retrieval</h3>
<p>The <code>getBatchDetails</code> method fetches detailed information about an entire disbursement batch, including the state of individual transactions.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">async</span> getBatchDetails(batchReference: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">any</span>&gt; {
<span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.ensureAuthenticated();
    <span class="hljs-keyword">if</span> (!batchReference) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Batch reference is required'</span>);
    }

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.client.get(
        <span class="hljs-string">`/api/v2/disbursements/batch/<span class="hljs-subst">${batchReference}</span>`</span>
      );
      <span class="hljs-keyword">return</span> response.data;
    } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
      <span class="hljs-built_in">console</span>.error(
        <span class="hljs-string">'Monnify batch details error:'</span>,
        error.response?.data || error.message
      );
      <span class="hljs-keyword">throw</span> error;
    }
}
</code></pre>
<p>This is particularly useful when reconciling payroll runs or recovering from partial failures.</p>
<h3 id="heading-wallet-balance-check">Wallet Balance Check</h3>
<p>Finally, we can query the available balance of the Monnify wallet.</p>
<p>The <code>getAccountBalance</code> method retrieves the available balance of the configured Monnify wallet (contract account).</p>
<p>Create <code>src/config/monnify.ts</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">async</span> getAccountBalance(): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">any</span>&gt; {
<span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.ensureAuthenticated();

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.client.get(
        <span class="hljs-string">`/api/v2/disbursements/wallet-balance?accountNumber=<span class="hljs-subst">${<span class="hljs-built_in">this</span>.contractCode}</span>`</span>
      );
      <span class="hljs-keyword">return</span> response.data;
    } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
      <span class="hljs-built_in">console</span>.error(
        <span class="hljs-string">'Monnify balance check error:'</span>,
        error.response?.data || error.message
      );
      <span class="hljs-keyword">throw</span> error;
    }
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> monnifyClient = <span class="hljs-keyword">new</span> MonnifyClient();
</code></pre>
<p>Key features of this client:</p>
<ol>
<li><p><strong>Automatic token management</strong>: The client automatically handles authentication and refreshes tokens before they expire.</p>
</li>
<li><p><strong>Request interceptor</strong>: Every API request automatically includes the authentication token.</p>
</li>
<li><p><strong>Bulk transfers</strong>: Uses Monnify's batch disbursement API for efficient payroll processing.</p>
</li>
<li><p><strong>Error handling</strong>: Comprehensive error handling with meaningful error messages.</p>
</li>
</ol>
<h2 id="heading-implementing-background-job-processing">Implementing Background Job Processing</h2>
<p>To avoid blocking HTTP requests and to ensure reliable retries, payroll execution is handled asynchronously using a background job processor. This worker is responsible for orchestrating bulk payroll disbursements, coordinating with Monnify, updating payroll and payroll item states, and handling retries safely.</p>
<p>Begin by creating a new file at <code>src/jobs/payroll.processor.ts</code>. All background payroll execution logic will live in this file.</p>
<h3 id="heading-set-up-the-payroll-processing-queue">Set Up the Payroll Processing Queue</h3>
<p>We’ll create a Bull queue named <code>payroll-processing</code> and a backed by Redis. Redis connection details are loaded from environment variables, allowing flexibility across environments.</p>
<p>Default job options are configured to retry failed jobs up to three times using an exponential backoff strategy. This ensures resilience against transient failures such as network issues or temporary payment gateway downtime. Completed jobs are automatically removed from the queue to keep Redis storage clean.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> Queue <span class="hljs-keyword">from</span> <span class="hljs-string">'bull'</span>;
<span class="hljs-keyword">import</span> { monnifyClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'../config/monnify'</span>;
<span class="hljs-keyword">import</span> {
 PayrollItemModel,
 PayrollModel,
 PayrollStatus,
} <span class="hljs-keyword">from</span> <span class="hljs-string">'../models/payroll'</span>;
<span class="hljs-keyword">import</span> { EmployeeModel } <span class="hljs-keyword">from</span> <span class="hljs-string">'../models/employee'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> payrollQueue = <span class="hljs-keyword">new</span> Queue(<span class="hljs-string">'payroll-processing'</span>, {
 redis: {
 host: process.env.REDIS_HOST || <span class="hljs-string">'localhost'</span>,
 port: <span class="hljs-built_in">Number</span>(process.env.REDIS_PORT || <span class="hljs-number">6379</span>),
},
defaultJobOptions: {
 attempts: <span class="hljs-number">3</span>,
 backoff: { 
  <span class="hljs-keyword">type</span>: <span class="hljs-string">'exponential'</span>, 
  delay: <span class="hljs-number">2000</span> 
},
 removeOnComplete: <span class="hljs-literal">true</span>,
},
});
</code></pre>
<h3 id="heading-queue-processor-registration">Queue Processor Registration</h3>
<p>The queue registers a processor function using <code>payrollQueue.process</code>, which receives jobs containing a <code>payrollId</code>. Each job triggers the <code>processBulkPayroll</code> function, making the queue responsible for executing one payroll batch at a time.</p>
<pre><code class="lang-typescript">payrollQueue.process(<span class="hljs-keyword">async</span> (job) =&gt; {
 <span class="hljs-keyword">return</span> processBulkPayroll(job.data.payrollId);
});
</code></pre>
<p>This design decouples payroll execution from HTTP requests and allows processing to happen asynchronously in background workers.</p>
<h3 id="heading-bulk-payroll-processing-flow-processbulkpayroll">Bulk Payroll Processing Flow (<code>processBulkPayroll</code>)</h3>
<p>When a payroll job is picked up, the system first fetches all payroll items associated with the given payroll ID. It filters out only items that are eligible for processing: those still in a <code>PENDING</code> state or previously marked as <code>PROCESSING</code> but missing a transaction reference.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">processBulkPayroll</span>(<span class="hljs-params">payrollId: <span class="hljs-built_in">number</span></span>) </span>{

<span class="hljs-keyword">const</span> items = <span class="hljs-keyword">await</span> PayrollItemModel.findByPayrollId(payrollId);
</code></pre>
<p>Also, it filters payroll items to include only those that still require processing. This prevents duplicate payments when jobs are retried.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> payable = items.filter(
  <span class="hljs-function">(<span class="hljs-params">i</span>) =&gt;</span>
    i.status === PayrollStatus.PENDING ||
    (i.status === PayrollStatus.PROCESSING &amp;&amp; !i.transaction_reference)
);

<span class="hljs-keyword">if</span> (payable.length === <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span>;
</code></pre>
<p>If no payable items remain, the function exits early, avoiding unnecessary API calls.</p>
<p>Once we confirm there are payable items, we update the overall payroll status:</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">await</span> PayrollModel.updateStatus(payrollId, PayrollStatus.PROCESSING);
</code></pre>
<p>This provides immediate visibility that disbursement is underway.</p>
<h3 id="heading-building-the-bulk-transfer-payload">Building the Bulk Transfer Payload</h3>
<p>Create a variable to store the transfer list that will be sent to Monnify.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">const</span> transfers = [];
</code></pre>
<p>For each payable payroll item, the corresponding employee record is fetched to retrieve bank and account details. A unique payment reference is generated using the payroll ID and payroll item ID, ensuring traceability across systems. Each payroll item is immediately marked as <code>PROCESSING</code> before initiating payment to prevent concurrent workers from attempting to process the same item.</p>
<p>A transfer object is then constructed containing the payment amount, recipient bank details, narration, and unique reference. These transfer objects are accumulated into a single batch request.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> item <span class="hljs-keyword">of</span> payable) {
<span class="hljs-keyword">const</span> employee = <span class="hljs-keyword">await</span> EmployeeModel.findById(item.employee_id);
<span class="hljs-keyword">if</span> (!employee) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Employee not found'</span>);

    <span class="hljs-keyword">const</span> reference = <span class="hljs-string">`PAYROLL_<span class="hljs-subst">${payrollId}</span>_<span class="hljs-subst">${item.id}</span>`</span>;

    <span class="hljs-keyword">await</span> PayrollItemModel.updateStatus(item.id, PayrollStatus.PROCESSING);

    transfers.push({
      amount: <span class="hljs-built_in">Number</span>(item.amount),
      reference,
      recipientAccountNumber: employee.account_number,
      recipientBankCode: employee.bank_code,
      recipientName: employee.name,
      narration: <span class="hljs-string">`Payroll payment`</span>,
    });

}
</code></pre>
<h3 id="heading-initiating-bulk-disbursement-via-monnify">Initiating Bulk Disbursement via Monnify</h3>
<p>Once all transfers are prepared, the system initiates a bulk transfer through the Monnify client.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> monnifyClient.initiateBulkTransfer(transfers);

<span class="hljs-keyword">if</span> (!response?.requestSuccessful) {
  <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Bulk transfer initiation failed'</span>);
}
</code></pre>
<p>If Monnify doesn’t confirm successful initiation, the job throws an error, allowing Bull’s retry mechanism to take over. This ensures failed initiation attempts are retried safely without manual intervention.</p>
<h3 id="heading-storing-transaction-references">Storing Transaction References</h3>
<p>After a successful bulk transfer initiation, Monnify returns a list of transactions containing unique transaction references. The system matches each response entry to its corresponding payroll item using the generated reference and updates the payroll item record with the Monnify transaction reference while keeping its status as <code>PROCESSING</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> results = response.responseBody?.transactionList || [];

<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> item <span class="hljs-keyword">of</span> payable) {
<span class="hljs-keyword">const</span> ref = <span class="hljs-string">`PAYROLL_<span class="hljs-subst">${payrollId}</span>_<span class="hljs-subst">${item.id}</span>`</span>;
<span class="hljs-keyword">const</span> match = results.find(<span class="hljs-function">(<span class="hljs-params">r: <span class="hljs-built_in">any</span></span>) =&gt;</span> r.reference === ref);

    <span class="hljs-keyword">if</span> (match?.transactionReference) {
      <span class="hljs-keyword">await</span> PayrollItemModel.updateStatus(
        item.id,
        PayrollStatus.PROCESSING,
        match.transactionReference
      );
    }

}

<span class="hljs-keyword">await</span> updatePayrollStats(payrollId);
}
</code></pre>
<p>This step is critical for later reconciliation through webhooks or status polling.</p>
<h3 id="heading-payroll-statistics-reconciliation-updatepayrollstats">Payroll Statistics Reconciliation (<code>updatePayrollStats</code>)</h3>
<p>After initiating payments, the system recalculates payroll-level statistics by refetching all payroll items.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updatePayrollStats</span>(<span class="hljs-params">payrollId: <span class="hljs-built_in">number</span></span>) </span>{
<span class="hljs-keyword">const</span> items = <span class="hljs-keyword">await</span> PayrollItemModel.findByPayrollId(payrollId);

<span class="hljs-keyword">const</span> completed = items.filter(
  <span class="hljs-function">(<span class="hljs-params">i</span>) =&gt;</span> i.status === PayrollStatus.COMPLETED
).length;
</code></pre>
<p>The overall payroll status is derived from these counts:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> failed = items.filter(<span class="hljs-function">(<span class="hljs-params">i</span>) =&gt;</span> i.status === PayrollStatus.FAILED).length;

<span class="hljs-keyword">let</span> status = PayrollStatus.PROCESSING;

<span class="hljs-keyword">if</span> (completed === items.length) {
  status = PayrollStatus.COMPLETED;
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (failed === items.length) {
  status = PayrollStatus.FAILED;
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (completed &gt; <span class="hljs-number">0</span>) {
  status = PayrollStatus.PARTIALLY_COMPLETED;
}

 <span class="hljs-keyword">await</span> PayrollModel.updateStatus(payrollId, status, completed, failed);
}
</code></pre>
<p>If all items are completed, the payroll is marked as <code>COMPLETED</code>. If all failed, it’s marked as <code>FAILED</code>. If some succeeded and some failed, it’s marked as <code>PARTIALLY_COMPLETED</code>. Otherwise, it remains in <code>PROCESSING</code>. The payroll record is then updated with the new status and aggregate counts, providing an accurate real-time snapshot of payroll execution.</p>
<h3 id="heading-queue-entry-point-processpayrollitems">Queue Entry Point (<code>processPayrollItems</code>)</h3>
<p>The <code>processPayrollItems</code> function serves as the public entry point for triggering payroll execution.</p>
<pre><code class="lang-typescript"><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">processPayrollItems</span>(<span class="hljs-params">payrollId: <span class="hljs-built_in">number</span></span>) </span>{
  <span class="hljs-keyword">await</span> payrollQueue.add({ payrollId, <span class="hljs-keyword">type</span>: <span class="hljs-string">'bulk'</span> });
}
</code></pre>
<p>It simply enqueues a payroll job with the relevant payroll ID, allowing controllers or services to initiate payroll processing without coupling themselves to queue logic or payment execution details.</p>
<h3 id="heading-role-in-the-overall-payroll-architecture">Role in the Overall Payroll Architecture</h3>
<p>This queue worker acts as the execution engine of the payroll system. It:</p>
<ul>
<li><p>Bridges payroll domain models with the Monnify payment gateway</p>
</li>
<li><p>Ensures safe retries through Bull’s job management and maintains idempotency</p>
</li>
<li><p>Continuously synchronizes payroll and payroll item states</p>
</li>
</ul>
<p>By offloading payment execution to background workers, the system achieves scalability, reliability, and operational resilience required for real-world payroll processing.</p>
<p>Key features of the job processor:</p>
<ol>
<li><p><strong>Exponential backoff</strong>: Failed jobs are retried with increasing delays (2s, 4s, 8s).</p>
</li>
<li><p><strong>Bulk processing</strong>: All payroll items are processed as a single batch transfer.</p>
</li>
<li><p><strong>Status tracking</strong>: Each item's status is updated throughout the process.</p>
</li>
<li><p><strong>Automatic cleanup</strong>: Completed jobs are automatically removed from the queue.</p>
</li>
</ol>
<h2 id="heading-creating-the-api-controllers">Creating the API Controllers</h2>
<p>Next, we’ll build the HTTP controller layer for managing employees in the payroll system using Express.js. It exposes RESTful API endpoints that handle incoming requests, perform validation, interact with the employee data model, and return appropriate HTTP responses.</p>
<p>The controller acts as the bridge between client-facing APIs and the underlying business logic encapsulated in the <code>EmployeeModel</code>.</p>
<h3 id="heading-controller-responsibilities">Controller Responsibilities</h3>
<p>The <code>EmployeeController</code> is responsible for:</p>
<ul>
<li><p>Validating incoming request data</p>
</li>
<li><p>Calling the appropriate model methods</p>
</li>
<li><p>Handling errors gracefully</p>
</li>
<li><p>Returning meaningful HTTP status codes and JSON responses</p>
</li>
</ul>
<p>Each method follows a consistent structure using <code>try–catch</code> blocks to ensure reliability and simplify error handling.</p>
<p>Start by creating a new file at <code>src/controllers/employee.controller.ts</code>. This file will contain all the endpoints needed to manage employees in the payroll system.</p>
<p>At the top of the file, import the required Express types and the employee model:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Request, Response } <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">import</span> { EmployeeModel, CreateEmployeeInput } <span class="hljs-keyword">from</span> <span class="hljs-string">'../models/employee'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> EmployeeController {
  <span class="hljs-comment">// Controller methods will go here</span>
}
</code></pre>
<p>Each method inside this class will map to a specific API endpoint.</p>
<h3 id="heading-creating-an-employee-createemployee">Creating an Employee (<code>createEmployee</code>)</h3>
<p>We’ll start with an endpoint for creating a new employee.</p>
<p>This endpoint handles the creation of a new employee record. It extracts the request body and validates the presence of required fields such as name, email, salary, bank account number, and bank code.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> createEmployee(req: Request, res: Response): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
<span class="hljs-keyword">try</span> {
<span class="hljs-keyword">const</span> data: CreateEmployeeInput = req.body;

      <span class="hljs-keyword">if</span> (
        !data.name ||
        !data.email ||
        !data.salary ||
        !data.account_number ||
        !data.bank_code
      ) {
        res.status(<span class="hljs-number">400</span>).json({
          error:
            <span class="hljs-string">'Missing required fields: name, email, salary, account_number, bank_code'</span>,
        });
        <span class="hljs-keyword">return</span>;
      }

      <span class="hljs-keyword">const</span> employee = <span class="hljs-keyword">await</span> EmployeeModel.create(data);
      res.status(<span class="hljs-number">201</span>).json({
        message: <span class="hljs-string">'Employee created successfully'</span>,
        data: employee,
      });
    } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error creating employee:'</span>, error);
      res
        .status(<span class="hljs-number">500</span>)
        .json({ error: error.message || <span class="hljs-string">'Failed to create employee'</span> });
    }

}
</code></pre>
<p>If any required field is missing, the request is rejected with a <code>400 Bad Request</code> response.</p>
<p>Upon successful validation, the controller delegates employee creation to the <code>EmployeeModel.create</code> method and returns a <code>201 Created</code> response containing the newly created employee. Any unexpected error during the process results in a <code>500 Internal Server Error</code>.</p>
<h3 id="heading-fetching-all-employees-getallemployees">Fetching All Employees (<code>getAllEmployees</code>)</h3>
<p>Next, we’ll add an endpoint for retrieving all employee records from the system.</p>
<p>This endpoint simply calls <code>EmployeeModel.findAll</code> and returns the result as a JSON response. This API is typically used for administrative dashboards, payroll preparation, or reporting purposes.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> getAllEmployees(req: Request, res: Response): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> employees = <span class="hljs-keyword">await</span> EmployeeModel.findAll();
    res.json({ data: employees });
  } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error fetching employees:'</span>, error);
    res
      .status(<span class="hljs-number">500</span>)
      .json({ error: error.message || <span class="hljs-string">'Failed to fetch employees'</span> });
  }
}
</code></pre>
<p>If the retrieval is successful, the controller responds with the full list of employees. If something goes wrong, such as a database or unexpected runtime error, the error is logged and a 500 Internal Server Error is returned to the client.</p>
<h3 id="heading-fetching-a-single-employee-getemployeebyid">Fetching a Single Employee (<code>getEmployeeById</code>)</h3>
<p>After listing all employees, the next logical step is being able to fetch a single employee by their ID.</p>
<p>This endpoint retrieves a specific employee by ID, which is parsed from the URL parameters. If the employee doesn’t exist, the controller responds with a <code>404 Not Found</code>. Otherwise, the employee data is returned in a successful response. This endpoint is useful for viewing or editing individual employee details.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> getEmployeeById(req: Request, res: Response): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> { id } = req.params;
      <span class="hljs-keyword">const</span> employee = <span class="hljs-keyword">await</span> EmployeeModel.findById(<span class="hljs-built_in">parseInt</span>(id));

      <span class="hljs-keyword">if</span> (!employee) {
        res.status(<span class="hljs-number">404</span>).json({ error: <span class="hljs-string">'Employee not found'</span> });
        <span class="hljs-keyword">return</span>;
      }

      res.json({ data: employee });
    } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error fetching employee:'</span>, error);
      res
        .status(<span class="hljs-number">500</span>)
        .json({ error: error.message || <span class="hljs-string">'Failed to fetch employee'</span> });
    }
  }
</code></pre>
<h3 id="heading-updating-an-employee-updateemployee">Updating an Employee (<code>updateEmployee</code>)</h3>
<p>Now that we can retrieve individual employees, the next step is allowing their details to be updated.</p>
<p>This endpoint allows partial updates to an existing employee record. It first checks whether the employee exists before attempting an update.</p>
<p>If the employee isn’t found, a <code>404 Not Found</code> response is returned. If the employee exists, the controller forwards the update payload to <code>EmployeeModel.update</code> and returns the updated employee record. This approach ensures data integrity and prevents silent failures.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> updateEmployee(req: Request, res: Response): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> { id } = req.params;
      <span class="hljs-keyword">const</span> data: Partial&lt;CreateEmployeeInput&gt; = req.body;

      <span class="hljs-keyword">const</span> employee = <span class="hljs-keyword">await</span> EmployeeModel.findById(<span class="hljs-built_in">parseInt</span>(id));
      <span class="hljs-keyword">if</span> (!employee) {
        res.status(<span class="hljs-number">404</span>).json({ error: <span class="hljs-string">'Employee not found'</span> });
        <span class="hljs-keyword">return</span>;
      }

      <span class="hljs-keyword">const</span> updated = <span class="hljs-keyword">await</span> EmployeeModel.update(<span class="hljs-built_in">parseInt</span>(id), data);
      res.json({
        message: <span class="hljs-string">'Employee updated successfully'</span>,
        data: updated,
      });
    } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error updating employee:'</span>, error);
      res
        .status(<span class="hljs-number">500</span>)
        .json({ error: error.message || <span class="hljs-string">'Failed to update employee'</span> });
    }
  }
</code></pre>
<h3 id="heading-deleting-an-employee-deleteemployee">Deleting an Employee (<code>deleteEmployee</code>)</h3>
<p>Finally, the last endpoint in the <code>EmployeeController</code> handles employee deletion.</p>
<p>Before deleting, it verifies that the employee exists to avoid invalid delete operations. If found, the employee record is removed using <code>EmployeeModel.delete</code>, and a success message is returned. If the employee doesn’t exist, the controller responds with a <code>404 Not Found</code>.</p>
<pre><code class="lang-typescript"> <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> deleteEmployee(req: Request, res: Response): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> { id } = req.params;

      <span class="hljs-keyword">const</span> employee = <span class="hljs-keyword">await</span> EmployeeModel.findById(<span class="hljs-built_in">parseInt</span>(id));
      <span class="hljs-keyword">if</span> (!employee) {
        res.status(<span class="hljs-number">404</span>).json({ error: <span class="hljs-string">'Employee not found'</span> });
        <span class="hljs-keyword">return</span>;
      }

      <span class="hljs-keyword">await</span> EmployeeModel.delete(<span class="hljs-built_in">parseInt</span>(id));
      res.json({ message: <span class="hljs-string">'Employee deleted successfully'</span> });
    } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error deleting employee:'</span>, error);
      res
        .status(<span class="hljs-number">500</span>)
        .json({ error: error.message || <span class="hljs-string">'Failed to delete employee'</span> });
    }
  }
</code></pre>
<h3 id="heading-error-handling-strategy">Error Handling Strategy</h3>
<p>All controller methods use structured error handling to log errors internally while returning clean and user-friendly error messages to API consumers. This separation ensures sensitive implementation details are not leaked while still providing useful feedback for debugging and client-side handling.</p>
<h3 id="heading-role-in-the-overall-payroll-system">Role in the Overall Payroll System</h3>
<p>The <code>EmployeeController</code> provides the foundational APIs required for managing employee records, which are essential inputs for payroll processing. By cleanly separating HTTP concerns from business logic and persistence layers, this controller supports maintainability, scalability, and clear system boundaries within the payroll architecture.</p>
<h3 id="heading-payroll-controller">Payroll Controller</h3>
<p>This module defines the PayrollController, which serves as the primary HTTP-facing orchestration layer for payroll operations in the system. It exposes RESTful APIs that allow clients to create payrolls, retrieve payroll data, trigger payroll processing, reconcile payment results, authorize bulk transfers, and monitor transaction and account statuses.</p>
<h3 id="heading-controller-responsibilities-1">Controller Responsibilities</h3>
<p>The <code>PayrollController</code> is responsible for:</p>
<ul>
<li><p>Accepting and validating client requests related to payrolls</p>
</li>
<li><p>Managing payroll lifecycle transitions (creation → processing → completion)</p>
</li>
<li><p>Triggering background job execution for bulk payroll disbursement</p>
</li>
<li><p>Reconciling payment results with Monnify</p>
</li>
<li><p>Providing real-time payroll and transaction status visibility</p>
</li>
<li><p>Acting as a safe boundary between external clients and internal services</p>
</li>
</ul>
<p>To get started, create a new file <code>src/controllers/payroll.controller.ts</code>. This is where we’ll define all payroll-related endpoints.</p>
<p>At the top of <code>src/controllers/payroll.controller.ts</code>, we start with the following imports:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Request, Response } <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">import</span> {
  PayrollModel,
  PayrollItemModel,
  PayrollStatus,
} <span class="hljs-keyword">from</span> <span class="hljs-string">'../models/payroll'</span>;
<span class="hljs-keyword">import</span> { processPayrollItems } <span class="hljs-keyword">from</span> <span class="hljs-string">'../jobs/payroll.processor'</span>;
<span class="hljs-keyword">import</span> { monnifyClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'../config/monnify'</span>;
</code></pre>
<p>Here’s what each of these is responsible for:</p>
<ul>
<li><p><code>Request</code> and <code>Response</code> (from Express): These types give us strongly typed access to incoming HTTP requests and outgoing responses.</p>
</li>
<li><p><code>PayrollModel</code>: This model handles payroll batch operations such as creating payrolls, fetching them, and updating their overall status.</p>
</li>
<li><p><code>PayrollItemModel</code>: This model lets us fetch and update those items, especially during processing and reconciliation.</p>
</li>
<li><p><code>PayrollStatus</code>: This is an enum that defines the valid states of a payroll or payroll item (for example: <code>PENDING</code>, <code>PROCESSING</code>, <code>COMPLETED</code>, <code>FAILED</code>). Using an enum helps keep state transitions explicit and consistent across the system.</p>
</li>
<li><p><code>processPayrollItems</code>: This function is responsible for handing off payroll processing to background workers. Instead of processing payrolls synchronously in the HTTP request, we queue the work and let workers handle it asynchronously.</p>
</li>
<li><p><code>monnifyClient</code>: This is our gateway to the external payment service. We use it to authorize bulk transfers, check transaction statuses, reconcile payments, and fetch account balances.</p>
</li>
</ul>
<p>Together, these imports give the controller everything it needs to process payroll operations.</p>
<p>With our imports in place, we can now define the controller class itself. This class will serve as the single home for all payroll-related endpoints.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> PayrollController {
  <span class="hljs-comment">// Payroll endpoints will live here</span>
}
</code></pre>
<h3 id="heading-creating-a-payroll-createpayroll">Creating a Payroll (<code>createPayroll</code>)</h3>
<p>With the controller in place, we’ll begin by implementing the endpoint create payroll. This endpoint initializes a new payroll batch, allowing us to either process all employees or a subset by their IDs.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> createPayroll(req: Request, res: Response): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
<span class="hljs-keyword">try</span> {
<span class="hljs-keyword">const</span> { payroll_period, employee_ids } = req.body;

      <span class="hljs-keyword">if</span> (!payroll_period) {
        res.status(<span class="hljs-number">400</span>).json({ error: <span class="hljs-string">'payroll_period is required'</span> });
        <span class="hljs-keyword">return</span>;
      }

      <span class="hljs-keyword">const</span> processedEmployeeIds = employee_ids
        ? employee_ids
            .map(<span class="hljs-function">(<span class="hljs-params">id: <span class="hljs-built_in">any</span></span>) =&gt;</span> <span class="hljs-built_in">parseInt</span>(id, <span class="hljs-number">10</span>))
            .filter(<span class="hljs-function">(<span class="hljs-params">id: <span class="hljs-built_in">number</span></span>) =&gt;</span> !<span class="hljs-built_in">isNaN</span>(id))
        : <span class="hljs-literal">undefined</span>;

      <span class="hljs-keyword">const</span> payroll = <span class="hljs-keyword">await</span> PayrollModel.create({
        payroll_period,
        employee_ids: processedEmployeeIds,
      });

      res.status(<span class="hljs-number">201</span>).json({
        message: <span class="hljs-string">'Payroll created successfully'</span>,
        data: payroll,
      });
    } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error creating payroll:'</span>, error);
      res
        .status(<span class="hljs-number">500</span>)
        .json({ error: error.message || <span class="hljs-string">'Failed to create payroll'</span> });
    }

}
</code></pre>
<p>Here’s what’s happening in the code:</p>
<ul>
<li><p>The endpoint requires a <code>payroll_period</code> and optionally accepts a list of employee IDs to support partial payroll runs.</p>
</li>
<li><p>Incoming employee IDs are normalized and validated to ensure they are valid integers before being passed to the payroll model.</p>
</li>
<li><p>The controller delegates the actual creation logic to <code>PayrollModel.create</code>, which computes totals and creates payroll items.</p>
</li>
<li><p>On success, the API responds with a <code>201 Created</code> status and the newly created payroll record.</p>
</li>
</ul>
<h3 id="heading-fetching-all-payrolls-getallpayrolls">Fetching All Payrolls (<code>getAllPayrolls</code>)</h3>
<p>This endpoint retrieves all payroll batches in the system. It’s typically used for administrative dashboards and payroll history views. The controller simply delegates to <code>PayrollModel.findAll</code> and returns the results in a structured JSON response.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> getAllPayrolls(req: Request, res: Response): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
<span class="hljs-keyword">try</span> {
<span class="hljs-keyword">const</span> payrolls = <span class="hljs-keyword">await</span> PayrollModel.findAll();
res.json({ data: payrolls });
} <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
<span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error fetching payrolls:'</span>, error);
res
.status(<span class="hljs-number">500</span>)
.json({ error: error.message || <span class="hljs-string">'Failed to fetch payrolls'</span> });
}
}

<span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> getPayrollById(req: Request, res: Response): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
<span class="hljs-keyword">try</span> {
<span class="hljs-keyword">const</span> { id } = req.params;
<span class="hljs-keyword">const</span> payroll = <span class="hljs-keyword">await</span> PayrollModel.findById(<span class="hljs-built_in">parseInt</span>(id));

      <span class="hljs-keyword">if</span> (!payroll) {
        res.status(<span class="hljs-number">404</span>).json({ error: <span class="hljs-string">'Payroll not found'</span> });
        <span class="hljs-keyword">return</span>;
      }

      <span class="hljs-keyword">const</span> items = <span class="hljs-keyword">await</span> PayrollItemModel.findByPayrollId(payroll.id);

      res.json({
        data: {
          ...payroll,
          items,
        },
      });
    } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error fetching payroll:'</span>, error);
      res
        .status(<span class="hljs-number">500</span>)
        .json({ error: error.message || <span class="hljs-string">'Failed to fetch payroll'</span> });
    }
}
</code></pre>
<h3 id="heading-fetching-a-payroll-with-items-getpayrollbyid">Fetching a Payroll with Items (<code>getPayrollById</code>)</h3>
<p>Next, we’ll implement an endpoint to retrieve a single payroll by its ID along with all associated payroll items. This is useful for administrative dashboards and payroll history views.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> getPayrollById(req: Request, res: Response): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
<span class="hljs-keyword">try</span> {
<span class="hljs-keyword">const</span> { id } = req.params;
<span class="hljs-keyword">const</span> payroll = <span class="hljs-keyword">await</span> PayrollModel.findById(<span class="hljs-built_in">parseInt</span>(id));

      <span class="hljs-keyword">if</span> (!payroll) {
        res.status(<span class="hljs-number">404</span>).json({ error: <span class="hljs-string">'Payroll not found'</span> });
        <span class="hljs-keyword">return</span>;
      }

      <span class="hljs-keyword">const</span> items = <span class="hljs-keyword">await</span> PayrollItemModel.findByPayrollId(payroll.id);

      res.json({
        data: {
          ...payroll,
          items,
        },
      });
    } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error fetching payroll:'</span>, error);
      res
        .status(<span class="hljs-number">500</span>)
        .json({ error: error.message || <span class="hljs-string">'Failed to fetch payroll'</span> });
    }

}
</code></pre>
<p>In the code, we read the <code>id</code> parameter from the URL and convert it to an integer.</p>
<p>If the payroll does not exist, a <code>404 Not Found</code> response is returned. When found, the controller aggregates payroll metadata and its child payroll items into a single response object, making it convenient for detailed payroll inspection and UI rendering.</p>
<h3 id="heading-processing-a-payroll-processpayroll">Processing a Payroll (<code>processPayroll</code>)</h3>
<p>Next, we implement the <code>processPayroll</code> endpoint. This endpoint initiates payroll execution. Before queuing the payroll for processing, the controller enforces important state checks to prevent duplicate or invalid execution, ensuring payrolls that are already <code>PROCESSING</code> or <code>COMPLETED</code> cannot be reprocessed.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> processPayroll(req: Request, res: Response): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
<span class="hljs-keyword">try</span> {
<span class="hljs-keyword">const</span> { id } = req.params;

      <span class="hljs-keyword">const</span> payroll = <span class="hljs-keyword">await</span> PayrollModel.findById(<span class="hljs-built_in">Number</span>(id));

      <span class="hljs-keyword">if</span> (!payroll) {
        res.status(<span class="hljs-number">404</span>).json({ error: <span class="hljs-string">'Payroll not found'</span> });
        <span class="hljs-keyword">return</span>;
      }

      <span class="hljs-keyword">if</span> (
        payroll.status === PayrollStatus.COMPLETED ||
        payroll.status === PayrollStatus.PROCESSING
      ) {
        res.status(<span class="hljs-number">400</span>).json({
          error: <span class="hljs-string">`Payroll already <span class="hljs-subst">${payroll.status}</span>`</span>,
        });
        <span class="hljs-keyword">return</span>;
      }

      <span class="hljs-comment">// Queue the payroll for processing</span>
      <span class="hljs-keyword">await</span> processPayrollItems(payroll.id);

      res.json({
        message: <span class="hljs-string">'Payroll queued for bulk processing'</span>,
        data: {
          payroll_id: payroll.id,
          processing_mode: <span class="hljs-string">'bulk'</span>,
        },
      });
    } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error processing payroll:'</span>, error);
      res.status(<span class="hljs-number">500</span>).json({
        error: error.message || <span class="hljs-string">'Failed to process payroll'</span>,
      });
    }
}
</code></pre>
<p>Here’s what’s happening in the code:</p>
<ul>
<li><p>We get the <code>id</code> parameter from the URL and convert it to a number.</p>
</li>
<li><p>If no payroll is found with the given ID, we return a <code>404 Not Found</code> response.</p>
</li>
<li><p>Before queuing, we check the payroll’s current status. Payrolls that are already <code>PROCESSING</code> or <code>COMPLETED</code> cannot be reprocessed.</p>
</li>
<li><p>Valid payrolls are handed off to <code>processPayrollItems</code>, which runs the bulk execution in background workers (Bull jobs).</p>
</li>
<li><p>Once queued, we respond with a JSON object confirming the payroll is ready for bulk processing.</p>
</li>
</ul>
<h3 id="heading-reconciling-payroll-payments-reconcilepayroll">Reconciling Payroll Payments (<code>reconcilePayroll</code>)</h3>
<p>Next, we’ll implement the endpoint that reconciles payroll payments. This ensures that the statuses of payroll items in our system match the actual payment outcomes from Monnify.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> reconcilePayroll(req: Request, res: Response): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
<span class="hljs-keyword">try</span> {
<span class="hljs-keyword">const</span> { id } = req.params;

      <span class="hljs-keyword">const</span> payroll = <span class="hljs-keyword">await</span> PayrollModel.findById(<span class="hljs-built_in">Number</span>(id));
      <span class="hljs-keyword">if</span> (!payroll) {
        res.status(<span class="hljs-number">404</span>).json({ error: <span class="hljs-string">'Payroll not found'</span> });
        <span class="hljs-keyword">return</span>;
      }

      <span class="hljs-keyword">const</span> items = <span class="hljs-keyword">await</span> PayrollItemModel.findByPayrollId(<span class="hljs-built_in">Number</span>(id));

      <span class="hljs-keyword">const</span> itemsToReconcile = items.filter(
        <span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span> item.transaction_reference
      );

      <span class="hljs-keyword">if</span> (itemsToReconcile.length === <span class="hljs-number">0</span>) {
        res.json({
          message: <span class="hljs-string">'No items to reconcile (no transaction references found)'</span>,
          reconciled: <span class="hljs-number">0</span>,
        });
        <span class="hljs-keyword">return</span>;
      }

      <span class="hljs-keyword">let</span> updated = <span class="hljs-number">0</span>;
      <span class="hljs-keyword">let</span> errors = <span class="hljs-number">0</span>;

      <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> item <span class="hljs-keyword">of</span> itemsToReconcile) {
        <span class="hljs-keyword">try</span> {
          <span class="hljs-keyword">const</span> txStatus = <span class="hljs-keyword">await</span> monnifyClient.getTransactionStatus(
            item.transaction_reference!
          );

          <span class="hljs-keyword">const</span> responseBody = txStatus.responseBody || txStatus;
          <span class="hljs-keyword">const</span> paymentStatus =
            responseBody.paymentStatus || responseBody.status;

          <span class="hljs-keyword">if</span> (
            paymentStatus === <span class="hljs-string">'PAID'</span> &amp;&amp;
            item.status !== PayrollStatus.COMPLETED
          ) {
            <span class="hljs-keyword">await</span> PayrollItemModel.updateStatus(
              item.id,
              PayrollStatus.COMPLETED,
              item.transaction_reference
            );
            updated++;
          } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (
            paymentStatus === <span class="hljs-string">'FAILED'</span> &amp;&amp;
            item.status !== PayrollStatus.FAILED
          ) {
            <span class="hljs-keyword">const</span> errorMessage =
              responseBody.paymentDescription ||
              responseBody.failureReason ||
              <span class="hljs-string">'Transaction failed'</span>;
            <span class="hljs-keyword">await</span> PayrollItemModel.updateStatus(
              item.id,
              PayrollStatus.FAILED,
              item.transaction_reference,
              errorMessage
            );
            updated++;
          }
        } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
          errors++;
          <span class="hljs-built_in">console</span>.error(<span class="hljs-string">`Error reconciling item <span class="hljs-subst">${item.id}</span>:`</span>, error.message);
        }
      }

      <span class="hljs-comment">// Update payroll stats</span>
      <span class="hljs-keyword">await</span> PayrollController.updatePayrollStats(<span class="hljs-built_in">Number</span>(id));

      res.json({
        message: <span class="hljs-string">'Payroll reconciled successfully'</span>,
        reconciled: updated,
        errors,
        total: itemsToReconcile.length,
      });
    } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error reconciling payroll:'</span>, error);
      res.status(<span class="hljs-number">500</span>).json({
        error: error.message || <span class="hljs-string">'Failed to reconcile payroll'</span>,
      });
    }
}
</code></pre>
<p>The endpoint retrieves all payroll items with transaction references and queries Monnify for each transaction’s status. Based on the response, payroll items are updated to either <code>COMPLETED</code> or <code>FAILED</code>, with failure reasons captured where applicable.</p>
<p>Errors during reconciliation are tracked and logged without aborting the entire reconciliation process. After reconciliation, payroll-level statistics are recalculated to ensure consistency between item-level and batch-level states.</p>
<h3 id="heading-payroll-statistics-update-internal-helper">Payroll Statistics Update (Internal Helper)</h3>
<p>The private <code>updatePayrollStats</code> method recalculates payroll status based on the aggregate states of its payroll items.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> updatePayrollStats(payrollId: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
<span class="hljs-keyword">const</span> items = <span class="hljs-keyword">await</span> PayrollItemModel.findByPayrollId(payrollId);

    <span class="hljs-keyword">const</span> completed = items.filter(
      <span class="hljs-function">(<span class="hljs-params">i</span>) =&gt;</span> i.status === PayrollStatus.COMPLETED
    ).length;
    <span class="hljs-keyword">const</span> failed = items.filter(
      <span class="hljs-function">(<span class="hljs-params">i</span>) =&gt;</span> i.status === PayrollStatus.FAILED
    ).length;
    <span class="hljs-keyword">const</span> total = items.length;

    <span class="hljs-keyword">let</span> status: PayrollStatus;
    <span class="hljs-keyword">if</span> (completed === total) {
      status = PayrollStatus.COMPLETED;
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (failed === total) {
      status = PayrollStatus.FAILED;
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (completed &gt; <span class="hljs-number">0</span>) {
      status = PayrollStatus.PARTIALLY_COMPLETED;
    } <span class="hljs-keyword">else</span> {
      status = PayrollStatus.PROCESSING;
    }

    <span class="hljs-keyword">await</span> PayrollModel.updateStatus(payrollId, status, completed, failed);

}
</code></pre>
<p>The endpoint determines whether a payroll is fully completed, fully failed, partially completed, or still processing, and updates the payroll record accordingly.</p>
<p>This logic guarantees that the payroll’s summary status always reflects the true execution state of its underlying payments.</p>
<h3 id="heading-fetching-payroll-status-summary-getpayrollstatus">Fetching Payroll Status Summary (<code>getPayrollStatus</code>)</h3>
<p>Next, we’ll implement the <code>getPayrollStatus</code> endpoint. This endpoint provides a comprehensive status snapshot of a payroll. In addition to returning payroll metadata and items, it computes a summary breakdown of completed, failed, pending, and processing items. This endpoint is particularly useful for real-time dashboards, monitoring tools, and operational visibility.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> getPayrollStatus(req: Request, res: Response): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
<span class="hljs-keyword">try</span> {
<span class="hljs-keyword">const</span> { id } = req.params;
<span class="hljs-keyword">const</span> payroll = <span class="hljs-keyword">await</span> PayrollModel.findById(<span class="hljs-built_in">parseInt</span>(id));

      <span class="hljs-keyword">if</span> (!payroll) {
        res.status(<span class="hljs-number">404</span>).json({ error: <span class="hljs-string">'Payroll not found'</span> });
        <span class="hljs-keyword">return</span>;
      }

      <span class="hljs-keyword">const</span> items = <span class="hljs-keyword">await</span> PayrollItemModel.findByPayrollId(payroll.id);

      res.json({
        data: {
          ...payroll,
          items,
          summary: {
            total: items.length,
            completed: items.filter(<span class="hljs-function">(<span class="hljs-params">i</span>) =&gt;</span> i.status === PayrollStatus.COMPLETED)
              .length,
            failed: items.filter(<span class="hljs-function">(<span class="hljs-params">i</span>) =&gt;</span> i.status === PayrollStatus.FAILED)
              .length,
            pending: items.filter(<span class="hljs-function">(<span class="hljs-params">i</span>) =&gt;</span> i.status === PayrollStatus.PENDING)
              .length,
            processing: items.filter(
              <span class="hljs-function">(<span class="hljs-params">i</span>) =&gt;</span> i.status === PayrollStatus.PROCESSING
            ).length,
          },
        },
      });
    } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error fetching payroll status:'</span>, error);
      res
        .status(<span class="hljs-number">500</span>)
        .json({ error: error.message || <span class="hljs-string">'Failed to fetch payroll status'</span> });
    }
}
</code></pre>
<h3 id="heading-authorizing-bulk-transfers-authorizebulktransfer">Authorizing Bulk Transfers (<code>authorizeBulkTransfer</code>)</h3>
<p>Next, we’ll implement the <code>authorizeBulkTransfer</code> endpoint. Some bulk disbursements require OTP authorization from Monnify. This endpoint accepts a batch reference and authorization code, validates their presence, and forwards them to the Monnify client for verification. Successful authorization allows the bulk transfer to proceed, while failures are clearly reported to the client.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> authorizeBulkTransfer(
req: Request,
res: Response
): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
<span class="hljs-keyword">try</span> {
<span class="hljs-keyword">const</span> { reference, authorizationCode, payrollId } = req.body;

      <span class="hljs-keyword">if</span> (!reference) {
        res.status(<span class="hljs-number">400</span>).json({ error: <span class="hljs-string">'Batch reference is required'</span> });
        <span class="hljs-keyword">return</span>;
      }

      <span class="hljs-keyword">if</span> (!authorizationCode) {
        res.status(<span class="hljs-number">400</span>).json({ error: <span class="hljs-string">'Authorization code (OTP) is required'</span> });
        <span class="hljs-keyword">return</span>;
      }

      <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> monnifyClient.authorizeBulkTransfer(
        reference,
        authorizationCode
      );

      res.json({
        message: <span class="hljs-string">'Bulk transfer authorized successfully'</span>,
        data: result,
      });
    } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error authorizing bulk transfer:'</span>, error);
      res.status(<span class="hljs-number">500</span>).json({
        error: error.message || <span class="hljs-string">'Failed to authorize bulk transfer'</span>,
      });
    }
}
</code></pre>
<p>Here is what’s happening in the code:</p>
<ul>
<li><p>Firstly, we get the batch reference, OTP, and optional payroll ID from the request body.</p>
</li>
<li><p>We return a <code>400 Bad Request</code> if the reference or OTP is missing.</p>
</li>
<li><p>Next, we send the reference and OTP to Monnify to approve the bulk transfer.</p>
</li>
<li><p>If successful, return a JSON confirmation with Monnify’s response.</p>
</li>
</ul>
<h3 id="heading-checking-transaction-status-checktransactionstatus">Checking Transaction Status (<code>checkTransactionStatus</code>)</h3>
<p>This endpoint allows clients or administrators to query the status of an individual transaction using its reference. It delegates the lookup to the Monnify client and returns the raw response, making it useful for debugging, audits, or manual verification workflows.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> checkTransactionStatus(
req: Request,
res: Response
): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
<span class="hljs-keyword">try</span> {
<span class="hljs-keyword">const</span> { reference } = req.params;

      <span class="hljs-keyword">if</span> (!reference) {
        res.status(<span class="hljs-number">400</span>).json({ error: <span class="hljs-string">'Transaction reference is required'</span> });
        <span class="hljs-keyword">return</span>;
      }

      <span class="hljs-keyword">const</span> status = <span class="hljs-keyword">await</span> monnifyClient.getTransactionStatus(reference);
      res.json({ data: status });
    } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error checking transaction status:'</span>, error);
      res
        .status(<span class="hljs-number">500</span>)
        .json({ error: error.message || <span class="hljs-string">'Failed to check transaction status'</span> });
    }
}
</code></pre>
<h3 id="heading-checking-wallet-balance-getaccountbalance">Checking Wallet Balance (<code>getAccountBalance</code>)</h3>
<p>This endpoint retrieves the current balance of the Monnify wallet associated with the payroll contract code. It’s typically used for pre-disbursement checks, monitoring available funds, or administrative reporting.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> getAccountBalance(req: Request, res: Response): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> balance = <span class="hljs-keyword">await</span> monnifyClient.getAccountBalance();
      res.json({ data: balance });
    } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error fetching account balance:'</span>, error);
      res
        .status(<span class="hljs-number">500</span>)
        .json({ error: error.message || <span class="hljs-string">'Failed to fetch account balance'</span> });
    }
  }
</code></pre>
<h3 id="heading-error-handling-and-resilience">Error Handling and Resilience</h3>
<p>All controller methods use structured <code>try–catch</code> blocks to ensure unexpected failures are logged and surfaced as controlled HTTP error responses. This approach prevents sensitive internal errors from leaking while maintaining clarity and debuggability for API consumers.</p>
<h3 id="heading-role-in-the-overall-payroll-architecture-1">Role in the Overall Payroll Architecture</h3>
<p>The <code>PayrollController</code> acts as the central coordinator of the payroll system. It bridges client requests, domain models, background job processing, and external payment services into a cohesive workflow.</p>
<p>By enforcing state transitions, delegating heavy processing to background workers, and providing reconciliation and monitoring capabilities, this controller ensures payroll execution remains reliable, auditable, and scalable in real-world production environments.</p>
<h2 id="heading-setting-up-webhook-handlers">Setting Up Webhook Handlers</h2>
<p>Webhooks are essential for receiving real-time payment status updates from Monnify. When a payment completes or fails, Monnify sends a notification to your webhook endpoint.</p>
<p>Start by creating a new file <code>src/routes/monnify.webhook.ts</code>. This file will contain everything related to handling Monnify webhook events.</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { Router, Request, Response } <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">import</span> crypto <span class="hljs-keyword">from</span> <span class="hljs-string">'crypto'</span>;
<span class="hljs-keyword">import</span> {
PayrollItemModel,
PayrollModel,
PayrollStatus,
} <span class="hljs-keyword">from</span> <span class="hljs-string">'../models/payroll'</span>;

<span class="hljs-keyword">const</span> router = Router();

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">verifySignature</span>(<span class="hljs-params">req: Request</span>): <span class="hljs-title">boolean</span> </span>{
<span class="hljs-keyword">const</span> signature = req.headers[<span class="hljs-string">'monnify-signature'</span>] <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;
<span class="hljs-keyword">if</span> (!signature) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;

<span class="hljs-keyword">const</span> secret = process.env.MONNIFY_WEBHOOK_SECRET!;
<span class="hljs-keyword">const</span> hash = crypto
.createHmac(<span class="hljs-string">'sha512'</span>, secret)
.update(<span class="hljs-built_in">JSON</span>.stringify(req.body))
.digest(<span class="hljs-string">'hex'</span>);

<span class="hljs-keyword">return</span> hash === signature;
}

router.post(<span class="hljs-string">'/monnify/webhook'</span>, <span class="hljs-keyword">async</span> (req: Request, res: Response) =&gt; {
<span class="hljs-keyword">try</span> {
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Monnify Webhook:'</span>, <span class="hljs-built_in">JSON</span>.stringify(req.body, <span class="hljs-literal">null</span>, <span class="hljs-number">2</span>));

    <span class="hljs-keyword">const</span> { eventType, eventData } = req.body;

    <span class="hljs-keyword">if</span> (!eventData?.reference) {
      <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">'Missing reference, ignoring webhook'</span>);
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">200</span>).send(<span class="hljs-string">'Ignored'</span>);
    }

    <span class="hljs-keyword">const</span> paymentReference = eventData.reference;
    <span class="hljs-keyword">const</span> transactionReference = eventData.transactionReference;
    <span class="hljs-keyword">const</span> description = eventData.transactionDescription || <span class="hljs-string">''</span>;

    <span class="hljs-comment">// Parse our reference format: PAYROLL_{payrollId}_{itemId}</span>
    <span class="hljs-keyword">const</span> [prefix, payrollIdStr, itemIdStr] = paymentReference.split(<span class="hljs-string">'_'</span>);

    <span class="hljs-keyword">if</span> (prefix !== <span class="hljs-string">'PAYROLL'</span>) {
      <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">'Invalid payment reference format:'</span>, paymentReference);
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">200</span>).send(<span class="hljs-string">'Ignored'</span>);
    }

    <span class="hljs-keyword">const</span> payrollId = <span class="hljs-built_in">Number</span>(payrollIdStr);
    <span class="hljs-keyword">const</span> itemId = <span class="hljs-built_in">Number</span>(itemIdStr);

    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">isNaN</span>(payrollId) || <span class="hljs-built_in">isNaN</span>(itemId)) {
      <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">'Invalid payroll/item IDs:'</span>, paymentReference);
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">200</span>).send(<span class="hljs-string">'Ignored'</span>);
    }

    <span class="hljs-keyword">const</span> item = <span class="hljs-keyword">await</span> PayrollItemModel.findById(itemId);

    <span class="hljs-keyword">if</span> (!item) {
      <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">'Payroll item not found:'</span>, itemId);
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">200</span>).send(<span class="hljs-string">'Ignored'</span>);
    }

    <span class="hljs-comment">// Idempotency check - don't process already finalized items</span>
    <span class="hljs-keyword">if</span> (
      item.status === PayrollStatus.COMPLETED ||
      item.status === PayrollStatus.FAILED
    ) {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Item <span class="hljs-subst">${itemId}</span> already finalized (<span class="hljs-subst">${item.status}</span>)`</span>);
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">200</span>).send(<span class="hljs-string">'Already processed'</span>);
    }

    <span class="hljs-comment">// Update status based on event type</span>
    <span class="hljs-keyword">if</span> (
      eventType === <span class="hljs-string">'SUCCESSFUL_DISBURSEMENT'</span> ||
      eventData.status === <span class="hljs-string">'SUCCESS'</span>
    ) {
      <span class="hljs-keyword">await</span> PayrollItemModel.updateStatus(
        itemId,
        PayrollStatus.COMPLETED,
        transactionReference
      );
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`✅ Payroll item <span class="hljs-subst">${itemId}</span> COMPLETED`</span>);
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (
      eventType === <span class="hljs-string">'FAILED_DISBURSEMENT'</span> ||
      eventType === <span class="hljs-string">'REVERSED_DISBURSEMENT'</span> ||
      eventData.status === <span class="hljs-string">'FAILED'</span>
    ) {
      <span class="hljs-keyword">await</span> PayrollItemModel.updateStatus(
        itemId,
        PayrollStatus.FAILED,
        transactionReference,
        description
      );
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Payroll item <span class="hljs-subst">${itemId}</span> FAILED`</span>);
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Unhandled Monnify eventType: <span class="hljs-subst">${eventType}</span>`</span>);
    }

    <span class="hljs-comment">// Update overall payroll stats</span>
    <span class="hljs-keyword">await</span> updatePayrollStats(payrollId);

    <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">200</span>).send(<span class="hljs-string">'OK'</span>);

} <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
<span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Monnify webhook error:'</span>, error.message);
<span class="hljs-keyword">return</span> res.status(<span class="hljs-number">200</span>).send(<span class="hljs-string">'OK'</span>); <span class="hljs-comment">// Always return 200 to prevent retries</span>
}
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> router;

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updatePayrollStats</span>(<span class="hljs-params">payrollId: <span class="hljs-built_in">number</span></span>) </span>{
<span class="hljs-keyword">const</span> items = <span class="hljs-keyword">await</span> PayrollItemModel.findByPayrollId(payrollId);

<span class="hljs-keyword">const</span> completed = items.filter(
<span class="hljs-function">(<span class="hljs-params">i</span>) =&gt;</span> i.status === PayrollStatus.COMPLETED
).length;

<span class="hljs-keyword">const</span> failed = items.filter(<span class="hljs-function">(<span class="hljs-params">i</span>) =&gt;</span> i.status === PayrollStatus.FAILED).length;

<span class="hljs-keyword">let</span> status = PayrollStatus.PROCESSING;

<span class="hljs-keyword">if</span> (completed === items.length) {
status = PayrollStatus.COMPLETED;
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (failed === items.length) {
status = PayrollStatus.FAILED;
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (completed &gt; <span class="hljs-number">0</span>) {
status = PayrollStatus.PARTIALLY_COMPLETED;
}

<span class="hljs-keyword">await</span> PayrollModel.updateStatus(payrollId, status, completed, failed);
}
</code></pre>
<p>Key webhook implementation details:</p>
<ol>
<li><p><strong>Signature verification</strong>: The <code>verifySignature</code> function validates that webhooks actually come from Monnify.</p>
</li>
<li><p><strong>Idempotency</strong>: The handler checks if an item is already finalized before processing.</p>
</li>
<li><p><strong>Always return 200</strong>: Even on errors, return 200 to prevent Monnify from retrying indefinitely.</p>
</li>
<li><p><strong>Reference parsing</strong>: Our reference format <code>PAYROLL_{payrollId}_{itemId}</code> lets us identify which payment item to update.</p>
</li>
</ol>
<h2 id="heading-wiring-up-routes">Wiring Up Routes</h2>
<h3 id="heading-employee-routes">Employee Routes</h3>
<p>We’ll start by defining routes for employee management. These routes expose CRUD operations for employees and simply delegate the actual logic to the <code>EmployeeController</code>.</p>
<p>Create the file <code>src/routes/employee.routes.ts</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Router } <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">import</span> { EmployeeController } <span class="hljs-keyword">from</span> <span class="hljs-string">'../controllers/employee.controller'</span>;

<span class="hljs-keyword">const</span> router = Router();

router.post(<span class="hljs-string">'/'</span>, EmployeeController.createEmployee);
router.get(<span class="hljs-string">'/'</span>, EmployeeController.getAllEmployees);
router.get(<span class="hljs-string">'/:id'</span>, EmployeeController.getEmployeeById);
router.put(<span class="hljs-string">'/:id'</span>, EmployeeController.updateEmployee);
router.delete(<span class="hljs-string">'/:id'</span>, EmployeeController.deleteEmployee);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> router;
</code></pre>
<p>What this gives us:</p>
<ul>
<li><p>A clean <code>/api/employees</code> entry point for all employee-related operations</p>
</li>
<li><p>Clear separation between routing (URLs) and business logic (controllers)</p>
</li>
<li><p>A predictable REST structure that’s easy to extend later</p>
</li>
</ul>
<h3 id="heading-payroll-routes">Payroll Routes</h3>
<p>Next, we define routes for payroll operations. Payroll is more complex than employees, so this router exposes endpoints for creation, processing, reconciliation, authorization, and monitoring.</p>
<p>Create the file <code>src/routes/payroll.routes.ts</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Router } <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">import</span> { PayrollController } <span class="hljs-keyword">from</span> <span class="hljs-string">'../controllers/payroll.controller'</span>;

<span class="hljs-keyword">const</span> router = Router();

router.post(<span class="hljs-string">'/'</span>, PayrollController.createPayroll);
router.get(<span class="hljs-string">'/'</span>, PayrollController.getAllPayrolls);
router.get(<span class="hljs-string">'/:id'</span>, PayrollController.getPayrollById);
router.post(<span class="hljs-string">'/:id/process'</span>, PayrollController.processPayroll);
router.post(<span class="hljs-string">'/batch/authorize'</span>, PayrollController.authorizeBulkTransfer);
router.get(<span class="hljs-string">'/:id/status'</span>, PayrollController.getPayrollStatus);
router.get(
  <span class="hljs-string">'/transaction/:reference/status'</span>,
  PayrollController.checkTransactionStatus
);
router.get(<span class="hljs-string">'/account/balance'</span>, PayrollController.getAccountBalance);
router.post(<span class="hljs-string">'/:id/reconcile'</span>, PayrollController.reconcilePayroll);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> router;
</code></pre>
<p>What’s happening here:</p>
<ul>
<li><p>Each route maps directly to a well-defined payroll operation</p>
</li>
<li><p>Long-running or sensitive actions (processing, reconciliation, authorization) are clearly separated</p>
</li>
<li><p>Monitoring and operational endpoints (status, transaction lookup, balance checks) are first-class citizens</p>
</li>
</ul>
<h3 id="heading-main-application-entry-point">Main Application Entry Point</h3>
<p>With all routes defined, we now bring everything together in the main application file. This is where we configure middleware, register routes, and start the server.</p>
<p>Create the file <code>src/index.ts</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> express, { Application, Request, Response } <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">import</span> cors <span class="hljs-keyword">from</span> <span class="hljs-string">'cors'</span>;
<span class="hljs-keyword">import</span> helmet <span class="hljs-keyword">from</span> <span class="hljs-string">'helmet'</span>;
<span class="hljs-keyword">import</span> dotenv <span class="hljs-keyword">from</span> <span class="hljs-string">'dotenv'</span>;
<span class="hljs-keyword">import</span> path <span class="hljs-keyword">from</span> <span class="hljs-string">'path'</span>;
<span class="hljs-keyword">import</span> { pool } <span class="hljs-keyword">from</span> <span class="hljs-string">'./config/database'</span>;
<span class="hljs-keyword">import</span> employeeRoutes <span class="hljs-keyword">from</span> <span class="hljs-string">'./routes/employee.routes'</span>;
<span class="hljs-keyword">import</span> payrollRoutes <span class="hljs-keyword">from</span> <span class="hljs-string">'./routes/payroll.routes'</span>;
<span class="hljs-keyword">import</span> monnifyWebhookRoutes <span class="hljs-keyword">from</span> <span class="hljs-string">'./routes/monnify.webhook'</span>;

dotenv.config();

<span class="hljs-keyword">const</span> app: Application = express();
<span class="hljs-keyword">const</span> PORT = process.env.PORT || <span class="hljs-number">3008</span>;

<span class="hljs-comment">// Middleware</span>
app.use(
  helmet({
    contentSecurityPolicy: <span class="hljs-literal">false</span>,
  })
);
app.use(
  cors({
    origin: <span class="hljs-string">'*'</span>,
    methods: [<span class="hljs-string">'GET'</span>, <span class="hljs-string">'POST'</span>, <span class="hljs-string">'PUT'</span>, <span class="hljs-string">'DELETE'</span>, <span class="hljs-string">'OPTIONS'</span>],
    allowedHeaders: [<span class="hljs-string">'Content-Type'</span>, <span class="hljs-string">'Authorization'</span>],
  })
);
app.use(express.json());
app.use(express.urlencoded({ extended: <span class="hljs-literal">true</span> }));

<span class="hljs-comment">// Health check endpoint</span>
app.get(<span class="hljs-string">'/health'</span>, <span class="hljs-keyword">async</span> (req: Request, res: Response) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">await</span> pool.query(<span class="hljs-string">'SELECT 1'</span>);
    res.json({ status: <span class="hljs-string">'healthy'</span>, database: <span class="hljs-string">'connected'</span> });
  } <span class="hljs-keyword">catch</span> (error) {
    res.status(<span class="hljs-number">500</span>).json({ status: <span class="hljs-string">'unhealthy'</span>, database: <span class="hljs-string">'disconnected'</span> });
  }
});

<span class="hljs-comment">// Routes</span>
app.use(<span class="hljs-string">'/api/employees'</span>, employeeRoutes);
app.use(<span class="hljs-string">'/api/payrolls'</span>, payrollRoutes);
app.use(<span class="hljs-string">'/api'</span>, monnifyWebhookRoutes);

<span class="hljs-comment">// 404 handler</span>
app.use(<span class="hljs-function">(<span class="hljs-params">req: Request, res: Response</span>) =&gt;</span> {
  res.status(<span class="hljs-number">404</span>).json({ error: <span class="hljs-string">'Route not found'</span> });
});

<span class="hljs-comment">// Error handler</span>
app.use(<span class="hljs-function">(<span class="hljs-params">err: <span class="hljs-built_in">any</span>, req: Request, res: Response, next: <span class="hljs-built_in">any</span></span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error:'</span>, err);
  res.status(err.status || <span class="hljs-number">500</span>).json({
    error: err.message || <span class="hljs-string">'Internal server error'</span>,
  });
});

app.listen(PORT, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server is running on port <span class="hljs-subst">${PORT}</span>`</span>);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Environment: <span class="hljs-subst">${process.env.NODE_ENV || <span class="hljs-string">'development'</span>}</span>`</span>);
});

<span class="hljs-comment">// Graceful shutdown</span>
process.on(<span class="hljs-string">'SIGTERM'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'SIGTERM signal received: closing HTTP server'</span>);
  <span class="hljs-keyword">await</span> pool.end();
  process.exit(<span class="hljs-number">0</span>);
});

process.on(<span class="hljs-string">'SIGINT'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'SIGINT signal received: closing HTTP server'</span>);
  <span class="hljs-keyword">await</span> pool.end();
  process.exit(<span class="hljs-number">0</span>);
});
</code></pre>
<h2 id="heading-testing-the-system">Testing the System</h2>
<p>Now let's test the complete payroll flow.</p>
<p>Start the application:</p>
<pre><code class="lang-bash">docker-compose up -d
npm run dev
</code></pre>
<p>Create employees:</p>
<pre><code class="lang-bash">curl -X POST http://localhost:3008/api/employees \
  -H <span class="hljs-string">"Content-Type: application/json"</span> \
  -d <span class="hljs-string">'{
    "name": "John Doe",
    "email": "john.doe@company.com",
    "salary": 50000,
    "account_number": "0123456789",
    "bank_code": "058",
    "bank_name": "GTBank"
  }'</span>
</code></pre>
<p>Create a few more employees with different salaries to see how it’s handled.</p>
<p>Create a payroll:</p>
<pre><code class="lang-bash">curl -X POST http://localhost:3008/api/payrolls \
  -H <span class="hljs-string">"Content-Type: application/json"</span> \
  -d <span class="hljs-string">'{
    "payroll_period": "2024-12"
  }'</span>
</code></pre>
<p>This creates a payroll with all active employees.</p>
<p>Process the payroll:</p>
<pre><code class="lang-bash">curl -X POST http://localhost:3008/api/payrolls/1/process
</code></pre>
<p>This queues the payroll for background processing. The system will:</p>
<ol>
<li><p>Create a bulk transfer request to Monnify</p>
</li>
<li><p>Update each payroll item with a transaction reference</p>
</li>
<li><p>Wait for webhooks to update final status</p>
</li>
</ol>
<p>Authorize the bulk transfer (if OTP is required):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766392280287/4d8ae61f-4ccf-4d63-86a1-a6f72d7286e1.png" alt="Monnify payroll authorization OTP email" class="image--center mx-auto" width="2358" height="1460" loading="lazy"></p>
<p>After processing, Monnify sends an OTP to your registered email. Use it to authorize:</p>
<pre><code class="lang-bash">curl -X POST http://localhost:3008/api/payrolls/batch/authorize \
  -H <span class="hljs-string">"Content-Type: application/json"</span> \
  -d <span class="hljs-string">'{
    "reference": "BATCH_1702123456789",
    "authorizationCode": "123456",
    "payrollId": 1
  }'</span>
</code></pre>
<p>Check the payroll status:</p>
<pre><code class="lang-bash">curl http://localhost:3008/api/payrolls/1/status
</code></pre>
<p>This returns detailed status including a summary of completed, failed, and pending items.</p>
<p>Now, reconcile if needed – if webhooks were missed or you need to sync status:</p>
<pre><code class="lang-bash">curl -X POST http://localhost:3008/api/payrolls/1/reconcile
</code></pre>
<h2 id="heading-setting-up-webhooks-for-production">Setting Up Webhooks for Production</h2>
<p>For Monnify to send webhooks to your local development environment, you'll need to expose your local server. You can use ngrok:</p>
<pre><code class="lang-bash">ngrok http 3008
</code></pre>
<p>Then configure the webhook URL in your <a target="_blank" href="https://app.monnify.com/developer#webhook-urls">Monnify dashboard</a>:</p>
<pre><code class="lang-plaintext">https://your-ngrok-url.ngrok.io/api/monnify/webhook
</code></pre>
<p>For production, use your actual server URL and ensure HTTPS is enabled.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766392444369/440bc1a9-7c70-42b0-9157-892f1ef07861.png" alt="Monnify webhook URL configuration" class="image--center mx-auto" width="3024" height="1722" loading="lazy"></p>
<p>Then when transactions are successful it will be revealed on the monnify dashboard as well as the transactions that failed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766392958199/e8abaa75-5a2f-44fd-b322-b110cf71e92d.png" alt="Monnify dashboard with payroll transaction status" class="image--center mx-auto" width="3024" height="2476" loading="lazy"></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You've built a complete payroll system that:</p>
<ul>
<li><p>Manages employees with their bank account details</p>
</li>
<li><p>Creates payroll batches with automatic amount calculation</p>
</li>
<li><p>Processes bulk payments using Monnify's disbursement API</p>
</li>
<li><p>Uses background jobs to prevent request timeouts</p>
</li>
<li><p>Handles webhooks for real-time status updates</p>
</li>
<li><p>Supports reconciliation to ensure data consistency</p>
</li>
</ul>
<h3 id="heading-key-takeaways">Key Takeaways</h3>
<ol>
<li><p><strong>Background jobs are essential</strong>: Processing payments synchronously would timeout for large payrolls. Bull and Redis provide reliable async processing.</p>
</li>
<li><p><strong>Idempotency matters</strong>: Both the webhook handler and reconciliation process check current status before updating, preventing duplicate processing.</p>
</li>
<li><p><strong>Bulk transfers save time</strong>: Monnify's batch API lets you process hundreds of payments with a single OTP authorization.</p>
</li>
<li><p><strong>Status tracking is critical</strong>: The system tracks status at both the payroll and individual item level, making it easy to identify and handle failures.</p>
</li>
<li><p><strong>Reconciliation is your safety net</strong>: When webhooks fail or get delayed, the reconciliation endpoint ensures your database stays in sync with actual payment status.</p>
</li>
</ol>
<h3 id="heading-references">References:</h3>
<ul>
<li><a target="_blank" href="https://developers.monnify.com/">Monnify Docs</a></li>
</ul>
 ]]>
                </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[ Learn REST API Principles by Building an Express App ]]>
                </title>
                <description>
                    <![CDATA[ Web development revolves around communication – communication between browsers and servers, as well as frontend applications and backends. At the centre of this is the API. And the REST architecture has become a popular way to design APIs that are cl... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-rest-api-principles-by-building-an-express-app/</link>
                <guid isPermaLink="false">68066255e4a48bef99b3c63e</guid>
                
                    <category>
                        <![CDATA[ REST API ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ikegah Oliver ]]>
                </dc:creator>
                <pubDate>Mon, 21 Apr 2025 15:20:53 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1745003938509/442fd99e-d098-4a5a-98d7-94c4af9a5d55.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Web development revolves around communication – communication between browsers and servers, as well as frontend applications and backends. At the centre of this is the API. And the REST architecture has become a popular way to design APIs that are clean, consistent, and easy to use in web development.</p>
<p>REST works so well because it speaks the web’s native language. It uses familiar HTTP methods like <code>GET</code>, <code>POST</code>, <code>PUT</code>, and <code>DELETE</code>, treats data as resources, and follows clear conventions. All this makes it easy to understand, quick to implement, and widely supported, which is why most modern web APIs follow REST principles.</p>
<p>In this article, I will explore REST concepts and core principles while we build a simple Express application step by step. You will learn:</p>
<ul>
<li><p>What is REST architecture, and what are its advantages in web development?</p>
</li>
<li><p>The core REST principles (statelessness, resources, HTTP methods, and so on)</p>
</li>
<li><p>How to implement these principles in a real Express.js app</p>
</li>
<li><p>Best practices for designing clean and consistent APIs</p>
</li>
</ul>
<h3 id="heading-heres-what-we-will-cover">Here’s what we will cover:</h3>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-rest">What is REST</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-use-rest">Why use REST?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-core-principles-of-rest-architecture">Core Principles of REST Architecture</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-build-a-simple-express-app">Build a Simple Express App</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-express">What is Express?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-set-up-the-express-app">Set Up the Express App</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-build-restful-resources">Build RESTful Resources</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-middlewares">Middlewares</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-test-your-express-app">Test your Express App</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-bad-rest-practices-to-avoid">Bad REST Practices to Avoid</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-is-rest">What is REST?</h2>
<p>Representational State Transfer (REST) is a style of designing networked applications that emphasises a stateless client-server communication model centred around resources. Think of it like ordering at a restaurant: each time you ask for something, you have to tell the waiter exactly what you want, and they don't remember your previous orders.</p>
<p>RESTful APIs treat data as resources, each accessible through a unique web address (URI). Then, it leverages the standard actions defined by HTTP methods – POST to create new resources, GET to retrieve them, PUT to modify existing ones, and DELETE to remove them – providing a consistent and well-understood way to interact with data over the internet.</p>
<h2 id="heading-why-use-rest">Why Use REST?</h2>
<p>REST architectural principles offer a compelling set of advantages that contribute to building robust, scalable, and maintainable web services. Below are some of the key benefits that make REST a preferred choice for modern web development:</p>
<ul>
<li><p><strong>Simplicity and familiarity:</strong> REST leverages standard HTTP, which is already well-understood by developers and infrastructure.</p>
</li>
<li><p><strong>Scalability:</strong> The stateless nature of REST allows for easy scaling of both client and server components independently.</p>
</li>
<li><p><strong>Flexibility and interoperability:</strong> RESTful APIs can be consumed by a wide variety of clients, regardless of the technology stack.</p>
</li>
<li><p><strong>Cacheability:</strong> REST's design supports caching mechanisms, leading to improved performance and reduced server load.</p>
</li>
<li><p><strong>Loose coupling:</strong> The client and server are independent, allowing for changes on either side without necessarily affecting the other.</p>
</li>
<li><p><strong>Visibility and monitoring:</strong> The straightforward nature of HTTP requests and responses makes it easier to monitor and debug interactions.</p>
</li>
</ul>
<h2 id="heading-core-principles-of-rest-architecture">Core Principles of REST Architecture</h2>
<p>REST (Representational State Transfer) is built on a few simple principles that make APIs easy to understand and use. Here’s a quick breakdown of the key ones:</p>
<h3 id="heading-statelesness">Statelesness</h3>
<p>Every request from the client to the server must contain all the information needed to process it. The server doesn’t store anything about the client between requests – no session, no memory of previous actions.</p>
<p><strong>Example:</strong> If a user sends <code>GET /movies/1</code>, the server returns the movie data without needing to know whether the user is logged in or what they requested before.</p>
<p>This makes APIs easier to scale, since each request can be handled independently.</p>
<h3 id="heading-resource-and-uris">Resource and URIs</h3>
<p>In REST, everything you work with is considered a resource – users, products, and so on. Each resource should be accessible via a unique, meaningful URL.</p>
<p><strong>Example:</strong></p>
<ul>
<li><p><code>/movies</code> – a collection of movie resources</p>
</li>
<li><p><code>/movies/42</code> – a specific movie with ID 42</p>
</li>
</ul>
<p>Resources are treated like nouns. Actions are determined by the HTTP method used.</p>
<h3 id="heading-standard-http-methods">Standard HTTP Methods</h3>
<p>REST takes full advantage of HTTP methods to describe what action you’re taking on a resource:</p>
<ul>
<li><p><code>GET</code> – retrieve data</p>
</li>
<li><p><code>POST</code> – create a new resource</p>
</li>
<li><p><code>PUT</code> – update or replace a resource</p>
</li>
<li><p><code>PATCH</code> – partially update a resource</p>
</li>
<li><p><code>DELETE</code> – remove a resource</p>
</li>
</ul>
<p><strong>Example:</strong> To delete a movie, you’d send a <code>DELETE</code> request to <code>/movies/42</code>. That’s clear, consistent, and intuitive.</p>
<h3 id="heading-uniform-interface">Uniform Interface</h3>
<p>REST enforces a consistent structure for communication between client and server. This means all REST APIs should behave similarly, no matter who built them. It includes:</p>
<ul>
<li><p>Using URIs to identify resources</p>
</li>
<li><p>Using standard HTTP methods</p>
</li>
<li><p>Representing data in formats like JSON or XML</p>
</li>
<li><p>Self-descriptive messages (for example, proper status codes and headers)</p>
</li>
</ul>
<p>This consistency makes it easier for developers to understand and integrate with RESTful APIs.</p>
<h3 id="heading-cacheability">Cacheability</h3>
<p>Servers should label responses as cacheable (stored to be retrieved later) or not, so clients can reuse responses when appropriate. This reduces unnecessary server load and improves performance.</p>
<p><strong>Example:</strong> A <code>GET /movies</code> response can be cached for 5 minutes if the data doesn’t change frequently. That means fewer repeated calls for the same info.</p>
<h3 id="heading-client-server-separation">Client-Server Separation</h3>
<p>The client (frontend) and server (backend) operate independently. The client just needs to know how to communicate with the API – it doesn’t care how the server handles data, and vice versa.</p>
<p>This separation allows teams to develop and scale frontend and backend systems separately.</p>
<p>The principles above help create APIs that are scalable, predictable, and easy to work with.</p>
<h2 id="heading-how-to-build-a-simple-express-app">How to Build a Simple Express App</h2>
<h3 id="heading-what-is-express">What is Express?</h3>
<p>Express.js is a lightweight and flexible Node.js web application framework. Built on top of Node.js, it provides a robust set of features for building single-page, multi-page, and hybrid web applications and APIs. Think of it as a helpful toolkit that streamlines the process of setting up and managing web servers and routing requests.</p>
<p>In this exercise, you will be building an Express app that will:</p>
<ol>
<li><p>Handle a simple in-memory collection of movies as a RESTful resource</p>
</li>
<li><p>Support basic CRUD operations using the appropriate HTTP methods (GET, POST, PUT, DELETE)</p>
</li>
<li><p>Parse incoming JSON requests using built-in middleware</p>
</li>
<li><p>Use a custom middleware function to validate movie input before creating or updating entries</p>
</li>
<li><p>Send clear and meaningful responses based on the outcome of each request</p>
</li>
</ol>
<p>By the end, you’ll have a working API that follows REST principles and can be tested using a tool like Thunder Client or Postman.</p>
<h3 id="heading-set-up-the-express-app">Set Up the Express App</h3>
<p>To get the most out of this exercise, there are a few tools and concepts you should already be familiar with. Since we’re focusing on REST principles and how to apply them using Express, we won’t dive deep into the basics of these prerequisites. Make sure you’re comfortable with the following:</p>
<ul>
<li><p>Node.js</p>
</li>
<li><p>Npm</p>
</li>
<li><p>Thunderclient extension</p>
</li>
<li><p>Basic JavaScript</p>
</li>
</ul>
<p>With that, let’s get started.</p>
<p>Open your command prompt and create a new directory (folder):</p>
<pre><code class="lang-bash">mkdir express-app
</code></pre>
<p>Navigate into your new directory:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> express-app
</code></pre>
<p>Initialize an npm project:</p>
<pre><code class="lang-bash">npm init -y
</code></pre>
<p>Install the Express package in your project:</p>
<pre><code class="lang-bash">npm install express
</code></pre>
<p>Now, open your directory in your code editor with the following command:</p>
<pre><code class="lang-bash">code .
</code></pre>
<p>Create a new file, server.js, and set up your Express app in that file:</p>
<pre><code class="lang-javascript"><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> app = express();
app.use(express.json());

app.listen(<span class="hljs-number">8000</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Server running on port 8000'</span>));
</code></pre>
<p>This snippet above sets up a basic Express server. It starts by importing the Express library you installed, enables JSON parsing for incoming requests (so we can work with request bodies), and listens on port 3000. It’s ready to handle RESTful routes like <code>GET</code>, <code>POST</code>, <code>PUT</code>, and <code>DELETE</code> as we build out our API.</p>
<p>To start your server, go back to your command prompt and type in this command:</p>
<pre><code class="lang-bash">node server.js
</code></pre>
<p>You should see this logged in your command prompt:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1744840192457/818bc1a4-3e96-4494-9895-d02cf816e3f3.png" alt="A screenshot of a command prompt or terminal window. The prompt shows the current directory as &quot;C:sersSEResktopest-tutorial>&quot;. The command &quot;node server.js&quot; has been executed, and the output below it reads &quot;Server is running on port 8000&quot;." width="600" height="400" loading="lazy"></p>
<h3 id="heading-build-restful-resources">Build RESTful Resources</h3>
<p>With our Express app set up, let’s build out the <code>/movies</code> resource using RESTful routes. We'll treat each movie as a resource and use HTTP methods to define what we want to do – retrieve, add, update, or delete movies. For simplicity, we'll store the movies in an in-memory array.</p>
<p>Here is the full set of routes. Add it in your server.js file just under your <code>app.use(express.json());</code> line:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// In-memory database (for demonstration purposes)</span>
<span class="hljs-comment">// In a real application, you would use a database like MongoDB or PostgreSQL</span>
<span class="hljs-keyword">const</span> movies = [];

<span class="hljs-comment">// Get all movies</span>
app.get(<span class="hljs-string">'/movies'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.json(movies);
  <span class="hljs-built_in">console</span>.log(movies);
});

<span class="hljs-comment">// Get a particular movie by ID</span>
app.get(<span class="hljs-string">'/movies/:id'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> movie = movies.find(<span class="hljs-function"><span class="hljs-params">m</span> =&gt;</span> m.id === <span class="hljs-built_in">parseInt</span>(req.params.id));
  <span class="hljs-keyword">if</span> (!movie) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">404</span>).send(<span class="hljs-string">'Movie not found'</span>);
  res.json(movie);
});

<span class="hljs-comment">// Add a new movie</span>
app.post(<span class="hljs-string">'/movies'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> movie = {
    <span class="hljs-attr">id</span>: movies.length + <span class="hljs-number">1</span>,
    <span class="hljs-attr">title</span>: req.body.title,
    <span class="hljs-attr">genre</span>: req.body.genre,
    <span class="hljs-attr">year</span>: req.body.year
  };
  movies.push(movie);
  res.status(<span class="hljs-number">201</span>).json(movie);
});

<span class="hljs-comment">// Update a movie</span>
app.put(<span class="hljs-string">'/movies/:id'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> movie = movies.find(<span class="hljs-function"><span class="hljs-params">m</span> =&gt;</span> m.id === <span class="hljs-built_in">parseInt</span>(req.params.id));
  <span class="hljs-keyword">if</span> (!movie) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">404</span>).send(<span class="hljs-string">'Movie not found'</span>);

  movie.title = req.body.title;
  movie.genre = req.body.genre;
  movie.year = req.body.year;

  res.json(movie);
});

<span class="hljs-comment">// Delete a movie</span>
app.delete(<span class="hljs-string">'/movies/:id'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> movieIndex = movies.findIndex(<span class="hljs-function"><span class="hljs-params">m</span> =&gt;</span> m.id === <span class="hljs-built_in">parseInt</span>(req.params.id));
  <span class="hljs-keyword">if</span> (movieIndex === <span class="hljs-number">-1</span>) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">404</span>).send(<span class="hljs-string">'Movie not found'</span>);

  <span class="hljs-keyword">const</span> deletedMovie = movies.splice(movieIndex, <span class="hljs-number">1</span>);
  res.json(deletedMovie);
});
</code></pre>
<p>The code above defines a complete set of RESTful routes for managing a movies resource using Express.</p>
<p>It begins with an in-memory array, <code>movies</code>, which acts as our temporary data store. The <code>GET /movies</code> route returns all books, while <code>GET /movies/:id</code> looks up a book by its ID using <code>Array.find()</code>, returning a 404 status if it's not found. The <code>POST /movies</code> route accepts JSON input, creates a new book with an auto-incremented ID, and adds it to the array, returning the new resource with a <code>201 Created</code> status.</p>
<p>The <code>PUT /movies/:id</code> route handles full updates. It first finds the book, and if found, updates its <code>title</code> , <code>genre</code>, and <code>year</code> with the new values from the request body. The <code>DELETE /movies/:id</code> route removes a movie by finding its index in the array and using <code>splice()</code>. If the movie doesn't exist, both PUT and DELETE return a 404 error.</p>
<p>These routes demonstrate idempotency – that is, sending the same PUT or DELETE request multiple times will have the same effect as sending it once, a key REST principle. Each route also returns appropriate HTTP status codes and JSON responses, following REST conventions closely.</p>
<p>Each route follows a REST principle:</p>
<ul>
<li><p>Uses nouns for endpoints (<code>/movies</code>)</p>
</li>
<li><p>Uses standard HTTP methods to express actions</p>
</li>
<li><p>Ensure idempotency where appropriate (PUT, DELETE)</p>
</li>
<li><p>Return appropriate status codes and messages</p>
</li>
</ul>
<p>This structure keeps your API predictable and easy to use – exactly what REST is all about.</p>
<h3 id="heading-middlewares">Middlewares</h3>
<p>In RESTful APIs built with frameworks like Express, middleware plays a key role in maintaining clean, modular, and consistent request handling.</p>
<p>Middlewares are functions that sit in the middle of the request-response cycle in an Express app. When a client sends a request, middleware functions have access to the <code>req</code> (request), <code>res</code> (response), and <code>next</code> objects. They can inspect, modify, or act on the request before it reaches the route handler or even terminate the response early.</p>
<p>You have already seen a middleware here: <code>app.use(express.json());</code>. This is a global middleware that is used for parsing JSON. We will be creating a custom middleware for validating input for our POST and PUT requests.</p>
<p>Add the following code in your server.js file just before your routes:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Middleware for simple validation</span>
<span class="hljs-keyword">const</span> validateMovie = <span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (!req.body.title || !req.body.genre || !req.body.year) {
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).send(<span class="hljs-string">'Title, genre, and year are required'</span>);
  }
  next();
};
</code></pre>
<p>This middleware function, <code>validateMovie</code>, performs basic validation on incoming requests before they reach the route handler. It checks if the <code>title</code>, <code>genre</code>, and <code>year</code> fields are present in the request body. If any of these fields are missing, it immediately responds with a <code>400 Bad Request</code> status and an error message. If all required fields are provided, it calls <code>next()</code> to pass control to the next middleware or route. This keeps validation logic separate and reusable, helping maintain clean and RESTful route handlers.</p>
<p>To use this middleware, pass it as an argument in your POST and PUT routes, for example example:</p>
<pre><code class="lang-javascript">app.post(<span class="hljs-string">'/movies'</span>, validateMovie, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> movie = {
    <span class="hljs-attr">id</span>: movies.length + <span class="hljs-number">1</span>,
    <span class="hljs-attr">title</span>: req.body.title,
    <span class="hljs-attr">genre</span>: req.body.genre,
    <span class="hljs-attr">year</span>: req.body.year
  };
  movies.push(movie);
  res.status(<span class="hljs-number">201</span>).json(movie);
});
</code></pre>
<h3 id="heading-test-your-express-app">Test Your Express App</h3>
<p>To test the changes you have made, you need to restart your server. Go to your command prompt and press CTRL + C (for Windows) or Command + . (for MacOS). Input the start command like before to start the server again.</p>
<p>For this exercise, you will be testing the endpoints with the Thunder Client extension on VSCode. Thunder Client is a lightweight REST API extension for VSCode. With Thunder Client, you can your REST API routes and endpoints directly in VSCode.</p>
<p>To download Thunder Client, click on the Extension icon on the taskbar on your left and search “Thunder Client”. Then click Install:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1744234771851/a5d7a3e4-384b-47e9-8b2d-47bc45a2007d.png" alt="A screenshot of the Extensions Marketplace in Visual Studio Code (VS Code). The search bar at the top contains the text &quot;Thunder&quot;. Below the search bar, a list of extensions related to &quot;Thunder&quot; is displayed. The first result, &quot;Thunder Client,&quot; shows an install button. A red arrow points to the Extensions icon in the VS Code activity bar on the left, which is highlighted to indicate that this is the icon to click." width="600" height="400" loading="lazy"></p>
<p>After installation, you'll see the Thunder Client icon appear in your sidebar below the Extensions icon. Click it, then hit New Request to open a new tab. The request tab will open with a clean layout, ready for you to send and test API calls:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1744282644259/dfe4b735-c465-4993-bc46-cce28966deb3.png" alt="A screenshot of the Thunder Client interface within Visual Studio Code. A new request tab is open, displaying a GET request configured to the URL &quot;https://www.thunderclient.com/welcome&quot;. The HTTP method is set to &quot;GET&quot; in a dropdown menu. Below the URL bar, tabs for &quot;Query,&quot; &quot;Headers,&quot; &quot;Auth,&quot; &quot;Body,&quot; &quot;Tests,&quot; and &quot;Pre Run&quot; are visible, with &quot;Query&quot; currently selected, showing a section for &quot;Query Parameters&quot; with fields for &quot;parameter&quot; and &quot;value&quot;. At the bottom, a message indicates &quot;Non-Commercial Use&quot; with a link to view terms. The left sidebar shows &quot;THUNDER CLIENT&quot; with options like &quot;New Request,&quot; &quot;Activity,&quot; and &quot;Collections." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>To start testing your API, set the request method (GET, POST, PUT, or DELETE) from the dropdown and enter the appropriate route, like <code>http://localhost:3000/movies</code>. For GET requests, just hit Send and you should see a response, in this case, an empty array <code>[]</code>. To get a specific movie, you include an ID (for example, <code>/movies/1</code>).</p>
<p>For <code>POST</code>, <code>PUT</code>, and <code>DELETE</code> requests, switch to the Body tab and select the JSON format. Then provide the data you want to send:</p>
<ul>
<li><p><strong>POST</strong> <code>/movies</code>: Add a new movie with <code>{"title": "Movie Title", "genre": "Genre Name", "year": 0000}</code>.</p>
</li>
<li><p><strong>PUT</strong> <code>/movies/1</code>: Update an existing movie with the same JSON structure.</p>
</li>
<li><p><strong>DELETE</strong> <code>/movies/1</code>: Remove a movie by its ID — no body is needed.</p>
</li>
</ul>
<p>After sending each request, Thunder Client will display the response body, headers, and status code. Example:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1744840699502/4ea83fbc-7b47-4aea-b8ff-a6e351bf54be.png" alt="A screenshot of the Thunder Client interface in Visual Studio Code, showing a successful POST request. On the left, the request details are visible: the HTTP method is set to &quot;POST&quot; with the URL &quot;http://localhost:8000/movies&quot;. The &quot;Body&quot; tab is selected, displaying JSON content: {&quot;title&quot;: &quot;Home Alone&quot;, &quot;genre&quot;: &quot;Comedy&quot;, &quot;year&quot;: 1999}. On the right, the response details indicate a &quot;Status: 201 Created&quot;, &quot;Size: 58 Bytes&quot;, and &quot;Time: 5 ms&quot;. The &quot;Response&quot; tab is selected, showing the JSON response: {&quot;id&quot;: 1, &quot;title&quot;: &quot;Home Alone&quot;, &quot;genre&quot;: &quot;Comedy&quot;, &quot;year&quot;: 1999}." width="600" height="400" loading="lazy"></p>
<h2 id="heading-bad-rest-practices-to-avoid">Bad REST Practices to Avoid</h2>
<p>When building REST APIs, aside from using the right HTTP methods, you also have to consider avoiding practices that break the core principles of REST. These bad practices can make your API harder to use, less predictable, or even misleading.</p>
<p>Here are some common REST bad practices and how to avoid them:</p>
<h3 id="heading-using-verbs-in-endpoints"><strong>Using verbs in endpoints</strong></h3>
<p>Avoid routes like <code>/getMovies</code> or <code>/createMovie</code>. RESTful APIs rely on HTTP methods to express actions, so use nouns for endpoints and let methods do the talking — for example, use <code>GET /movies</code> to retrieve movies and <code>POST /movie</code> to create one.</p>
<h3 id="heading-ignoring-http-status-codes"><strong>Ignoring HTTP status codes</strong></h3>
<p>Returning <code>200 OK</code> for every response, even errors, breaks REST conventions. Use the appropriate status codes: <code>201 Created</code> for successful POSTs, <code>404 Not Found</code> when a resource doesn’t exist, and <code>400 Bad Request</code> for validation issues. This helps clients interpret responses correctly.</p>
<h3 id="heading-overloading-a-single-endpoint"><strong>Overloading a single endpoint</strong></h3>
<p>Avoid writing one endpoint that changes behaviour based on the request body or headers. Each route should clearly map to a resource and method, like <code>GET /movies/1</code> to fetch, and <code>DELETE /movies/1</code> to remove, making the API predictable and easy to follow.</p>
<h3 id="heading-not-being-idempotent-where-expected"><strong>Not being idempotent where expected</strong></h3>
<p><code>PUT</code> and <code>DELETE</code> should be idempotent, meaning repeated requests should have the same effect. If calling <code>DELETE /movies/1</code> twice causes an error or unexpected behaviour, that’s a red flag. Design your handlers to handle these cases gracefully.</p>
<h3 id="heading-exposing-internal-logic-or-database-structure"><strong>Exposing internal logic or database structure</strong></h3>
<p>Don’t leak internal details like database table names or query logic in your route naming (<code>/api/movie_table_data</code>). Keep your URIs clean, abstracted, and centred around the actual resource, like <code>/movies</code>.</p>
<p>Avoiding these practices not only keeps your API RESTful but also improves developer experience, consistency, and long-term maintainability.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You have explored the core ideas behind REST and put them into practice by building and testing a real Express API. This hands-on approach should help bridge the gap between theory and real-world application.</p>
<p>Along the way, you learned how REST treats resources, how to write clean and predictable routes, and why these principles matter when designing APIs that are easy to use and maintain.</p>
<p>Here’s a quick recap of what you’ve built and learned:</p>
<ul>
<li><p>Defined what REST is and why it’s widely used in web development</p>
</li>
<li><p>Explored core REST principles like statelessness, resource-based routing, HTTP methods, status codes, and idempotency</p>
</li>
<li><p>Set up a simple Express server to serve as the base for a RESTful API</p>
</li>
<li><p>Built a complete set of routes (GET, POST, PUT, DELETE) for managing a <code>movies</code> resource</p>
</li>
<li><p>Used middleware for tasks like JSON parsing and input validation</p>
</li>
<li><p>Tested routes using Thunder Client and learned how to interact with each HTTP method</p>
</li>
<li><p>Identified common REST anti-patterns and how to avoid them for cleaner design</p>
</li>
</ul>
<p>To take your API further and follow more advanced RESTful practices, consider:</p>
<ul>
<li><p>Organising routes with Express Router to keep your code modular</p>
</li>
<li><p>Adding robust error handling to return consistent and informative responses</p>
</li>
<li><p>Using logging tools like <code>morgan</code> to monitor requests and debug more easily</p>
</li>
<li><p>Securing endpoints with authentication methods like JWT or API keys</p>
</li>
<li><p>Structuring responses consistently, possibly with pagination and filtering for larger datasets</p>
</li>
<li><p>Validating user input thoroughly with libraries like <code>Joi</code> or <code>express-validator</code></p>
</li>
</ul>
<p>Explore the full project on <a target="_blank" href="https://github.com/oliverTwist2/rest-tutorial">GitHub</a>, review the code, and try extending it with your features. Happy coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Video Subtitle Generator using the Gemini API ]]>
                </title>
                <description>
                    <![CDATA[ In this tutorial, you'll build an AI-powered subtitle generator using Google's Gemini API. We'll create a project called “AI-Subtitle-Generator” using React for the front end and Express for the back end. Get ready for a fun and practical project. Ta... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-video-subtitle-generator-using-the-gemini-api/</link>
                <guid isPermaLink="false">6759af8bb972ec12da4879d3</guid>
                
                    <category>
                        <![CDATA[ gemini ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Sanjay ]]>
                </dc:creator>
                <pubDate>Wed, 11 Dec 2024 15:28:11 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733638398422/2f468b16-5801-4f8c-bf40-c24d07e219b7.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this tutorial, you'll build an AI-powered subtitle generator using Google's Gemini API. We'll create a project called “AI-Subtitle-Generator” using React for the front end and Express for the back end. Get ready for a fun and practical project.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-get-your-api-key">How to Get Your API Key</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-project-setup">Project Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-front-end-setup">Front End Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-server-setup">Server Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-update-the-front-end">Update the Front End</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-summary">Summary</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>To build this project, you should know the basics of React and Express.</p>
<h2 id="heading-what-is-the-gemini-api">What is the Gemini API?</h2>
<p>Google's Gemini API is a powerful tool that lets you integrate advanced AI capabilities into your applications. Gemini is a multimodal model, which means you can use various types of input, like text, images, audio, and video.</p>
<p>It’s good at analyzing and processing large amounts of text as well as pulling information from videos – which makes it great for our use case of a subtitle generator.</p>
<h2 id="heading-how-to-get-your-api-key">How to Get Your API Key</h2>
<p>An API key acts as a unique identifier and authenticates your requests to the service. It's essential for accessing and using Gemini AI’s capabilities. This key will allow our application to communicate with Gemini and help us build our project.</p>
<p>Go to <a target="_blank" href="https://aistudio.google.com/prompts/new_chat">Google AI Studio</a>, then click “Get API Key”:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733571839232/f5636fd0-c3cd-4c1b-bf7f-5200bce41444.png" alt="Screenshot of Google AI Studio showing the 'Get API Key' button" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>After you are redirected to the API KEY page, click “Create API Key“:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733572045638/c950f7a2-613c-4976-905a-ce5c9dceb901.png" alt="Screenshot showing how to create an API key in Google AI Studio." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>A new API KEY will be created. Then make sure you copy the key.</p>
<p>This is your API key. This key is used to authenticate your application's requests to the Gemini API. Each time your application sends a request to Gemini, this key must be included. Gemini uses this key to verify that the request is coming from an authorized source. Without this API key, your requests will be rejected, and you won't be able to access Gemini's services.</p>
<h2 id="heading-project-setup">Project Setup</h2>
<p>Start by creating a new folder for your project. Let's call it <code>ai-subtitle-generator</code>.</p>
<p>Inside the <code>ai-subtitle-generator</code> folder, create two subfolders: <code>client</code> and <code>server</code>. The <code>client</code> folder will contain the React frontend, and the <code>server</code> folder will contain the Express backend.</p>
<h2 id="heading-front-end-setup">Front End Setup</h2>
<p>First, we will focus on the front end and set up a basic React application.</p>
<p>Navigate to the <code>client</code> folder:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> client
</code></pre>
<p>Then create a new React project using Vite. To do that, run the following command:</p>
<pre><code class="lang-bash">npm create vite@latest .
</code></pre>
<p>When prompted, choose “React“. Select “React + TS” or “React + JS”. In this tutorial, I will use React + TS. You can also follow along with JS.</p>
<p>Next, install the dependencies with this command:</p>
<pre><code class="lang-bash">npm install
</code></pre>
<p>Then start the development server:</p>
<pre><code class="lang-bash">npm run dev
</code></pre>
<h4 id="heading-how-to-handle-file-uploads-in-the-frontend">How to Handle File Uploads in the Frontend</h4>
<p>Now in <code>client/src/App.tsx</code>, add the following code:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">//  client/src/App.tsx</span>

<span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> handleSubmit = <span class="hljs-keyword">async</span> (e: React.FormEvent&lt;HTMLFormElement&gt;): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; =&gt; {
    e.preventDefault();
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> formData = <span class="hljs-keyword">new</span> FormData(e.currentTarget);
      <span class="hljs-built_in">console</span>.log(formData)
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.log(error);
    }
  };

  <span class="hljs-keyword">return</span> (
    &lt;div&gt;
      &lt;form onSubmit={handleSubmit}&gt;
        &lt;input <span class="hljs-keyword">type</span>=<span class="hljs-string">"file"</span> accept=<span class="hljs-string">"video/*,.mkv"</span> name=<span class="hljs-string">"video"</span> /&gt;
        &lt;input <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span> /&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>In the above code, we have used an input tag that will accept the video and name it as <code>video</code>. This name will be appended to the <code>FormData</code> object.</p>
<p>While sending the video to the server, we need to send it as a key-value pair, where the key is a <code>video</code> and the value is the file data.</p>
<p>Why key-value pairs? Because when the server receives the request, it needs to parse the incoming chunks. After parsing, the video data will be available in <code>req.files[key]</code>, where the <code>key</code> is the name we have assigned in the frontend (<code>video</code> in this case).</p>
<p>This is why we are using the <code>FormData</code> object. When we create a new <code>FormData</code> instance and pass <code>e.target</code> to it, all the form fields and their names will automatically be available as key-value pairs.</p>
<h2 id="heading-server-setup">Server Setup</h2>
<p>Now that we have our API key, let's set up the backend server. This server will handle video uploads from the frontend and communicate with the Gemini API for subtitle generation.</p>
<p>Navigate to <code>server</code> folder:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> server
</code></pre>
<p>And initialize the project:</p>
<pre><code class="lang-bash">npm init -y
</code></pre>
<p>Then install the necessary packages:</p>
<pre><code class="lang-bash">npm install express dotenv cors @google/generative-ai express-fileupload nodemon
</code></pre>
<p>These are the back-end dependencies we’re using in this project:</p>
<ul>
<li><p><code>express</code><strong>:</strong> The web framework for creating the backend API.</p>
</li>
<li><p><code>dotenv</code><strong>:</strong> Loads environment variables from a <code>.env</code> file.</p>
</li>
<li><p><code>cors</code><strong>:</strong> Enables Cross-Origin Resource Sharing, allowing your frontend to communicate with your backend.</p>
</li>
<li><p><code>@google/generative-ai</code><strong>:</strong> The Google AI library for interacting with the Gemini API.</p>
</li>
<li><p><code>express-fileupload</code><strong>:</strong> Handles file uploads, making it easy to access uploaded files on the server.</p>
</li>
<li><p><code>nodemon</code><strong>:</strong> Automatically restarts the server when you make changes to your code.</p>
</li>
</ul>
<h3 id="heading-set-up-the-environment-variables">Set Up the Environment Variables</h3>
<p>Now, create a file called <code>.env</code>. This is where you’ll manage your API keys.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//.env</span>
API_KEY = YOUR_API_API
PORT = <span class="hljs-number">3000</span>
</code></pre>
<h3 id="heading-update-the-packagejson">Update the <code>package.json</code></h3>
<p>For this project, we are using ES6 modules instead of CommonJS. To enable this, update your <code>package.json</code> file with the following code:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"server"</span>,
  <span class="hljs-attr">"version"</span>: <span class="hljs-string">"1.0.0"</span>,
  <span class="hljs-attr">"main"</span>: <span class="hljs-string">"index.js"</span>,
  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"module"</span>,       <span class="hljs-comment">//Add "type": "module" to enable ES6 modules</span>
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"node server.js"</span>,
    <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"nodemon server.js"</span>    <span class="hljs-comment">//configure nodemon</span>
  },
  <span class="hljs-attr">"keywords"</span>: [],
  <span class="hljs-attr">"author"</span>: <span class="hljs-string">""</span>,
  <span class="hljs-attr">"license"</span>: <span class="hljs-string">"ISC"</span>,
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">""</span>,
  <span class="hljs-attr">"dependencies"</span>: {
    <span class="hljs-attr">"@google/generative-ai"</span>: <span class="hljs-string">"^0.21.0"</span>,
    <span class="hljs-attr">"cors"</span>: <span class="hljs-string">"^2.8.5"</span>,
    <span class="hljs-attr">"dotenv"</span>: <span class="hljs-string">"^16.4.7"</span>,
    <span class="hljs-attr">"express"</span>: <span class="hljs-string">"^4.21.1"</span>,
    <span class="hljs-attr">"express-fileupload"</span>: <span class="hljs-string">"^1.5.1"</span>,
    <span class="hljs-attr">"nodemon"</span>: <span class="hljs-string">"^3.1.7"</span>
  }
}
</code></pre>
<h3 id="heading-basic-setup-of-express">Basic Setup of Express</h3>
<p>Create a file <code>server.js</code>. Now, let’s set up a basic Express application.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//  server/server.js</span>

<span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">"express"</span>;
<span class="hljs-keyword">import</span> { configDotenv } <span class="hljs-keyword">from</span> <span class="hljs-string">"dotenv"</span>;
<span class="hljs-keyword">import</span> fileUpload <span class="hljs-keyword">from</span> <span class="hljs-string">"express-fileupload"</span>;
<span class="hljs-keyword">import</span> cors <span class="hljs-keyword">from</span> <span class="hljs-string">"cors"</span>

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

configDotenv();           <span class="hljs-comment">//configure the env</span>
app.use(fileUpload());    <span class="hljs-comment">//it will parse the mutipart data</span>
app.use(express.json());  <span class="hljs-comment">// Enable JSON parsing for request bodies</span>
app.use(cors())           <span class="hljs-comment">//configure cors</span>

app.use(<span class="hljs-string">"/api/subs"</span>,subRoutes);  <span class="hljs-comment">// Use routes for the "/api/subs" endpoint</span>

app.listen(process.env.PORT, <span class="hljs-function">() =&gt;</span> {   <span class="hljs-comment">//access the PORT from the .env</span>
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"server started"</span>);         
});
</code></pre>
<p>In this code, we create an Express app instance and then load our environment variables. This is where we keep sensitive data like API keys secure. Next, we apply middleware functions: <code>fileUpload</code> prepares the server to receive uploaded videos, <code>express.json</code> allows us to receive JSON data, and <code>cors</code> enables communication between our frontend and backend.</p>
<p>We define a route <code>(/api/subs)</code> that will handle all requests related to subtitle generation. The specific logic for these routes will be defined in <code>subs.routes.js</code>. Finally, we start the server, telling it to listen for requests on the port specified in our <code>.env</code> file.</p>
<p>Now we need to create some folders to manage the code. You can also manage the entire code in a single file, but structuring it into separate folders and managing them all that way will be easier.</p>
<p>This is the final folder structure for the server:</p>
<pre><code class="lang-plaintext">server/
├── server.js
├── controller/
│   └── subs.controller.js
├── gemini/
│   ├── gemini.config.js
├── routes/
│   └── subs.routes.js
├── uploads/
├── utils/
│   ├── fileUpload.js
│   └── genContent.js
└── .env
</code></pre>
<p><strong>Note:</strong> Don’t worry about creating this folder structure now. This is just for reference. Follow along with me step by step, and we will build this structure together.</p>
<h3 id="heading-create-the-routes">Create the Routes</h3>
<p>Now create a <code>routes</code> folder and then create <code>subs.routes.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// server/routes/sub.routes.js</span>

<span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">"express"</span>
<span class="hljs-keyword">import</span> { uploadFile } <span class="hljs-keyword">from</span> <span class="hljs-string">"../controller/subs.controller.js"</span>    <span class="hljs-comment">// import the uploadFile function from the controller folder</span>

<span class="hljs-keyword">const</span> router = express.Router()

router.post(<span class="hljs-string">"/"</span>,uploadFile)    <span class="hljs-comment">// define a POST route that calls the uploadFile function</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> router     <span class="hljs-comment">// export the router to use in the main server.js file</span>
</code></pre>
<p>This code defines the routes for our server, specifically the route that handles video uploads and subtitle generation.</p>
<p>We create a new router instance using <code>express.Router()</code>. This allows us to define routes separate from our main server file, improving code organization. We define a POST route at the root path <code>("/")</code> of our API endpoint. When a POST request is made to this route (which will happen when a user submits the video upload form on the frontend), the <code>uploadFile</code> function is called. This function will handle the actual upload and subtitle generation.</p>
<p>Finally, we export the router so that it can be used in our main server file <code>(server.js)</code> to connect this route to the main application.</p>
<h3 id="heading-configure-gemini">Configure Gemini</h3>
<p>Now, let's configure how our application will interact with Gemini.</p>
<p>Create a <code>gemini</code> folder and then create a new file called <code>gemini.config.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//  server/gemini/gemini.config.js</span>

<span class="hljs-keyword">import</span> {
  GoogleGenerativeAI,
  HarmBlockThreshold,
  HarmCategory,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@google/generative-ai"</span>;
<span class="hljs-keyword">import</span> { configDotenv } <span class="hljs-keyword">from</span> <span class="hljs-string">"dotenv"</span>;
configDotenv();

<span class="hljs-keyword">const</span> genAI = <span class="hljs-keyword">new</span> GoogleGenerativeAI(process.env.API_KEY);  <span class="hljs-comment">// Initialize Google Generative AI with the API key</span>

<span class="hljs-keyword">const</span> safetySettings = [
  {
    <span class="hljs-attr">category</span>: HarmCategory.HARM_CATEGORY_HARASSMENT,
    <span class="hljs-attr">threshold</span>: HarmBlockThreshold.BLOCK_NONE,
  },
  {
    <span class="hljs-attr">category</span>: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
    <span class="hljs-attr">threshold</span>: HarmBlockThreshold.BLOCK_NONE,
  },
  {
    <span class="hljs-attr">category</span>: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
    <span class="hljs-attr">threshold</span>: HarmBlockThreshold.BLOCK_NONE,
  },
  {
    <span class="hljs-attr">category</span>: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
    <span class="hljs-attr">threshold</span>: HarmBlockThreshold.BLOCK_NONE,
  },
];

<span class="hljs-keyword">const</span> model = genAI.getGenerativeModel({
  <span class="hljs-attr">model</span>: <span class="hljs-string">"gemini-1.5-flash-001"</span>,    <span class="hljs-comment">//choose the model</span>
  <span class="hljs-attr">safetySettings</span>: safetySettings,   <span class="hljs-comment">//optional safety settings</span>
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> model;    <span class="hljs-comment">//export the model</span>
</code></pre>
<p>In the code above, the <code>safetySettings</code> are optional. These settings allow you to define thresholds for potentially harmful content (like hate speech, violence, or explicit material) in Gemini's output.</p>
<p>You can read more about Gemini’s safety settings <a target="_blank" href="https://ai.google.dev/gemini-api/docs/safety-settings">here</a>.</p>
<h3 id="heading-create-a-controller-to-handle-endpoint-logic">Create a Controller to Handle Endpoint Logic</h3>
<p>Now, create a <code>controller</code> folder, and inside it create a file named <code>subs.controller.js</code>. In this file, you'll handle the endpoint logic for interacting with the Gemini model.</p>
<p>In <code>server/controller/subs.controller.js</code>, add this code:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// server/controller/subs.controller.js</span>

<span class="hljs-keyword">import</span> { fileURLToPath } <span class="hljs-keyword">from</span> <span class="hljs-string">"url"</span>;
<span class="hljs-keyword">import</span> path <span class="hljs-keyword">from</span> <span class="hljs-string">"path"</span>;
<span class="hljs-keyword">import</span> fs <span class="hljs-keyword">from</span> <span class="hljs-string">"fs"</span>;

<span class="hljs-keyword">const</span> __filename = fileURLToPath(<span class="hljs-keyword">import</span>.meta.url);  <span class="hljs-comment">//converts the module URL to a file path</span>
<span class="hljs-keyword">const</span> __dirname = path.dirname(__filename);   <span class="hljs-comment">//get the current file directory</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> uploadFile = <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">if</span> (!req.files || !req.files.video) {   <span class="hljs-comment">//if there is no file available, return error to the client</span>
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"No video uploaded"</span> });
    }

    <span class="hljs-keyword">const</span> videoFile = req.files.video;   <span class="hljs-comment">//access the video</span>
    <span class="hljs-keyword">const</span> uploadDir = path.join(__dirname, <span class="hljs-string">".."</span>, <span class="hljs-string">"uploads"</span>);   <span class="hljs-comment">//path to upload the video temporarily</span>

    <span class="hljs-keyword">if</span> (!fs.existsSync(uploadDir)) {   <span class="hljs-comment">//check if the directory exists</span>
      fs.mkdirSync(uploadDir);      <span class="hljs-comment">//if not create a new one</span>
    }

    <span class="hljs-keyword">const</span> uploadPath = path.join(uploadDir, videoFile.name);  

    <span class="hljs-keyword">await</span> videoFile.mv(uploadPath);  <span class="hljs-comment">//it moves the video from the buffer to the "upload" folder</span>

    <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">200</span>).json({ <span class="hljs-attr">message</span>:<span class="hljs-string">"file uploaded sucessfully"</span> });
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-keyword">return</span> res
      .status(<span class="hljs-number">500</span>)
      .json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"Internal server error: "</span> + error.message });
  }
};
</code></pre>
<p>Since we are using an ES6 module, the <code>__dirname</code> is not available by default. The file handling mechanism is different compared to CommonJS. Because of this, we’ll use <code>fileURLToPath</code> to handle file paths.</p>
<p>We moved the file from the default temporary location which is the buffer to the <code>uploads</code> folder.</p>
<p>But the file upload process is not yet complete. We still need to send the file to Google AI File Manager, and after uploading, it will return a URI. This URI will then be passed to the model for video analysis.</p>
<h3 id="heading-how-to-upload-a-file-to-the-google-ai-file-manager">How to Upload a File to the Google AI File Manager</h3>
<p>Create a folder <code>utils</code> and create a file <code>fileUpload.js</code>. You can refer to the folder structure provided above.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//  server/utils/fileUpload.js</span>

<span class="hljs-keyword">import</span> { GoogleAIFileManager, FileState } <span class="hljs-keyword">from</span> <span class="hljs-string">"@google/generative-ai/server"</span>;
<span class="hljs-keyword">import</span> { configDotenv } <span class="hljs-keyword">from</span> <span class="hljs-string">"dotenv"</span>;
configDotenv();

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> fileManager = <span class="hljs-keyword">new</span> GoogleAIFileManager(process.env.API_KEY);  <span class="hljs-comment">//create a new GoogleAIFileManager instance</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">fileUpload</span>(<span class="hljs-params">path, videoData</span>) </span>{  
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> uploadResponse = <span class="hljs-keyword">await</span> fileManager.uploadFile(path, {   <span class="hljs-comment">//give the path as an argument</span>
      <span class="hljs-attr">mimeType</span>: videoData.mimetype,  
      <span class="hljs-attr">displayName</span>: videoData.name,
    });
    <span class="hljs-keyword">const</span> name = uploadResponse.file.name;
    <span class="hljs-keyword">let</span> file = <span class="hljs-keyword">await</span> fileManager.getFile(name);    
    <span class="hljs-keyword">while</span> (file.state === FileState.PROCESSING) {     <span class="hljs-comment">//check the state of the file</span>
      process.stdout.write(<span class="hljs-string">"."</span>);
      <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> <span class="hljs-built_in">setTimeout</span>(res, <span class="hljs-number">10000</span>));   <span class="hljs-comment">//check every 10 second</span>
      file = <span class="hljs-keyword">await</span> fileManager.getFile(name);
    }
    <span class="hljs-keyword">if</span> (file.state === FileState.FAILED) {   
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Video processing failed"</span>);
    }
    <span class="hljs-keyword">return</span> file;   <span class="hljs-comment">// return the file object, containing the upload file information and the uri</span>
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-keyword">throw</span> error;
  }
}
</code></pre>
<p>In the code above, we created a function called <code>fileUpload</code> that takes two arguments. These arguments will be passed from the controller function, which we'll set up later.</p>
<p>The <code>fileUpload</code> function uses the <code>fileManager.uploadFile</code> method to send the video to Google's servers. This method needs two arguments: the file path and an object containing metadata about the file (its MIME type and display name).</p>
<p>Because video processing on Google's servers takes time, we need to check the file's status. We do this using a loop that checks the file's state every 10 seconds using <code>fileManager.getFile()</code>. The loop continues as long as the file's state is <code>PROCESSING</code>. Once the state changes to either <code>SUCCESS</code> or <code>FAILED</code>, the loop stops.</p>
<p>The function then checks if the processing was successful. If so, it returns the file object, which contains information about the uploaded and processed video, including its URI. Otherwise, if the state is <code>FAILED</code>, the function throws an error.</p>
<h3 id="heading-pass-the-uri-to-the-gemini-model">Pass the URI to the Gemini Model</h3>
<p>Now in the <code>utils</code> folder, create a file called <code>genContent.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// server/utils/genContent.js</span>

<span class="hljs-keyword">import</span> model <span class="hljs-keyword">from</span> <span class="hljs-string">"../gemini/gemini.config.js"</span>;
<span class="hljs-keyword">import</span> { configDotenv } <span class="hljs-keyword">from</span> <span class="hljs-string">"dotenv"</span>;
configDotenv();

<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">getContent</span>(<span class="hljs-params">file</span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> model.generateContent([
      {
        <span class="hljs-attr">fileData</span>: {
          <span class="hljs-attr">mimeType</span>: file.mimeType,
          <span class="hljs-attr">fileUri</span>: file.uri,
        },
      },
      {
        <span class="hljs-attr">text</span>: <span class="hljs-string">"You need to write a subtitle for this full video, write the subtitle in the SRT format, don't write anything else other than a subtitle in the response, create accurate subtitle."</span>,
      },
    ]);
    <span class="hljs-keyword">return</span> result.response.text();
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-keyword">throw</span> error;
  }
}
</code></pre>
<p>Import the model that we configured earlier. Create a function called <code>getContent</code>. The <code>getContent</code> function takes the file object (returned from the <code>fileUpload</code> function).</p>
<p>Pass the file URI and the <code>mimi</code> to the model. Then we’ll provide a prompt instructing the model to generate subtitles for the entire video in SRT format. You can also add your prompt if you want. Then return the response.</p>
<h3 id="heading-update-the-subscontrollerjs-file">Update the <code>subs.controller.js</code> File</h3>
<p>Finally, we need to update the controller file. We've created the <code>fileUpload</code> and <code>getContent</code> functions, and now we'll use them in the controller and provide the required arguments.</p>
<p>In the <code>server/controller/subs.controller.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//  server/controller/subs.controller.js</span>

<span class="hljs-keyword">import</span> { fileURLToPath } <span class="hljs-keyword">from</span> <span class="hljs-string">"url"</span>;
<span class="hljs-keyword">import</span> path <span class="hljs-keyword">from</span> <span class="hljs-string">"path"</span>;
<span class="hljs-keyword">import</span> fs <span class="hljs-keyword">from</span> <span class="hljs-string">"fs"</span>;
<span class="hljs-keyword">import</span> { fileUpload } <span class="hljs-keyword">from</span> <span class="hljs-string">"../utils/fileUpload.js"</span>;
<span class="hljs-keyword">import</span> { getContent } <span class="hljs-keyword">from</span> <span class="hljs-string">"../utils/genContent.js"</span>;

<span class="hljs-keyword">const</span> __filename = fileURLToPath(<span class="hljs-keyword">import</span>.meta.url);
<span class="hljs-keyword">const</span> __dirname = path.dirname(__filename);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> uploadFile = <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">if</span> (!req.files || !req.files.video) {
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"No video uploaded"</span> });
    }

    <span class="hljs-keyword">const</span> videoFile = req.files.video;
    <span class="hljs-keyword">const</span> uploadDir = path.join(__dirname, <span class="hljs-string">".."</span>, <span class="hljs-string">"uploads"</span>);

    <span class="hljs-keyword">if</span> (!fs.existsSync(uploadDir)) {
      fs.mkdirSync(uploadDir);
    }

    <span class="hljs-keyword">const</span> uploadPath = path.join(uploadDir, videoFile.name);

    <span class="hljs-keyword">await</span> videoFile.mv(uploadPath);

    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fileUpload(uploadPath, req.files.video);  <span class="hljs-comment">//we pass 'uploadPath' and the video file data to 'fileUpload'</span>
    <span class="hljs-keyword">const</span> genContent = <span class="hljs-keyword">await</span> getContent(response);   <span class="hljs-comment">//the 'response' (containing the file URI) is passed to 'getContent'</span>

    <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">200</span>).json({ <span class="hljs-attr">subs</span>: genContent });   <span class="hljs-comment">//// return the generated subtitles to the client</span>
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error uploading video:"</span>, error);
    <span class="hljs-keyword">return</span> res
      .status(<span class="hljs-number">500</span>)
      .json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"Internal server error: "</span> + error.message });
  }
};
</code></pre>
<p>With this, the backend API is complete. Now, we'll move on to updating the front end.</p>
<h2 id="heading-update-the-front-end">Update the Front End</h2>
<p>Our frontend currently only allows users to select a video. In this section, we'll update it to send the video data to our backend for processing. The frontend will then receive the generated subtitles from the backend and initiate a download of the <code>.srt</code> file.</p>
<p>Navigate to the <code>client</code> folder:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> client
</code></pre>
<p>Install <code>axios</code>. We’ll use it to handle HTTP requests.</p>
<pre><code class="lang-bash">npm install axios
</code></pre>
<p>In the <code>client/src/App.tsx</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">//   client/src/App.tsx</span>

<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">"axios"</span>;

<span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> handleSubmit = <span class="hljs-keyword">async</span> (e: React.FormEvent&lt;HTMLFormElement&gt;): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; =&gt; {
    e.preventDefault();
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> formData = <span class="hljs-keyword">new</span> FormData(e.currentTarget);
      <span class="hljs-comment">// sending a POST request with form data</span>
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> axios.post(
        <span class="hljs-string">"http://localhost:3000/api/subs/"</span>,   
        formData
      );
<span class="hljs-comment">// creating a Blob from the server response and triggering the file download</span>
      <span class="hljs-keyword">const</span> blob = <span class="hljs-keyword">new</span> Blob([response.data.subs], { <span class="hljs-keyword">type</span>: <span class="hljs-string">"text/plain"</span> }); 
      <span class="hljs-keyword">const</span> link = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">"a"</span>);
      link.href = URL.createObjectURL(blob);
      link.download = <span class="hljs-string">"subtitle.srt"</span>;
      link.click();
      link.remove();
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.log(error);
    }
  };

  <span class="hljs-keyword">return</span> (
    &lt;div&gt;
      &lt;form onSubmit={handleSubmit}&gt;
        &lt;input <span class="hljs-keyword">type</span>=<span class="hljs-string">"file"</span> accept=<span class="hljs-string">"video/*,.mkv"</span> name=<span class="hljs-string">"video"</span> /&gt;
        &lt;input <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span> /&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p><code>axios</code> makes the POST request to your backend API endpoint <code>(/api/subs)</code>. The server will process the video, and this might take some time.</p>
<p>After the server sends the generated subtitles, the frontend receives them as a response. To handle this response and allow users to download the subtitles, we'll use a Blob. A Blob (Binary Large Object) is a web API object that represents raw binary data, essentially acting like a file. In our case, the subtitles returned from the server will be converted into a Blob, which will then allow us to trigger a download in the user's browser.</p>
<h2 id="heading-summary">Summary</h2>
<p>In this tutorial, you learned how to build an AI-powered subtitle generator using Google's Gemini API, React, and Express. You can upload videos, send them to the Gemini API for subtitle generation, and provide the generated subtitles for download.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>That's it! You've successfully built an AI-powered subtitle generator using the Gemini API. For quicker testing, start with shorter video clips (3-5 minutes). Longer videos might take more time to process.</p>
<p>Want to create a customizable video prompting application? Just add an input field to let users enter their prompts, send that prompt to the server, and use it in place of the hardcoded prompt. That's all it takes.</p>
<p>For more information about the Gemini API, refer to the official <a target="_blank" href="https://ai.google.dev/gemini-api/docs#node.js">Gemini API Docs</a></p>
<p>You can find the full code here: <a target="_blank" href="https://github.com/sanjayr-12/ai-subtitle-generator">AI-Subtitle-Generator</a></p>
<p>If there are any mistakes or you have any questions, contact me on <a target="_blank" href="https://www.linkedin.com/in/sanjay-r-ab6064294/">LinkedIn</a> or <a target="_blank" href="https://www.instagram.com/heheheh_pet/profilecard/?igsh=eXh3MWw4ZzZ3NTRq">Instagram</a>.</p>
<p>Thank you for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Understand How Express.js Works by Building Your Own Server Multiplexer from Scratch ]]>
                </title>
                <description>
                    <![CDATA[ Kata Machines have become the go-to method for mastering tough concepts, and it's hard to find a better tool for deliberate practice. If you haven’t come across a kata yet, trust me—you will soon enough. There’s a reason why developers love katas, wh... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/understand-how-expressjs-works-by-building-your-own-server-multiplexer-from-scratch/</link>
                <guid isPermaLink="false">66feb8e20772faf5564db923</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ backend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ webdev ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Sifundo ]]>
                </dc:creator>
                <pubDate>Thu, 03 Oct 2024 15:31:46 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/urBiLDuUhMU/upload/65f541a7f0d11691008b4e93d89f2d29.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Kata Machines have become the go-to method for mastering tough concepts, and it's hard to find a better tool for deliberate practice.</p>
<p>If you haven’t come across a kata yet, trust me—you will soon enough.</p>
<p>There’s a reason why developers love katas, whether they use them to sharpen their skills for personal projects or prepare for interviews.</p>
<p>A kata is all about <strong>deliberate</strong> practice. It comes from martial arts like Karate and Judo, and, according to Wikipedia, it’s defined as a pre-determined sequence of movements, techniques, and patterns that follow a specific order (source: <a target="_blank" href="https://en.wikipedia.org/wiki/Kata">wikipedia</a>).</p>
<p>Kata Machines come from this idea: learning through drills and deliberate, conscious (choreographed) practice.</p>
<p>I realized just how perfect katas are when I was learning Haskell back in the day. If you know, you know. Haskell was a beast to learn for me back then!</p>
<p>So, I thought, why not do the same for the backend? Just pick one high-level concept, and drill down on it repeatedly and deliberately to its core and first principles.</p>
<p>In this article, I picked server-side frameworks. We're going to pick apart the idea of a "framework" using Express as an example.</p>
<p>We’re going to take high-level Express:</p>
<pre><code class="lang-js"><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> PORT = <span class="hljs-number">3000</span>;
<span class="hljs-keyword">const</span> app = express();

app.get(<span class="hljs-string">"/"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.send(<span class="hljs-string">"Hello, world!"</span>);
});

app.listen(PORT, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Server listening on port:"</span>, PORT));
</code></pre>
<p>And drill all the way down, repeatedly, until we touch Node.js native code:</p>
<pre><code class="lang-c"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">TCPWrap::New</span><span class="hljs-params">(<span class="hljs-keyword">const</span> FunctionCallbackInfo&lt;Value&gt;&amp; args)</span> </span>{
  CHECK(args.IsConstructCall());
  CHECK(args[<span class="hljs-number">0</span>]-&gt;IsInt32());
  Environment* env = Environment::GetCurrent(args);
  <span class="hljs-keyword">int</span> type_value = args[<span class="hljs-number">0</span>].As&lt;Int32&gt;()-&gt;Value();
  TCPWrap::SocketType type = <span class="hljs-keyword">static_cast</span>&lt;TCPWrap::SocketType&gt;(type_value);
  ProviderType provider;
  <span class="hljs-keyword">switch</span> (type) {
    <span class="hljs-keyword">case</span> SOCKET:
      provider = PROVIDER_TCPWRAP;
      <span class="hljs-keyword">break</span>;
    <span class="hljs-keyword">case</span> SERVER:
      provider = PROVIDER_TCPSERVERWRAP;
      <span class="hljs-keyword">break</span>;
    <span class="hljs-keyword">default</span>:
      UNREACHABLE();
  }
  <span class="hljs-keyword">new</span> TCPWrap(env, args.This(), provider);
}
</code></pre>
<p>And having gained this new intuition, we’ll build back up with a custom "Express" implementation:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">serverMux</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">hook</span>(<span class="hljs-params">req, res</span>) </span>{
    <span class="hljs-comment">// To be implemented</span>
  }
  <span class="hljs-keyword">return</span> {
    hook
  };
}

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

<span class="hljs-keyword">const</span> server = http.createServer(<span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  app.hook(req, res);
});
</code></pre>
<p>It’s going to be quite the journey – and a rewarding one at that!</p>
<p>I'm assuming you have some backend knowledge and classify yourself as an advanced beginner who’s looking to level up.</p>
<p>If that sounds like you, we’re ready to proceed.</p>
<h3 id="heading-heres-what-well-cover">Here’s what we’ll cover:</h3>
<ul>
<li><p><a class="post-section-overview" href="#form-1--server-side-frameworks">Form 1: Server-Side Frameworks</a></p>
<ul>
<li><p><a class="post-section-overview" href="#first-drill--unpacking-expressjs">First Drill: Unpacking Express.js</a></p>
</li>
<li><p><a class="post-section-overview" href="#the-server">The Server</a></p>
</li>
<li><p><a class="post-section-overview" href="#the-socket-in-nodejs">The Socket in Node.js</a></p>
</li>
<li><p><a class="post-section-overview" href="#the-socket-in-nodejs-source-code">The Socket in Node.js Source Code</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#form-2--implementing-a-custom-server-mux">Form 2: Implementing a Custom Server Mux</a></p>
<ul>
<li><p><a class="post-section-overview" href="#creating-our-custom-router">Creating Our Custom Router</a></p>
</li>
<li><p><a class="post-section-overview" href="#basic-mux-skeleton">Basic Mux Skeleton</a></p>
</li>
<li><p><a class="post-section-overview" href="#the-hook-function">The Hook Function</a></p>
</li>
<li><p><a class="post-section-overview" href="#why-use-a-queue-">Why Use a Queue?</a></p>
<ul>
<li><a class="post-section-overview" href="#queue-operations">Queue Operations</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#request-wrapper">Request Wrapper</a></p>
</li>
<li><p><a class="post-section-overview" href="#testing-the-queue">Testing the Queue</a></p>
</li>
<li><p><a class="post-section-overview" href="#processing-requests">Processing Requests</a></p>
</li>
<li><p><a class="post-section-overview" href="#lookup-table-and-handlers">Lookup Table and Handlers</a></p>
</li>
<li><p><a class="post-section-overview" href="#registering-handlers">Registering Handlers</a></p>
</li>
</ul>
</li>
</ul>
<ul>
<li><a class="post-section-overview" href="#wrapping-up">Wrapping Up</a></li>
</ul>
<h2 id="heading-form-1-server-side-frameworks">Form 1: Server-Side Frameworks</h2>
<p>The term "server-side framework" is broad. Think about it: <code>mysql2</code> could be considered a framework depending on how you classify frameworks and libraries. Even <code>sharp.js</code> for image editing could fit under the umbrella of server-side frameworks, right?</p>
<p>But the question is, what type of framework is Express.js?</p>
<p>Express is a multiplexer—specifically, a server multiplexer (server mux). I promise, the term isn’t as complex as it sounds. The implementation, though—that’s a whole different story.</p>
<p>In simple terms, a server mux is a router. Of course, Express and other server muxes handle more than just routing, but that’s the core idea.</p>
<p>Express takes in <code>request</code> and <code>response</code> objects from the server and routes them. Don’t worry, we’ll dive into routing soon.</p>
<p>Here’s an interesting point: if Express isn’t the server, then what exactly is the server?</p>
<p>To answer that, we need to look at the Express.js source code, which you can clone from GitHub:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/expressjs/express.git
</code></pre>
<p>Once you’re set, we can dive right in with our first deep dive.</p>
<h3 id="heading-first-drill-unpacking-expressjs">First Drill: Unpacking Express.js</h3>
<p>Open your Express source code in an editor. You’ll find the entry file <code>express.js</code> in the <code>lib</code> folder.</p>
<p>You can skim the file, but we’re going to focus on lines 42 and 43—the heart of it all:</p>
<pre><code class="lang-js">mixin(app, EventEmitter.prototype, <span class="hljs-literal">false</span>);
mixin(app, proto, <span class="hljs-literal">false</span>);
</code></pre>
<p>What you’re looking at is object composition: a design pattern where an object is created by combining the properties and methods of other objects.</p>
<p>Our target object here is <code>proto</code>, which is imported from <code>application.js</code>, the core of Express.</p>
<p>Let’s open that file. There’s a lot of code, but remember, our goal is to figure out where the server is within Express.</p>
<p>If there’s one function in Express that everyone likely knows, it’s <code>listen</code>. The essence of a server is to "listen" over a network. So, do a quick <code>Ctrl+F</code> for "listen," and you’ll find the definition on line 633:</p>
<pre><code class="lang-js">app.listen = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">listen</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">var</span> server = http.createServer(<span class="hljs-built_in">this</span>);
  <span class="hljs-keyword">return</span> server.listen.apply(server, <span class="hljs-built_in">arguments</span>);
};
</code></pre>
<p>There it is, the famous <code>listen</code> function. Did we just find the server?</p>
<pre><code class="lang-js"><span class="hljs-keyword">var</span> server = http.createServer(<span class="hljs-built_in">this</span>);
</code></pre>
<p>We’ve already seen a version of this in the intro:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> server = http.createServer(<span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  app.hook(req, res);
});
</code></pre>
<p>This confirms that Express is indeed a server mux, and the actual server is returned by the Node.js <code>createServer</code> function from the <code>http</code> package.</p>
<p>That’s some solid progress!</p>
<p>We’ve peeled back a layer, but we can go deeper. What exactly does <code>createServer</code> do, and what is this <code>server</code> object?</p>
<h3 id="heading-the-server">The Server</h3>
<p>A server is the basic unit of the backend. At its core, the concept is simple: how can two or more processes communicate over a network?</p>
<p>This is the fundamental idea behind network programming. We have devices equipped with IP addresses for identification and ports for data exchange over a network.</p>
<p>The communication itself is complex, which is where protocols come in to facilitate the process.</p>
<p>The most common protocols are UDP and TCP:</p>
<ul>
<li><p><strong>UDP</strong> is a connectionless protocol and does not guarantee reliable communication, but allows for low-latency and efficient data transfer. This is ideal for time-sensitive applications such as video conferencing, online gaming, and voice over IP (VoIP) (source <a target="_blank" href="https://en.wikipedia.org/wiki/User_Datagram_Protocol">Wikipedia</a>).</p>
</li>
<li><p><strong>TCP</strong> is a connection-oriented protocol with reliable, ordered, and error-checked data transmission between applications on networked devices. It’s a major part of internet applications (source <a target="_blank" href="https://en.wikipedia.org/wiki/Transmission_Control_Protocol">Wikipedia</a>).</p>
</li>
</ul>
<p>TCP is the most widely used protocol due to its reliability, and most server-side applications you’ll work with, including Express, are TCP-based.</p>
<p>Although I love the quirks and power of UDP, we’ll focus on TCP, tracing its roots in Node.js.</p>
<p>We’ve already seen a glimpse of this:</p>
<pre><code class="lang-c"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">TCPWrap::New</span><span class="hljs-params">(<span class="hljs-keyword">const</span> FunctionCallbackInfo&lt;Value&gt;&amp; args)</span> </span>{
  <span class="hljs-comment">// some code</span>
  <span class="hljs-keyword">new</span> TCPWrap(env, args.This(), provider);
}
</code></pre>
<p>Before we dig into that, we need to answer a key question: What does it really mean to be a server process?</p>
<p>Without getting too deep into file descriptors, sockets, or network layers, a server is an OS-level object responsible for handling communication between nodes. When you call:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> http = <span class="hljs-built_in">require</span>(<span class="hljs-string">"node:http"</span>);

<span class="hljs-keyword">const</span> something = http.createServer({});
</code></pre>
<p>You’re creating an OS-level object, commonly known as a <strong>socket</strong>. This socket facilitates network communication between devices, along with handling data encoding and decoding.</p>
<p>In short, <code>createServer</code> abstracts and returns this socket object.</p>
<p>And, yes, we can implement this socket in Node.js. Remember, Node.js has native access to the OS, allowing JavaScript to function at the system level.</p>
<h3 id="heading-the-socket-in-nodejs">The Socket in Node.js</h3>
<p>Here’s some code that creates a server socket:</p>
<pre><code class="lang-js"><span class="hljs-comment">// Using Node v20</span>
<span class="hljs-keyword">const</span> net = <span class="hljs-built_in">require</span>(<span class="hljs-string">'node:net'</span>);

<span class="hljs-keyword">const</span> server = net.createServer(<span class="hljs-function">(<span class="hljs-params">c</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'client connected'</span>, c.remoteAddress);
  c.write(<span class="hljs-string">"Hello; world"</span>);

  c.on(<span class="hljs-string">'end'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'client disconnected'</span>);
  });
});

server.on(<span class="hljs-string">'error'</span>, <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
  <span class="hljs-keyword">throw</span> err;
});

server.listen(<span class="hljs-number">3000</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'server bound'</span>);
});
</code></pre>
<p>While <code>net.createServer((c)</code> is still a high-level abstraction like <code>http.createServer</code>, it returns the raw socket.</p>
<p>The <code>c</code> object represents the client that made the connection (dial). Beyond writing to it, we can do much more.</p>
<p>For instance, here’s a simple write operation:</p>
<pre><code class="lang-js">c.write(<span class="hljs-string">"Hello world"</span>);
</code></pre>
<p>Our socket is running on <code>localhost:3000</code>. If you make a request (or use <code>curl</code>):</p>
<pre><code class="lang-bash">curl localhost:3000
</code></pre>
<p>The OS-level network stack encodes not only your data but also information about who you are and where to find you—in the form of a response, among other things.</p>
<p>This is what the server receives, and it’s important to know where to send the response (like IP, and so on).</p>
<p>So, the <code>c</code> object represents all of that!</p>
<p>We’ve covered a lot of the surface-level concepts, but before we wrap up this part, here’s a bonus challenge:</p>
<p>Try writing a class on the server to manage multiple connections. You could store these connections in a data structure and periodically send data to them while the connection remains open.</p>
<p>We’re about three layers deep now, but the journey isn’t over. Remember the goal?</p>
<p>Now it’s time to clone the Node.js source code. Don’t worry, we’ll only focus on the relevant parts.</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/nodejs/node.git
</code></pre>
<h3 id="heading-the-socket-in-nodejs-source-code">The Socket in Node.js Source Code</h3>
<p>Let the tracing begin! Node.js is a massive codebase – it’s an entire engine that does way more than just handle sockets. But we only care about the networking part today.</p>
<p>First, navigate to the <code>lib</code> folder, and inside you’ll find a file called <code>net.js</code>. This is where most of the work happens for network applications. If you scroll down to line 210, you’ll see a familiar sight:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createServer</span>(<span class="hljs-params">options, connectionListener</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Server(options, connectionListener);
}
</code></pre>
<p>That’s it! Every time we create a server, it calls this function and returns a <code>Server</code> object. Anytime you see <code>new</code> in JavaScript, you should have a lightbulb moment—it means a new object or class (blueprint) is being created.</p>
<p>So we can trace and find the <code>Server</code> definition:</p>
<p>On line 1737</p>
<p>At first glance, it might seem like nothing special is happening. But JavaScript has a sneaky way of hiding complexity.</p>
<p>Here’s the thing: JavaScript is a prototype-based language. This means that objects can inherit features from other objects through prototypes. On line 1791, we see this in action:</p>
<pre><code class="lang-js">ObjectSetPrototypeOf(Server.prototype, EventEmitter.prototype);
</code></pre>
<p>In plain English: our <code>Server</code> object is inheriting all the behavior from other objects like <code>EventEmitter</code>, for example. This is a common pattern in JavaScript libraries – remember the mixin in Express?</p>
<p>At this point, if you’ve never worked with prototypes or Object-Oriented JavaScript (OOJS), this might feel like advanced territory. But don’t worry – the good folks at <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain">MDN</a> have an excellent guide on prototypes to get you up to speed.</p>
<p>Now, what’s one thing we know for sure about a Node.js server? It has a <code>listen</code> function. We use it all the time in server-side code (even in frameworks like Express). So, let’s check if our <code>Server</code> object has a <code>listen</code> function.</p>
<p>Scroll down a bit more, and there it is on line 2006:</p>
<pre><code class="lang-js">Server.prototype.listen = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">...args</span>) </span>{}
</code></pre>
<p>This function handles a lot of stuff—like validating the port number—but the key part starts around line 2016, where the comment clearly tells us:</p>
<pre><code class="lang-js"><span class="hljs-comment">// start TCP server listening on host:port</span>
</code></pre>
<p>We know what TCP is!</p>
<p>The important functions here are <code>lookupAndListen</code> and <code>listenInCluster</code>. They are responsible for starting the actual TCP server:</p>
<pre><code class="lang-js"><span class="hljs-comment">// start TCP server listening on host:port</span>
<span class="hljs-keyword">if</span> (options.host) {
  lookupAndListen(<span class="hljs-built_in">this</span>, options.port | <span class="hljs-number">0</span>, options.host, backlog, options.exclusive, flags);
} <span class="hljs-keyword">else</span> {
  listenInCluster(<span class="hljs-built_in">this</span>, <span class="hljs-literal">null</span>, options.port | <span class="hljs-number">0</span>, <span class="hljs-number">4</span>, backlog, <span class="hljs-literal">undefined</span>, options.exclusive);
}
</code></pre>
<p>Digging into <code>lookupAndListen</code> (line 2156), we find that it calls <code>listenInCluster</code>, which leads us to another function: <code>server._listen2</code> (yep, more tracing!):</p>
<pre><code class="lang-js">server._listen2(address, port, addressType, backlog, fd, flags);
</code></pre>
<p>As the comments explain, this is all about backward compatibility:</p>
<pre><code class="lang-js"><span class="hljs-comment">// _listen2 sets up the listened handle, it is still named like this</span>
<span class="hljs-comment">// to avoid breaking code that wraps this method</span>
</code></pre>
<p>I know this might feel like a wild goose chase, but trust me, tracing through a large codebase like Node.js requires patience. We’re getting close.</p>
<p>So, <code>._listen2</code> is defined in our <code>Server</code> object’s prototype and points to a function called <code>setupListenHandle</code> (line 1856). This function is the real hub where everything comes together.</p>
<p>Around line 1870 and 1883, you’ll find the function <code>createServerHandle</code>:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createServerHandle</span>(<span class="hljs-params">address, port, addressType, fd, flags</span>) </span>{
   handle = <span class="hljs-keyword">new</span> TCP(TCPConstants.SERVER);
   isTCP = <span class="hljs-literal">true</span>;
   <span class="hljs-keyword">return</span> handle;
}
</code></pre>
<p>Finally! We’ve hit the core: the <code>TCP</code> object. This is where the actual TCP server is created, the core. We could stop here, satisfied that we’ve found the TCP server, but why not dig deeper?</p>
<p>Remember that <code>new TCP</code> is creating an object, so we need to figure out what <code>TCP</code> actually represents.</p>
<p>Go back up to line 68, where you’ll see the following import:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> {
  TCP,
  TCPConnectWrap,
  <span class="hljs-attr">constants</span>: TCPConstants,
} = internalBinding(<span class="hljs-string">'tcp_wrap'</span>);
</code></pre>
<p>This is where things get interesting. You might wonder: “What kind of import is that? It’s not your regular <code>require</code> or <code>import</code> statement.” That’s because JavaScript alone can’t handle TCP servers—it needs help from C++.</p>
<p>Node.js, which is built on the V8 engine, relies on C++ bindings to do the heavy lifting. These bindings are like a bridge, allowing JavaScript to communicate with low-level system functions (like creating a TCP server). <code>internalBinding('tcp_wrap')</code> is one of these bridges.</p>
<p>To truly trace things to their source, we need to dive into the Node.js C++ code. You’ll find <code>tcp_wrap.cc</code> in the <code>src</code> folder (among others like <code>crypto</code>, <code>streams</code>, <code>async</code>, <code>fs</code>). Open it, and you’ll find this function:</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">TCPWrap::New</span><span class="hljs-params">(<span class="hljs-keyword">const</span> FunctionCallbackInfo&lt;Value&gt;&amp; args)</span> </span>{
  CHECK(args.IsConstructCall());
  CHECK(args[<span class="hljs-number">0</span>]-&gt;IsInt32());
  Environment* env = Environment::GetCurrent(args);
  <span class="hljs-keyword">int</span> type_value = args[<span class="hljs-number">0</span>].As&lt;Int32&gt;()-&gt;Value();
  TCPWrap::SocketType type = <span class="hljs-keyword">static_cast</span>&lt;TCPWrap::SocketType&gt;(type_value);
  ProviderType provider;
  <span class="hljs-keyword">switch</span> (type) {
    <span class="hljs-keyword">case</span> SOCKET:
      provider = PROVIDER_TCPWRAP;
      <span class="hljs-keyword">break</span>;
    <span class="hljs-keyword">case</span> SERVER:
      provider = PROVIDER_TCPSERVERWRAP;
      <span class="hljs-keyword">break</span>;
    <span class="hljs-keyword">default</span>:
      UNREACHABLE();
  }
  <span class="hljs-keyword">new</span> TCPWrap(env, args.This(), provider);
}
</code></pre>
<p>This is where the TCP server is <em>actually</em> created. You can see more familiar functions like <code>bind</code>, and everything JavaScript does is just a mirror of these lower-level operations.</p>
<p>We’ve traced our way from high-level JavaScript all the way down to C++—the true beginning of a TCP server in Node.js.</p>
<p>We've completed the first part of the introduction: "And drill all the way down repeatedly until we touch Node.js native code" and now it's time to build up.</p>
<h2 id="heading-form-2-implementing-a-custom-server-mux">Form 2: Implementing a Custom Server Mux</h2>
<p>Before diving into the code, the goal isn’t to focus on the complexity of mux (multiplexer) development (because that can get complicated). Instead, it’s to show how the <strong>server</strong> and <strong>mux</strong> fit together.</p>
<p>If anything, this is the key takeaway: the flow from the server to the router, and ultimately to the caller (the client that made the request).</p>
<p>Remember, we've already seen a similar concept in Express:</p>
<pre><code class="lang-js"><span class="hljs-comment">// Express app inherits from Node's EventEmitter</span>
mixin(app, EventEmitter.prototype, <span class="hljs-literal">false</span>);
<span class="hljs-comment">// Implements the server mux with functions like listen, handle, middleware</span>
mixin(app, proto, <span class="hljs-literal">false</span>);
</code></pre>
<p>Behind the scenes, a lot of complex code is abstracted away. This helps simplify things and makes the code cleaner, but for teaching purposes, we’ll take a more verbose approach. This way, you can see how everything connects.</p>
<h3 id="heading-creating-our-custom-router">Creating Our Custom Router</h3>
<p>Let's start simple and build a basic server. You probably already know how to create a native server in Node.js:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> server = http.createServer(<span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
   app.hook(req, res);
});
</code></pre>
<p>Here, we’re introducing an object <code>app</code> with a <code>hook</code> function (which we’ll implement shortly). This is where the server redirects the <code>req</code> and <code>res</code> to our custom router. This <strong>hook</strong> is the meeting point—the interaction between the server and the router (mux).</p>
<h3 id="heading-basic-mux-skeleton">Basic Mux Skeleton</h3>
<p>Let's start by creating the structure of our mux:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">serverMux</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">hook</span>(<span class="hljs-params">req, res</span>) </span>{
    <span class="hljs-comment">// To be implemented</span>
  }
  <span class="hljs-keyword">return</span> {
    hook
  };
}

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

<span class="hljs-keyword">const</span> server = http.createServer(<span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  app.hook(req, res);
});
</code></pre>
<h3 id="heading-the-hook-function">The Hook Function</h3>
<p>The <code>hook</code> function is our middleman between the server and the mux. It receives the request (<code>req</code>) and response (<code>res</code>) objects from the server and passes them to our mux:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">hook</span>(<span class="hljs-params">req, res</span>) </span>{
    requestsQueue.push(requestWrapper(req, res));
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"new request!"</span>);
    processRequests();
}
</code></pre>
<p>Here, we introduced a few new things:</p>
<ul>
<li><p><code>requestWrapper</code>: A function to wrap the <code>req</code> and <code>res</code>.</p>
</li>
<li><p><code>processRequests</code>: A function to handle the request processing.</p>
</li>
<li><p><code>requestsQueue</code>: A basic JavaScript array that will act as our queue for handling requests.</p>
</li>
</ul>
<p>Let's update <code>serverMux</code> to reflect this:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">serverMux</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> requestsQueue = [];

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">processRequests</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-comment">// To be implemented</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">hook</span>(<span class="hljs-params">req, res</span>) </span>{
      requestsQueue.push(requestWrapper(req, res));
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"new request!"</span>);
      processRequests();
    }

    <span class="hljs-keyword">return</span> {
      hook
    };
}
</code></pre>
<h3 id="heading-why-use-a-queue">Why Use a Queue?</h3>
<p>You might be wondering why we’re using a queue instead of handling requests immediately like Express does with <code>app.handle</code>. Well, storing requests in a queue helps simulate an event loop. This will give us better visibility into how requests are processed, one at a time.</p>
<h4 id="heading-queue-operations">Queue Operations</h4>
<p>A queue is a first-in, first-out (FIFO) data structure. Just like a line at the store, the request that arrives first gets processed first.</p>
<p>In our case, the <code>requestsQueue</code> is an array. Here’s how we’ll handle enqueueing and dequeueing:</p>
<ul>
<li><p><strong>Enqueue (push)</strong>: We push requests into the queue with <code>requestsQueue.push(requestWrapper(req, res));</code></p>
</li>
<li><p><strong>Dequeue (shift)</strong>: We pull the next request out of the queue with <code>const c = requestsQueue.shift();</code></p>
</li>
</ul>
<h3 id="heading-request-wrapper">Request Wrapper</h3>
<p>The <code>requestWrapper</code> function is a simple utility that wraps the incoming <code>req</code> and <code>res</code> objects, and extracts some useful information:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">requestWrapper</span>(<span class="hljs-params">req, res</span>) </span>{
    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">url</span>: req.url,
      <span class="hljs-attr">method</span>: req.method,
      req,
      res
    };
}
</code></pre>
<p>In more advanced frameworks like <a target="_blank" href="https://hono.dev/">Hono.js</a>, the request wrapper might add additional functionality, such as helper methods for setting headers or parsing body content. For now, we’re keeping things simple and just returning the request and response with the URL and method.</p>
<h3 id="heading-testing-the-queue">Testing the Queue</h3>
<p>Let’s test this out by logging the request queue on every new request. Update your <code>hook</code> function:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">hook</span>(<span class="hljs-params">req, res</span>) </span>{
    requestsQueue.push(requestWrapper(req, res));
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"New request queued!"</span>, requestsQueue);
    processRequests();
}
</code></pre>
<p>Start the server with:</p>
<pre><code class="lang-bash">node index.js
</code></pre>
<p>Now, open another terminal and make a request to the server:</p>
<pre><code class="lang-bash">curl http://localhost:3000
</code></pre>
<p>You should see the queue logged in the console. The terminal might look like it's hanging because we haven’t responded to the request yet. You can exit the process manually for now.</p>
<h3 id="heading-processing-requests">Processing Requests</h3>
<p>Here’s the full <code>processRequests</code> function:</p>
<pre><code class="lang-js"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">processRequests</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">while</span> (requestsQueue.length &gt; <span class="hljs-number">0</span>) {
        <span class="hljs-keyword">const</span> c = requestsQueue.shift();
        <span class="hljs-keyword">if</span> (c) {
            <span class="hljs-keyword">const</span> handler = lookupTable[c.url] || lookupTable[<span class="hljs-string">"/notfound"</span>];
            <span class="hljs-keyword">if</span> (handler) {
                (<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
                    handler(c.req, c.res);
                })();
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Missing not found handler!"</span>);
            }
        }
    }
}
</code></pre>
<p>Let’s break it down:</p>
<ol>
<li><p><strong>Queue processing</strong>: We loop through the queue, dequeueing each request one by one.</p>
</li>
<li><p><strong>Handler lookup</strong>: For each request, we check if a handler exists in the <code>lookupTable</code> for the URL. If it doesn’t, we fall back to a <code>/notfound</code> handler.</p>
</li>
<li><p><strong>Handler execution</strong>: We execute the handler, passing the request and response objects.</p>
</li>
</ol>
<h3 id="heading-lookup-table-and-handlers">Lookup Table and Handlers</h3>
<p>We need a way to map URLs to their respective handlers. This is where the <code>lookupTable</code> comes in:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> lookupTable = {
    <span class="hljs-string">"/"</span>: <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
      res.writeHead(<span class="hljs-number">200</span>, { <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'text/plain'</span> });
      res.end(<span class="hljs-string">'Hello, World!\n'</span>);
    },
    <span class="hljs-string">"/notfound"</span>: <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
      res.writeHead(<span class="hljs-number">404</span>, { <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'text/plain'</span> });
      res.end(<span class="hljs-string">'404 Not Found\n'</span>);
    }
};
</code></pre>
<p>When a request comes in, we check if the URL matches an entry in the table. If it does, we call the corresponding handler function.</p>
<p>For example, calling <code>curl http://localhost:3000</code> will hit the <code>/</code> route and return "Hello, World!". If you hit a non-existent route like <code>/random</code>, it will trigger the 404 handler.</p>
<h3 id="heading-registering-handlers">Registering Handlers</h3>
<p>Finally, let’s add a method to register new handlers dynamically:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">serverMux</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> lookupTable = {};

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">registerHandler</span>(<span class="hljs-params">path, handler</span>) </span>{
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> path !== <span class="hljs-string">'string'</span> || !path) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Path must be a non-empty string"</span>);
    }
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> handler !== <span class="hljs-string">'function'</span>) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Handler must be a function"</span>);
    }
    lookupTable[path] = handler;
  }

  <span class="hljs-keyword">return</span> {
    hook,
    registerHandler
  };
}
</code></pre>
<p>Now, we can dynamically register new routes with their handlers:</p>
<pre><code class="lang-js">app.registerHandler(<span class="hljs-string">"/"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
    res.writeHead(<span class="hljs-number">200</span>, { <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'text/plain'</span> });
    res.end(<span class="hljs-string">'Home Page\n'</span>);
});

app.registerHandler(<span class="hljs-string">"/about"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
    res.writeHead(<span class="hljs-number">200</span>, { <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'text/plain'</span> });
    res.end(<span class="hljs-string">'About Us\n'</span>);
});
</code></pre>
<p>Here's a full example of <code>registerHandler</code> in action:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> app =  serverMux()


app.registerHandler(<span class="hljs-string">"/"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>)=&gt;</span> {

  res.writeHead(<span class="hljs-number">200</span>, { <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'text/plain'</span> });

  res.end(<span class="hljs-string">'Hello, World!'</span>);



})

app.registerHandler(<span class="hljs-string">"/about"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>)=&gt;</span> {

    res.writeHead(<span class="hljs-number">200</span>, { <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'text/html'</span> });

    res.end(<span class="hljs-string">'&lt;h1&gt;About Us&lt;/h1&gt;'</span>);

})


app.registerHandler(<span class="hljs-string">"/contact"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>)=&gt;</span> {

  res.writeHead(<span class="hljs-number">200</span>, { <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'text/html'</span> });

  res.end(<span class="hljs-string">'&lt;h1&gt;Contact Us&lt;/h1&gt;'</span>);

})


app.registerHandler(<span class="hljs-string">"/api/data"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>)=&gt;</span> {

  res.writeHead(<span class="hljs-number">200</span>, { <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span> });

  res.end(<span class="hljs-built_in">JSON</span>.stringify({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Some data from the API'</span> }));

})


app.registerHandler(<span class="hljs-string">"/notfound"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>)=&gt;</span> {

   res.writeHead(<span class="hljs-number">404</span>, { <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'text/plain'</span> });

    res.end(<span class="hljs-string">'404 Not Found'</span>);

})



<span class="hljs-keyword">const</span> server = http.createServer(<span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {

   <span class="hljs-built_in">console</span>.log(req.url)

   app.hook(req, res)

});
</code></pre>
<p>Notice how similar it is to Express?</p>
<pre><code class="lang-js">app.get(<span class="hljs-string">"/"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>)=&gt;</span> {

})
</code></pre>
<p>Just a bit more verbose!</p>
<p>Now, run the server and put it to the test by pasting this into your terminal:</p>
<pre><code class="lang-bash"><span class="hljs-keyword">for</span> /l %i <span class="hljs-keyword">in</span> (1,1,100) <span class="hljs-keyword">do</span> curl -X GET http://localhost:3000
</code></pre>
<p>This will send 100 requests. Try opening two or more terminals and running the same command concurrently to see how your server handles the load.</p>
<p>Congratulations! You’ve built a basic server multiplexer (mux). It may not revolutionize the world, but it's a solid starting point to understand how routing works in web frameworks.</p>
<h1 id="heading-wrapping-up">Wrapping Up</h1>
<p>In this article, we took a deep dive into the concept of server-side frameworks, using Express as our primary example. We traced it from its high-level abstractions all the way down to the native TCP server built in C++. Then, to cement these ideas, we built our own simple server mux.</p>
<p>It’s a powerful learning exercise, because we stripped away the magic and dug into the core of how things work. While this example is just the tip of the iceberg, it gives you the tools to explore even deeper. For a challenge, look into how Express handles pattern matching and registering routes—try improving our simple mux!</p>
<p>I left out more advanced topics like updating our queue with a linked list and simulating concurrent requests, so this is something you can explore.</p>
<p>Thanks for reading! I hope you enjoyed this exploration as much as I did writing it. If you have any thoughts, questions, or just want to connect I am on <a target="_blank" href="https://x.com/codelit09">x</a>, feel free to reach out.</p>
<p>And of course, enjoy your timezone!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ APIs with Node.js and Express – Course in Spanish for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ An application programming interface (API) is software that acts as an intermediary, allowing two applications to communicate. This project will teach you how to develop an API step by step and connect it to a database. We just published a course on ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/apis-with-nodejs-and-express-course-in-spanish-for-beginners/</link>
                <guid isPermaLink="false">664da8a406db8b7935b9f445</guid>
                
                    <category>
                        <![CDATA[ APIs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Estefania Cassingena Navone ]]>
                </dc:creator>
                <pubDate>Wed, 22 May 2024 08:11:16 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1715802086520/639186b7-74e2-4ad0-bafb-9b8aa63cc5ba.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>An application programming interface (API) is software that acts as an intermediary, allowing two applications to communicate. This project will teach you how to develop an API step by step and connect it to a database.</p>
<p>We just published a course on the <a target="_blank" href="https://www.youtube.com/freecodecampespanol">freeCodeCamp.org Spanish YouTube channel</a> that is designed to teach you how to develop REST APIs step by step. You will also learn how to connect them to databases.</p>
<p>You will develop your API with TypeScript, Node.js, Express, MySQL, and TypeORM and you will test it using Postman, a platform for testing APIs.</p>
<p>You will start from the basics of Node.js and Express and gradually dive into more advanced concepts that will prepare you for connecting your API to a database. By the end of the course, you will be able to create your own APIs with Node.js and Express.</p>
<p>If you have Spanish-speaking friends, you are welcome to share the <a target="_blank" href="https://www.freecodecamp.org/espanol/news/aprende-a-crear-apis-desde-cero-con-node-js-y-express-curso-desde-cero/"><strong>Spanish version of this article</strong></a> with them.</p>
<p>This course was created by Leonardo José Castillo. Leonardo is a software developer and content creator who loves teaching programming and sharing his knowledge.</p>
<p>Are you ready? Let's see a quick overview of APIs and what you will learn during the course.</p>
<h2 id="heading-what-is-an-api"><strong>What is an API?</strong></h2>
<p>If you ever need to make two applications communicate with each other, APIs are exactly what you are looking for. They are software that you can use to send data between two applications through requests and responses.</p>
<p><strong>💡 Tip:</strong> API stands for Application Programming Interface.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-14-at-6.23.02-PM.png" alt="Screenshot-2024-05-14-at-6.23.02-PM" width="600" height="400" loading="lazy"></p>
<p>The developers of the application that will <strong>send</strong> the data to the other application will implement an API and document its functionality and endpoints, so other developers can use it and access its resources and data.</p>
<p>💡 <strong>Tip:</strong> An endpoint is a location in the API that accepts requests and sends back responses.</p>
<p>The developers of the application that will <strong>receive</strong> data from the API will write code for making these requests, specifying the endpoints and handling the response received from the API appropriately.</p>
<h2 id="heading-weather-api-example">Weather API Example</h2>
<p>For example, a weather application may access an API to get the current weather data of a location entered by the user.</p>
<p>The developers of the weather app write code to make requests to the weather API, following its guidelines and documentation. The API will then access the data on a database and send it to the client who made the request.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-14-at-6.32.00-PM.png" alt="Screenshot-2024-05-14-at-6.32.00-PM" width="600" height="400" loading="lazy"></p>
<p>This is the role of APIs. It's a very important role in the world of back-end web development.</p>
<p>You can implement them with many different technologies, including Node.js and Express.</p>
<p>Let's see what they are:</p>
<ul>
<li><p><strong>Node.js</strong> is a JavaScript runtime environment that allows you to run JavaScript code outside of the browser.</p>
</li>
<li><p><strong>Express</strong> is a Node.js framework that makes developing servers and APIs much easier.</p>
</li>
</ul>
<p>Learning how to design and implement APIs can open many career opportunities for you.</p>
<h2 id="heading-career-opportunities"><strong>Career Opportunities</strong></h2>
<p>Talking about career opportunities – TypeScript, Node.js, Express, and MySQL, the technologies that you will be practicing in this project, are very popular and in high demand in the programming industry.</p>
<p>To show you how important they are, here we have the results of the <a target="_blank" href="https://survey.stackoverflow.co/2023/#most-popular-technologies-language-prof">Stack Overflow 2023 Developer Survey</a>.</p>
<p>Node.js and Express were the first and fourth most popular web frameworks and technologies:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/node.png" alt="Results for all respondents of the Web Frameworks and Technologies category of the Stack Overflow 2023 Developer Survey." width="600" height="400" loading="lazy"></p>
<p>MySQL was also very high in the rankings. It was the second most popular database:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/mysql-survey-1.png" alt="Results of all respondents for the database category of the Stack Overflow 2023 Developer Survey." width="600" height="400" loading="lazy"></p>
<p>TypeScript was the fifth most popular language among all respondents:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/Screenshot-2024-04-29-at-2.56.30-PM.png" alt="Results of the most popular technologies of the Stack Overflow 2023 Developer Survey for all respondents." width="600" height="400" loading="lazy"></p>
<p>These results show you how relevant these technologies are are will be for web development in 2024 and beyond.</p>
<p>💡 <strong>Tip:</strong> During the project, you will also use TypeORM, an Object-Relational Mapping tool that helps you to work with databases in JavaScript, TypeScript, and other programming languages.</p>
<h2 id="heading-apis-course-with-nodejs-and-express"><strong>APIs Course with Node.js and Express</strong></h2>
<p>Great. Now that you know why APIs are so important, let's check out the topics that you will learn about during the course:</p>
<ul>
<li><p>Introduction to Node.js and Express</p>
</li>
<li><p>Application Architecture</p>
</li>
<li><p>Dynamic routing</p>
</li>
<li><p>Controllers</p>
</li>
<li><p>Database structure</p>
</li>
<li><p>Connecting the API to a database</p>
</li>
<li><p>Implementing CRUD operations in TypeScript</p>
</li>
<li><p>Modeling with TypeORM</p>
</li>
<li><p>Implementing controllers with TypeORM</p>
</li>
</ul>
<p>And more!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/frame.png" alt="Course screenshot. Implementing the API endpoints with Node.js and Express" width="600" height="400" loading="lazy"></p>
<p><strong>💡 Tip:</strong> To build this project, it's recommended to have a basic understanding of TypeScript and web development. If you need to review these topics, we have these courses on the channel:</p>
<ul>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=1hpc70_OoAg">Aprende Node.js y Express - Curso desde cero</a></p>
</li>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=T7uaEZ3ZoZE">Aprende TypeScript - Curso desde cero</a></p>
</li>
</ul>
<p>If you are ready to start building this API, check out the course in Spanish on the <a target="_blank" href="https://www.youtube.com/freecodecampespanol">freeCodeCamp.org Spanish YouTube channel</a>:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/yd_QpXWrbtQ" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<p>✍️ Course created by Leonardo José Castillo.</p>
<ul>
<li><p>Youtube: <a target="_blank" href="https://www.youtube.com/leonardocastillo79">@LeonardoCastillo79</a></p>
</li>
<li><p>LinkedIn: <a target="_blank" href="https://www.linkedin.com/in/leonardo-castillo-4911571a/">Leonardo José Castillo Lacruz</a></p>
</li>
<li><p>Twitter: <a target="_blank" href="https://twitter.com/ljcl79">@ljcl79</a></p>
</li>
<li><p>GitHub: <a target="_blank" href="https://github.com/ljcl79">@ljcl79</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ MERN Stack Roadmap – How to Learn MERN and Become a Full-Stack Developer ]]>
                </title>
                <description>
                    <![CDATA[ Have you ever wondered how modern web applications are built? How you can learn and master the technologies that you can use to build your own full stack projects from scratch? In this handbook, I'm going to introduce you to the MERN stack, a widely-... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/mern-stack-roadmap-what-you-need-to-know-to-build-full-stack-apps/</link>
                <guid isPermaLink="false">66c8c8f7fe21816c4cb75d21</guid>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MongoDB ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chris Blakely ]]>
                </dc:creator>
                <pubDate>Thu, 04 Jan 2024 00:57:45 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/01/Copy-of-mern-stack-hotel-booking-website--1-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Have you ever wondered how modern web applications are built? How you can learn and master the technologies that you can use to build your own full stack projects from scratch?</p>
<p>In this handbook, I'm going to introduce you to the MERN stack, a widely-used technology stack embraced by many leading companies. I'll guide you through 7 essential steps to start learning these technologies on your own. </p>
<p>By the time you finish reading, you'll have a solid understanding of what the MERN stack entails, its component technologies, and various resources for learning. You'll also have 10 project ideas that you can develop and showcase in your portfolio.</p>
<h1 id="heading-table-of-contents">Table of Contents</h1>
<ol>
<li><a class="post-section-overview" href="#heading-what-is-the-mern-stack">What is the MERN stack?</a></li>
<li><a class="post-section-overview" href="#heading-the-mern-stack-roadmap">The MERN Stack Roadmap</a><ul>
<li><a class="post-section-overview" href="#heading-step-1-learn-the-right-amount-of-html-javascript-and-css">STEP 1: Learn the right amount of HTML, JavaScript, and CSS</a></li>
<li><a class="post-section-overview" href="#heading-step-2-get-familiar-with-react">STEP 2: Get familiar with React</a></li>
<li><a class="post-section-overview" href="#heading-step-3-understand-rest-apis-and-how-a-backend-server-works-using-expressnode">STEP 3: Understand REST API's and how a backend server works using Express/Node</a></li>
<li><a class="post-section-overview" href="#heading-step-4-storing-data-with-mongodb-and-mongoose">STEP 4: Storing data with MongoDB and Mongoose</a></li>
<li><a class="post-section-overview" href="#heading-step-5-writing-tests">STEP 5: Writing tests</a></li>
<li><a class="post-section-overview" href="#heading-step-6-using-git">STEP 6: Using Git</a></li>
<li><a class="post-section-overview" href="#heading-step-7-deployments">STEP 7: Deployments</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-top-resources-to-learn-the-mern-stack">Top Resources to learn the MERN Stack</a></li>
<li><a class="post-section-overview" href="#heading-10-project-ideas-you-can-try-today">10 project ideas you can try today</a></li>
<li><a class="post-section-overview" href="#heading-wrapping-up-the-mern-stack-journey">Wrapping up the MERN stack journey</a></li>
</ol>
<h2 id="heading-what-is-the-mern-stack">What is the MERN stack?</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/MERN-Stack-wallpaper-gigapixel-hq-scale-6_00x.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The MERN stack, comprising MongoDB, Express.js, React, and Node.js, is a cohesive set of technologies used for building efficient and scalable web applications. </p>
<p>Its popularity stems from the seamless integration of each component: MongoDB's flexible data handling, Express.js's efficient server-side networking, React's dynamic user interfaces, and Node.js's powerful back-end runtime environment. </p>
<p>For beginners, the MERN stack is a smart choice because it uses JavaScript across all layers, simplifying the learning curve. This uniformity, coupled with a strong community and ample learning resources, makes it an accessible and practical toolkit for anyone looking to dive into full-stack development.</p>
<p>The MERN stack is also heavily utilized in the industry, favored by startups and large enterprises alike for its efficiency and the robust, modern web applications it can produce. This industry adoption not only validates its effectiveness but also opens up numerous career opportunities for those skilled in these technologies.</p>
<p>Let's look at a brief overview of what each part of the MERN Stack looks like:</p>
<h3 id="heading-frontend-react">Frontend (React)</h3>
<p>The frontend of a website is like the dining area of a restaurant. It's everything you see and interact with directly on a website – the layout, design, buttons, and text.  </p>
<p><strong>Example</strong>: When you visit a website and see a beautiful homepage, interact with menus, or fill out forms, you're experiencing the frontend.</p>
<h3 id="heading-backend-nodejs">Backend (Node.js)</h3>
<p>The backend is like the kitchen in a restaurant. It's where all the behind-the-scenes work happens. It includes the server, applications, and databases that work together to process the information you see on the frontend.  </p>
<p><strong>Example</strong>: When you order food (submit a form on the website), the kitchen (backend) processes the order (the data) and prepares your meal (the information or service you requested).</p>
<h3 id="heading-database-mongodb">Database (MongoDB)</h3>
<p>A database in web development is similar to a restaurant’s pantry or storage where all the ingredients (data) are kept. It stores and manages all the information needed by the website, like user profiles, content, and other data.  </p>
<p><strong>Example</strong>: In an online store, the database stores product information, prices, user reviews, and customer details.</p>
<h3 id="heading-rest-apis-express">REST APIs (Express)</h3>
<p>REST APIs are like the waiters in a restaurant. They are the messengers or go-betweens for the frontend and backend. They take requests (like orders) from the frontend (customer), fetch or update data in the backend (kitchen), and then return responses (prepared orders). </p>
<p>The terms POST, PUT, DELETE, and GET are types of requests used in REST APIs:</p>
<ul>
<li><strong>POST</strong>: Used to create new data. Like placing a new order in the restaurant.</li>
<li><strong>PUT</strong>: Used to update existing data. Similar to changing an order you've already placed.</li>
<li><strong>DELETE</strong>: Used to remove data. Like cancelling an order.</li>
<li><strong>GET</strong>: Used to retrieve data. Comparable to asking about the menu or checking the status of your order.</li>
</ul>
<h2 id="heading-the-mern-stack-roadmap">The MERN Stack Roadmap</h2>
<h3 id="heading-step-1-learn-the-right-amount-of-html-javascript-and-css">STEP 1: Learn the right amount of HTML, JavaScript, and CSS</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/b59a78e2ed76c705f3c0dcb300f3f222aefdcd99-gigapixel-hq-scale-6_00x.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The MERN stack uses JavaScript heavily, so its a natural first step to learn. In this section you will look at the main things you will use day-to-day when creating full-stack MERN apps. </p>
<p>Understanding JavaScript is like knowing the right amount of ingredients needed for a recipe. You don't need to master everything at once, just the essential ingredients that make your particular dish (or your web project) come to life.</p>
<h3 id="heading-variables">Variables</h3>
<p><a target="_blank" href="https://www.freecodecamp.org/news/javascript-variables-beginners-guide/">Variables in JavaScript</a> are like labelled jars in your kitchen. You can store things in them (like numbers, text) and use these jars later in your cooking (or coding).</p>
<p><strong>Example</strong>: A variable storing the user's name, so you can use it later to say "Hello, [Name]!"</p>
<h3 id="heading-functions">Functions</h3>
<p><a target="_blank" href="https://www.freecodecamp.org/news/javascript-functions-and-scope/">Functions</a> are like recipes in a cookbook. They are sets of instructions that perform a specific task. You can reuse these recipes whenever you need to perform that task again.</p>
<p><strong>Example</strong>: A function that calculates the total price of items in a shopping cart.</p>
<h3 id="heading-objects-amp-arrays">Objects &amp; Arrays</h3>
<p><a target="_blank" href="https://www.freecodecamp.org/news/javascript-basics-strings-arrays-objects/">Objects</a> are like information cards holding details about something (like a contact card), and arrays are like lists.  </p>
<p><strong>Example of an Object</strong>: A card holding a user's information (name, age, email).<br><strong>Example of an Array</strong>: A list of all the user's favorite book titles.</p>
<h3 id="heading-ifelse-statements-switch-statements">If/else Statements, Switch Statements</h3>
<p>These are like decision-making processes. <a target="_blank" href="https://www.freecodecamp.org/news/javascript-if-else-and-if-then-js-conditional-statements/">If/else statements</a> are like choosing what to wear based on the weather, and switch statements are like a more complex decision, like choosing what to cook based on multiple ingredients you have.  </p>
<p><strong>Example</strong>: If it's raining (if), take an umbrella (else), take sunglasses.</p>
<h3 id="heading-callbackspromisesasync-await">Callbacks/Promises/Async Await</h3>
<p>These are ways to handle tasks that take some time, like ordering food and waiting for it. <a target="_blank" href="https://www.freecodecamp.org/news/what-is-a-callback-function-in-javascript-js-callbacks-example-tutorial/">Callbacks</a> are like calling a friend to do something when they’re free. <a target="_blank" href="https://www.freecodecamp.org/news/javascript-promises-async-await-and-promise-methods/">Promises</a> are like your friend promising to do it. <a target="_blank" href="https://www.freecodecamp.org/news/javascript-async-await/">Async-await</a> is like making a plan to do tasks one after another in an organized way.  </p>
<p><strong>Example</strong>: Ordering a coffee (a task) and waiting to get it before leaving the cafe (ensuring order of actions).</p>
<h3 id="heading-ecmascript-template-strings-destructuring-assignment-spread-operator-default-parameters-and-so-on">ECMAScript (Template Strings, Destructuring Assignment, Spread Operator, Default Parameters, and so on)</h3>
<p>These are advanced tools and shortcuts in JavaScript to make coding easier and cleaner. It's like having a food processor in your kitchen that makes chopping and mixing faster and more efficient.  </p>
<p><strong>Example</strong>: Automatically creating a welcome message like "Hello, [Name]!" without manually joining words and variables.</p>
<h3 id="heading-typescript">TypeScript</h3>
<p><a target="_blank" href="https://www.freecodecamp.org/news/typescript-tutorial-for-react-developers/">TypeScript</a> is like JavaScript but with more rules for organizing your code (like a more detailed recipe book). It helps in managing larger projects by adding types to your code, making sure you don’t mix incompatible ingredients. <a target="_blank" href="https://www.freecodecamp.org/news/learn-typescript-beginners-guide/">This guide</a> teaches you TypeScript basics.  </p>
<p><strong>Example</strong>: Specifying that a function should only take a number as an input, not text or anything else. </p>
<h2 id="heading-step-2-get-familiar-with-react">STEP 2: Get Familiar with React</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/communityIcon_4g1uo0kd87c61.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Once you've got a feel for JavaScript, its time to venture into the wonderful world of frontend development. </p>
<p>React.js is a popular JavaScript library used for building user interfaces, particularly known for its efficiency in rendering dynamic, interactive web pages. It enables developers to create large web applications that can change data, without reloading the page, making for a smoother user experience.</p>
<p>Below is a list of common things you want to know when working with React.</p>
<h3 id="heading-components">Components</h3>
<p>Think of <a target="_blank" href="https://www.freecodecamp.org/news/how-to-use-react-components/">components</a> as individual LEGO blocks in a large LEGO model. Each block is a small, reusable piece that you can use to build different parts of your web application.  </p>
<p><strong>Example</strong>: A 'button' component in a website that can be used in many places, like for submitting a form or closing a pop-up.</p>
<h3 id="heading-jsx-javascript-xml">JSX (JavaScript XML)</h3>
<p><a target="_blank" href="https://www.freecodecamp.org/news/jsx-in-react-introduction/">JSX</a> lets you write your website's design code (like HTML) inside your JavaScript code. It's like writing the recipe and the cooking instructions in one place for easier reference.  </p>
<p><strong>Example</strong>: Writing a piece of JSX code that includes both JavaScript and HTML-like tags to create a user login form.</p>
<h3 id="heading-props-properties">Props (Properties)</h3>
<p><a target="_blank" href="https://www.freecodecamp.org/news/props-in-react/">Props are like instructions</a> or settings you pass to your LEGO blocks (components) to tell them what they should look like or do.  </p>
<p><strong>Example</strong>: Passing a 'color' prop to a 'button' component to make it red or blue depending on the situation.</p>
<h3 id="heading-state">State</h3>
<p><a target="_blank" href="https://www.freecodecamp.org/news/usestate-vs-redux-state-management/">State</a> is like a personal notebook for each component, where it keeps track of its own information, like whether a button is clicked or not. Here's a course all about <a target="_blank" href="https://www.freecodecamp.org/news/how-to-manage-state-in-react/">state management in React</a>.</p>
<p><strong>Example</strong>: A 'like' button keeping track of whether it has been clicked (liked) or not.</p>
<h3 id="heading-hooks">Hooks</h3>
<p><a target="_blank" href="https://www.freecodecamp.org/news/react-hooks-useeffect-usestate-and-usecontext/">Hooks are special tools in React</a> that let you add features like state to your components without needing to use complex code structures.</p>
<p><strong>Example</strong>: Using the useState hook to keep track of a counter in a component.</p>
<h3 id="heading-event-handling">Event Handling</h3>
<p>This is how you tell a component to do something when a user interacts with it, like clicking a button or entering text in a form.  </p>
<p><strong>Example</strong>: Setting up an event handler so that when a user clicks a 'submit' button, it sends their information to the server.</p>
<h3 id="heading-conditional-rendering">Conditional Rendering</h3>
<p>This is like having a magic painting that can change its picture based on certain conditions. In React, you can <a target="_blank" href="https://www.freecodecamp.org/news/react-conditional-rendering/">show different components based on different conditions</a>.</p>
<p><strong>Example</strong>: Showing a 'login' button if a user is not logged in, and a 'logout' button if they are.</p>
<h3 id="heading-lists-and-keys">Lists and Keys</h3>
<p>Keys are like name tags for items in a list. They help React keep track of which items are new, changed, or removed.  </p>
<p><strong>Example</strong>: Displaying a list of messages in a chat app, where each message has a unique key.</p>
<h3 id="heading-context-api">Context API</h3>
<p><a target="_blank" href="https://www.freecodecamp.org/news/context-api-in-react/">The Context API</a> a way to share information (like a theme setting or user data) across many components without passing the information through each level manually.</p>
<p><strong>Example</strong>: Using Context API to share the current logged-in user's information across different components in a web app.</p>
<h3 id="heading-fragment">Fragment</h3>
<p>Fragments let you group several components or elements together without adding extra layers to the website. It's like putting multiple LEGO pieces on a baseplate without the baseplate being part of the final model.</p>
<p><strong>Example</strong>: Grouping a list of items together in a menu without adding extra wrappers or divs around them.</p>
<h2 id="heading-step-3-understand-rest-apis-and-how-a-backend-server-works-using-expressnode">STEP 3: Understand REST API's and how a backend server works using Express/Node</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/express-and-node-opengraph-v1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Every UI needs a way to store and retrieve the data it needs to make the frontend work. This is where our backend comes in. </p>
<p>In the MERN stack, the backend is composed of 3 main bits: Express, a Node.js server, and a database. We'll cover the database shortly, for now we'll focus on the Express/Node pieces of the MERN stack, as they're closely related.  </p>
<p>Express.js is a lightweight, flexible framework for Node.js, designed to build web applications and <a target="_blank" href="https://www.freecodecamp.org/news/build-consume-and-document-a-rest-api/">REST APIs</a> with ease and efficiency. Node.js is a powerful JavaScript runtime that allows for the development of scalable server-side applications, making the duo a popular choice for backend development.</p>
<h3 id="heading-nodejs-foundation-for-building-web-applications">Node.js: Foundation for Building Web Applications</h3>
<p>Think of Node.js as the foundation for constructing a modern building. It's a platform that lets you build web applications, similar to how you would use a set of basic tools and materials to start building a house. You might hear this being to referred to as the "backend".  </p>
<p><strong>Example</strong>: Building a chat application where multiple users can send messages in real-time.</p>
<h3 id="heading-express-streamlining-rest-api-development">Express: Streamlining REST API Development</h3>
<p>Express is a helper tool for Node.js. It's like having a pre-built kit that makes building certain parts of your house easier and faster, providing templates and shortcuts so you don't have to start from scratch. </p>
<p><strong>Example</strong>: Using Express to quickly set up routes for a website, like a route to a contact page or a product catalog.</p>
<h3 id="heading-modules-and-packages-ready-made-components">Modules and Packages: Ready-Made Components</h3>
<p>In Node.js, modules are like pre-made components or sections of a house (like a bathroom unit or a kitchen set) that you can simply choose and add to your building project.</p>
<p><strong>Example</strong>: Adding a 'date-time' module to display current times and dates on your web application.</p>
<h3 id="heading-node-package-manager-npm-the-tool-and-material-warehouse">Node Package Manager (NPM): The Tool and Material Warehouse</h3>
<p>NPM acts like a vast warehouse where you can find all sorts of tools and materials (modules) you might need. It's a central place to get additional resources for building your web applications.</p>
<p><strong>Example</strong>: Installing 'body-parser' from npm to handle JSON data in your web application.</p>
<h3 id="heading-routing-directing-web-traffic">Routing: Directing Web Traffic</h3>
<p>Routing in Express is like setting up roads and paths in a housing complex. It's about directing the flow of traffic (data and user requests) to different parts of your web application.</p>
<p><strong>Example</strong>: Creating routes in an online store, like <code>/products</code> for the product list and <code>/products/:id</code> for individual product details.</p>
<h3 id="heading-middleware-additional-functional-layers">Middleware: Additional Functional Layers</h3>
<p>Middleware in Express can be seen as extra layers or services in your building, like security, plumbing, or electrical systems. They add specific functionalities to your web application.</p>
<p><strong>Example</strong>: Adding 'cookie-parser' middleware to handle cookies in your web application.</p>
<h3 id="heading-request-and-response-communication-channels">Request and Response: Communication Channels</h3>
<p>Requests and responses in Express are like sending and receiving mail or messages. They are the way your web application communicates with users, sending them data or receiving their inputs.</p>
<p><strong>Example</strong>: Your application receiving a user's login request (request) and then sending back a confirmation message (response).</p>
<h3 id="heading-environment-variables-secure-storage-spaces">Environment Variables: Secure Storage Spaces</h3>
<p>Think of environment variables as secure storage spaces or safes in your building. They're used to store sensitive information like passwords or personal settings, keeping them secure and separate from the main construction.</p>
<p><strong>Example</strong>: Storing the database connection string in an environment variable to keep it secure.</p>
<h3 id="heading-security-building-safeguards">Security: Building Safeguards</h3>
<p>In the context of web applications, security is about building safeguards into your project. It's like installing locks, security systems, and fire safety measures in a building to protect it from various threats.</p>
<p><strong>Example</strong>: Implementing HTTPS to secure data transmission and using JWT (JSON Web Tokens) for user authentication to protect user data.</p>
<h2 id="heading-step-4-storing-data-with-mongodb-and-mongoose">STEP 4: Storing data with MongoDB and Mongoose</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/MongoDB_Logo.svg.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>MongoDB is a NoSQL database that offers high flexibility and scalability for storing and managing data, making it ideal for handling large volumes and diverse types of data. </p>
<p>Mongoose is an Object Data Modeling (ODM) library for MongoDB, providing a straightforward schema-based solution to model application data, enhancing database interaction with useful features and validation.</p>
<h3 id="heading-mongodb-a-nosql-database">MongoDB: A NoSQL Database</h3>
<p>MongoDB is a type of database that stores data in a flexible, JSON-like format. This makes it different from traditional databases, which use tables and rows. It's great for handling large volumes of data and is very scalable.</p>
<p><strong>Example</strong>: Using MongoDB to store user profiles where each profile can have different fields.</p>
<h3 id="heading-collections-and-documents">Collections and Documents</h3>
<p>In MongoDB, data is stored in 'collections', which are similar to tables in a relational database. Inside these collections, data is stored in 'documents'. Think of a document as a single record in your collection, like an entry in a diary or a contact in an address book.</p>
<p><strong>Example</strong>: A 'users' collection with documents each representing a user with details like name and email.</p>
<h3 id="heading-mongoose-a-mongodb-object-modeling-tool">Mongoose: A MongoDB Object Modeling Tool</h3>
<p>Mongoose is a library for Node.js that makes it easier to interact with MongoDB. It provides a straightforward, schema-based solution to model your application data. It's like having a personal assistant to help manage the communication between your application and MongoDB.</p>
<p><strong>Example</strong>: Using Mongoose to easily add, retrieve, and manage user data in a MongoDB database.</p>
<h3 id="heading-schemas">Schemas</h3>
<p>In Mongoose, a schema is a structure that defines the format of the data to be stored in MongoDB (like defining fields and data types). Think of it as a blueprint for how your data should look.</p>
<p><strong>Example</strong>: Creating a Mongoose schema for a blog post with fields like title, author, and content.</p>
<h3 id="heading-models">Models</h3>
<p>A model in Mongoose acts as a constructor, compiled from the Schema definitions. It represents documents in a MongoDB database. Models are responsible for creating and reading documents from the underlying MongoDB database.</p>
<p><strong>Example</strong>: Defining a 'User' model based on a user schema to interact with the 'users' collection in the database.</p>
<h3 id="heading-crud-operations">CRUD Operations</h3>
<p>CRUD stands for Create, Read, Update, Delete. These are the basic operations you can perform on the database. Mongoose provides easy methods to execute these operations on your data.</p>
<p><strong>Example</strong>: Using Mongoose methods to add new users, find users by name, update user information, or delete users.</p>
<h3 id="heading-connecting-to-mongodb">Connecting to MongoDB</h3>
<p>You can use Mongoose to connect your Node.js application to a MongoDB database. This is like setting up a phone line between your application and the database so they can talk to each other.</p>
<p><strong>Example</strong>: Writing a Mongoose connect function to link your Node.js application to a MongoDB database hosted on Atlas.</p>
<h3 id="heading-querying-data">Querying Data</h3>
<p>Mongoose allows you to query your MongoDB database. This means you can search for specific data, filter your data based on certain criteria, and more.</p>
<p><strong>Example</strong>: Using a Mongoose query to find all blog posts written by a specific author.</p>
<h3 id="heading-data-validation">Data Validation</h3>
<p>Mongoose provides built-in validation. This is a way to make sure the data being saved to your database is in the right format, like checking if an email address looks like an email address.</p>
<p><strong>Example</strong>: Defining a schema in Mongoose where the email field must match the format of an email address.</p>
<h3 id="heading-middleware-pre-and-post-hooks">Middleware (Pre and Post Hooks)</h3>
<p>Mongoose middleware are functions which can be executed automatically before or after certain operations, like saving a document. They're useful for complex logic like hashing passwords before saving them to the database.</p>
<p><strong>Example</strong>: Using a pre-save middleware in Mongoose to hash user passwords before saving them to the database.</p>
<h3 id="heading-indexes">Indexes</h3>
<p>Indexes in MongoDB are used to improve the performance of searches. They are similar to indexes in a book, helping the database find data faster.</p>
<p><strong>Example</strong>: Creating an index on the 'email' field in a user collection to speed up the search for users by email.</p>
<h3 id="heading-aggregation">Aggregation</h3>
<p>Aggregation in MongoDB is a powerful way to process data and get computed results. It's like having a sophisticated calculator to perform complex operations on your data, such as summing up values or averaging them.</p>
<p><strong>Example</strong>: Using MongoDB's aggregation framework to calculate the average number of comments on blog posts.</p>
<h2 id="heading-step-5-writing-tests">STEP 5: Writing Tests</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/opengraph.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Testing in software development is like a safety check to ensure everything in your application works as expected. It's a crucial step in the development process where you look for bugs or issues before your users do. Think of it like proofreading an essay or checking a car before a road trip – it helps catch and fix problems early.</p>
<p>There are different types of testing, each serving a unique purpose:</p>
<h3 id="heading-unit-testing">Unit Testing</h3>
<p>This is the most basic form of testing. Here, you test individual parts of your code (like functions or components) in isolation. It's like checking each light bulb in a string of Christmas lights. </p>
<p>In the MERN stack, tools like Jest or Mocha are commonly used for this. They let you write small tests to check if a specific part of your application behaves as expected.</p>
<h3 id="heading-integration-testing">Integration Testing</h3>
<p>This type of testing checks how different parts of your application work together. It's like making sure all the light bulbs light up when connected and the string is plugged in. </p>
<p>For the MERN stack, you might still use Jest or Mocha, combined with other tools like Chai for assertions, to ensure that different components or services in your application interact correctly.</p>
<h3 id="heading-end-to-end-e2e-testing">End-to-End (E2E) Testing</h3>
<p>Here, you test your application's workflow from start to finish. It's like checking if the entire Christmas tree lights up and twinkles as expected. </p>
<p>For a MERN stack application, Cypress or Selenium are popular choices. They simulate real user scenarios, ensuring the entire application, from the front end in React to the back end with Express and Node.js, functions smoothly together.</p>
<h3 id="heading-performance-testing">Performance Testing</h3>
<p>This checks if your application can handle stress, like heavy traffic or data processing. It's akin to ensuring the Christmas tree lights don't blow a fuse when they're all on. Tools like Loader.io or Apache JMeter can be used here.</p>
<p>Each type of testing serves to ensure a different aspect of your application is working correctly, and together, they contribute to building a robust, reliable, and user-friendly application. </p>
<p>By employing these tests in the MERN stack, you not only catch bugs early but also maintain a high standard of quality for your application.</p>
<h2 id="heading-step-6-using-git">STEP 6: Using Git</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/629b7adc7c5cd817694c3231.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Git is a version control system, a tool that tracks changes in your code over time. Think of it like a detailed diary for your coding project. Every time you make changes to your code, Git keeps a record of what was changed, when, and by whom. This becomes incredibly useful when you’re working on complex projects, like those involving the MERN stack.</p>
<p>Why is Git so important when building with the MERN stack, or any software for that matter? </p>
<h3 id="heading-collaboration">Collaboration</h3>
<p>Git is like a team playbook. It allows multiple developers to work on the same project without stepping on each other's toes. </p>
<p>Everyone can work on different features or parts of the application simultaneously (like MongoDB database schemas, React components, or Express.js routes). Git helps manage these contributions, ensuring changes can be merged smoothly into the main project.</p>
<h3 id="heading-tracking-changes">Tracking Changes</h3>
<p>Imagine you've made changes to your React component, and suddenly things aren't working as they used to. Git allows you to go back in time and see what changes were made and by whom. </p>
<p>This historical data is invaluable for understanding how your project evolved and for fixing issues without having to start from scratch.</p>
<h3 id="heading-branching-and-merging">Branching and Merging</h3>
<p>Git’s branching feature lets you diverge from the main line of development and experiment with new features or ideas in a controlled way. </p>
<p>You can create a branch, make your changes, and then merge those changes back into the main project when they're ready. This ensures the main project (often referred to as the 'master' or 'main' branch) remains stable.</p>
<h3 id="heading-backup-and-restore">Backup and Restore</h3>
<p>With Git, your project's entire history is stored in the repository. If anything goes wrong, you can roll back to a previous state. It’s like having a fail-safe backup system.</p>
<h3 id="heading-documentation">Documentation</h3>
<p>Commit messages in Git provide a narrative for your project. They allow you to document what changes were made and why, which is extremely helpful for both your future self and other developers who might work on the project.</p>
<p>When building applications with the MERN stack, Git offers a safety net and a collaborative workspace. It keeps your project organized, tracks every change, and allows multiple developers to work together more efficiently. </p>
<p>Using Git is essential for managing complex development projects in today's software development world.</p>
<h2 id="heading-step-7-deployments">STEP 7: Deployments</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/img-blog-cico.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Code deployment is the process of taking code written by developers and making it operational on a live environment where users can interact with it. </p>
<p>In the context of the MERN stack, which involves technologies like MongoDB, Express.js, React, and Node.js, deployment is the final step in the journey of bringing a full-stack application to life.</p>
<p>Imagine you've built a house (your web application). Deployment is like moving it from the construction site (development environment) to its actual location where people can live in it (production environment). This process involves several key steps to ensure that everything works as expected when users access your app.</p>
<h3 id="heading-preparing-for-deployment">Preparing for Deployment</h3>
<p>Before deploying, you need to prepare your application. This involves ensuring your code is complete and tested, dependencies are properly managed, and your app is configured for the production environment. </p>
<p>For MERN stack applications, this might mean setting up environment variables, configuring your database (MongoDB) connection for production, and optimizing your React front-end for performance.</p>
<h3 id="heading-hosting-and-servers">Hosting and Servers</h3>
<p>Choosing where to host your application is crucial. For MERN stack apps, you can use cloud-based hosting services like AWS, Heroku, or DigitalOcean. These platforms offer services to host both your Node.js backend and MongoDB database, and they often provide additional features like scaling, monitoring, and security.</p>
<h3 id="heading-continuous-integrationcontinuous-deployment-cicd">Continuous Integration/Continuous Deployment (CI/CD)</h3>
<p>CI/CD is a methodology that automates the deployment process. Whenever you make changes to your codebase (like fixing a bug or adding a new feature), CI/CD tools automatically test your code and deploy it if the tests pass. This ensures that your application is always up-to-date with the latest changes. </p>
<p>Tools like Jenkins, Travis CI, or GitHub Actions are commonly used for this purpose.</p>
<h3 id="heading-monitoring-and-maintenance">Monitoring and Maintenance</h3>
<p>After deployment, it’s important to monitor your application for any issues and perform regular maintenance. This could involve checking server logs, updating dependencies, or rolling out fixes for any bugs that might arise.</p>
<h3 id="heading-rollbacks">Rollbacks</h3>
<p>A good deployment strategy also includes plans for rollbacks. If something goes wrong after deployment, you need to be able to revert to the previous version quickly to minimize downtime.</p>
<p>In the MERN stack, each component (MongoDB, Express.js, React, and Node.js) might require specific considerations for deployment. For instance, you might use Docker containers to package your Node.js and Express.js backend, ensuring it runs consistently across different environments. React apps might be minified and bundled for optimal performance.</p>
<p>In essence, deployment in the MERN stack is about getting your application from your local machine to a server where it can serve users reliably and efficiently. It involves careful planning, choosing the right hosting solutions, automating the process as much as possible, and being prepared to address issues post-deployment.</p>
<h2 id="heading-top-resources-to-learn-the-mern-stack">Top Resources to Learn the MERN Stack</h2>
<p>Now that you have a pretty good idea of what you need to learn to master the MERN stack, lets look at a list of free resources you can start using today to begin your journey!</p>
<ul>
<li>The <a target="_blank" href="https://www.freecodecamp.org/learn/">freeCodeCamp curriculum</a> is one of the best places to learn how to code for free. The curriculum is super in depth and covers alot of the topics mentioned in this post.</li>
<li>The freeCodeCamp YouTube channel has a ton of free courses you can try as well. <a target="_blank" href="https://www.youtube.com/watch?v=-42K44A1oMA&amp;t=2950s">This MERN stack book project is an excellent one for beginners</a>.</li>
<li><a target="_blank" href="https://fullstackopen.com/">Full Stack open</a> is another good resource. It doesn't teach MongoDB but you learn about SQL instead which is equally as useful.</li>
<li>My <a target="_blank" href="https://www.youtube.com/watch?v=YdBy9-0pER4">MERN stack hotel booking app project on YouTube is a free 15 hour course</a> where you will learn everything talked about in this post.</li>
</ul>
<h2 id="heading-10-project-ideas-you-can-try-today">10 Project Ideas You Can Try Today</h2>
<p>One of my favourite ways to learn new technologies is to build projects. If you're struggling with coming up with ideas, here's 10 projects you can start building today. I'll be building some of these on my <a target="_blank" href="https://www.youtube.com/@ChrisBlakely">YouTube channel</a> so feel free to subscribe to stay up to date!</p>
<h3 id="heading-personal-blog-website">Personal Blog Website</h3>
<ul>
<li><strong>Functionality</strong>: Users can read posts, and the admin can create, edit, or delete posts.</li>
<li><strong>Learning Focus</strong>: Basic CRUD operations, user authentication, integrating React with Express API, and using MongoDB for data storage.</li>
</ul>
<h3 id="heading-task-manager-to-do-list">Task Manager (To-Do List)</h3>
<ul>
<li><strong>Functionality</strong>: Users can add, view, edit, and delete tasks. Tasks can have deadlines and priority levels.</li>
<li><strong>Learning Focus</strong>: React state management, Express route handling, MongoDB operations, and basic UI development.</li>
</ul>
<h3 id="heading-simple-e-commerce-site">Simple E-commerce Site</h3>
<ul>
<li><strong>Functionality</strong>: Display products, add to cart, and checkout functionality. Admin features for adding or removing products.</li>
<li><strong>Learning Focus</strong>: React components for product listing, cart management, Express.js for product APIs, MongoDB for product data, and handling user inputs.</li>
</ul>
<h3 id="heading-recipe-sharing-application">Recipe Sharing Application</h3>
<ul>
<li><strong>Functionality</strong>: Users can post, view, edit, and delete recipes. Implement a rating or comment system for interaction.</li>
<li><strong>Learning Focus</strong>: File upload for images, user-generated content management, and MongoDB schema design.</li>
</ul>
<h3 id="heading-budget-tracker">Budget Tracker</h3>
<ul>
<li><strong>Functionality</strong>: Users can input their expenses and income, categorize them, and view a summary of their finances.</li>
<li><strong>Learning Focus</strong>: Handling forms in React, creating RESTful services with Express, and effective data structuring in MongoDB.</li>
</ul>
<h3 id="heading-event-planning-application">Event Planning Application:</h3>
<ul>
<li><strong>Functionality</strong>: Users can create and manage events, send invitations, and track RSVPs.</li>
<li><strong>Learning Focus</strong>: Date handling in JavaScript, complex MongoDB documents, and Express middleware for authentication.</li>
</ul>
<h3 id="heading-fitness-tracker">Fitness Tracker:</h3>
<ul>
<li><strong>Functionality</strong>: Log workouts, track progress over time, set fitness goals.</li>
<li><strong>Learning Focus</strong>: Data visualization with React, creating API endpoints for varied data types, and MongoDB for storing time-series data.</li>
</ul>
<h3 id="heading-chat-application">Chat Application:</h3>
<ul>
<li><strong>Functionality</strong>: Real-time chat rooms, private messaging, and user profiles.</li>
<li><strong>Learning Focus</strong>: WebSockets with Node.js for real-time communication, MongoDB for message storage, and React for dynamic UI updates.</li>
</ul>
<h3 id="heading-book-review-platform">Book Review Platform:</h3>
<ul>
<li><strong>Functionality</strong>: Users can post book reviews, rate books, and browse reviews by other users.</li>
<li><strong>Learning Focus</strong>: Integrating external APIs for book data, user-generated content moderation, and complex querying in MongoDB.</li>
</ul>
<h3 id="heading-local-business-directory">Local Business Directory:</h3>
<ul>
<li><strong>Functionality</strong>: Listing of local businesses with categories, user reviews, and contact information.</li>
<li><strong>Learning Focus</strong>: Geolocation, advanced querying and indexing in MongoDB, and creating a responsive layout with React.</li>
</ul>
<h2 id="heading-wrapping-up-the-mern-stack-journey">Wrapping Up the MERN Stack Journey</h2>
<p>As we reach the end of our exploration of the MERN stack, I hope this guide has illuminated the path for your journey into the world of full-stack development. </p>
<p>We've covered the fundamental components – MongoDB, Express.js, React, and Node.js – and delved into the practical aspects of building, testing, and deploying applications using this versatile stack.</p>
<p>Remember, the road to mastering the MERN stack is both challenging and rewarding. Each project you undertake will add to your skills and confidence. Use the resources and project ideas provided as stepping stones to build your portfolio and deepen your understanding. </p>
<p>The beauty of the MERN stack lies in its community-driven nature, so don't hesitate to seek help, collaborate, and share your experiences.  </p>
<p>If you want to get in touch, you can reach me over at <a target="_blank" href="https://www.linkedin.com/in/chrisblakely01/">LinkedIn</a>, or drop a comment on my <a target="_blank" href="https://www.youtube.com/@ChrisBlakely">YouTube channel</a>. Good luck and happy coding! </p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/undefined" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ MERN App Development – How to Build a CI/CD Pipeline with Jenkins ]]>
                </title>
                <description>
                    <![CDATA[ By Rakesh Potnuru As you continue to develop your software, you must also continue to integrate it with previous code and deploy it to servers.  Manually doing this is a time-consuming process that can occasionally result in errors. So we need to do ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/automate-mern-app-deployment-with-jenkins/</link>
                <guid isPermaLink="false">66d460c5d14641365a05095d</guid>
                
                    <category>
                        <![CDATA[ continuous deployment ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Continuous Integration ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Jenkins ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mongo ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 08 Mar 2023 17:42:07 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/03/CICD-Pipeline-with-Jenkins.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Rakesh Potnuru</p>
<p>As you continue to develop your software, you must also continue to integrate it with previous code and deploy it to servers. </p>
<p>Manually doing this is a time-consuming process that can occasionally result in errors. So we need to do this in a continuous and automated manner – which is what you will learn in this article.</p>
<p>We'll go over how you can improve your MERN (MongoDB, Express, React, and NodeJs) app development process by setting up a CI/CD pipeline with Jenkins. You'll see how to automate deployment for faster, more efficient releases.</p>
<h2 id="heading-lets-get-started">Let's Get Started</h2>
<h3 id="heading-prerequisites">Prerequisites</h3>
<ul>
<li>Basic understanding of MERN stack technologies.</li>
<li>Basic understanding of Docker.</li>
<li>Get source code from <a target="_blank" href="https://github.com/itsrakeshhq/productivity-app">GitHub</a></li>
</ul>
<h2 id="heading-the-problem">The Problem</h2>
<p>Consider this <a target="_blank" href="https://github.com/itsrakeshhq/productivity-app">productivity app</a> – it's a MERN project that we are going to use in this article. There are numerous steps we must complete, from building the application to pushing it to the Docker hub. </p>
<p>First, we must run tests with a command to determine whether all tests pass or not. If all tests pass, we build the Docker images and then push those images to Docker Hub. If your application is extremely complex, you may need to take additional steps. </p>
<p>Now, imagine that we're doing everything manually, which takes time and can lead to mistakes.</p>
<p><img src="https://i.imgur.com/iWAmMm4.jpg" alt="Waiting for deployment without devops meme" width="1600" height="840" loading="lazy">
<em>Waiting for deployment without devops meme</em></p>
<h2 id="heading-the-solution">The Solution</h2>
<p>To address this problem, we can create a CI/CD <strong>Pipeline</strong>. So, whenever you add a feature or fix a bug, this pipeline gets triggered. This automatically performs all of the steps from testing to deploying.</p>
<h2 id="heading-what-is-cicd-and-why-is-it-important">What is CI/CD and Why is it Important?</h2>
<p><strong>C</strong>ontinuous <strong>I</strong>ntegration and <strong>C</strong>ontinuous <strong>D</strong>eployment is a series of steps performed to automate software integration and deployment. CI/CD is the heart of DevOps.</p>
<p><img src="https://i.imgur.com/uMFtPwJ.png" alt="ci cd steps" width="1920" height="1080" loading="lazy">
<em>CI/CD steps</em></p>
<p>From development to deployment, our MERN app goes through four major stages: testing, building Docker images, pushing to a registry, and deploying to a cloud provider. All of this is done manually by running various commands. And we need to do this every time a new feature is added or a bug is fixed. </p>
<p>But this will significantly reduce developer productivity, which is why CI/CD can be so helpful in automating this process. In this article, we will cover the steps up until pushing to the registry.</p>
<p><img src="https://i.imgur.com/g2omESy.png" alt="ci cd meme" width="1600" height="840" loading="lazy">
<em>CI/CD meme</em></p>
<h2 id="heading-the-project">The Project</h2>
<p>The project we are going to use in this tutorial is a very simple full-stack MERN application.</p>
<p><img src="https://i.imgur.com/GSvRlQ0.gif" alt="project demo" width="600" height="338" loading="lazy">
<em>Project demo</em></p>
<p>It contains two microservices.</p>
<ol>
<li>Frontend</li>
<li>Backend</li>
</ol>
<p>You can learn more about the project <a target="_blank" href="https://blog.itsrakesh.co/lets-build-and-deploy-a-full-stack-mern-web-application">here</a>.</p>
<p>Both of these applications contains a Dockerfile. You can learn how to dockerize a MERN application <a target="_blank" href="https://blog.itsrakesh.co/dockerizing-your-mern-stack-app-a-step-by-step-guide">here</a>.</p>
<h2 id="heading-what-is-jenkins">What is Jenkins?</h2>
<p>To run a CI/CD pipeline, we need a CI/CD server. This is where all of the steps written in a pipeline run. </p>
<p>There are numerous services available on the market, including GitHub Actions, Travis CI, Circle CI, GitLab CI/CD, AWS CodePipeline, Azure DevOps, and Google Cloud Build. Jenkins is one of the popular CI/CD tools, and it's what we'll use here.</p>
<h2 id="heading-how-to-set-up-jenkins-server-on-azure">How to Set Up Jenkins Server on Azure</h2>
<p>Because Jenkins is open source and it doesn't provide a cloud solution, we must either run it locally or self-host on a cloud provider. Now, running locally can be difficult, particularly for Windows users. As a result, I've chosen to self-host it on Azure for this demo.</p>
<p>If you want to run locally or self-host somewhere other than Azure (follow <a target="_blank" href="https://www.jenkins.io/doc/book/installing/">these</a> guides by Jenkins), skip this section and proceed to the <strong>How to Configure Jenkins</strong> section.</p>
<p>First, you'll need to sign in to your <a target="_blank" href="https://Azure.microsoft.com?wt.mc_id=studentamb_90351">Azure</a> account (Create one if you don't have one already). Open Azure Cloud Shell.</p>
<p><img src="https://i.imgur.com/IN6RXAe.png" alt="opening azure cloud shell" width="2120" height="467" loading="lazy">
<em>Opening Azure Cloud Shell</em></p>
<p>Then create a directory called <code>jenkins</code> to store all the Jenkins config, and switch to that directory:</p>
<pre><code class="lang-bash">mkdir jenkins
<span class="hljs-built_in">cd</span> jenkins
</code></pre>
<p>Create a file called <code>cloud-init-jenkins.txt</code>. Open with nano or vim,</p>
<pre><code class="lang-bash">touch cloud-init-jenkins.txt
nano cloud-init-jenkins.txt
</code></pre>
<p>and paste this code into it:</p>
<pre><code class="lang-bash"><span class="hljs-comment">#cloud-config</span>
package_upgrade: <span class="hljs-literal">true</span>
runcmd:
  - sudo apt install openjdk-11-jre -y
  - wget -qO - https://pkg.jenkins.io/debian-stable/jenkins.io.key | sudo apt-key add -
  - sh -c <span class="hljs-string">'echo deb https://pkg.jenkins.io/debian-stable binary/ &gt; /etc/apt/sources.list.d/jenkins.list'</span>
  - sudo apt-get update &amp;&amp; sudo apt-get install jenkins -y
  - sudo service jenkins restart
</code></pre>
<p>Here, we'll use this file to install Jenkins after creating a virtual machine. First, we install openjdk, which is required for Jenkins to function. The Jenkins service is then restarted after we install it.</p>
<p>Next, create a resource group. (A resource group in Azure is like a container that holds all the related resources of a project in one group. Learn more about resource groups <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal#what-is-a-resource-group?wt.mc_id=studentamb_90351">here</a>.)</p>
<pre><code class="lang-bash">az group create --name jenkins-rg --location centralindia
</code></pre>
<p><strong>Note:</strong> make sure to change the location to the one closest to you.</p>
<p>Now, create a virtual machine.</p>
<pre><code class="lang-bash">az vm create \
--resource-group jenkins-rg \
--name jenkins-vm \
--image UbuntuLTS \
--admin-username <span class="hljs-string">"azureuser"</span> \
--generate-ssh-keys \
--public-ip-sku Standard \
--custom-data cloud-init-jenkins.txt
</code></pre>
<p>You can verify the VM installation with this command:</p>
<pre><code class="lang-bash">az vm list -d -o table --query <span class="hljs-string">"[?name=='jenkins-vm']"</span>
</code></pre>
<p>Don't be confused. This command simply displays JSON data in a tabular format for easy verification.</p>
<p>Jenkins server runs on port <code>8080</code>, so we need to expose this port on our VM. You can do that like this:</p>
<pre><code class="lang-bash">az vm open-port \
--resource-group jenkins-rg \
--name jenkins-vm  \
--port 8080 --priority 1010
</code></pre>
<p>Now we can access the Jenkins dashboard in the browser with the URL <code>http://&lt;your-vm-ip&gt;:8080</code>. Use this command to get the VM IP address:</p>
<pre><code class="lang-bash">az vm show \
--resource-group jenkins-rg \
--name jenkins-vm -d \
--query [publicIps] \
--output tsv
</code></pre>
<p>You can now see the Jenkins application in your browser.</p>
<p><img src="https://i.imgur.com/Sy1Glar.png" alt="jenkins dashboard" width="2007" height="1025" loading="lazy">
<em>Jenkins dashboard</em></p>
<p>As you'll notice, Jenkins is asking us to provide an admin password which is automatically generated during its installation.</p>
<p>But first let's SSH into our virtual machine where Jenkins is installed.</p>
<pre><code class="lang-bash">ssh azureuser@&lt;ip_address&gt;
</code></pre>
<p>Now, type in the below command to get the password:</p>
<pre><code class="lang-bash">sudo cat /var/lib/jenkins/secrets/initialAdminPassword
</code></pre>
<p>Copy and paste it. Then click <strong>Continue</strong>.</p>
<h2 id="heading-how-to-configure-jenkins">How to Configure Jenkins</h2>
<p>First, you'll need to click <strong>Install suggested plugins</strong>. It will take some time to install all the plugins.</p>
<p><img src="https://i.imgur.com/vDaaqE3.png" alt="installing suggested plugins" width="2010" height="1095" loading="lazy">
<em>Installing suggested plugins</em></p>
<p>An admin user is needed to restrict access to Jenkins. So go ahead and create one. After finishing, click <strong>Save and continue</strong>.</p>
<p><img src="https://i.imgur.com/qqkwQN6.png" alt="create an admin user" width="2010" height="1036" loading="lazy">
<em>Create an admin user</em></p>
<p>Now you will be presented with the Jenkins dashboard.</p>
<p>The first step is to install the "Blue Ocean" plugin. Jenkins has a very old interface, which may make it difficult for some people to use. This blue ocean plugin provides a modern interface for some Jenkins components (like creating a pipeline).</p>
<p>To install plugins, go to <strong>Manage Jenkins</strong> -&gt; click <strong>Manage Plugins</strong> under "System Configuration" -&gt; <strong>Available plugins</strong>. Search for "Blue Ocean" -&gt; check the box and click <strong>Download now and install after restart</strong>.</p>
<p><img src="https://i.imgur.com/dAKBLiq.png" alt="blue ocean" width="1920" height="1080" loading="lazy">
<em>Blue ocean</em></p>
<p>Great, we're all set. Now let's create a pipeline.</p>
<h2 id="heading-how-to-write-a-jenkinsfile">How to Write a Jenkinsfile</h2>
<p>To create a pipeline, we need a <strong>Jenkinsfile</strong>. This file contains all the pipeline configurations – stages, steps, and so on. Jenkinsfile is to Jenkins as a Dockerfile is to Docker.</p>
<p>Jenkinsfile uses the <strong>Groovy</strong> syntax. The syntax is very simple. You can understand everything by just looking at it.</p>
<p>Let's start by writing:</p>
<pre><code class="lang-groovy">pipeline {

}
</code></pre>
<p>The word 'agent' should be the first thing you mention in the pipeline. An agent is similar to a container or environment in which jobs run. You can use multiple agents to run jobs in parallel. You can find more information about Jenkins agents can <a target="_blank" href="https://www.jenkins.io/doc/book/using/using-agents/">here</a>.</p>
<pre><code class="lang-groovy">pipeline {
    agent any
}
</code></pre>
<p>Here we are telling Jenkins to use any available agent.</p>
<p>We have a total of 5 stages in our pipeline:</p>
<p><img src="https://i.imgur.com/ezvdElo.png" alt="ci cd pipeline stages" width="1920" height="1080" loading="lazy">
<em>CI/CD pipeline stages</em></p>
<h3 id="heading-stage-1-checkout-code">Stage 1: Checkout code</h3>
<p>Different CI/CD tools use different naming conventions. In Jenkins, these are referred to as stages. In each stage we write various steps.</p>
<p>Our first stage is checking out code from a source code management system (in our case, GitHub).</p>
<pre><code class="lang-groovy">pipeline {
    agent any

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }
    }
}
</code></pre>
<p>Commit the changes and push to your GitHub repo.</p>
<p>Since we haven't created any pipelines yet, let's do that now.</p>
<p>Before we begin, we must ensure that Git is installed on our system. If you followed my previous steps to install Jenkins on an Azure VM, Git is already installed. </p>
<p>You can test it by running the following command (make you are still SSHed into the VM):</p>
<pre><code class="lang-bash">git --version
</code></pre>
<p>If it isn't already installed, you can do so with:</p>
<pre><code class="lang-bash">sudo apt install git
</code></pre>
<p>Open blue ocean. Click <strong>Create new pipeline</strong>.</p>
<p><img src="https://i.imgur.com/FNffT6p.png" alt="creating new pipeline" width="2010" height="1036" loading="lazy">
<em>Creating new pipeline</em></p>
<p>Then select your source code management system. If you chose GitHub, you must provide an access token for Jenkins to access your repository. I recommend clicking on <strong>Create an access token here</strong> because it is a template with all of the necessary permissions. Then click <strong>Connect</strong>.</p>
<p><img src="https://i.imgur.com/H9TUsHV.png" alt="selecting scm" width="2010" height="1036" loading="lazy">
<em>Selecting scm</em></p>
<p>After that, a pipeline will be created. Since our repository already contains a Jenkinsfile, Jenkins automatically detects it and runs the stages and steps we mentioned in the pipeline.</p>
<p>If everything went well, the entire page will turn green. (Other colors: <strong>blue</strong> indicates that the pipeline is running, <strong>red</strong> indicates that something went wrong in the pipeline, and <strong>gray</strong> indicates that we stopped the pipeline.)</p>
<p><img src="https://i.imgur.com/FtvJlND.png" alt="stage one successful" width="1884" height="486" loading="lazy">
<em>Stage one successful</em></p>
<h3 id="heading-stage-2-run-frontend-tests">Stage 2: Run frontend tests</h3>
<p>In general, all the CI/CD pipelines contains some tests that needs to be run before deploying. So I added simple tests to both the frontend and backend. Let's start with the frontend tests.</p>
<pre><code class="lang-groovy">stage('Client Tests') {
    steps {
        dir('client') {
            sh 'npm install'
            sh 'npm test'
        }
    }
}
</code></pre>
<p>We're changing the directory to <code>client/</code> because that's where the frontend code is. And then install the dependencies with <code>npm install</code> and run the tests with <code>npm test</code> in a shell.</p>
<p>Again, before we restart the pipeline, we have to make sure node and npm are installed or not. Install node and npm with these commands in the virtual machine:</p>
<pre><code class="lang-bash">curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -
</code></pre>
<p>After that, run the following:</p>
<pre><code class="lang-bash">sudo apt-get install -y nodejs
</code></pre>
<p>Now, commit the code and restart the pipeline.</p>
<p><img src="https://i.imgur.com/OWYcdDu.png" alt="run client tests" width="1886" height="438" loading="lazy">
<em>Run client tests</em></p>
<h3 id="heading-stage-3-run-backend-tests">Stage 3: Run backend tests</h3>
<p>Now do the same thing for the backend tests.</p>
<p>But there is one thing we need to do before we proceed. If you take a look at the codebase and <code>activity.test.js</code>, we are using a few environment variables. So let's add these environment varibales in Jenkins.</p>
<h4 id="heading-how-to-add-environment-variables-in-jenkins">How to add environment variables in Jenkins</h4>
<p>To add environment variables, go to <strong>Manage Jenkins</strong> -&gt; click <strong>Manage Credentials</strong> under "Security" -&gt;  <strong>System</strong> -&gt; <strong>Global credentials (unrestricted)</strong> -&gt; click <strong>+ Add Credentials</strong>.</p>
<p>For <strong>Kind</strong> select "Secret text", leave <strong>Scope</strong> default, and for <strong>Secret</strong> write the secret value and <strong>ID</strong>. This is what we use when using these environment variables in the Jenkinsfile.</p>
<p>Add the following env variables:</p>
<p><img src="https://i.imgur.com/xGjg2mG.png" alt="environment variables" width="2091" height="796" loading="lazy">
<em>Environment variables</em></p>
<p>Then in the Jenkinsfile, use these env variables:</p>
<pre><code class="lang-groovy">environment {
    MONGODB_URI = credentials('mongodb-uri')
    TOKEN_KEY = credentials('token-key')
    EMAIL = credentials('email')
    PASSWORD = credentials('password')
}
</code></pre>
<p>Add a stage to install dependencies, set these variables in the Jenkins environment, and run the tests:</p>
<pre><code class="lang-groovy">stage('Server Tests') {
    steps {
        dir('server') {
            sh 'npm install'
            sh 'export MONGODB_URI=$MONGODB_URI'
            sh 'export TOKEN_KEY=$TOKEN_KEY'
            sh 'export EMAIL=$EMAIL'
            sh 'export PASSWORD=$PASSWORD'
            sh 'npm test'
        }
    }
}
</code></pre>
<p>Again, commit the code and restart the pipeline.</p>
<p><img src="https://i.imgur.com/hpjMUyT.png" alt="run server tests" width="1059" height="595" loading="lazy">
<em>Run server tests</em></p>
<h3 id="heading-stage-4-build-docker-images">Stage 4: Build Docker images</h3>
<p>Now, we have to specify a step to build the Docker images from the Dockerfiles.</p>
<p>Before we proceed, install Docker in the VM (if you don't already have it installed).</p>
<p>To install Docker:</p>
<pre><code class="lang-bash">sudo apt install docker.io
</code></pre>
<p>Add the user <code>jenkins</code> to the <code>docker</code> group so that Jenkins can access the Docker daemon – otherwise you'll get a permission denied error.</p>
<pre><code class="lang-bash">sudo usermod -a -G docker jenkins
</code></pre>
<p>Then restart the <code>jenkins</code> service.</p>
<pre><code class="lang-bash">sudo systemctl restart jenkins
</code></pre>
<p>Add a stage in the Jenkinsfile.</p>
<pre><code class="lang-groovy">stage('Build Images') {
    steps {
        sh 'docker build -t rakeshpotnuru/productivity-app:client-latest client'
        sh 'docker build -t rakeshpotnuru/productivity-app:server-latest server'
    }
}
</code></pre>
<p>Commit the code and restart the pipeline.</p>
<p><img src="https://i.imgur.com/USh63SD.png" alt="build docker images" width="1984" height="914" loading="lazy">
<em>Build docker images</em></p>
<h3 id="heading-stage-5-push-images-to-the-registry">Stage 5: Push images to the registry</h3>
<p>As a final stage, we will push the images to Docker hub.</p>
<p>Before that, add your docker hub username and password to the Jenkins credentials manager, but for <strong>Kind</strong> choose "Username with password".</p>
<p><img src="https://i.imgur.com/ue0MMKM.png" alt="username with password type credential" width="2010" height="1095" loading="lazy">
<em>Username with password type credential</em></p>
<p>Add the final stage where we login and push images to Docker hub.</p>
<pre><code class="lang-groovy">stage('Push Images to DockerHub') {
    steps {
        withCredentials([usernamePassword(credentialsId: 'dockerhub', passwordVariable: 'DOCKER_PASSWORD', usernameVariable: 'DOCKER_USERNAME')]) {
            sh 'docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD'
            sh 'docker push rakeshpotnuru/productivity-app:client-latest'
            sh 'docker push rakeshpotnuru/productivity-app:server-latest'
        }
    }
}
</code></pre>
<p><img src="https://i.imgur.com/copfIou.png" alt="push images to dockerhub" width="1991" height="919" loading="lazy">
<em>Push images to dockerhub</em></p>
<p>Here is the complete Jenkinsfile:</p>
<pre><code class="lang-groovy">// This is a Jenkinsfile. It is a script that Jenkins will run when a build is triggered.
pipeline {
    // Telling Jenkins to run the pipeline on any available agent.
    agent any

    // Setting environment variables for the build.
    environment {
        MONGODB_URI = credentials('mongodb-uri')
        TOKEN_KEY = credentials('token-key')
        EMAIL = credentials('email')
        PASSWORD = credentials('password')
    }

    // This is the pipeline. It is a series of stages that Jenkins will run.
    stages {
        // This state is telling Jenkins to checkout the source code from the source control management system.
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        // This stage is telling Jenkins to run the tests in the client directory.
        stage('Client Tests') {
            steps {
                dir('client') {
                    sh 'npm install'
                    sh 'npm test'
                }
            }
        }

        // This stage is telling Jenkins to run the tests in the server directory.
        stage('Server Tests') {
            steps {
                dir('server') {
                    sh 'npm install'
                    sh 'export MONGODB_URI=$MONGODB_URI'
                    sh 'export TOKEN_KEY=$TOKEN_KEY'
                    sh 'export EMAIL=$EMAIL'
                    sh 'export PASSWORD=$PASSWORD'
                    sh 'npm test'
                }
            }
        }

        // This stage is telling Jenkins to build the images for the client and server.
        stage('Build Images') {
            steps {
                sh 'docker build -t rakeshpotnuru/productivity-app:client-latest client'
                sh 'docker build -t rakeshpotnuru/productivity-app:server-latest server'
            }
        }

        // This stage is telling Jenkins to push the images to DockerHub.
        stage('Push Images to DockerHub') {
            steps {
                withCredentials([usernamePassword(credentialsId: 'dockerhub', passwordVariable: 'DOCKER_PASSWORD', usernameVariable: 'DOCKER_USERNAME')]) {
                    sh 'docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD'
                    sh 'docker push rakeshpotnuru/productivity-app:client-latest'
                    sh 'docker push rakeshpotnuru/productivity-app:server-latest'
                }
            }
        }
    }
}
</code></pre>
<p><img src="https://i.imgur.com/NQxFXhO.png" alt="pipeline ran successfully" width="2120" height="1155" loading="lazy">
<em>Pipeline ran successfully</em></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In summary, let's review what we've covered:</p>
<ul>
<li>We explored the significance of implementing Continuous Integration and Continuous Deployment (CI/CD) in software development.</li>
<li>We delved into the fundamentals of Jenkins and acquired knowledge on how to deploy a Jenkins server on the Azure cloud platform.</li>
<li>We customized Jenkins to meet our specific requirements.</li>
<li>Lastly, we wrote a Jenkinsfile and built a pipeline utilizing the user-friendly interface of Jenkins Blue Ocean.</li>
</ul>
<p>That's all for now! Thanks for reading 🙂.</p>
<p>Connect with me on <a target="_blank" href="https://twitter.com/rakesh_at_tweet">twitter</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What Are Node Modules and How Do You Use Them? ]]>
                </title>
                <description>
                    <![CDATA[ Every Node.js application has modules. These modules form part of the building blocks of the application. They help developers work faster and write more structured code. In this tutorial, you will learn what node modules are. You will also learn abo... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-are-node-modules/</link>
                <guid isPermaLink="false">66d45df0182810487e0ce11d</guid>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ modules ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node ]]>
                    </category>
                
                    <category>
                        <![CDATA[ npm ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Benjamin Semah ]]>
                </dc:creator>
                <pubDate>Tue, 06 Dec 2022 17:54:33 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/11/stock.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Every Node.js application has modules. These modules form part of the building blocks of the application. They help developers work faster and write more structured code.</p>
<p>In this tutorial, you will learn what node modules are. You will also learn about the three types of node modules. And we'll go over the right way to use them in your applications.</p>
<h2 id="heading-what-is-a-module-in-javascript">What is a Module in JavaScript?</h2>
<p>In simple terms, a module is a piece of reusable JavaScript code. It could be a <code>.js</code> file or a directory containing <code>.js</code> files. You can export the content of these files and use them in other files.</p>
<p>Modules help developers adhere to the DRY (Don't Repeat Yourself) principle in programming. They also help to break down complex logic into small, simple, and manageable chunks.</p>
<h2 id="heading-types-of-node-modules">Types of Node Modules</h2>
<p>There are three main types of Node modules that you will work with as a Node.js developer. They include the following.</p>
<ul>
<li><p>Built-in modules</p>
</li>
<li><p>Local modules</p>
</li>
<li><p>Third-party modules</p>
</li>
</ul>
<h3 id="heading-built-in-modules">Built-in Modules</h3>
<p>Node.js comes with some modules out of the box. These modules are available for use when you install Node.js. Some common examples of built-in Node modules are the following:</p>
<ul>
<li><p>http</p>
</li>
<li><p>url</p>
</li>
<li><p>path</p>
</li>
<li><p>fs</p>
</li>
<li><p>os</p>
</li>
</ul>
<p>You can use the built-in modules with the syntax below.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> someVariable = <span class="hljs-built_in">require</span>(<span class="hljs-string">'nameOfModule'</span>)
</code></pre>
<p>You load the module with the <code>require</code> function. You need to pass the name of the module you're loading as an argument to the <code>require</code> function.</p>
<p><strong>Note:</strong> The name of the module must be in quotation marks. Also, using <code>const</code> to declare the variable ensures that you do not overwrite the value when calling it.</p>
<p>You also need to save the returned value from the <code>require</code> function in <code>someVariable</code>. You can name that variable anything you want. But often, you will see programmers give the same to the variable as the name of the module (see example below).</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> http = <span class="hljs-built_in">require</span>(<span class="hljs-string">'http'</span>) 

server = http.createServer(<span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> { 
    res.writeHead(<span class="hljs-number">200</span>, {<span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'text/plain'</span>}) 
    res.end(<span class="hljs-string">'Hello World!'</span>)
})

server.listen(<span class="hljs-number">3000</span>)
</code></pre>
<p>You use the <code>require</code> function to load the built-in <code>http</code> module. Then, you save the returned value in a variable named <code>http</code>.</p>
<p>The returned value from the <code>http</code> module is an object. Since you've loaded it using the <code>require</code> function, you can now use it in your code. For example, call the <code>.createServer</code> property to create a server.</p>
<h3 id="heading-local-modules">Local Modules</h3>
<p>When you work with Node.js, you create local modules that you load and use in your program. Let's see how to do that.</p>
<p>Create a simple <code>sayHello</code> module. It takes a <code>userName</code> as a parameter and prints "hello" and the user's name.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sayHello</span>(<span class="hljs-params">userName</span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Hello <span class="hljs-subst">${userName}</span>!`</span>)
}

<span class="hljs-built_in">module</span>.exports = sayHello
</code></pre>
<p>First, you need to create the function. Then you export it using the syntax <code>module.exports</code>. It doesn't have to be a function, though. Your module can export an object, array, or any data type.</p>
<h4 id="heading-how-to-load-your-local-modules">How to load your local modules</h4>
<p>You can load your local modules and use them in other files. To do so, you use the <code>require</code> function as you did for the built-in modules.</p>
<p>But with your custom functions, you need to provide the path of the file as an argument. In this case, the path is <code>'./sayHello</code>' (which is referencing the <code>sayHello.js</code> file).</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> sayHello = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./sayHello'</span>)
sayHello(<span class="hljs-string">"Maria"</span>) <span class="hljs-comment">// Hello Maria!</span>
</code></pre>
<p>Once you've loaded your module, you can make a reference to it in your code.</p>
<h3 id="heading-third-party-modules">Third-Party Modules</h3>
<p>A cool thing about using modules in Node.js is that you can share them with others. The Node Package Manager (NPM) makes that possible. When you install Node.js, NPM comes along with it.</p>
<p>With NPM, you can share your modules as packages via <a target="_blank" href="https://www.npmjs.com/">the NPM registry.</a> And you can also use packages others have shared.</p>
<h4 id="heading-how-to-use-third-party-packages">How to use third-party packages</h4>
<p>To use a third-party package in your application, you first need to install it. You can run the command below to install a package.</p>
<pre><code class="lang-javascript">npm install &lt;name-<span class="hljs-keyword">of</span>-package&gt;
</code></pre>
<p>For example, there's a package called <code>capitalize</code>. It performs functions like capitalizing the first letter of a word.</p>
<p>Running the command below will install the capitalize package:</p>
<pre><code class="lang-javascript">npm install capitalize
</code></pre>
<p>To use the installed package, you need to load it with the <code>require</code> function.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> capitalize = <span class="hljs-built_in">require</span>(<span class="hljs-string">'capitalize)</span>
</code></pre>
<p>And then you can use it in your code, like this for example:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> capitalize = <span class="hljs-built_in">require</span>(<span class="hljs-string">'capitalize'</span>)
<span class="hljs-built_in">console</span>.log(capitalize(<span class="hljs-string">"hello"</span>)) <span class="hljs-comment">// Hello</span>
</code></pre>
<p>This is a simple example. But there are packages that perform more complex tasks and can save you loads of time.</p>
<p>For example, you can use the Express.js package which is a Node.js framework. It makes building apps faster and simple. To learn more about NPM, read this <a target="_blank" href="https://www.freecodecamp.org/news/what-is-npm-a-node-package-manager-tutorial-for-beginners/">freeCodeCamp article on the Node Package Manager</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, you learned about what Node modules are and the three types of node modules. You also learned about how to use the different types in your application.</p>
<p>Thanks for reading. And happy coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What Exactly is Node.js? Explained for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ Node.js allows developers to create both front-end and back-end applications using JavaScript. It was released in 2009 by Ryan Dahl. In this article, you will learn about Node.js. You will learn the following: What is Node.js? How the Node.js envir... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-is-node-js/</link>
                <guid isPermaLink="false">66d45df255db48792eed3f4f</guid>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node ]]>
                    </category>
                
                    <category>
                        <![CDATA[ npm ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Benjamin Semah ]]>
                </dc:creator>
                <pubDate>Mon, 05 Dec 2022 15:18:12 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/11/What-is.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Node.js allows developers to create both front-end and back-end applications using JavaScript. It was released in 2009 by Ryan Dahl.</p>
<p>In this article, you will learn about Node.js. You will learn the following:</p>
<ul>
<li><p>What is Node.js?</p>
</li>
<li><p>How the Node.js environment differs from the browser.</p>
</li>
<li><p>Why you should learn Node.js.</p>
</li>
<li><p>How to get started with Node.js.</p>
</li>
<li><p>Resources to help you learn Node.js.</p>
</li>
</ul>
<h2 id="heading-what-is-nodejs">What is Node.js?</h2>
<blockquote>
<p>"Node.js is an open-source and cross-platform JavaScript runtime environment." - <a target="_blank" href="https://nodejs.dev/en/learn/introduction-to-nodejs/">Nodejs.dev Docs</a></p>
</blockquote>
<p>This sounds like a cool, straightforward answer. But for a beginner, this definition might raise further questions. So let's break it down and understand what it means.</p>
<p><strong>Node.js is open-source:</strong> This means that the source code for Node.js is publicly available. And it's maintained by contributors from all over the world. The <a target="_blank" href="https://nodejs.org/en/get-involved/contribute/">Node.js contribution guide</a> shows you how to contribute.</p>
<p><strong>Node.js is cross-platform:</strong> Node.js is not dependent on any operating system software. It can work on Linux, macOS, or Windows.</p>
<p><strong>Node.js is a JavaScript runtime environment:</strong> When you write JavaScript code in your text editor, that code cannot perform any task unless you execute (or run) it. And to run your code, you need a runtime environment.</p>
<p>Browsers like Chrome and Firefox have runtime environments. That is why they can run JavaScript code. Before Node.js was created, JavaScript could only run in a browser. And it was used to build only front-end applications.</p>
<p>Node.js provides a runtime environment outside of the browser. It's also built on the <a target="_blank" href="https://www.freecodecamp.org/news/javascript-under-the-hood-v8/">Chrome V8 JavaScript engine</a>. This makes it possible to build back-end applications using the same JavaScript programming language you may be familiar with.</p>
<h2 id="heading-differences-between-the-browser-and-nodejs-runtime-environments">Differences Between the Browser and Node.js Runtime Environments</h2>
<p>Both the browser and Node.js are capable of executing JavaScript programs. But there are some key differences that you need to know. They include the following.</p>
<h3 id="heading-access-to-the-dom-apis">Access to the DOM APIs</h3>
<p>With the browser runtime, you can access the Document Object Model (DOM). And you can perform all the DOM operations. But Node.js does not have access to the DOM.</p>
<p>Node.js exposes almost all the system resources to your programs. This means you can interact with the operating system, access the file systems, and read and write to the files. But, you do not have access to operating systems and file systems from the browser.</p>
<h3 id="heading-window-vs-global-object">Window vs Global object</h3>
<p>JavaScript has a built-in global object. The JavaScript global object for the browser is called the <code>window</code> object. In Node.js, the global object goes by the name <code>global</code>.</p>
<p>The <code>window</code> object contains methods and properties available only in the browser environment.</p>
<h3 id="heading-control-over-runtime-versions">Control over runtime versions</h3>
<p>With Node.js, you can choose which version to run your server-side application on. As a result, you can use modern JavaScript features without worrying about any version-specific inconsistencies.</p>
<p>Contrast this to the browser runtime environment. As a developer, you have no control over the version of browsers your clients use to access your app.</p>
<h3 id="heading-loading-modules-import-vs-require-keywords">Loading modules (<code>import</code> vs <code>require</code> keywords)</h3>
<p>Node.js offers out-of-the-box support for CommonJS and ES modules. You can load modules using the <code>require</code> keyword (CommonJS syntax) and the <code>import</code> keyword (ES syntax).</p>
<p>Some modern browsers support ES modules. This means you can use <code>import</code> ES modules. But you will still need to create bundles to cater to older browsers that do not support ES modules.</p>
<h2 id="heading-how-much-javascript-do-you-need-to-get-started-with-node">How Much JavaScript Do You Need to Get Started with Node?</h2>
<p>If you are an absolute beginner to JavaScript, I recommend that you start with the basics.</p>
<p>Become familiar with basic JavaScript concepts first. Then, you can move on to learning to build server-side applications with Node.js.</p>
<p>There's no way you'll ever exhaust all there is to learn about JavaScript. So, how to determine when you know enough JavaScript to get started with Node.js?</p>
<p>The Node.js documentation provides a <a target="_blank" href="https://nodejs.org/en/learn/getting-started/how-much-javascript-do-you-need-to-know-to-use-nodejs">list of JavaScript topics to learn</a> before diving deep with Node.js.</p>
<p>Once you have a grasp of JavaScript basics, then you can get started with Node.js</p>
<h2 id="heading-how-to-get-started-with-nodejs">How to Get Started with Node.js</h2>
<p>Let's see how you can create your first Node.js application. This section will show you how to run Node.js scripts from the command line.</p>
<h3 id="heading-how-to-download-and-install-nodejs">How to download and install Node.js</h3>
<p>First, you need to download and install Node.js. There are different ways you can do that. If you are a beginner, I would suggest that you <a target="_blank" href="https://nodejs.org/en/download/">download Node.js from the official website</a>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/11/node1.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Screenshot of the official Node.js website</em></p>
<p>Official packages are available on the website for all major platforms (Windows, macOS, and Linux). Download and install the appropriate package for your system.</p>
<h3 id="heading-how-to-check-the-nodejs-version">How to check the Node.js version</h3>
<p>To check the Node.js version, run the command <code>node --version</code> in your terminal.<br>If the installation was successful, you will see the version of Node.js you installed. You should get a response like the screenshot below.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/11/node2.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-how-to-run-nodejs-from-the-command-line">How to run Node.js from the command line</h3>
<p>Let's build a simple <code>Hello World</code> app.</p>
<p>Create a new project folder. You can call it <code>my-project.</code> Open the project in your code editor. Inside the folder, create an <code>app.js</code> file.</p>
<p>Add the following code to <code>app.js</code></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/11/node3.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>As you can see, this is JavaScript code.</p>
<p>You can run the script in the command line by running the command <code>node &lt;fileName&gt;</code>. In this case, the file name is <code>app.js</code>.</p>
<p>Run the following command in your terminal to execute the <code>Hello world.</code> program:</p>
<pre><code class="lang-bash">node app.js
</code></pre>
<p>You should see the string "Hello world." logged in your terminal like so.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/11/node4.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Congratulations! You just ran your first Node.js application.</p>
<h2 id="heading-should-you-learn-nodejs">Should You Learn Node.js?</h2>
<p>Here are some reasons why you should consider learning Node.js</p>
<h3 id="heading-nodejs-allows-you-to-write-javascript-on-both-client-and-server">Node.js allows you to write JavaScript on both client and server.</h3>
<p>One of the advantages of Node.js is that it allows you to work on both the front-end and back-end of your application. And you use one programming language – JavaScript – to do so.</p>
<p>This is good news for front-end developers who work with JavaScript. If you want to start working on the server side, it's easier compared to learning a new back-end language from scratch.</p>
<h3 id="heading-node-has-a-vibrant-community">Node has a vibrant community.</h3>
<p>As I mentioned earlier in the article, Node.js is open-sourced. It is actively maintained by developers from all over the world.</p>
<p>There is a vibrant community surrounding Node.js. You can find excellent tutorials and solutions to problems when you get stuck.</p>
<h3 id="heading-node-is-built-on-top-of-google-chromes-v8-engine">Node is built on top of Google Chrome's V8 engine.</h3>
<p>Node.js is built on top of the Chrome V8 JavaScript engine. This is significant because the V8 engine powers some of Google's in-browser applications like Gmail. As such, Google invests heavily to ensure it offers high performance.</p>
<h3 id="heading-demand-in-the-market">Demand in the market</h3>
<p>Many big names like Netflix, Uber, Paypal, and LinkedIn, and others use Node.js. Apart from the big names, many startups also use Node.js in developing their applications.</p>
<p>Learning to work with Node.js will make you a desirable candidate in the job market.</p>
<h3 id="heading-the-npm-library">The NPM library</h3>
<p>The NPM library is one of the excellent resources that comes with Node.js.<br>The library contains a registry of over a million packages. A package is a reusable piece of code.</p>
<p>You can create a package for a recurring task or problem and share the code with others via the registry.</p>
<p>You can also download packages that others have shared. For many tasks that developers perform regularly, there are packages available for that.</p>
<h2 id="heading-resources-to-learn-node">Resources to Learn Node</h2>
<p>If you are curious about learning how to build Node.js applications, I recommend the following resources.</p>
<ul>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=Oe421EPjeBE">8-Hour Node.js and Express.js Course on freeCodeCamp YouTube Channel</a>.</p>
</li>
<li><p><a target="_blank" href="https://www.freecodecamp.org/learn/back-end-development-and-apis/">The freeCodeCamp Backend Development and APIs curriculum</a></p>
</li>
<li><p><a target="_blank" href="https://nodejs.dev/en/learn">Nodejs.dev Documentation</a></p>
</li>
</ul>
<p>Also, below is a link to a video of Ryan Dahl when he first presented Node.js.</p>
<p><a target="_blank" href="https://www.youtube.com/watch?v=ztspvPYybIY">Ryan Dahl: Original Node.js presentation at JSConf 2009</a></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>A single blog post like this is not enough to learn all there is to know about Node.js. The purpose of this article was to give you an overview of what Node.js is.</p>
<p>If you were not sure what Node.js is, I hope this article addressed your concerns and cleared your confusion.</p>
<p>Thanks for reading. And happy coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Express +  Node.js Handbook – Learn the Express JavaScript Framework for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ What is Express? Express is a Web Framework built upon Node.js. Node.js is an amazing tool for building networking services and applications. Express builds on top of its features to provide easy to use functionality that satisfies the needs of the W... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-express-handbook/</link>
                <guid isPermaLink="false">66bb5aa724dd38751ddeb5d5</guid>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Flavio Copes ]]>
                </dc:creator>
                <pubDate>Fri, 18 Nov 2022 18:52:29 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/11/pexels-paul-ijsendoorn-1181202.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <h2 id="heading-what-is-express">What is Express?</h2>
<p>Express is a Web Framework built upon Node.js.</p>
<p>Node.js is an amazing tool for building networking services and applications.</p>
<p>Express builds on top of its features to provide easy to use functionality that satisfies the needs of the Web Server use-case. It's Open Source, free, easy to extend, and very performant.</p>
<p>There are also lots and lots of pre-built packages you can just drop in and use to do all kinds of things.</p>
<p><a target="_blank" href="https://thevalleyofcode.com/download/express/">You can get a PDF and ePub version of this Express Handbook</a></p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-install-express">How to Install Express</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-first-hello-world-example">The first "Hello, World" example</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-request-parameters">Request Parameters</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-send-a-response-to-the-client">How to Send a Response to the Client</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-send-a-json-response">How to Send a JSON Response</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-manage-cookies">How to Manage Cookies</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-work-with-http-headers">How to Work with HTTP Headers</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-handle-redirects">How to Handle Redirects</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-routing-in-express">Routing in Express</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-templates-in-express">Templates in Express</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-express-middleware">Express Middleware</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-serve-static-assets-with-express">How to Serve Static Assets with Express</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-send-files-to-the-client">How to Send Files to the Client</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-sessions-in-express">Sessions in Express</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-validate-input-in-express">How to Validate Input in Express</a></p>
</li>
<li><p><a class="post-section-overview" href="#how-to-sanitize-input-in-express">How to Sanitize Input in Express</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-handle-forms-in-express">How to Handle Forms in Express</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-handle-file-uploads-in-forms-in-express">How to Handle File Uploads in Forms in Express</a></p>
</li>
</ul>
<h2 id="heading-how-to-install-express">How to Install Express</h2>
<p>You can install Express into any project with npm.</p>
<p>If you're in an empty folder, first create a new Node.js project with this command:</p>
<pre><code class="lang-javascript">npm init -y
</code></pre>
<p>then run this:</p>
<pre><code class="lang-javascript">npm install express
</code></pre>
<p>to install Express into the project.</p>
<h2 id="heading-the-first-hello-world-example">The First "Hello, World" Example</h2>
<p>The first example we're going to create is a simple Express Web Server.</p>
<p>Copy this code:</p>
<pre><code class="lang-js"><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> app = express()

app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> res.send(<span class="hljs-string">'Hello World!'</span>))
app.listen(<span class="hljs-number">3000</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Server ready'</span>))
</code></pre>
<p>Save this to an <code>index.js</code> file in your project root folder, and start the server using this command:</p>
<pre><code class="lang-javascript">node index.js
</code></pre>
<p>You can open the browser to port 3000 on localhost and you should see the <code>Hello World!</code> message.</p>
<p>Those 4 lines of code do a lot behind the scenes.</p>
<p>First, we import the <code>express</code> package to the <code>express</code> value.</p>
<p>We instantiate an application by calling the <code>express()</code> method.</p>
<p>Once we have the application object, we tell it to listen for GET requests on the <code>/</code> path, using the <code>get()</code> method.</p>
<p>There is a method for every HTTP <strong>verb</strong>: <code>get()</code>, <code>post()</code>, <code>put()</code>, <code>delete()</code>, and <code>patch()</code>:</p>
<pre><code class="lang-js">app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> { <span class="hljs-comment">/* */</span> })
app.post(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> { <span class="hljs-comment">/* */</span> })
app.put(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> { <span class="hljs-comment">/* */</span> })
app.delete(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> { <span class="hljs-comment">/* */</span> })
app.patch(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> { <span class="hljs-comment">/* */</span> })
</code></pre>
<p>Those methods accept a callback function – which is called when a request is started – and we need to handle it.</p>
<p>We pass in an arrow function:</p>
<pre><code class="lang-js">(req, res) =&gt; res.send(<span class="hljs-string">'Hello World!'</span>)
</code></pre>
<p>Express sends us two objects in this callback, which we called <code>req</code> and <code>res</code>. They represent the Request and the Response objects.</p>
<p>Both are standards and you can read more on them <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Request">here</a> and <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Response">here</a>.</p>
<p>Request is the HTTP request. It gives us all the request information, including the request parameters, the headers, the body of the request, and more.</p>
<p>Response is the HTTP response object that we'll send to the client.</p>
<p>What we do in this callback is send the 'Hello World!' string to the client, using the <code>Response.send()</code> method.</p>
<p>This method sets that string as the body, and it closes the connection.</p>
<p>The last line of the example actually starts the server, and tells it to listen on port <code>3000</code>. We pass in a callback that is called when the server is ready to accept new requests.</p>
<h2 id="heading-request-parameters">Request Parameters</h2>
<p>I mentioned how the Request object holds all the HTTP request information.</p>
<p>These are the main properties you'll likely use:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Property</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td>.app</td><td>holds a reference to the Express app object</td></tr>
<tr>
<td>.baseUrl</td><td>the base path on which the app responds</td></tr>
<tr>
<td>.body</td><td>contains the data submitted in the request body (must be parsed and populated manually before you can access it)</td></tr>
<tr>
<td>.cookies</td><td>contains the cookies sent by the request (needs the <code>cookie-parser</code> middleware)</td></tr>
<tr>
<td>.hostname</td><td>the hostname as defined in the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host">Host HTTP header</a> value</td></tr>
<tr>
<td>.ip</td><td>the client IP</td></tr>
<tr>
<td>.method</td><td>the HTTP method used</td></tr>
<tr>
<td>.params</td><td>the route named parameters</td></tr>
<tr>
<td>.path</td><td>the URL path</td></tr>
<tr>
<td>.protocol</td><td>the request protocol</td></tr>
<tr>
<td>.query</td><td>an object containing all the query strings used in the request</td></tr>
<tr>
<td>.secure</td><td>true if the request is secure (uses HTTPS)</td></tr>
<tr>
<td>.signedCookies</td><td>contains the signed cookies sent by the request (needs the <code>cookie-parser</code> middleware)</td></tr>
<tr>
<td>.xhr</td><td>true if the request is an <a target="_blank" href="https://www.freecodecamp.org/news/xhr/">XMLHttpRequest</a></td></tr>
</tbody>
</table>
</div><h2 id="heading-how-to-send-a-response-to-the-client">How to Send a Response to the Client</h2>
<p>In the Hello World example, we used the <code>send()</code> method of the Response object to send a simple string as a response, and to close the connection:</p>
<pre><code class="lang-js">(req, res) =&gt; res.send(<span class="hljs-string">'Hello World!'</span>)
</code></pre>
<p>If you pass in a string, it sets the <code>Content-Type</code> header to <code>text/html</code>.</p>
<p>If you pass in an object or an array, it sets the <code>application/json</code> <code>Content-Type</code> header, and parses that parameter into <a target="_blank" href="https://www.freecodecamp.org/news/json/">JSON</a>.</p>
<p>After this, <code>send()</code> closes the connection.</p>
<p><code>send()</code> automatically sets the <code>Content-Length</code> HTTP response header, unlike <code>end()</code> which requires you to do that.</p>
<h3 id="heading-how-to-use-end-to-send-an-empty-response">How to use end() to send an empty response</h3>
<p>An alternative way to send the response, without any body, is by using the <code>Response.end()</code> method:</p>
<pre><code class="lang-js">res.end()
</code></pre>
<h3 id="heading-how-to-set-the-http-response-status">How to set the HTTP response status</h3>
<p>Use the <code>status()</code> method on the response object:</p>
<pre><code class="lang-js">res.status(<span class="hljs-number">404</span>).end()
</code></pre>
<p>or</p>
<pre><code class="lang-js">res.status(<span class="hljs-number">404</span>).send(<span class="hljs-string">'File not found'</span>)
</code></pre>
<p><code>sendStatus()</code> is a shortcut:</p>
<pre><code class="lang-js">res.sendStatus(<span class="hljs-number">200</span>)
<span class="hljs-comment">// === res.status(200).send('OK')</span>

res.sendStatus(<span class="hljs-number">403</span>)
<span class="hljs-comment">// === res.status(403).send('Forbidden')</span>

res.sendStatus(<span class="hljs-number">404</span>)
<span class="hljs-comment">// === res.status(404).send('Not Found')</span>

res.sendStatus(<span class="hljs-number">500</span>)
<span class="hljs-comment">// === res.status(500).send('Internal Server Error')</span>
</code></pre>
<h2 id="heading-how-to-send-a-json-response">How to Send a JSON Response</h2>
<p>When you listen for connections on a route in Express, the callback function will be invoked on every network call with a Request object instance and a Response object instance.</p>
<p>Example:</p>
<pre><code class="lang-js">app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> res.send(<span class="hljs-string">'Hello World!'</span>))
</code></pre>
<p>Here we used the <code>Response.send()</code> method, which accepts any string.</p>
<p>You can send <a target="_blank" href="https://www.freecodecamp.org/news/json/">JSON</a> to the client by using <code>Response.json()</code>, a useful method.</p>
<p>It accepts an object or array, and converts it to JSON before sending it:</p>
<pre><code class="lang-js">res.json({ <span class="hljs-attr">username</span>: <span class="hljs-string">'Flavio'</span> })
</code></pre>
<h2 id="heading-how-to-manage-cookies">How to Manage Cookies</h2>
<p>Use the <code>Response.cookie()</code> method to manipulate your cookies.</p>
<p>Examples:</p>
<pre><code class="lang-js">res.cookie(<span class="hljs-string">'username'</span>, <span class="hljs-string">'Flavio'</span>)
</code></pre>
<p>This method accepts a third parameter, which contains various options:</p>
<pre><code class="lang-js">res.cookie(<span class="hljs-string">'username'</span>, <span class="hljs-string">'Flavio'</span>, { <span class="hljs-attr">domain</span>: <span class="hljs-string">'.flaviocopes.com'</span>, <span class="hljs-attr">path</span>: <span class="hljs-string">'/administrator'</span>, <span class="hljs-attr">secure</span>: <span class="hljs-literal">true</span> })

res.cookie(<span class="hljs-string">'username'</span>, <span class="hljs-string">'Flavio'</span>, { <span class="hljs-attr">expires</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-built_in">Date</span>.now() + <span class="hljs-number">900000</span>), <span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span> })
</code></pre>
<p>The most useful parameters you can set are:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Value</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><code>domain</code></td><td>The <a target="_blank" href="https://www.freecodecamp.org/news/cookies/#set-a-cookie-domain">cookie domain name</a></td></tr>
<tr>
<td><code>expires</code></td><td>Set the <a target="_blank" href="https://www.freecodecamp.org/news/cookies/#set-a-cookie-expiration-date">cookie expiration date</a>. If missing, or 0, the cookie is a session cookie</td></tr>
<tr>
<td><code>httpOnly</code></td><td>Set the cookie to be accessible only by the web server. See <a target="_blank" href="https://www.freecodecamp.org/news/cookies/#httponly">HttpOnly</a></td></tr>
<tr>
<td><code>maxAge</code></td><td>Set the expiry time relative to the current time, expressed in milliseconds</td></tr>
<tr>
<td><code>path</code></td><td>The <a target="_blank" href="https://www.freecodecamp.org/news/cookies/#set-a-cookie-path">cookie path</a>. Defaults to '/'</td></tr>
<tr>
<td><code>secure</code></td><td>Marks the <a target="_blank" href="https://www.freecodecamp.org/news/cookies/#secure">cookie HTTPS only</a></td></tr>
<tr>
<td><code>signed</code></td><td>Set the cookie to be signed</td></tr>
<tr>
<td><code>sameSite</code></td><td>Value of <a target="_blank" href="https://www.freecodecamp.org/news/cookies/#samesite"><code>SameSite</code></a></td></tr>
</tbody>
</table>
</div><p>A cookie can be cleared with:</p>
<pre><code class="lang-js">res.clearCookie(<span class="hljs-string">'username'</span>)
</code></pre>
<h2 id="heading-how-to-work-with-http-headers">How to Work with HTTP Headers</h2>
<h3 id="heading-how-to-access-http-headers-values-from-a-request">How to access HTTP headers values from a request</h3>
<p>You can access all the HTTP headers using the <code>Request.headers</code> property:</p>
<pre><code class="lang-js">app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(req.headers)
})
</code></pre>
<p>Use the <code>Request.header()</code> method to access one individual request header's value:</p>
<pre><code class="lang-js">app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  req.header(<span class="hljs-string">'User-Agent'</span>)
})
</code></pre>
<h3 id="heading-how-to-change-any-http-header-value-for-a-response">How to change any HTTP header value for a response</h3>
<p>You can change any HTTP header value using <code>Response.set()</code>:</p>
<pre><code class="lang-js">res.set(<span class="hljs-string">'Content-Type'</span>, <span class="hljs-string">'text/html'</span>)
</code></pre>
<p>There is a shortcut for the Content-Type header, however:</p>
<pre><code class="lang-js">res.type(<span class="hljs-string">'.html'</span>)
<span class="hljs-comment">// =&gt; 'text/html'</span>

res.type(<span class="hljs-string">'html'</span>)
<span class="hljs-comment">// =&gt; 'text/html'</span>

res.type(<span class="hljs-string">'json'</span>)
<span class="hljs-comment">// =&gt; 'application/json'</span>

res.type(<span class="hljs-string">'application/json'</span>)
<span class="hljs-comment">// =&gt; 'application/json'</span>

res.type(<span class="hljs-string">'png'</span>)
<span class="hljs-comment">// =&gt; image/png:</span>
</code></pre>
<h2 id="heading-how-to-handle-redirects">How to Handle Redirects</h2>
<p>Redirects are common in Web Development. You can create a redirect using the <code>Response.redirect()</code> method:</p>
<pre><code class="lang-js">res.redirect(<span class="hljs-string">'/go-there'</span>)
</code></pre>
<p>This creates a 302 redirect.</p>
<p>A 301 redirect is made in this way:</p>
<pre><code class="lang-js">res.redirect(<span class="hljs-number">301</span>, <span class="hljs-string">'/go-there'</span>)
</code></pre>
<p>You can specify an absolute path (<code>/go-there</code>), an absolute URL (<code>https://anothersite.com</code>), a relative path (<code>go-there</code>) or use the <code>..</code> to go back one level:</p>
<pre><code class="lang-js">res.redirect(<span class="hljs-string">'../go-there'</span>)
res.redirect(<span class="hljs-string">'..'</span>)
</code></pre>
<p>You can also redirect back to the Referrer HTTP header value (defaulting to <code>/</code> if not set) using</p>
<pre><code class="lang-js">res.redirect(<span class="hljs-string">'back'</span>)
</code></pre>
<h2 id="heading-routing-in-express">Routing in Express</h2>
<p>Routing is the process of determining what should happen when a URL is called, or also which parts of the application should handle a specific incoming request.</p>
<p>In the Hello World example we used this code:</p>
<pre><code class="lang-js">app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> { <span class="hljs-comment">/* */</span> })
</code></pre>
<p>This creates a route that maps accessing the root domain URL <code>/</code> using the HTTP GET method to the response we want to provide.</p>
<h3 id="heading-named-parameters">Named parameters</h3>
<p>What if we want to listen for custom requests? Maybe we want to create a service that accepts a string and returns it as uppercase – and we don't want the parameter to be sent as a query string, but as part of the URL. In a case like that, we use named parameters:</p>
<pre><code class="lang-js">app.get(<span class="hljs-string">'/uppercase/:theValue'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> res.send(req.params.theValue.toUpperCase()))
</code></pre>
<p>If we send a request to <code>/uppercase/test</code>, we'll get <code>TEST</code> in the body of the response.</p>
<p>You can use multiple named parameters in the same URL, and they will all be stored in <code>req.params</code>.</p>
<h3 id="heading-how-to-use-a-regular-expression-to-match-a-path">How to use a regular expression to match a path</h3>
<p>You can use <a target="_blank" href="https://flaviocopes.com/javascript-regular-expressions/">regular expressions</a> to match multiple paths with one statement:</p>
<pre><code class="lang-js">app.get(<span class="hljs-regexp">/post/</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> { <span class="hljs-comment">/* */</span> })
</code></pre>
<p>will match <code>/post</code>, <code>/post/first</code>, <code>/thepost</code>, <code>/posting/something</code>, and so on.</p>
<h2 id="heading-templates-in-express">Templates in Express</h2>
<p>Express is capable of handling server-side template engines.</p>
<p>Template engines allow us to add data to a view, and generate HTML dynamically.</p>
<p>Express uses Jade as the default. Jade is the old version of Pug, specifically Pug 1.0.</p>
<p>Note that the name was changed from Jade to Pug due to a trademark issue in 2016, when the project released version 2. You can still use Jade, aka Pug 1.0, but going forward, it's best to use Pug 2.0</p>
<p>Although the last version of Jade is getting old, it's still the default in Express for backward compatibility reasons.</p>
<p>In any new project, you should use Pug or another engine of your choice. The official site of Pug is <a target="_blank" href="https://pugjs.org/">https://pugjs.org/</a>.</p>
<p>You can use many different template engines, including Pug, Handlebars, Mustache, EJS and more.</p>
<p>To use Pug we must first install it:</p>
<pre><code class="lang-bash">npm install pug
</code></pre>
<p>and when initializing the Express app, we need to set it:</p>
<pre><code class="lang-js"><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> app = express()
app.set(<span class="hljs-string">'view engine'</span>, <span class="hljs-string">'pug'</span>)
</code></pre>
<p>We can now start writing our templates in <code>.pug</code> files.</p>
<p>Create an about view:</p>
<pre><code class="lang-js">app.get(<span class="hljs-string">'/about'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.render(<span class="hljs-string">'about'</span>)
})
</code></pre>
<p>and the template in <code>views/about.pug</code>:</p>
<pre><code class="lang-javascript">p Hello <span class="hljs-keyword">from</span> Flavio
</code></pre>
<p>This template will create a <code>p</code> tag with the content <code>Hello from Flavio</code>.</p>
<p>You can interpolate a variable using this code:</p>
<pre><code class="lang-js">app.get(<span class="hljs-string">'/about'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.render(<span class="hljs-string">'about'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-string">'Flavio'</span> })
})
</code></pre>
<pre><code class="lang-javascript">p Hello <span class="hljs-keyword">from</span> #{name}
</code></pre>
<p>Look at the <a target="_blank" href="https://flaviocopes.com/pug">Pug guide</a> for more information on how to use Pug.</p>
<p>This online converter from HTML to Pug will be a great help: <a target="_blank" href="https://html-to-pug.com/">https://html-to-pug.com/</a>.</p>
<h2 id="heading-express-middleware">Express Middleware</h2>
<p>A middleware is a function that hooks into the routing process, performing an arbitrary operation at some point in the chain (depending on what we want it to do).</p>
<p>It's commonly used to edit the request or response objects, or terminate the request before it reaches the route handler code.</p>
<p>Middleware is added to the execution stack like so:</p>
<pre><code class="lang-js">app.use(<span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =&gt;</span> { <span class="hljs-comment">/* */</span> })
</code></pre>
<p>This is similar to defining a route, but in addition to the Request and Response objects instances, we also have a reference to the <em>next</em> middleware function, which we assign to the variable <code>next</code>.</p>
<p>We always call <code>next()</code> at the end of our middleware function, in order to pass the execution to the next handler. That is unless we want to prematurely end the response and send it back to the client.</p>
<p>You typically use pre-made middleware, in the form of <code>npm</code> packages. You can find a big list of the available ones <a target="_blank" href="https://expressjs.com/en/resources/middleware.html">here</a>.</p>
<p>One example is <code>cookie-parser</code>, which is used to parse cookies into the <code>req.cookies</code> object. You can install it using <code>npm install cookie-parser</code> and you use it like this:</p>
<pre><code class="lang-js"><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> app = express()
<span class="hljs-keyword">const</span> cookieParser = <span class="hljs-built_in">require</span>(<span class="hljs-string">'cookie-parser'</span>)

app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> res.send(<span class="hljs-string">'Hello World!'</span>))

app.use(cookieParser())
app.listen(<span class="hljs-number">3000</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Server ready'</span>))
</code></pre>
<p>We can also set a middleware function to run for specific routes only (not for all), by using it as the second parameter of the route definition:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> myMiddleware = <span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =&gt;</span> {
  <span class="hljs-comment">/* ... */</span>
  next()
}

app.get(<span class="hljs-string">'/'</span>, myMiddleware, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> res.send(<span class="hljs-string">'Hello World!'</span>))
</code></pre>
<p>If you need to store data that's generated in a middleware to pass it down to subsequent middleware functions, or to the request handler, you can use the <code>Request.locals</code> object. It will attach that data to the current request:</p>
<pre><code class="lang-js">req.locals.name = <span class="hljs-string">'Flavio'</span>
</code></pre>
<h2 id="heading-how-to-serve-static-assets-with-express">How to Serve Static Assets with Express</h2>
<p>It's common to have images, CSS, and more in a <code>public</code> subfolder, and expose them to the root level:</p>
<pre><code class="lang-js"><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> app = express()

app.use(express.static(<span class="hljs-string">'public'</span>))

<span class="hljs-comment">/* ... */</span>

app.listen(<span class="hljs-number">3000</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Server ready'</span>))
</code></pre>
<p>If you have an <code>index.html</code> file in <code>public/</code>, that will be served if you now hit the root domain URL (<code>http://localhost:3000</code>)</p>
<h2 id="heading-how-to-send-files-to-the-client">How to Send Files to the Client</h2>
<p>Express provides a handy method to transfer a file as an attachment: <code>Response.download()</code>.</p>
<p>Once a user hits a route that sends a file using this method, browsers will prompt the user to download.</p>
<p>The <code>Response.download()</code> method allows you to send a file attached to the request, and the browser, instead of showing it in the page, will save it to disk.</p>
<pre><code class="lang-js">app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> res.download(<span class="hljs-string">'./file.pdf'</span>))
</code></pre>
<p>In the context of an app:</p>
<pre><code class="lang-js"><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> app = express()

app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> res.download(<span class="hljs-string">'./file.pdf'</span>))
app.listen(<span class="hljs-number">3000</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Server ready'</span>))
</code></pre>
<p>You can set the file to be sent with a custom filename:</p>
<pre><code class="lang-js">res.download(<span class="hljs-string">'./file.pdf'</span>, <span class="hljs-string">'user-facing-filename.pdf'</span>)
</code></pre>
<p>This method provides a callback function which you can use to execute code once the file has been sent:</p>
<pre><code class="lang-js">res.download(<span class="hljs-string">'./file.pdf'</span>, <span class="hljs-string">'user-facing-filename.pdf'</span>, <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-comment">//handle error</span>
    <span class="hljs-keyword">return</span>
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">//do something</span>
  }
})
</code></pre>
<h2 id="heading-sessions-in-express">Sessions in Express</h2>
<p>By default Express requests are sequential and no request can be linked to another. There is no way to know if this request comes from a client that already performed a request previously.</p>
<p>Users cannot be identified unless using some kind of mechanism that makes it possible.</p>
<p>That's what sessions are.</p>
<p>When implemented, every user of your API or website will be assigned a unique session, and this allows you to store the user's state.</p>
<p>We'll use the <code>express-session</code> module, which is maintained by the Express team.</p>
<p>You can install it using this command:</p>
<pre><code class="lang-bash">npm install express-session
</code></pre>
<p>and once you're done, you can instantiate it in your application with this one:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> session = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express-session'</span>)
</code></pre>
<p>This is a middleware, so you <em>install</em> it in Express using the following:</p>
<pre><code class="lang-js"><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> session = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express-session'</span>)

<span class="hljs-keyword">const</span> app = express()
app.use(session({
  <span class="hljs-string">'secret'</span>: <span class="hljs-string">'343ji43j4n3jn4jk3n'</span>
}))
</code></pre>
<p>After this is done, all the requests to the app routes are now using sessions.</p>
<p><code>secret</code> is the only required parameter, but there are many more you can use. It should be a randomly unique string for your application.</p>
<p>The session is attached to the request, so you can access it using <code>req.session</code> here:</p>
<pre><code class="lang-js">app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =&gt;</span> {
  <span class="hljs-comment">// req.session</span>
}
</code></pre>
<p>This object can be used to get data out of the session, and also to set data:</p>
<pre><code class="lang-js">req.session.name = <span class="hljs-string">'Flavio'</span>
<span class="hljs-built_in">console</span>.log(req.session.name) <span class="hljs-comment">// 'Flavio'</span>
</code></pre>
<p>This data is serialized as <a target="_blank" href="https://www.freecodecamp.org/news/json/">JSON</a> when stored, so you are safe to use nested objects.</p>
<p>You can use sessions to communicate data to middleware that's executed later, or to retrieve it later on, on subsequent requests.</p>
<p>Where is the session data stored? It depends on how you set up the <code>express-session</code> module.</p>
<p>It can store session data in:</p>
<ul>
<li><p><strong>memory</strong>, not meant for production</p>
</li>
<li><p>a <strong>database</strong> like MySQL or Mongo</p>
</li>
<li><p>a <strong>memory cache</strong> like Redis or Memcached</p>
</li>
</ul>
<p>There is a big list of 3rd packages that implement a wide variety of different compatible caching stores in <a target="_blank" href="https://github.com/expressjs/session">https://github.com/expressjs/session</a>.</p>
<p>All solutions store the session id in a cookie, and keep the data server-side. The client will receive the session id in a cookie, and will send it along with every HTTP request.</p>
<p>We'll reference that server-side to associate the session id with the data stored locally.</p>
<p>Memory is the default, and it requires no special setup on your part. It's the simplest thing but it's meant only for development purposes.</p>
<p>The best choice is a memory cache like Redis, for which you need to setup its own infrastructure.</p>
<p>Another popular package to manage sessions in Express is <code>cookie-session</code>, which has a big difference: it stores data client-side in the cookie.</p>
<p>I do not recommend doing that because storing data in cookies means that it's stored client-side, and sent back and forth in every single request made by the user. It's also limited in size, as it can only store 4 kilobytes of data.</p>
<p>Cookies also need to be secured, but by default they are not, since secure Cookies are possible on HTTPS sites and you need to configure them if you have proxies.</p>
<h2 id="heading-how-to-validate-input-in-express">How to Validate Input in Express</h2>
<p>Let's see how to validate any data coming in as input in your Express endpoints.</p>
<p>Say you have a POST endpoint that accepts the name, email, and age parameters:</p>
<pre><code class="lang-js"><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> app = express()

app.use(express.json())

app.post(<span class="hljs-string">'/form'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> name  = req.body.name
  <span class="hljs-keyword">const</span> email = req.body.email
  <span class="hljs-keyword">const</span> age   = req.body.age
})
</code></pre>
<p>How do you perform server-side validation on those results to make sure that:</p>
<ul>
<li><p>name is a string of at least 3 characters?</p>
</li>
<li><p>email is a real email?</p>
</li>
<li><p>age is a number, between 0 and 110?</p>
</li>
</ul>
<p>The best way to handle validation on any kind of input coming from outside in Express is by using the <a target="_blank" href="https://express-validator.github.io"><code>express-validator</code> package</a>:</p>
<pre><code class="lang-bash">npm install express-validator
</code></pre>
<p>You require the <code>check</code> and <code>validationResult</code> objects from the package:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> { check, validationResult } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express-validator'</span>);
</code></pre>
<p>We pass an array of <code>check()</code> calls as the second argument of the <code>post()</code> call. Every <code>check()</code> call accepts the parameter name as argument. Then we call <code>validationResult()</code> to verify there were no validation errors. If there are any, we tell them to the client:</p>
<pre><code class="lang-js">app.post(<span class="hljs-string">'/form'</span>, [
  check(<span class="hljs-string">'name'</span>).isLength({ <span class="hljs-attr">min</span>: <span class="hljs-number">3</span> }),
  check(<span class="hljs-string">'email'</span>).isEmail(),
  check(<span class="hljs-string">'age'</span>).isNumeric()
], <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> errors = validationResult(req)
  <span class="hljs-keyword">if</span> (!errors.isEmpty()) {
    <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">422</span>).json({ <span class="hljs-attr">errors</span>: errors.array() })
  }

  <span class="hljs-keyword">const</span> name  = req.body.name
  <span class="hljs-keyword">const</span> email = req.body.email
  <span class="hljs-keyword">const</span> age   = req.body.age
})
</code></pre>
<p>Notice I used:</p>
<ul>
<li><p><code>isLength()</code></p>
</li>
<li><p><code>isEmail()</code></p>
</li>
<li><p><code>isNumeric()</code></p>
</li>
</ul>
<p>There are many more of these methods, all coming from <a target="_blank" href="https://github.com/chriso/validator.js#validators">validator.js</a>, including:</p>
<ul>
<li><p><code>contains()</code>, checks if value contains the specified value</p>
</li>
<li><p><code>equals()</code>, checks if value equals the specified value</p>
</li>
<li><p><code>isAlpha()</code></p>
</li>
<li><p><code>isAlphanumeric()</code></p>
</li>
<li><p><code>isAscii()</code></p>
</li>
<li><p><code>isBase64()</code></p>
</li>
<li><p><code>isBoolean()</code></p>
</li>
<li><p><code>isCurrency()</code></p>
</li>
<li><p><code>isDecimal()</code></p>
</li>
<li><p><code>isEmpty()</code></p>
</li>
<li><p><code>isFQDN()</code>, checks if it's a fully qualified domain name</p>
</li>
<li><p><code>isFloat()</code></p>
</li>
<li><p><code>isHash()</code></p>
</li>
<li><p><code>isHexColor()</code></p>
</li>
<li><p><code>isIP()</code></p>
</li>
<li><p><code>isIn()</code>, checks if the value is in an array of allowed values</p>
</li>
<li><p><code>isInt()</code></p>
</li>
<li><p><code>isJSON()</code></p>
</li>
<li><p><code>isLatLong()</code></p>
</li>
<li><p><code>isLength()</code></p>
</li>
<li><p><code>isLowercase()</code></p>
</li>
<li><p><code>isMobilePhone()</code></p>
</li>
<li><p><code>isNumeric()</code></p>
</li>
<li><p><code>isPostalCode()</code></p>
</li>
<li><p><code>isURL()</code></p>
</li>
<li><p><code>isUppercase()</code></p>
</li>
<li><p><code>isWhitelisted()</code>, checks the input against a whitelist of allowed characters</p>
</li>
</ul>
<p>You can validate the input against a regular expression using <code>matches()</code>.</p>
<p>Dates can be checked using:</p>
<ul>
<li><p><code>isAfter()</code>, checks if the entered date is after the one you pass</p>
</li>
<li><p><code>isBefore()</code>, checks if the entered date is before the one you pass</p>
</li>
<li><p><code>isISO8601()</code></p>
</li>
<li><p><code>isRFC3339()</code></p>
</li>
</ul>
<p>For exact details on how to use those validators, <a target="_blank" href="https://github.com/chriso/validator.js#validators">refer to the docs here</a>.</p>
<p>All those checks can be combined by piping them:</p>
<pre><code class="lang-js">check(<span class="hljs-string">'name'</span>)
  .isAlpha()
  .isLength({ <span class="hljs-attr">min</span>: <span class="hljs-number">10</span> })
</code></pre>
<p>If there is any error, the server automatically sends a response to communicate the error. For example if the email is not valid, this is what will be returned:</p>
<pre><code class="lang-js">{
  <span class="hljs-string">"errors"</span>: [{
    <span class="hljs-string">"location"</span>: <span class="hljs-string">"body"</span>,
    <span class="hljs-string">"msg"</span>: <span class="hljs-string">"Invalid value"</span>,
    <span class="hljs-string">"param"</span>: <span class="hljs-string">"email"</span>
  }]
}
</code></pre>
<p>This default error can be overridden for each check you perform, using <code>withMessage()</code>:</p>
<pre><code class="lang-js">check(<span class="hljs-string">'name'</span>)
  .isAlpha()
  .withMessage(<span class="hljs-string">'Must be only alphabetical chars'</span>)
  .isLength({ <span class="hljs-attr">min</span>: <span class="hljs-number">10</span> })
  .withMessage(<span class="hljs-string">'Must be at least 10 chars long'</span>)
</code></pre>
<p>What if you want to write your own special, custom validator? You can use the <code>custom</code> validator.</p>
<p>In the callback function you can reject the validation either by throwing an exception, or by returning a rejected promise:</p>
<pre><code class="lang-js">app.post(<span class="hljs-string">'/form'</span>, [
  check(<span class="hljs-string">'name'</span>).isLength({ <span class="hljs-attr">min</span>: <span class="hljs-number">3</span> }),
  check(<span class="hljs-string">'email'</span>).custom(<span class="hljs-function"><span class="hljs-params">email</span> =&gt;</span> {
    <span class="hljs-keyword">if</span> (alreadyHaveEmail(email)) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Email already registered'</span>)
    }
  }),
  check(<span class="hljs-string">'age'</span>).isNumeric()
], <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> name  = req.body.name
  <span class="hljs-keyword">const</span> email = req.body.email
  <span class="hljs-keyword">const</span> age   = req.body.age
})
</code></pre>
<p>The custom validator:</p>
<pre><code class="lang-js">check(<span class="hljs-string">'email'</span>).custom(<span class="hljs-function"><span class="hljs-params">email</span> =&gt;</span> {
  <span class="hljs-keyword">if</span> (alreadyHaveEmail(email)) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Email already registered'</span>)
  }
})
</code></pre>
<p>can be rewritten like this:</p>
<pre><code class="lang-js">check(<span class="hljs-string">'email'</span>).custom(<span class="hljs-function"><span class="hljs-params">email</span> =&gt;</span> {
  <span class="hljs-keyword">if</span> (alreadyHaveEmail(email)) {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.reject(<span class="hljs-string">'Email already registered'</span>)
  }
})
</code></pre>
<h2 id="heading-how-to-sanitizing-input-in-express">How to Sanitizing Input in Express</h2>
<p>You've seen how to validate input that comes from the outside world to your Express app.</p>
<p>There's one thing you quickly learn when you run a public-facing server: never trust the input.</p>
<p>Even if you sanitize and make sure that people can't enter weird things using client-side code, you'll still be subject to people using tools (even just the browser devtools) to POST directly to your endpoints.</p>
<p>Or bots trying every possible combination of exploit known to humans.</p>
<p>What you need to do is sanitize your input.</p>
<p>The <a target="_blank" href="https://express-validator.github.io"><code>express-validator</code> package</a> you already use to validate input can also conveniently be used to perform sanitization.</p>
<p>Say you have a POST endpoint that accepts the name, email, and age parameters:</p>
<pre><code class="lang-js"><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> app = express()

app.use(express.json())

app.post(<span class="hljs-string">'/form'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> name  = req.body.name
  <span class="hljs-keyword">const</span> email = req.body.email
  <span class="hljs-keyword">const</span> age   = req.body.age
})
</code></pre>
<p>You might validate it using:</p>
<pre><code class="lang-js"><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> app = express()

app.use(express.json())

app.post(<span class="hljs-string">'/form'</span>, [
  check(<span class="hljs-string">'name'</span>).isLength({ <span class="hljs-attr">min</span>: <span class="hljs-number">3</span> }),
  check(<span class="hljs-string">'email'</span>).isEmail(),
  check(<span class="hljs-string">'age'</span>).isNumeric()
], <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> name  = req.body.name
  <span class="hljs-keyword">const</span> email = req.body.email
  <span class="hljs-keyword">const</span> age   = req.body.age
})
</code></pre>
<p>You can add sanitization by piping the sanitization methods after the validation ones:</p>
<pre><code class="lang-js">app.post(<span class="hljs-string">'/form'</span>, [
  check(<span class="hljs-string">'name'</span>).isLength({ <span class="hljs-attr">min</span>: <span class="hljs-number">3</span> }).trim().escape(),
  check(<span class="hljs-string">'email'</span>).isEmail().normalizeEmail(),
  check(<span class="hljs-string">'age'</span>).isNumeric().trim().escape()
], <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-comment">//...</span>
})
</code></pre>
<p>Here I used the methods:</p>
<ul>
<li><p><code>trim()</code> trims characters (whitespace by default) at the beginning and at the end of a string</p>
</li>
<li><p><code>escape()</code> replaces <code>&lt;</code>, <code>&gt;</code>, <code>&amp;</code>, <code>'</code>, <code>"</code> and <code>/</code> with their corresponding HTML entities</p>
</li>
<li><p><code>normalizeEmail()</code> canonicalizes an email address. Accepts several options to lowercase email addresses or subaddresses (for example <code>flavio+newsletters@gmail.com</code>)</p>
</li>
</ul>
<p>Other sanitization methods:</p>
<ul>
<li><p><code>blacklist()</code> removes characters that appear in the blacklist</p>
</li>
<li><p><code>whitelist()</code> removes characters that do not appear in the whitelist</p>
</li>
<li><p><code>unescape()</code> replaces HTML encoded entities with <code>&lt;</code>, <code>&gt;</code>, <code>&amp;</code>, <code>'</code>, <code>"</code> and <code>/</code></p>
</li>
<li><p><code>ltrim()</code> like trim(), but only trims characters at the start of the string</p>
</li>
<li><p><code>rtrim()</code> like trim(), but only trims characters at the end of the string</p>
</li>
<li><p><code>stripLow()</code> removes ASCII control characters, which are normally invisible</p>
</li>
</ul>
<p>Force conversion to a format:</p>
<ul>
<li><p><code>toBoolean()</code> converts the input string to a boolean. Everything except for '0', 'false' and '' returns true. In strict mode only '1' and 'true' return true.</p>
</li>
<li><p><code>toDate()</code> converts the input string to a date, or null if the input is not a date</p>
</li>
<li><p><code>toFloat()</code> converts the input string to a float, or NaN if the input is not a float</p>
</li>
<li><p><code>toInt()</code> converts the input string to an integer, or NaN if the input is not an integer</p>
</li>
</ul>
<p>Like with custom validators, you can create a custom sanitizer.</p>
<p>In the callback function you just return the sanitized value:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> sanitizeValue = <span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
  <span class="hljs-comment">//sanitize...</span>
}

app.post(<span class="hljs-string">'/form'</span>, [
  check(<span class="hljs-string">'value'</span>).customSanitizer(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> sanitizeValue(value)
  }),
], <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> value  = req.body.value
})
</code></pre>
<h2 id="heading-how-to-handle-forms-in-express">How to Handle Forms in Express</h2>
<p>This is an example of an HTML form:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"POST"</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"/submit-form"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"username"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
</code></pre>
<p>When the user presses the submit button, the browser will automatically make a <code>POST</code> request to the <code>/submit-form</code> URL on the same origin of the page. The browser sends the data contained, encoded as <code>application/x-www-form-urlencoded</code>. In this particular example, the form data contains the <code>username</code> input field value.</p>
<p>Forms can also send data using the <code>GET</code> method, but the vast majority of the forms you'll build will use <code>POST</code>.</p>
<p>The form data will be sent in the POST request body.</p>
<p>To extract it, you will need to use the <code>express.urlencoded()</code> middleware:</p>
<pre><code class="lang-js"><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> app = express()

app.use(express.urlencoded({
  <span class="hljs-attr">extended</span>: <span class="hljs-literal">true</span>
}))
</code></pre>
<p>Now, you need to create a <code>POST</code> endpoint on the <code>/submit-form</code> route, and any data will be available on <code>Request.body</code>:</p>
<pre><code class="lang-js">app.post(<span class="hljs-string">'/submit-form'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> username = req.body.username
  <span class="hljs-comment">//...</span>
  res.end()
})
</code></pre>
<p>Don't forget to validate the data before using it with <code>express-validator</code>.</p>
<h2 id="heading-how-to-handle-file-uploads-in-forms-in-express">How to Handle File Uploads in Forms in Express</h2>
<p>This is an example of an HTML form that allows a user to upload a file:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"POST"</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"/submit-form"</span> <span class="hljs-attr">enctype</span>=<span class="hljs-string">"multipart/form-data"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"file"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"document"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
</code></pre>
<p>Don't forget to add <code>enctype="multipart/form-data"</code> to the form, or files won't be uploaded.</p>
<p>When the user press the submit button, the browser will automatically make a <code>POST</code> request to the <code>/submit-form</code> URL on the same origin of the page. The browser sends the data contained, not encoded as as a normal form <code>application/x-www-form-urlencoded</code>, but as <code>multipart/form-data</code>.</p>
<p>On the server-side, handling multipart data can be tricky and error prone, so we are going to use a utility library called <strong>formidable</strong>. <a target="_blank" href="https://github.com/felixge/node-formidable">Here's the GitHub repo</a> – it has over 4000 stars and is well-maintained.</p>
<p>You can install it using:</p>
<pre><code class="lang-bash">npm install formidable
</code></pre>
<p>Then include it in your Node.js file:</p>
<pre><code class="lang-js"><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> app = express()
<span class="hljs-keyword">const</span> formidable = <span class="hljs-built_in">require</span>(<span class="hljs-string">'formidable'</span>)
</code></pre>
<p>Now, in the <code>POST</code> endpoint on the <code>/submit-form</code> route, we instantiate a new Formidable form using <code>formidable.IncomingForm()</code>:</p>
<pre><code class="lang-js">app.post(<span class="hljs-string">'/submit-form'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">new</span> formidable.IncomingForm()
})
</code></pre>
<p>After doing so, we need to be able to parse the form. We can do so synchronously by providing a callback, which means all files are processed. Once formidable is done, it makes them available:</p>
<pre><code class="lang-js">app.post(<span class="hljs-string">'/submit-form'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">new</span> formidable.IncomingForm().parse(req, <span class="hljs-function">(<span class="hljs-params">err, fields, files</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (err) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error'</span>, err)
      <span class="hljs-keyword">throw</span> err
    }
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Fields'</span>, fields)
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Files'</span>, files)
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> file <span class="hljs-keyword">of</span> <span class="hljs-built_in">Object</span>.entries(files)) {
      <span class="hljs-built_in">console</span>.log(file)
    }
  })
})
</code></pre>
<p>Or, you can use events instead of a callback. For example, you can be notified when each file is parsed, or other events such as completion of file processing, receiving a non-file field, or if an error occurred:</p>
<pre><code class="lang-js">app.post(<span class="hljs-string">'/submit-form'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">new</span> formidable.IncomingForm().parse(req)
    .on(<span class="hljs-string">'field'</span>, <span class="hljs-function">(<span class="hljs-params">name, field</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Field'</span>, name, field)
    })
    .on(<span class="hljs-string">'file'</span>, <span class="hljs-function">(<span class="hljs-params">name, file</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Uploaded file'</span>, name, file)
    })
    .on(<span class="hljs-string">'aborted'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Request aborted by the user'</span>)
    })
    .on(<span class="hljs-string">'error'</span>, <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error'</span>, err)
      <span class="hljs-keyword">throw</span> err
    })
    .on(<span class="hljs-string">'end'</span>, <span class="hljs-function">() =&gt;</span> {
      res.end()
    })
})
</code></pre>
<p>Whichever way you choose, you'll get one or more Formidable.File objects, which give you information about the file uploaded. These are some of the methods you can call:</p>
<ul>
<li><p><code>file.size</code>, the file size in bytes</p>
</li>
<li><p><code>file.path</code>, the path the file is written to</p>
</li>
<li><p><code>file.name</code>, the name of the file</p>
</li>
<li><p><code>file.type</code>, the MIME type of the file</p>
</li>
</ul>
<p>The path defaults to the temporary folder and can be modified if you listen for the <code>fileBegin</code> event:</p>
<pre><code class="lang-js">app.post(<span class="hljs-string">'/submit-form'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">new</span> formidable.IncomingForm().parse(req)
    .on(<span class="hljs-string">'fileBegin'</span>, <span class="hljs-function">(<span class="hljs-params">name, file</span>) =&gt;</span> {
        file.path = __dirname + <span class="hljs-string">'/uploads/'</span> + file.name
    })
    .on(<span class="hljs-string">'file'</span>, <span class="hljs-function">(<span class="hljs-params">name, file</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Uploaded file'</span>, name, file)
    })
    <span class="hljs-comment">//...</span>
})
</code></pre>
<h2 id="heading-thank-you-for-reading">Thank you for reading!</h2>
<p>That's it for this handbook. And don't forget that <a target="_blank" href="https://thevalleyofcode.com/download/express/">you can get a PDF and ePub version of this Express Handbook</a> if you'd like.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn Node.js and Express in Spanish – Course for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ Hi! If you speak Spanish and you want to learn Node.js, and Express, you are in the right place.  In this article, you will find a brief introduction to back-end web development, Node.js, and Express. You will learn why they are very powerful tools f... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-node-js-and-express-in-spanish-course-for-beginners/</link>
                <guid isPermaLink="false">66b1f84e0968943127cc6022</guid>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Estefania Cassingena Navone ]]>
                </dc:creator>
                <pubDate>Mon, 08 Aug 2022 04:35:40 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/07/thumbnail.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Hi! If you speak Spanish and you want to learn Node.js, and Express, you are in the right place. </p>
<p>In this article, you will find a brief introduction to back-end web development, Node.js, and Express. You will learn why they are very powerful tools for developing web servers and why you should learn them if your goal is to be a back-end web developer. </p>
<p>Then, you will find <a target="_blank" href="https://www.youtube.com/watch?v=1hpc70_OoAg">an <strong>8.5 hour</strong> Node.js and Express course</a> on freeCodeCamp's Spanish YouTube channel where you can learn the fundamentals in Spanish and build a project step by step.</p>
<p>If you have Spanish-speaking friends, you are welcome to share the <strong><a target="_blank" href="https://www.freecodecamp.org/espanol/news/aprende-node-js-y-express-curso-desde-cero/">Spanish version of this article</a></strong> with them.</p>
<p>Let's begin! ✨</p>
<h2 id="heading-what-is-back-end-web-development"><strong>🔸 What is Back-End Web Development?</strong></h2>
<p>Web development has transformed our modern world. Every day we access the internet to find information, learn, buy products, share our thoughts, and connect with family and friends. </p>
<p>Basically, our lives would never be the same without websites and web applications. Do you agree? 🙂 </p>
<p>If you do, then learning web development can lead you down a very rewarding career path because you can have a tremendous impact in the lives of thousands or even millions of users. </p>
<p>Let's talk a little bit about the different areas of web development. </p>
<h3 id="heading-front-end-vs-back-end-web-development">◼️ Front-End vs. Back-End Web Development</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-174.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The developers who create amazing web applications are called <strong>web developers</strong>. They can specialize to develop different parts of a web application:</p>
<ul>
<li><strong>Font-End Web Developers</strong> implement the part of the web application that users interact with directly. They develop the visible part of the amazing platforms we use and love every day. In the store analogy that you can see above, the front-end would be represented by the part of the store that clients can see. </li>
<li><strong>Back-End Web Developers</strong> implement all the functionality that users don't see, such as servers, databases, and their interactions with the font-end part of the applications. In our store analogy, the back-end would be represented by the warehouse, the part of the store that supports everything that clients do see. </li>
<li><strong>Full-stack Web Developers</strong> are in charge of both areas. They have thorough knowledge of front-end and back-end web development. </li>
</ul>
<p>Interesting, right? ✨</p>
<p>Now let's dive deeper into back-end web development because that is one of the main applications of Node.js and Express.  </p>
<h3 id="heading-the-client-server-model">The Client-Server Model</h3>
<p>The internet is based on the <strong>client-server model</strong>, in which two devices (the client and the server) communicate with each other. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-173.png" alt="Image" width="600" height="400" loading="lazy">
<em>Illustration of the client-server model.</em></p>
<h3 id="heading-what-is-a-client">What is a Client?</h3>
<p>When you try to access a website in your browser, the browser (<strong>client</strong>) sends an HTTP request for that website to the server. </p>
<h3 id="heading-what-is-a-server">What is a Server?</h3>
<p>The <strong>server</strong> is a program that listens for requests and generates appropriate responses. These responses often include:</p>
<ul>
<li>Sending data to the client.</li>
<li>Running some task.</li>
<li>Working with or updating a database. </li>
</ul>
<p>For example, we may send a request to the server to add a new user to the database of a web application. The server should make the necessary updates in the database and notify the client that this change was successful. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-175.png" alt="Image" width="600" height="400" loading="lazy">
<em>Client (left) - Server (center) - Database (right)</em></p>
<p>Developing and maintaining servers is one of the main tasks of back-end web developers, and that is precisely what <strong>Node.js and Express</strong> are used for. </p>
<h2 id="heading-what-is-nodejs">🔹 What is Node.js?</h2>
<p><strong>Node.js</strong> is an asynchronous event-driven JavaScript runtime built on Chrome's V8 JavaScript engine. It gives us all the tools we need to run JavaScript in the terminal without a web browser. </p>
<p>💡 <strong>Tip:</strong> Before Node.js, we could not run JavaScript programs without a browser. Only browsers were designed for this task since JavaScript is one of the main programming languages of the web. </p>
<p>The awesome thing about Node.js is that it lets us build scalable network applications with high performance. </p>
<p>According to its <a target="_blank" href="https://nodejs.org/en/about/">official documentation</a>:</p>
<blockquote>
<p>Users of Node.js are free from worries of dead-locking the process, since there are no locks. Almost no function in Node.js directly performs I/O, so the process never blocks except when the I/O is performed using synchronous methods of Node.js standard library. <strong>Because nothing blocks, scalable systems are very reasonable to develop in Node.js.</strong></p>
</blockquote>
<p>🚩 It is important to note that <strong>Node.js is not</strong> a:</p>
<ul>
<li>Programming language.</li>
<li>Framework.</li>
<li>Library.</li>
</ul>
<p>It is a <strong>JavaScript runtime</strong> developed to run JavaScript code. </p>
<h3 id="heading-why-should-you-learn-nodejs"><strong>Why should you learn Node.js?</strong></h3>
<p>Now that you know what Node.js is, let's see <strong>why</strong> you should learn it. </p>
<p><strong>Node.js</strong> is one of the most popular web technologies among developers, including beginners who are learning how to code, as well as professionals.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/node-logo.png" alt="Image" width="600" height="400" loading="lazy">
<em>Node.js Logo</em></p>
<p><strong>Node.js</strong> is extremely popular. According to the <a target="_blank" href="https://survey.stackoverflow.co/2022/#most-popular-technologies-webframe">2022 Stack Overflow Developer Survey</a>, it is one of the most popular web technologies used by professional developers and by those who are learning to code. </p>
<p>Node.js got <strong>47.12%</strong> of the votes when respondents were asked which web frameworks and web technologies they had done extensive development work in over the past year, and which they wanted to work in over the next year. </p>
<p><strong>💡 Tip:</strong> that is almost half of the 58,743 responses!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-171.png" alt="Image" width="600" height="400" loading="lazy">
<em>The results of the Web Frameworks and technologies category in the <a target="_blank" href="https://survey.stackoverflow.co/2022/#most-popular-technologies-webframe">2022 Stack Overflow Developer</a> Survey. <strong>Node</strong>.js leads with 4<strong>7.12</strong>%<strong><em>**</em></strong>of the responses.<em>**</em></em></p>
<p>That percentage was even higher among respondents who were learning how to code: <strong>52.86%</strong>. Awesome, right? 😁</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-172.png" alt="Image" width="600" height="400" loading="lazy">
<em>The results of the Web Frameworks and technologies category in the <a target="_blank" href="https://survey.stackoverflow.co/2022/#most-popular-technologies-webframe">2022 Stack Overflow Developer</a> Survey when Learning to Code was selected. <strong>Node</strong>.js leads with<strong> 52.86</strong>%of the responses.<em>**</em></em></p>
<p>This is clear evidence of the impact that Node.js is having in web development. By learning Node.js, you will be investing your time and resources wisely. You will acquire valuable skills that are highly demanded in this field. </p>
<h2 id="heading-what-is-express">🔸 What is Express?</h2>
<p>If your goal is to develop a server with Node.js, the process can be much easier if you use <strong>Express</strong>. It's a web application framework specifically developed for Node.js. </p>
<p>According to the <a target="_blank" href="https://expressjs.com/">official documentation</a> of Express:</p>
<blockquote>
<p>Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.</p>
</blockquote>
<p><strong>💡 Tip:</strong> Express includes many tools that you can use to write more concise, readable, and maintainable code. Trust me. Once you start working with Express, you will never want to stop.</p>
<p>Express has many HTTP utility methods and middleware that you can use to create robust APIs (Application Programming Interfaces), which are fundamental for back-end and full-stack web development.</p>
<p>In the <a target="_blank" href="https://survey.stackoverflow.co/2022/#most-popular-technologies-webframe">2022 Stack Overflow Developer Survey</a>, <strong>Express</strong> was the fourth most used web technology or framework with <strong>22.99%</strong> of all votes:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-177.png" alt="Image" width="600" height="400" loading="lazy">
<em>The results of the Web Frameworks and technologies category in the <a target="_blank" href="https://survey.stackoverflow.co/2022/#most-popular-technologies-webframe">2022 Stack Overflow Developer</a> Survey. <strong>Express</strong> was fourth with <strong>22.99</strong>%of the responses.<em>**</em></em></p>
<p>Express also got <strong>25.72%</strong> of the votes of the respondents who are learning how to code:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-176.png" alt="Image" width="600" height="400" loading="lazy">
<em><strong>Express</strong> had <strong>25.72%</strong> of the votes of the respondents who are learning how to code. It was third in the results of the Web Frameworks and technologies category.</em></p>
<p><strong>Awesome!</strong> Now you know why you should learn Node.js and Express. I promise you that it will be totally worth it. ✨</p>
<h2 id="heading-nodejs-and-express-course-content">🔹 <strong>Node.js and Express Course Content</strong></h2>
<p>Now let's review what you will learn during the course. </p>
<p><strong>💡 Tip:</strong> to take the course, you should have previous knowledge of <strong>JavaScript</strong>. If you need to review these topics in Spanish, I recommend watching this <a target="_blank" href="https://www.youtube.com/watch?v=ivdTnPl1ND0&amp;t=3s">JavaScript course</a> on the freeCodeCamp Spanish YouTube channel.</p>
<h3 id="heading-introduction-to-nodejs-and-basic-concepts">Introduction to Node.js and Basic Concepts</h3>
<ul>
<li>Introduction to Node.js.</li>
<li>Basic concepts of Back-End Web Development.</li>
<li>Applications of Node.js.</li>
<li>APIs and what they are used for.</li>
<li>Advantages of Node.js.</li>
<li>How to download and install Node.js.</li>
<li>How to confirm that Node.js was installed successfully.</li>
<li>How to check your current version of Node.js.</li>
<li>The Node.js REPL.</li>
</ul>
<h3 id="heading-your-first-nodejs-project-and-node-modules">Your First Node.js Project and Node Modules</h3>
<ul>
<li>What is a module? Concept and advantages.</li>
<li>How to export and import modules.</li>
<li>How to export multiple elements from a JavaScript module. </li>
<li>How to run a JavaScript file with the <code>node</code> command. </li>
<li>Node.js core modules.</li>
<li>The <code>console</code> module.</li>
<li>The <code>process</code> module.</li>
<li>The <code>os</code> module.</li>
<li>The <code>fs</code> module.</li>
<li>The <code>timers</code> module.</li>
</ul>
<h3 id="heading-introduction-to-npm-and-json">Introduction to npm and JSON</h3>
<ul>
<li>What is npm? </li>
<li>Basic npm concepts.</li>
<li>How to initialize a package with <code>npm init</code>.</li>
<li>The <code>package.json</code> file.</li>
<li>Introduction to JSON.</li>
<li>How to install and uninstall packages with npm. </li>
<li>The <code>package-lock.json</code> file.</li>
</ul>
<h3 id="heading-events-and-asynchronous-operations">Events and Asynchronous Operations</h3>
<ul>
<li>What is an event?</li>
<li>Events in Node.js.</li>
<li>Asynchronous vs. synchronous events.</li>
<li>Promises and callback functions in JavaScript.</li>
<li>Promises, <code>.then()</code>, and <code>.catch()</code>.</li>
<li>Asynchronous functions with <code>async</code> and <code>await</code>.</li>
</ul>
<h3 id="heading-nodejs-servers-and-the-http-protocol">Node.js Servers and the HTTP Protocol</h3>
<ul>
<li>The client-server model.</li>
<li>The format of HTTP requests and responses. </li>
<li>HTTP verbs: GET, POST, PUT, DELETE.</li>
<li>HTTP state codes. </li>
<li>The <code>http</code> module in Node.js</li>
<li>How to create a server in Node.js.</li>
<li>The <code>req</code> and <code>res</code> objects. </li>
<li>Structure of a URL.</li>
<li>Routing in Node.js.</li>
</ul>
<h3 id="heading-nodemon">Nodemon</h3>
<ul>
<li>What is Nodemon?</li>
<li>How to install Nodemon globally.</li>
<li>How to use Nodemon to update Node.js applications automatically. </li>
<li>Concepts: CRUD, REST, API.</li>
</ul>
<h3 id="heading-express">Express</h3>
<ul>
<li>How to install Express and how to start a project. </li>
<li>Routing in Express.</li>
<li>Express and Nodemon. </li>
<li>How to match multiple routes. </li>
<li>Route parameters and dynamic routes. </li>
<li>Middleware in Express.</li>
<li>Handling GET, POST, PUT, PATCH, and DELETE requests. </li>
<li>Query parameters. </li>
<li>Routers in Express.</li>
</ul>
<p>💡 <strong>Tip:</strong> We will work with Visual Studio Code during the course and we will install an extension to simulate POST, PUT, and DELETE requests. </p>
<h2 id="heading-nodejs-and-express-project">🔸 <strong>Node.js and Express Project</strong></h2>
<p>During the course, you will learn through practical examples and you will apply everything you learn step by step.</p>
<p><img src="https://www.freecodecamp.org/espanol/news/content/images/2022/08/image-3.png" alt="Image" width="1920" height="1030" loading="lazy">
<em>Proyect that we will build with Node.js and Express</em></p>
<p>You will learn how to work with Promises with a pizza example 🍕, how to work with asynchronous JavaScript, and you will develop a simple server and API with Node.js to send information about programming and mathematics courses to the browser.</p>
<p>Then, we will adapt this simple server to work with Express. You will apply previous and new concepts step by step to create a server that will handle multiple routes, parameters, and different types of HTTP requests. </p>
<h2 id="heading-nodejs-and-express-course-on-youtube"><strong>📌</strong> Node.js and Express <strong>Course</strong> on YouTube</h2>
<p>Awesome. Now that you know more about Node.js and Express and what you will learn during the course, you are welcome to start taking the course in <strong>Spanish</strong>:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/1hpc70_OoAg" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>✍️ Course created by <strong>Estefania Cassingena Navone</strong> (Twitter: <a target="_blank" href="https://twitter.com/EstefaniaCassN">@EstefaniaCassN</a>, YouTube: <a target="_blank" href="https://youtube.com/codingwithestefania">Coding with Estefania</a>).</p>
<p>I really hope you like the course and find it helpful to take your first steps into the world of back-end web development.</p>
<p>You are also welcome to continue learning with our <strong>Spanish</strong> courses:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/XqFR2lqBYPs" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/ivdTnPl1ND0" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/DLikpfc64cA" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/6Jfk8ic3KVk" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build and Deploy a Backend App with Express, Postgres, Github, and Heroku ]]>
                </title>
                <description>
                    <![CDATA[ By Njoku Samson Ebere In this tutorial, we will be learning how to build and deploy an image management application backend.  It will be able to store a record of an image in the database, get the image's record back from the database, update the rec... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-backend-application/</link>
                <guid isPermaLink="false">66d84fa05a0b456f4d321471</guid>
                
                    <category>
                        <![CDATA[ Back end development  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ deployment ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Heroku ]]>
                    </category>
                
                    <category>
                        <![CDATA[ postgres ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 03 Mar 2022 20:46:30 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/03/pexels-pixabay-417273.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Njoku Samson Ebere</p>
<p>In this tutorial, we will be learning how to build and deploy an image management application backend. </p>
<p>It will be able to store a record of an image in the database, get the image's record back from the database, update the record, and even delete the record completely as the case may be.</p>
<p>To achieve all of this, we will be using Express (a Node.js framework), Postgres (a database), Cloudinary (a cloud based image storage), GitHub (for version control/storage) and Heroku (a hosting platform).</p>
<p>These tools are all free. So you don’t have to bother about how to go about paying for them. Thanks to these great innovators.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>If you are new to most of these technologies, I would advise you go through my other tutorial on how to <a target="_blank" href="https://www.freecodecamp.org/news/build-a-secure-server-with-node-and-express/">create a server and upload images to Cloudinary</a>.</p>
<p>If you are totally to Postgres, then check out this <a target="_blank" href="https://dev.to/ogwurujohnson/-persisting-a-node-api-with-postgresql-without-the-help-of-orms-like-sequelize-5dc5">tutorial</a>.</p>
<p>Whenever you are ready, let’s get to work!</p>
<h2 id="heading-how-to-store-and-retrieve-an-image-record">How to Store and Retrieve an Image Record</h2>
<h3 id="heading-create-database-and-table">Create Database and Table</h3>
<p>So you'll want to start by cloning this <a target="_blank" href="https://github.com/EBEREGIT/server-tutorial/tree/cloudinary-upload">project</a> if you don't already have it.</p>
<p>In your <strong>pgAdmin</strong>:</p>
<ul>
<li>Create a database and name it <code>tutorial</code></li>
<li>Create a table and name it <code>tutorial</code></li>
<li>Create a Login/Group Role and name it <code>tutorial</code>. <strong>(Do not forget to give it all privileges.)</strong></li>
</ul>
<p>Back in your project directory, install the <a target="_blank" href="https://node-postgres.com/">node-postgres</a> (<code>npm i pg</code>) and <a target="_blank" href="https://www.npmjs.com/package/make-runnable">make-runnnable</a> (<code>npm i make-runnable</code>) packages.</p>
<p>In your <code>package.json</code> file, replace the contents of the <code>"scripts"</code> with <code>"create": "node ./services/dbConnect createTables"</code>. We will use this to execute the <code>dbConnect</code> file we are about to create.</p>
<p>Create a <code>services/dbConnect</code> file to contain the following code:</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">const</span> pg = <span class="hljs-built_in">require</span>(<span class="hljs-string">"pg"</span>);

<span class="hljs-keyword">const</span> config = {
  <span class="hljs-attr">user</span>: <span class="hljs-string">"tutorial"</span>,
  <span class="hljs-attr">database</span>: <span class="hljs-string">"tutorial"</span>,
  <span class="hljs-attr">password</span>: <span class="hljs-string">"tutorial"</span>,
  <span class="hljs-attr">port</span>: <span class="hljs-number">5432</span>,
  <span class="hljs-attr">max</span>: <span class="hljs-number">10</span>, <span class="hljs-comment">// max number of clients in the pool</span>
  <span class="hljs-attr">idleTimeoutMillis</span>: <span class="hljs-number">30000</span>,
};

<span class="hljs-keyword">const</span> pool = <span class="hljs-keyword">new</span> pg.Pool(config);

pool.on(<span class="hljs-string">"connect"</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"connected to the Database"</span>);
});

<span class="hljs-keyword">const</span> createTables = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> imageTable = <span class="hljs-string">`CREATE TABLE IF NOT EXISTS
    images(
      id SERIAL PRIMARY KEY,
      title VARCHAR(128) NOT NULL,
      cloudinary_id VARCHAR(128) NOT NULL,
      image_url VARCHAR(128) NOT NULL
    )`</span>;
  pool
    .query(imageTable)
    .then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(res);
      pool.end();
    })
    .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(err);
      pool.end();
    });
};

pool.on(<span class="hljs-string">"remove"</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"client removed"</span>);
  process.exit(<span class="hljs-number">0</span>);
});

<span class="hljs-comment">//export pool and createTables to be accessible from anywhere within the application</span>
<span class="hljs-built_in">module</span>.exports = {
  createTables,
  pool,
};

<span class="hljs-built_in">require</span>(<span class="hljs-string">"make-runnable"</span>);
</code></pre>
<p>Now we are all set to create the table in our database. If you are ready, let's rock and roll!</p>
<p>Execute the following code in your terminal:</p>
<pre><code class="lang-javascript">
  npm run create
</code></pre>
<p>If the image below is your result, then you are good to go:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/fjk5ty113j5p7kaxveqc.JPG" alt="Alt Text" width="697" height="726" loading="lazy"></p>
<p>Check your <strong>pgAdmin</strong>, and you should have your table seated properly in your database like in the image below:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/sj7gy6d86vm7ay3d8a74.JPG" alt="Alt Text" width="1366" height="694" loading="lazy"></p>
<p>Alright, it's been a long road. It's time to unite Node, Postgres, and Cloudinary.</p>
<h2 id="heading-how-to-create-endpoints-to-store-and-retrieve-image-records">How to Create Endpoints to Store and Retrieve Image Records</h2>
<h3 id="heading-endpoint-1-persist-image">Endpoint 1: Persist Image</h3>
<p>First, require the <code>dbConnect.js</code> file on the top of the <code>app.js</code> file like so:</p>
<pre><code class="lang-javascript">  <span class="hljs-keyword">const</span> db = <span class="hljs-built_in">require</span>(<span class="hljs-string">'services/dbConnect.js'</span>);
</code></pre>
<p>Then in the <code>app.js</code> file, make a new endpoint <em>(persist-image)</em> with the following code:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// persist image</span>
app.post(<span class="hljs-string">"/persist-image"</span>, <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {
  <span class="hljs-comment">// collected image from a user</span>
  <span class="hljs-keyword">const</span> data = {
    <span class="hljs-attr">title</span>: request.body.title,
    <span class="hljs-attr">image</span>: request.body.image,
  }

  <span class="hljs-comment">// upload image here</span>
  cloudinary.uploader.upload(data.image)
  .then().catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
    response.status(<span class="hljs-number">500</span>).send({
      <span class="hljs-attr">message</span>: <span class="hljs-string">"failure"</span>,
      error,
    });
  });
})
</code></pre>
<p>Replace the <code>then</code> block with the following code:</p>
<pre><code class="lang-javascript">
.then(<span class="hljs-function">(<span class="hljs-params">image</span>) =&gt;</span> {
    db.pool.connect(<span class="hljs-function">(<span class="hljs-params">err, client</span>) =&gt;</span> {
      <span class="hljs-comment">// inset query to run if the upload to cloudinary is successful</span>
      <span class="hljs-keyword">const</span> insertQuery = <span class="hljs-string">'INSERT INTO images (title, cloudinary_id, image_url) 
         VALUES($1,$2,$3) RETURNING *'</span>;
      <span class="hljs-keyword">const</span> values = [data.title, image.public_id, image.secure_url];
    })
  })
</code></pre>
<p>The <code>image.public_id</code> and <code>image.secure_url</code> are gotten as part of the details returned for an image after the image has been successfully uploaded to Cloudinary. </p>
<p>We are now keeping a record of the <code>image.public_id</code> and <code>image.secure_url</code> (as you can see in the code above) in order to use it to retrieve, update, or delete the image record when we see fit. </p>
<p>Alright, let's move forward!</p>
<p>Still in the <code>then</code> block, add the following code under the query we created:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// execute query</span>
client.query(insertQuery, values)
      .then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
        result = result.rows[<span class="hljs-number">0</span>];

        <span class="hljs-comment">// send success response</span>
        response.status(<span class="hljs-number">201</span>).send({
          <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
          <span class="hljs-attr">data</span>: {
            <span class="hljs-attr">message</span>: <span class="hljs-string">"Image Uploaded Successfully"</span>,
            <span class="hljs-attr">title</span>: result.title,
            <span class="hljs-attr">cloudinary_id</span>: result.cloudinary_id,
            <span class="hljs-attr">image_url</span>: result.image_url,
          },
        })
      }).catch(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
        response.status(<span class="hljs-number">500</span>).send({
          <span class="hljs-attr">message</span>: <span class="hljs-string">"failure"</span>,
          e,
        });
      })
</code></pre>
<p>So our <code>persist-image</code> endpoint now looks like this:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// persist image</span>
app.post(<span class="hljs-string">"/persist-image"</span>, <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {
  <span class="hljs-comment">// collected image from a user</span>
  <span class="hljs-keyword">const</span> data = {
    <span class="hljs-attr">title</span>: request.body.title,
    <span class="hljs-attr">image</span>: request.body.image
  }

  <span class="hljs-comment">// upload image here</span>
  cloudinary.uploader.upload(data.image)
  .then(<span class="hljs-function">(<span class="hljs-params">image</span>) =&gt;</span> {
    db.pool.connect(<span class="hljs-function">(<span class="hljs-params">err, client</span>) =&gt;</span> {
      <span class="hljs-comment">// inset query to run if the upload to cloudinary is successful</span>
      <span class="hljs-keyword">const</span> insertQuery = <span class="hljs-string">'INSERT INTO images (title, cloudinary_id, image_url) 
         VALUES($1,$2,$3) RETURNING *'</span>;
      <span class="hljs-keyword">const</span> values = [data.title, image.public_id, image.secure_url];

      <span class="hljs-comment">// execute query</span>
      client.query(insertQuery, values)
      .then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
        result = result.rows[<span class="hljs-number">0</span>];

        <span class="hljs-comment">// send success response</span>
        response.status(<span class="hljs-number">201</span>).send({
          <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
          <span class="hljs-attr">data</span>: {
            <span class="hljs-attr">message</span>: <span class="hljs-string">"Image Uploaded Successfully"</span>,
            <span class="hljs-attr">title</span>: result.title,
            <span class="hljs-attr">cloudinary_id</span>: result.cloudinary_id,
            <span class="hljs-attr">image_url</span>: result.image_url,
          },
        })
      }).catch(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
        response.status(<span class="hljs-number">500</span>).send({
          <span class="hljs-attr">message</span>: <span class="hljs-string">"failure"</span>,
          e,
        });
      })
    })  
  }).catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
    response.status(<span class="hljs-number">500</span>).send({
      <span class="hljs-attr">message</span>: <span class="hljs-string">"failure"</span>,
      error,
    });
  });
});
</code></pre>
<p><strong>Now let's test out all our hard work:</strong></p>
<p>Open your <em>postman</em> and test out your endpoint like the image below. Mine was successful. Hope yours had no errors too?</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/euxsoyb821v85uv5rhml.JPG" alt="Alt Text" width="1351" height="720" loading="lazy"></p>
<p>Open your Cloudinary console/dashboard and check your <code>media Library</code>. Your new image should be sitting there comfortably like mine below:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/dxy5fl8fodqpn89oltzh.JPG" alt="Alt Text" width="1366" height="735" loading="lazy"></p>
<p>And now to the main reason why we are here: check the <code>images</code> table in your <strong>pgAdmin</strong>. Mine is what you see below:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/ypv6vcrocsgp13owq5dv.JPG" alt="Alt Text" width="1366" height="693" loading="lazy"></p>
<p>Oohlala! We made it this far. Please take a break if you need one. I will be here waiting when you return. :)</p>
<p>If you are ready, then let's retrieve the image we persisted a moment ago.</p>
<h3 id="heading-endpoint-2-retrieve-image">Endpoint 2: Retrieve Image</h3>
<p>Start with this code:</p>
<pre><code class="lang-javascript">
app.get(<span class="hljs-string">"/retrieve-image/:cloudinary_id"</span>, <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {

});
</code></pre>
<p>Next, we will need to collect a unique ID from the user to retrieve a particular image. So add <code>const { id } = request.params;</code> to the code above like so:</p>
<pre><code class="lang-javascript">
app.get(<span class="hljs-string">"/retrieve-image/:cloudinary_id"</span>, <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {
  <span class="hljs-comment">// data from user</span>
  <span class="hljs-keyword">const</span> { cloudinary_id } = request.params;

});
</code></pre>
<p>Add the following code just below the code above:</p>
<pre><code class="lang-javascript">
db.pool.connect(<span class="hljs-function">(<span class="hljs-params">err, client</span>) =&gt;</span> {
      <span class="hljs-comment">// query to find image</span>
    <span class="hljs-keyword">const</span> query = <span class="hljs-string">"SELECT * FROM images WHERE cloudinary_id = $1"</span>;
    <span class="hljs-keyword">const</span> value = [cloudinary_id];
    });
</code></pre>
<p>Under the query, execute the query with the following code:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// execute query</span>
    client
      .query(query, value)
      .then(<span class="hljs-function">(<span class="hljs-params">output</span>) =&gt;</span> {
        response.status(<span class="hljs-number">200</span>).send({
          <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
          <span class="hljs-attr">data</span>: {
            <span class="hljs-attr">id</span>: output.rows[<span class="hljs-number">0</span>].cloudinary_id,
            <span class="hljs-attr">title</span>: output.rows[<span class="hljs-number">0</span>].title,
            <span class="hljs-attr">url</span>: output.rows[<span class="hljs-number">0</span>].image_url,
          },
        });
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
        response.status(<span class="hljs-number">401</span>).send({
          <span class="hljs-attr">status</span>: <span class="hljs-string">"failure"</span>,
          <span class="hljs-attr">data</span>: {
            <span class="hljs-attr">message</span>: <span class="hljs-string">"could not retrieve record!"</span>,
            error,
          },
        });
      });
</code></pre>
<p>Now our <code>retrieve-image</code> API looks like this:</p>
<pre><code class="lang-javascript">
app.get(<span class="hljs-string">"/retrieve-image/:cloudinary_id"</span>, <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {
  <span class="hljs-comment">// data from user</span>
  <span class="hljs-keyword">const</span> { cloudinary_id } = request.params;

  db.pool.connect(<span class="hljs-function">(<span class="hljs-params">err, client</span>) =&gt;</span> {
    <span class="hljs-comment">// query to find image</span>
    <span class="hljs-keyword">const</span> query = <span class="hljs-string">"SELECT * FROM images WHERE cloudinary_id = $1"</span>;
    <span class="hljs-keyword">const</span> value = [cloudinary_id];

    <span class="hljs-comment">// execute query</span>
    client
      .query(query, value)
      .then(<span class="hljs-function">(<span class="hljs-params">output</span>) =&gt;</span> {
        response.status(<span class="hljs-number">200</span>).send({
          <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
          <span class="hljs-attr">data</span>: {
            <span class="hljs-attr">id</span>: output.rows[<span class="hljs-number">0</span>].cloudinary_id,
            <span class="hljs-attr">title</span>: output.rows[<span class="hljs-number">0</span>].title,
            <span class="hljs-attr">url</span>: output.rows[<span class="hljs-number">0</span>].image_url,
          },
        });
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
        response.status(<span class="hljs-number">401</span>).send({
          <span class="hljs-attr">status</span>: <span class="hljs-string">"failure"</span>,
          <span class="hljs-attr">data</span>: {
            <span class="hljs-attr">message</span>: <span class="hljs-string">"could not retrieve record!"</span>,
            error,
          },
        });
      });
  });
});
</code></pre>
<p><strong>Let's see how well we did:</strong></p>
<p>In your postman, copy the <code>cloudinary_id</code> and add it to the URL like in the image below:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/9bmutxbmitgznqnnwmny.JPG" alt="Alt Text" width="1348" height="719" loading="lazy"></p>
<p>YEEESSS! We can also retrieve our image.</p>
<p>If you are here, then you deserve a round of applause and a standing ovation for your industriousness.</p>
<p>Congratulations! You just reached a great milestone.</p>
<p>The code for storing and retrieving image records is <a target="_blank" href="https://github.com/EBEREGIT/server-tutorial/tree/create-APIs">here</a>.</p>
<h2 id="heading-how-to-update-and-delete-an-image-record">How to Update and Delete an Image Record</h2>
<p>We will now see how to delete and update an image record as the case maybe. Let's begin with the delete endpoint.</p>
<h3 id="heading-delete-endpoint">Delete Endpoint</h3>
<p>In the app.js file, start with the following code:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// delete image</span>
app.delete(<span class="hljs-string">"delete-image/:cloudinary_id"</span>, <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {

});
</code></pre>
<p>Next, we want to get the unique ID of the image we want to delete from the URL, that is <code>cloudinary_id</code>. So inside the code above add:</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">const</span> { cloudinary_id } = request.params;
</code></pre>
<p>We now start the deleting process.</p>
<p>First, we delete from Cloudinary. Add the following code to delete the image from Cloudinary:</p>
<pre><code class="lang-javascript">
cloudinary.uploader
    .destroy(cloudinary_id)
    .then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
      response.status(<span class="hljs-number">200</span>).send({
        <span class="hljs-attr">message</span>: <span class="hljs-string">"success"</span>,
        result,
      });
    })
    .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
      response.status(<span class="hljs-number">500</span>).send({
        <span class="hljs-attr">message</span>: <span class="hljs-string">"Failure"</span>,
        error,
      });
    });
</code></pre>
<p>At this point, our API can delete the image from Cloudinary only (you can check it out in postman). But we also want to get rid of the record we have in our Postgres database.</p>
<p>Second, we delete from our Postgres database. To do so, replace the code in the <code>then</code> block with the following <code>query</code>:</p>
<pre><code class="lang-javascript">
db.pool.connect(<span class="hljs-function">(<span class="hljs-params">err, client</span>) =&gt;</span> {

      <span class="hljs-comment">// delete query</span>
      <span class="hljs-keyword">const</span> deleteQuery = <span class="hljs-string">"DELETE FROM images WHERE cloudinary_id = $1"</span>;
      <span class="hljs-keyword">const</span> deleteValue = [cloudinary_id];

})
</code></pre>
<p>Execute the query with the following code underneath it:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// execute delete query</span>
      client.query(deleteQuery, deleteValue)
      .then(<span class="hljs-function">(<span class="hljs-params">deleteResult</span>) =&gt;</span> {
        response.status(<span class="hljs-number">200</span>).send({
          <span class="hljs-attr">message</span>: <span class="hljs-string">"Image Deleted Successfully!"</span>,
          deleteResult
        });
      }).catch(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
        response.status(<span class="hljs-number">500</span>).send({
          <span class="hljs-attr">message</span>: <span class="hljs-string">"Image Couldn't be Deleted!"</span>,
          e
        });
      });
</code></pre>
<p>So our Endpoint should look like this:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// delete image</span>
app.delete(<span class="hljs-string">"/delete-image/:cloudinary_id"</span>, <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {
  <span class="hljs-comment">// unique ID</span>
  <span class="hljs-keyword">const</span> { cloudinary_id } = request.params;

  <span class="hljs-comment">// delete image from cloudinary first</span>
  cloudinary.uploader
    .destroy(cloudinary_id)

    <span class="hljs-comment">// delete image record from postgres also</span>
    .then(<span class="hljs-function">() =&gt;</span> {
      db.pool.connect(<span class="hljs-function">(<span class="hljs-params">err, client</span>) =&gt;</span> {

      <span class="hljs-comment">// delete query</span>
      <span class="hljs-keyword">const</span> deleteQuery = <span class="hljs-string">"DELETE FROM images WHERE cloudinary_id = $1"</span>;
      <span class="hljs-keyword">const</span> deleteValue = [cloudinary_id];

      <span class="hljs-comment">// execute delete query</span>
      client
        .query(deleteQuery, deleteValue)
        .then(<span class="hljs-function">(<span class="hljs-params">deleteResult</span>) =&gt;</span> {
          response.status(<span class="hljs-number">200</span>).send({
            <span class="hljs-attr">message</span>: <span class="hljs-string">"Image Deleted Successfully!"</span>,
            deleteResult,
          });
        })
        .catch(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
          response.status(<span class="hljs-number">500</span>).send({
            <span class="hljs-attr">message</span>: <span class="hljs-string">"Image Couldn't be Deleted!"</span>,
            e,
          });
        });
      })
    })
    .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
      response.status(<span class="hljs-number">500</span>).send({
        <span class="hljs-attr">message</span>: <span class="hljs-string">"Failure"</span>,
        error,
      });
    });
});
</code></pre>
<p>The time has arrived for us to put our Endpoint to the test.</p>
<p>The following is my Cloudinary <code>media library</code> with two images I uploaded already. Take note of their unique ID (<code>public_id</code>). </p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/sjir185on5pqrlzrc1hl.JPG" alt="Alt Text" width="1365" height="766" loading="lazy"></p>
<p>If you don't already have that, please use the persist-image endpoint to upload some images.</p>
<p>Now let's proceed to postman:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/beu5lleymnffa5vyzj97.JPG" alt="Alt Text" width="1365" height="730" loading="lazy"></p>
<p>Notice, the unique ID as it matches one of the image in my Cloudinary media library.</p>
<p>From the output, we executed the DELETE command and that deleted one ROW from our image TABLE in our database.</p>
<p>Now this is my media library with one of the images remaining:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/8d7rs33580c4ewpcipbr.JPG" alt="Alt Text" width="1366" height="768" loading="lazy"></p>
<p>Walahhhh... We are now able to get rid of an image.</p>
<p>Do take a break if you need one. ✌🏾</p>
<p>If you are ready, I am ready to update images.</p>
<h3 id="heading-update-image-api">Update Image API</h3>
<p>Below the <code>delete-image</code> API, let's start creating the <code>update-image</code> API with the following code:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// update image</span>
app.put(<span class="hljs-string">"/update-image/:cloudinary_id"</span>, <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {

});

All codes will live <span class="hljs-keyword">in</span> there.
</code></pre>
<p>Collect the unique Cloudinary ID and new image details from the user with the following code:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// unique ID</span>
  <span class="hljs-keyword">const</span> { cloudinary_id } = request.params;

<span class="hljs-comment">// collected image from a user</span>
  <span class="hljs-keyword">const</span> data = {
    <span class="hljs-attr">title</span>: request.body.title,
    <span class="hljs-attr">image</span>: request.body.image,
  };
</code></pre>
<p>Delete the image from Cloudinary with the following code:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// delete image from cloudinary first</span>
  cloudinary.uploader
    .destroy(cloudinary_id)
      <span class="hljs-comment">// upload image here</span>
    .then()
    .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
      response.status(<span class="hljs-number">500</span>).send({
        <span class="hljs-attr">message</span>: <span class="hljs-string">"failed"</span>,
        error,
      });
    });
</code></pre>
<p>Next, upload another image to Cloudinary. To do that, enter the following code into the <code>then</code> block:</p>
<pre><code class="lang-javascript">
() =&gt; {
      cloudinary.uploader
        .upload(data.image)
        .then()
        .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
          response.status(<span class="hljs-number">500</span>).send({
            <span class="hljs-attr">message</span>: <span class="hljs-string">"failed"</span>,
            err,
          });
        });
    }
</code></pre>
<p>Now let's replace our initial record with the new image details. Replace the content of the <code>then</code> block with the following:</p>
<pre><code class="lang-javascript">
(result) =&gt; {
          db.pool.connect(<span class="hljs-function">(<span class="hljs-params">err, client</span>) =&gt;</span> {

            <span class="hljs-comment">// update query</span>
            <span class="hljs-keyword">const</span> updateQuery =
              <span class="hljs-string">"UPDATE images SET title = $1, cloudinary_id = $2, image_url = $3 WHERE cloudinary_id = $4"</span>;
            <span class="hljs-keyword">const</span> value = [
              data.title,
              result.public_id,
              result.secure_url,
              cloudinary_id,
            ];
          });
        }
</code></pre>
<p>We execute the query using the following code just beneath the query declaration:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// execute query</span>
            client
              .query(updateQuery, value)
              .then(<span class="hljs-function">() =&gt;</span> {

                <span class="hljs-comment">// send success response</span>
                response.status(<span class="hljs-number">201</span>).send({
                  <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
                  <span class="hljs-attr">data</span>: {
                    <span class="hljs-attr">message</span>: <span class="hljs-string">"Image Updated Successfully"</span>
                  },
                });
              })
              .catch(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
                response.status(<span class="hljs-number">500</span>).send({
                  <span class="hljs-attr">message</span>: <span class="hljs-string">"Update Failed"</span>,
                  e,
                });
              });
</code></pre>
<p>At this point, this is what I have:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// update image</span>
app.put(<span class="hljs-string">"/update-image/:cloudinary_id"</span>, <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {
  <span class="hljs-comment">// unique ID</span>
  <span class="hljs-keyword">const</span> { cloudinary_id } = request.params;

  <span class="hljs-comment">// collected image from a user</span>
  <span class="hljs-keyword">const</span> data = {
    <span class="hljs-attr">title</span>: request.body.title,
    <span class="hljs-attr">image</span>: request.body.image,
  };

    <span class="hljs-comment">// delete image from cloudinary first</span>
    cloudinary.uploader
      .destroy(cloudinary_id)

      <span class="hljs-comment">// upload image here</span>
      .then(<span class="hljs-function">() =&gt;</span> {
        cloudinary.uploader
          .upload(data.image)

          <span class="hljs-comment">// update the database here</span>
          .then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
            db.pool.connect(<span class="hljs-function">(<span class="hljs-params">err, client</span>) =&gt;</span> {
            <span class="hljs-comment">// update query</span>
            <span class="hljs-keyword">const</span> updateQuery =
              <span class="hljs-string">"UPDATE images SET title = $1, cloudinary_id = $2, image_url = $3 WHERE cloudinary_id = $4"</span>;
            <span class="hljs-keyword">const</span> value = [
              data.title,
              result.public_id,
              result.secure_url,
              cloudinary_id,
            ];

            <span class="hljs-comment">// execute query</span>
            client
              .query(updateQuery, value)
              .then(<span class="hljs-function">() =&gt;</span> {

                <span class="hljs-comment">// send success response</span>
                response.status(<span class="hljs-number">201</span>).send({
                  <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
                  <span class="hljs-attr">data</span>: {
                    <span class="hljs-attr">message</span>: <span class="hljs-string">"Image Updated Successfully"</span>
                  },
                });
              })
              .catch(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
                response.status(<span class="hljs-number">500</span>).send({
                  <span class="hljs-attr">message</span>: <span class="hljs-string">"Update Failed"</span>,
                  e,
                });
              });
            });
          })
          .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
            response.status(<span class="hljs-number">500</span>).send({
              <span class="hljs-attr">message</span>: <span class="hljs-string">"failed"</span>,
              err,
            });
          });
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
        response.status(<span class="hljs-number">500</span>).send({
          <span class="hljs-attr">message</span>: <span class="hljs-string">"failed"</span>,
          error,
        });
      });

});
</code></pre>
<p>It's testing time!</p>
<p>This is my postman in the image below:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/jowr0guiazmqkhx1mmls.JPG" alt="Alt Text" width="1366" height="768" loading="lazy"></p>
<p>Take note of the unique cloudinary ID which matches the image left in my Cloudinary media library.</p>
<p>Now take a look at my Cloudinary media library in the image that follows:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/td9rpqovhh2kl6ytc2u4.JPG" alt="Alt Text" width="1366" height="768" loading="lazy"></p>
<p>Take note of the new image replacing the initial one in my media library above.</p>
<p>Also, see that the unique Cloudinary ID matches that in my database with the new title. See image below:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/wu3mydrn71x3azyd75ee.JPG" alt="Alt Text" width="1366" height="729" loading="lazy"></p>
<p>Yayeh! You did awesomely great! 💪</p>
<p>We just completed an image management application with Node.js, Cloudinary and Postgres.</p>
<h2 id="heading-code-optimisation-with-express-routing">Code Optimisation With Express Routing</h2>
<p>Express Routing enables us to make our Node.js code more optimized or give it a more modular structure by separating the business logic from the controllers. We want to use that to clean up our code so far. </p>
<p>We'll begin by creating a new folder with the name <code>routes</code> in the root directory:</p>
<pre><code class="lang-javascript">
mk dir routes
</code></pre>
<p>In the routes folder, create a file with the name: <code>routes.js</code>.</p>
<p>For Windows:</p>
<pre><code class="lang-javascript">
echo . &gt; routes.js
</code></pre>
<p>For Mac:</p>
<pre><code class="lang-javascript">
touch routes.js
</code></pre>
<p>Empty the <code>routes.js</code> file if anything is there and enter the following code:</p>
<pre><code class="lang-javascript">
<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> router = express.Router();



<span class="hljs-built_in">module</span>.exports = router;
</code></pre>
<p>Add the following code above the last line:</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">const</span> cloudinary = <span class="hljs-built_in">require</span>(<span class="hljs-string">"cloudinary"</span>).v2;
<span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>).config();
<span class="hljs-keyword">const</span> db = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../services/dbConnect.js"</span>);

<span class="hljs-comment">// cloudinary configuration</span>
cloudinary.config({
  <span class="hljs-attr">cloud_name</span>: process.env.CLOUD_NAME,
  <span class="hljs-attr">api_key</span>: process.env.API_KEY,
  <span class="hljs-attr">api_secret</span>: process.env.API_SECRET,
});
</code></pre>
<p>Back in the App.js file, delete the following code:</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">const</span> cloudinary = <span class="hljs-built_in">require</span>(<span class="hljs-string">"cloudinary"</span>).v2;
<span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>).config();
<span class="hljs-keyword">const</span> db = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./services/dbConnect.js"</span>);

<span class="hljs-comment">// cloudinary configuration</span>
cloudinary.config({
  <span class="hljs-attr">cloud_name</span>: process.env.CLOUD_NAME,
  <span class="hljs-attr">api_key</span>: process.env.API_KEY,
  <span class="hljs-attr">api_secret</span>: process.env.API_SECRET,
});
</code></pre>
<p>Move all the APIs to <code>routes.js</code>.</p>
<p>Change all occurence of <code>app</code> to <code>router</code> carefully.</p>
<p>My <code>routes.js</code> file now looks like <a target="_blank" href="https://github.com/EBEREGIT/server-tutorial/blob/routing/routes/routes.js">this</a>.</p>
<p>Back in the <code>app.js</code> file, import the <code>routes.js</code> file like so:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// import the routes file</span>
<span class="hljs-keyword">const</span> routes = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./routes/routes"</span>)
</code></pre>
<p>Now register the routes like so:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// register the routes </span>
app.use(<span class="hljs-string">'/'</span>, routes);
</code></pre>
<p>This is my <code>app.js</code> file at the moment:</p>
<pre><code class="lang-javascript">
<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> app = express();

<span class="hljs-comment">// import the routes file</span>
<span class="hljs-keyword">const</span> routes = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./routes/routes"</span>)

<span class="hljs-comment">// body parser configuration</span>
<span class="hljs-keyword">const</span> bodyParser = <span class="hljs-built_in">require</span>(<span class="hljs-string">"body-parser"</span>);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ <span class="hljs-attr">extended</span>: <span class="hljs-literal">true</span> }));

<span class="hljs-comment">// register the routes </span>
app.use(<span class="hljs-string">'/'</span>, routes);

<span class="hljs-built_in">module</span>.exports = app;
</code></pre>
<p>It's time to test and see if our routes are still working like before.</p>
<p>Make sure yours are working like mine below:</p>
<h4 id="heading-persist-image">persist-image</h4>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/r2uz54xix054li4lgw6i.JPG" alt="persist image" width="1366" height="730" loading="lazy"></p>
<h4 id="heading-retrieve-image">retrieve-image</h4>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/o44a1dnfgvz3r9sv3a1j.JPG" alt="retrieve image" width="1366" height="727" loading="lazy"></p>
<h4 id="heading-update-image">update-image</h4>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/6t9k5m079rwx4p6ouax3.JPG" alt="update image" width="1365" height="728" loading="lazy"></p>
<h4 id="heading-delete-image">delete-image</h4>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/nkkydplioi68la4eynhp.JPG" alt="delete image" width="1366" height="728" loading="lazy"></p>
<p>Wow! We have been able to separate our routes from our <code>app.js</code> file.</p>
<p>The code for this is <a target="_blank" href="https://github.com/EBEREGIT/server-tutorial/tree/routing">here</a>.</p>
<p>Even though our <code>routes.js</code> file is still lengthy, we have a good basis to separate our business logic from our controllers. The time has arrived to do just that.</p>
<h2 id="heading-how-to-move-each-endpoint-to-a-different-file">How to Move Each Endpoint to a Different File</h2>
<p>Begin by creating a new folder in the <code>routes</code> folder and name it <code>controllers</code>.</p>
<p>In the controllers folder, create 5 files and name them after the 5 endpoints.</p>
<p>Our folder and files should be structured as follows:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/dmren2r2w2801lf28wjy.JPG" alt="folder and files structure" width="242" height="728" loading="lazy"></p>
<p>Back in the routes.js file, let's work on the <code>image-upload</code> API. Cut the following code:</p>
<pre><code class="lang-javascript">
(request, response) =&gt; {
  <span class="hljs-comment">// collected image from a user</span>
  <span class="hljs-keyword">const</span> data = {
    <span class="hljs-attr">image</span>: request.body.image,
  };

  <span class="hljs-comment">// upload image here</span>
  cloudinary.uploader
    .upload(data.image)
    .then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
      response.status(<span class="hljs-number">200</span>).send({
        <span class="hljs-attr">message</span>: <span class="hljs-string">"success"</span>,
        result,
      });
    })
    .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
      response.status(<span class="hljs-number">500</span>).send({
        <span class="hljs-attr">message</span>: <span class="hljs-string">"failure"</span>,
        error,
      });
    });
}
</code></pre>
<p>In the <code>imageUpload</code> file, equate the code you already cut from the <code>image-upload</code> endpoint to <code>exports.imageUpload</code> like so:</p>
<pre><code class="lang-javascript">
<span class="hljs-built_in">exports</span>.imageUpload = <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {
    <span class="hljs-comment">// collected image from a user</span>
    <span class="hljs-keyword">const</span> data = {
      <span class="hljs-attr">image</span>: request.body.image,
    };

    <span class="hljs-comment">// upload image here</span>
    cloudinary.uploader
      .upload(data.image)
      .then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
        response.status(<span class="hljs-number">200</span>).send({
          <span class="hljs-attr">message</span>: <span class="hljs-string">"success"</span>,
          result,
        });
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
        response.status(<span class="hljs-number">500</span>).send({
          <span class="hljs-attr">message</span>: <span class="hljs-string">"failure"</span>,
          error,
        });
      });
  }
</code></pre>
<p>Now let's import what is necessary for this code to work. So this is my <code>imageUpload</code> file right now:</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">const</span> cloudinary = <span class="hljs-built_in">require</span>(<span class="hljs-string">"cloudinary"</span>).v2;
<span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>).config();

<span class="hljs-comment">// cloudinary configuration</span>
cloudinary.config({
  <span class="hljs-attr">cloud_name</span>: process.env.CLOUD_NAME,
  <span class="hljs-attr">api_key</span>: process.env.API_KEY,
  <span class="hljs-attr">api_secret</span>: process.env.API_SECRET,
});

<span class="hljs-built_in">exports</span>.imageUpload = <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {
    <span class="hljs-comment">// collected image from a user</span>
    <span class="hljs-keyword">const</span> data = {
      <span class="hljs-attr">image</span>: request.body.image,
    };

    <span class="hljs-comment">// upload image here</span>
    cloudinary.uploader
      .upload(data.image)
      .then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
        response.status(<span class="hljs-number">200</span>).send({
          <span class="hljs-attr">message</span>: <span class="hljs-string">"success"</span>,
          result,
        });
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
        response.status(<span class="hljs-number">500</span>).send({
          <span class="hljs-attr">message</span>: <span class="hljs-string">"failure"</span>,
          error,
        });
      });
  }
</code></pre>
<p>Let's import and register the <code>imageUpload</code> API in the <code>routes.js</code> file like so:</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">const</span> imageUpload = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./controllers/imageUpload"</span>);

<span class="hljs-comment">// image upload API</span>
router.post(<span class="hljs-string">"image-upload"</span>, imageUpload.imageUpload);
</code></pre>
<p>Now we have this line of code pointing to the <code>imageUpload</code> API in the <code>imageUpload.js</code> file from the <code>routes.js</code> file.</p>
<p>How awesome! Our code is more readable.</p>
<p>Make sure to test the API to be sure it's working properly. Mine works perfectly. See image below:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/t456z1fz1537nja0huyr.JPG" alt="image upload test result" width="1358" height="1024" loading="lazy"></p>
<p>Now, it's your turn!</p>
<p>Apply what you have learnt to the other APIs. Let's see what you have got.</p>
<p>I will be waiting on the other side...</p>
<p>If you are here, then I believe you have done yours and they're working perfectly – or at least, you already gave it your best shot. Kudos!</p>
<p>Checkout mine <a target="_blank" href="https://github.com/EBEREGIT/server-tutorial/tree/controller-setup/routes">here</a>.</p>
<p>Congratulations. You are awesome :)</p>
<p>The code optimisation code is <a target="_blank" href="https://github.com/EBEREGIT/server-tutorial/tree/controller-setup">here</a>.</p>
<p>Alright, on to the next step.</p>
<h2 id="heading-how-to-deploy-to-github-and-heroku">How to Deploy to GitHub And Heroku</h2>
<p>Now that we've completed our application, let's deploy it on Heroku so that we can access it even without being on our laptop where the code was written.</p>
<p>I will be walking you through uploading our application to <a target="_blank" href="https://github.com/">GitHub</a> and deploying it to <a target="_blank" href="https://heroku.com/">Heroku</a>.</p>
<p>Without further ado, let's get our hands dirty.</p>
<h2 id="heading-how-to-upload-the-code-to-github">How to Upload the Code to GitHub</h2>
<p>Uploading or pushing to GitHub is as easy as eating your favorite meal. Check out <a target="_blank" href="https://docs.github.com/en/get-started/importing-your-projects-to-github/importing-source-code-to-github/adding-an-existing-project-to-github-using-the-command-line">this resource</a> to learn how to push your project from you local machine to GitHub.</p>
<h2 id="heading-how-to-deploy-to-heroku">How to Deploy to Heroku</h2>
<p>Let's begin by creating an account on <a target="_blank" href="https://heroku.com/">Heroku</a>.</p>
<p>If you have created an account, you may have been prompted to create an app (that is a folder where your app will be housed). You can do that, but I will do mine using my terminal since the terminal comes with a few added functionalities that we will need later.</p>
<p>Open your project in a terminal if you have not done so already. I will be using the VS Code integrated terminal.</p>
<p>Install Heroku CLI:</p>
<pre><code class="lang-javascript">npm install heroku
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/716g1v0nnea93rdrhmk5.JPG" alt="Alt Text" width="666" height="273" loading="lazy"></p>
<p>Login to Heroku CLI. This will open a browser window, which you can use to log in.</p>
<pre><code class="lang-javascript">heroku login
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/bky4mywipzua8cj6d0kf.JPG" alt="Alt Text" width="741" height="181" loading="lazy"></p>
<p>Create an app. It can have any name. I am using <code>node-postgres-cloudinary</code>.</p>
<pre><code class="lang-javascript">heroku create node-postgres-cloudinary
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/ibofc7xcyqgi165f5dz1.JPG" alt="Alt Text" width="743" height="155" loading="lazy"></p>
<p>Go to your <a target="_blank" href="https://dashboard.heroku.com/apps">Heroku dashboard</a> and you will find the newly created app.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/d84emoge8pi9qt0a2qrb.JPG" alt="Alt Text" width="1366" height="624" loading="lazy"></p>
<p>Waalaah!</p>
<p>That is how mine looks in the image above. I have some apps there already but you can see the one I just created.</p>
<p>Let's now add the PostgreSQL database to the app.</p>
<h3 id="heading-how-to-add-heroku-postgres">How to Add Heroku Postgres</h3>
<p>Click on the app you just created. It will take you to the app's dashboard.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/g48iadulci1evt6j8ftf.JPG" alt="Alt Text" width="1365" height="625" loading="lazy"></p>
<p>Click on the <code>Resources</code> tab/menu.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/e6bvzsv7cn6ebgwi0y65.JPG" alt="Alt Text" width="1366" height="173" loading="lazy"></p>
<p>In the <code>Add-ons</code> Section, search and select <code>Heroku Postgres</code>.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/91nwppclfdkvitp56rrj.JPG" alt="Alt Text" width="1365" height="625" loading="lazy"></p>
<p>Make sure you select the <code>Hobby Dev - Free</code> plan in the pop-up window that follows:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/hd7zj1qa2t4e051zn654.JPG" alt="Alt Text" width="1366" height="625" loading="lazy"></p>
<p>Click on the <code>provision</code> button to add it to the app like so:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/8w2z6tok8plmp154ovuj.JPG" alt="Alt Text" width="1366" height="628" loading="lazy"></p>
<p>Click on the <code>Heroku Postgres</code> to take you to the <code>Heroku Postgres</code> dashboard.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/nvrdf902ypuz2frqiu1q.JPG" alt="Alt Text" width="1365" height="625" loading="lazy"></p>
<p>Click on the <code>settings</code> tab:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/6k81alizfc5yzgtbzlcv.JPG" alt="Alt Text" width="1366" height="630" loading="lazy"></p>
<p>Click on <code>View Credentials</code>:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/33p2ceyfkbmr590s1r6b.JPG" alt="Alt Text" width="1347" height="165" loading="lazy"></p>
<p>In the Credentials, we are interested in the Heroku CLI. We will be using it in a bit.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/xzmtjtqrg8a5vqrfjkg3.JPG" alt="Alt Text" width="1366" height="614" loading="lazy"></p>
<p>Back to the terminal.</p>
<p>Let's confirm if the <code>Heroku Postgres</code> was added successfully. Enter the following in the terminal:</p>
<pre><code class="lang-javascript">heroku addons
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/7hhl70mw99b2mrji9i35.JPG" alt="Alt Text" width="841" height="249" loading="lazy"></p>
<p>Yeeeeaaaah! It was added successfully.</p>
<p>Before we proceed, <strong>make sure that your PostgreSQL <code>path</code> is set correctly if you are on Windows</strong>. Follow this <a target="_blank" href="https://www.computerhope.com/issues/ch000549.htm">link</a> to learn how to set a <code>path</code>. The path should be like this: <code>C:\Program Files\PostgreSQL\&lt;VERSION&gt;\bin</code>. </p>
<p>The version will depend on the one installed on you machine. Mine is: <code>C:\Program Files\PostgreSQL\12\bin</code> since I am using the <code>version 12</code>.</p>
<p>The following image might be helpful:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/dl3wv0z1dtvyjvvusx26.JPG" alt="Alt Text" width="1366" height="728" loading="lazy"></p>
<p>You may have to navigate to the folder where PostgreSQL is installed on your machine to find out your own path.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/bzf04c5827d4tri3ngrg.JPG" alt="Alt Text" width="1366" height="725" loading="lazy"></p>
<p>Login into the <code>Heroku Postgres</code> using the <a target="_blank" href="https://devcenter.heroku.com/articles/heroku-cli">Heroku CLI</a> from our <code>Heroku Postgres</code> <a target="_blank" href="https://devcenter.heroku.com/articles/heroku-postgresql-credentials">credentials</a>. This is mine – yours will be different:</p>
<pre><code class="lang-javascript">heroku pg:psql postgresql-slippery<span class="hljs-number">-19135</span> --app node-postgres-cloudinary
</code></pre>
<p>If you got an error, it is most likely because your path is not set properly.</p>
<h3 id="heading-how-to-prepare-our-database-connection-to-match-herokus">How to Prepare our Database Connection to Match Heroku's</h3>
<p>At the moment, my database looks like this:</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">const</span> pg = <span class="hljs-built_in">require</span>(<span class="hljs-string">"pg"</span>);

<span class="hljs-keyword">const</span> config = {
  <span class="hljs-attr">user</span>: <span class="hljs-string">"tutorial"</span>,
  <span class="hljs-attr">database</span>: <span class="hljs-string">"tutorial"</span>,
  <span class="hljs-attr">password</span>: <span class="hljs-string">"tutorial"</span>,
  <span class="hljs-attr">port</span>: <span class="hljs-number">5432</span>,
  <span class="hljs-attr">max</span>: <span class="hljs-number">10</span>, <span class="hljs-comment">// max number of clients in the pool</span>
  <span class="hljs-attr">idleTimeoutMillis</span>: <span class="hljs-number">30000</span>,
};

<span class="hljs-keyword">const</span> pool = <span class="hljs-keyword">new</span> pg.Pool(config);

pool.on(<span class="hljs-string">"connect"</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"connected to the Database"</span>);
});
</code></pre>
<p>If you try connecting Heroku to this, you are going to get an error. This is because Heroku has a <code>connection string</code> setup already. So we have to setup our connection such that Heroku can easily connect. </p>
<p>I am going to refactor my database connection file (<code>dbConnect.js</code>) and <code>.env</code> file to make this happen.</p>
<ul>
<li>dbConnect.js</li>
</ul>
<pre><code class="lang-javascript">
<span class="hljs-keyword">const</span> pg = <span class="hljs-built_in">require</span>(<span class="hljs-string">'pg'</span>);
<span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>).config();

<span class="hljs-comment">// set production variable. This will be called when deployed to a live host</span>
<span class="hljs-keyword">const</span> isProduction = process.env.NODE_ENV === <span class="hljs-string">'production'</span>;

<span class="hljs-comment">// configuration details</span>
<span class="hljs-keyword">const</span> connectionString = <span class="hljs-string">`postgresql://<span class="hljs-subst">${process.env.DB_USER}</span>:<span class="hljs-subst">${process.env.DB_PASSWORD}</span>@<span class="hljs-subst">${process.env.DB_HOST}</span>:<span class="hljs-subst">${process.env.DB_PORT}</span>/<span class="hljs-subst">${process.env.DB_DATABASE}</span>`</span>;

<span class="hljs-comment">// if project has been deployed, connect with the host's DATABASE_URL</span>
<span class="hljs-comment">// else connect with the local DATABASE_URL</span>
<span class="hljs-keyword">const</span> pool = <span class="hljs-keyword">new</span> pg.Pool({
  <span class="hljs-attr">connectionString</span>: isProduction ? process.env.DATABASE_URL : connectionString,
  <span class="hljs-attr">ssl</span>: isProduction,
});

<span class="hljs-comment">// display message on success if successful</span>
pool.on(<span class="hljs-string">'connect'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Teamwork Database connected successfully!'</span>);
});
</code></pre>
<ul>
<li>.env file</li>
</ul>
<pre><code class="lang-javascript">
DB_USER=<span class="hljs-string">"tutorial"</span>
DB_PASSWORD=<span class="hljs-string">"tutorial"</span>
DB_HOST=<span class="hljs-string">"localhost"</span>
DB_PORT=<span class="hljs-string">"5432"</span>
DB_DATABASE=<span class="hljs-string">"tutorial"</span>
</code></pre>
<p>With the setup of the <code>dbconnect</code> and <code>.env</code> file, we are ready to export our database and tables from our local machine to <code>heroku postgres</code>.</p>
<h3 id="heading-how-to-export-database-and-tables">How to Export Database and Tables</h3>
<p>Go to your <code>pgAdmin</code> and locate the database for this tutorial. Mine is tutorial.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/m454dij54j7dghsxqa25.JPG" alt="Alt Text" width="1366" height="693" loading="lazy"></p>
<p>Right-Click on it and select <code>Backup</code>. This will bring up a new window.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/u57h6kpw5gtbhublc1la.JPG" alt="Alt Text" width="1366" height="695" loading="lazy"></p>
<p>Enter a name for the SQL file like I did. Select the <code>plain</code> format. Then click Backup. This will save the file to your documents folder.</p>
<p>Locate the file and move it into the project directory. It can be anywhere in the directory but I choose to move mine into the <code>services</code> directory because it holds the database related files.</p>
<p>Back in the terminal, navigate to the folder containing the SQL file and run the following code to add the tables we just exported to the <code>heroku postgres</code> database:</p>
<pre><code class="lang-html">cat <span class="hljs-tag">&lt;<span class="hljs-name">your-SQL-file</span>&gt;</span> | <span class="hljs-tag">&lt;<span class="hljs-name">heroku-CLI-from-the-heroku-posgres-credentials</span>&gt;</span>
</code></pre>
<p>This is what mine looks like:</p>
<pre><code class="lang-javascript">cat tutorial.sql | heroku pg:psql postgresql-slippery<span class="hljs-number">-19135</span> --app node-postgres-cloudinary
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/67x0f9521g5savkrafpc.JPG" alt="Alt Text" width="738" height="677" loading="lazy"></p>
<p>Did you notice that I changed directory to services (<code>cd services</code>)? That is where my <code>sql</code> file is located.</p>
<p>Wow! We have just successfully exported our database and tables to Heroku.</p>
<p>It is almost over...</p>
<h3 id="heading-how-to-tell-github-that-we-made-changes">How to Tell GitHub that We Made Changes</h3>
<p>Add the files we have made changes to:</p>
<pre><code class="lang-javascript">$ git add .
</code></pre>
<p>The period (<code>.</code>) adds all files.</p>
<p>Commit your latest changes:</p>
<pre><code class="lang-javascript">$ git commit -m <span class="hljs-string">"refactored the dbConnect and .env file to fit in to heroku; Added the database SQL file"</span>
</code></pre>
<p>Push the committed files:</p>
<pre><code class="lang-javascript">$ git push origin -u master
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/u8kogsya1gt3uxnujz35.JPG" alt="Alt Text" width="1318" height="577" loading="lazy"></p>
<h3 id="heading-finally-deploying-our-app">Finally deploying our App</h3>
<p>Go to you app's dashboard:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/4j4bi64n0f0bp65j7pb7.JPG" alt="Alt Text" width="1366" height="171" loading="lazy"></p>
<p>Select the GitHub Deployment method:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/5edwt90cpy75b3uyqgvy.JPG" alt="Alt Text" width="1319" height="143" loading="lazy"></p>
<p>Search and select a repo, and click on <code>connect</code>:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/ifg4zh7jzab3edpaxvnf.JPG" alt="Alt Text" width="1279" height="222" loading="lazy"></p>
<p>Select the branch you want to deploy (in my own case, it is the <code>master</code> branch):</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/4ka9c6781vft1gio7p0k.JPG" alt="Alt Text" width="1307" height="316" loading="lazy"></p>
<p>Enable automatic deployment by clicking the <code>Enable automatic deployment</code> button as in the image above.</p>
<p>Click on the <code>Deploy</code> button in the manual deploy</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/7ksrclnfyjzzvn2nr4gc.JPG" alt="Alt Text" width="1251" height="200" loading="lazy"></p>
<p>We will not have to do all this for subsequent deployments.</p>
<p>Now you have a button telling you to "view site" after build is completed. Click it. This will open your app in a new tab.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/9xi5vx6lx99z49uscre6.JPG" alt="Alt Text" width="1366" height="692" loading="lazy"></p>
<p><strong>Oh no! A bug? Application error??</strong></p>
<p>Don't worry, it just a small issue. Something you should never forget to do while making deployments. Most hosting service will require it.</p>
<h3 id="heading-how-to-fix-the-heroku-application-error">How to Fix the Heroku Application Error</h3>
<p>Get back to the root directory of your project.</p>
<p>Create a file and name it <code>Procfile</code> (it has no extension).</p>
<p>In the file, enter the following code:</p>
<pre><code class="lang-javascript">web: node index.js
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/k3jb0rof1g6bs1zs2eh6.JPG" alt="Alt Text" width="603" height="708" loading="lazy"></p>
<p>This directs Heroku to the server file (<code>index.js</code>) which is the entry point of the application. If your server is in a different file, please modify as required.</p>
<p>Save the file and push the new changes to GitHub.</p>
<p>Wait 2 to 5 minutes for Heroku to automatically detect changes in your GitHub repo and render the changes on the app.</p>
<p>You can now refresh that error page and see your hard work paying off:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/n4t9yp4598wc5i7v8p82.JPG" alt="Alt Text" width="1366" height="694" loading="lazy"></p>
<p>You can also test the <code>retrieve image</code> route and see it working.</p>
<p>Congratulations! What a feat you have attained.</p>
<p>Other routes <strong>(persist-image, update-image, and delete-image)</strong> will not be working because we have not provisioned or added <code>cloudinary</code> add-on. It is as simple as the <code>PostgreSQL</code> one we just did. So you can give it a shot.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>We started this tutorial with the aim of learning how to build a backend application using Express, Postgres, Cloudinary, Github and Heroku. </p>
<p>We learned how to store, retrieve, delete, and update an image record. We then organised our code with Express Routing, pushed it to GitHub, and deployed it on Heroku. That was a lot.</p>
<p>I hope you will agree that it was worth it because we learnt a lot. You should try adding the Cloudinary add-on yourself to sharpen your knowledge even more.</p>
<p>Thanks for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
