<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Ameh Mathias Ejeh</title>
    <description>The latest articles on Forem by Ameh Mathias Ejeh (@ameh_mathias).</description>
    <link>https://forem.com/ameh_mathias</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2496485%2Ff6c03eca-4783-4a72-9b77-bfbb911145c1.JPG</url>
      <title>Forem: Ameh Mathias Ejeh</title>
      <link>https://forem.com/ameh_mathias</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ameh_mathias"/>
    <language>en</language>
    <item>
      <title>School Library System API</title>
      <dc:creator>Ameh Mathias Ejeh</dc:creator>
      <pubDate>Sun, 29 Mar 2026 16:45:39 +0000</pubDate>
      <link>https://forem.com/ameh_mathias/school-library-system-api-4p6e</link>
      <guid>https://forem.com/ameh_mathias/school-library-system-api-4p6e</guid>
      <description>&lt;p&gt;A RESTful API for managing a school library — authors, books, students, library attendants, and book borrowing/returns. Built with &lt;strong&gt;Node.js (ESM)&lt;/strong&gt;, &lt;strong&gt;Express.js&lt;/strong&gt;, and &lt;strong&gt;MongoDB/Mongoose&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Features&lt;/li&gt;
&lt;li&gt;Tech Stack&lt;/li&gt;
&lt;li&gt;Project Structure&lt;/li&gt;
&lt;li&gt;Setup &amp;amp; Installation&lt;/li&gt;
&lt;li&gt;Environment Variables&lt;/li&gt;
&lt;li&gt;
API Documentation

&lt;ul&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Authors&lt;/li&gt;
&lt;li&gt;Books&lt;/li&gt;
&lt;li&gt;Students&lt;/li&gt;
&lt;li&gt;Attendants&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Postman Collection&lt;/li&gt;

&lt;li&gt;Business Rules&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt; Full CRUD for Authors, Books, Students, and Library Attendants&lt;/li&gt;
&lt;li&gt; Book borrowing and returning with full relationship tracking&lt;/li&gt;
&lt;li&gt; Auto-population of authors, student, and attendant when a book is OUT&lt;/li&gt;
&lt;li&gt; Overdue detection (&lt;code&gt;isOverdue&lt;/code&gt; flag on responses)&lt;/li&gt;
&lt;li&gt; Pagination on all list endpoints&lt;/li&gt;
&lt;li&gt; Search by title or author name&lt;/li&gt;
&lt;li&gt; Duplicate ISBN prevention (unique index + 409 error)&lt;/li&gt;
&lt;li&gt; Request validation with &lt;code&gt;express-validator&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; JWT authentication (attendants log in and receive a token)&lt;/li&gt;
&lt;li&gt; Global error handler with meaningful messages&lt;/li&gt;
&lt;li&gt; ESM imports throughout (no CommonJS)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;Node.js (ESM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;Express.js&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;MongoDB via Mongoose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;JSON Web Tokens (jsonwebtoken)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Passwords&lt;/td&gt;
&lt;td&gt;bcryptjs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validation&lt;/td&gt;
&lt;td&gt;express-validator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Config&lt;/td&gt;
&lt;td&gt;dotenv&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;library-system/
├── config/
│   └── db.js                  # MongoDB connection
├── controllers/
│   ├── authorController.js
│   ├── bookController.js
│   ├── studentController.js
│   └── attendantController.js
├── middleware/
│   ├── auth.js                # JWT protect middleware
│   ├── errorHandler.js        # Global error handler
│   └── validators.js          # express-validator rules
├── models/
│   ├── Author.js
│   ├── Book.js
│   ├── Student.js
│   └── LibraryAttendant.js
├── routes/
│   ├── authorRoutes.js
│   ├── bookRoutes.js
│   ├── studentRoutes.js
│   └── attendantRoutes.js
├── .env.example
├── package.json
├── app.js
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Setup &amp;amp; Installation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;li&gt;MongoDB running locally or a MongoDB Atlas URI&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Steps
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Clone the repository&lt;/span&gt;
git clone &amp;lt;repo-url&amp;gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;school-library-system

&lt;span class="c"&gt;# 2. Install dependencies&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# 3. Configure environment&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;span class="c"&gt;# Edit .env with your values&lt;/span&gt;

&lt;span class="c"&gt;# 4. Start the server&lt;/span&gt;
npm start          &lt;span class="c"&gt;# production&lt;/span&gt;
npm run dev        &lt;span class="c"&gt;# development (auto-restarts on file changes)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API will be available at: &lt;code&gt;http://localhost:5000&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Health check: &lt;code&gt;GET http://localhost:5000/health&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Environment Variables
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PORT=5000
MONGO_URI=mongodb://localhost:27017/library-system
JWT_SECRET=your_super_secret_jwt_key_change_in_production
JWT_EXPIRES_IN=7d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PORT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Port the server listens on&lt;/td&gt;
&lt;td&gt;&lt;code&gt;5000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MONGO_URI&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MongoDB connection string&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mongodb://localhost:27017/library-system&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;JWT_SECRET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Secret for signing JWT tokens&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(required)&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;JWT_EXPIRES_IN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Token expiry duration&lt;/td&gt;
&lt;td&gt;&lt;code&gt;7d&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  API Documentation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Base URL:&lt;/strong&gt; &lt;code&gt;http://localhost:5000/api/v1&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Response Format
&lt;/h3&gt;

&lt;p&gt;All responses follow a consistent envelope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pagination"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"pages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Human-readable error message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Invalid email"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pagination &amp;amp; Filtering (all GET list endpoints)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query Param&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;Page number (default: 1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;limit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;Results per page (default: 10)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;search&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;Search term (varies by resource)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  Authentication
&lt;/h3&gt;

&lt;p&gt;JWT is required for all write operations and sensitive reads. Login as an attendant to get a token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Protected routes&lt;/strong&gt; require the header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Authorization: Bearer &amp;lt;token&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  POST &lt;code&gt;/attendants/login&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Login and receive a JWT token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request Body:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ameh@library.edu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"secret123"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"664abc..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ameh Mathias"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"staffId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"STF001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ameh@library.edu"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Authors
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Auth&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/authors&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;List all authors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/authors/:id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Get single author&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/authors&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Create author&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/authors/:id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Update author&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/authors/:id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Delete author&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  GET &lt;code&gt;/authors&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Query params: &lt;code&gt;page&lt;/code&gt;, &lt;code&gt;limit&lt;/code&gt;, &lt;code&gt;search&lt;/code&gt; (matches name).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"664abc123..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chinua Achebe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"bio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Nigerian novelist and poet."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"createdAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-06-01T10:00:00.000Z"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pagination"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"pages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  POST &lt;code&gt;/authors&lt;/code&gt; 🔒
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chinua Achebe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Nigerian novelist and poet."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  PUT &lt;code&gt;/authors/:id&lt;/code&gt; 🔒
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Updated biography text."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Books
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Auth&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/books&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;List books (paginated)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/books/:id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Get single book&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/books&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Create book&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/books/:id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Update book&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/books/:id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Delete book (must be IN)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/books/:id/borrow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Borrow a book&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/books/:id/return&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Return a book&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  GET &lt;code&gt;/books&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Query params: &lt;code&gt;page&lt;/code&gt;, &lt;code&gt;limit&lt;/code&gt;, &lt;code&gt;search&lt;/code&gt; (title), &lt;code&gt;author&lt;/code&gt; (author name), &lt;code&gt;status&lt;/code&gt; (&lt;code&gt;IN&lt;/code&gt; or &lt;code&gt;OUT&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Response when status = OUT:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"664abc..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Things Fall Apart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"isbn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"978-0-385-47454-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OUT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"isOverdue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"returnDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-07-01T00:00:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"authors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chinua Achebe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"bio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"borrowedBy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Emeka Obi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"studentId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"STU001"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"issuedBy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jane Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"staffId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"STF001"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  POST &lt;code&gt;/books&lt;/code&gt; 🔒
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Things Fall Apart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"isbn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"978-0-385-47454-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"authors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"664abc123..."&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  POST &lt;code&gt;/books/:id/borrow&lt;/code&gt; 🔒
&lt;/h4&gt;

&lt;p&gt;Book must have &lt;code&gt;status: "IN"&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"studentId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"664def456..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attendantId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"664ghi789..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"returnDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-07-15T00:00:00.000Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Book borrowed successfully."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Things Fall Apart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OUT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"returnDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-07-15T00:00:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"borrowedBy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Emeka Obi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"studentId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"STU001"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"issuedBy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jane Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"staffId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"STF001"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  POST &lt;code&gt;/books/:id/return&lt;/code&gt; 🔒
&lt;/h4&gt;

&lt;p&gt;Book must have &lt;code&gt;status: "OUT"&lt;/code&gt;. No request body needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Book returned successfully."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Things Fall Apart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"borrowedBy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"issuedBy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"returnDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Students
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Auth&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/students&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;List all students&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/students/:id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Get single student (includes borrowed books)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/students&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Create student&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/students/:id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Update student&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/students/:id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Delete student (cannot have active loans)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  POST &lt;code&gt;/students&lt;/code&gt; 🔒
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Emeka Obi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"emeka@school.edu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"studentId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"STU001"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  GET &lt;code&gt;/students/:id&lt;/code&gt; 🔒
&lt;/h4&gt;

&lt;p&gt;Includes currently borrowed books with overdue status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Emeka Obi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"emeka@school.edu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"studentId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"STU001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"borrowedBooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Things Fall Apart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"isbn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"978-0-385-47454-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"returnDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-06-30T00:00:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"isOverdue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Attendants
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Auth&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/attendants/login&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Login and get token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/attendants&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;List all attendants&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/attendants/:id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Get single attendant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/attendants&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Create attendant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/attendants/:id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Update attendant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/attendants/:id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Delete attendant&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  POST &lt;code&gt;/attendants&lt;/code&gt; 🔒
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"James Godwin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"staffId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"STF001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jame@library.edu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"secret123"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Business Rules
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Borrow&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Book &lt;code&gt;status&lt;/code&gt; must be &lt;code&gt;"IN"&lt;/code&gt;. Sets &lt;code&gt;status → "OUT"&lt;/code&gt;, &lt;code&gt;borrowedBy&lt;/code&gt;, &lt;code&gt;issuedBy&lt;/code&gt;, &lt;code&gt;returnDate&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Return&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Book &lt;code&gt;status&lt;/code&gt; must be &lt;code&gt;"OUT"&lt;/code&gt;. Clears &lt;code&gt;status → "IN"&lt;/code&gt;, nullifies &lt;code&gt;borrowedBy&lt;/code&gt;, &lt;code&gt;issuedBy&lt;/code&gt;, &lt;code&gt;returnDate&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Delete Book&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Blocked if book is currently &lt;code&gt;"OUT"&lt;/code&gt; (borrowed).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Delete Student&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Blocked if student has active borrowed books.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Duplicate ISBN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Returns &lt;code&gt;409 Conflict&lt;/code&gt; if a book with the same ISBN already exists.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Overdue&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Any book where &lt;code&gt;status = "OUT"&lt;/code&gt; and &lt;code&gt;returnDate &amp;lt; now&lt;/code&gt; will have &lt;code&gt;isOverdue: true&lt;/code&gt; in the response.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Populate on OUT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;When a book is &lt;code&gt;"OUT"&lt;/code&gt;, &lt;code&gt;authors&lt;/code&gt;, &lt;code&gt;borrowedBy&lt;/code&gt;, and &lt;code&gt;issuedBy&lt;/code&gt; are always populated in responses.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auth&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;All write operations and student/attendant reads require a valid JWT (&lt;code&gt;Authorization: Bearer &amp;lt;token&amp;gt;&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Error Reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;Bad request / business rule violated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;401&lt;/td&gt;
&lt;td&gt;Missing or invalid JWT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;td&gt;Resource not found&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;409&lt;/td&gt;
&lt;td&gt;Duplicate key (e.g., ISBN, email)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;422&lt;/td&gt;
&lt;td&gt;Validation error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;Internal server error&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;GitHub Repo: &lt;a href="https://github.com/ameh0429/school-library-system.git" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Onebody API Documentation</title>
      <dc:creator>Ameh Mathias Ejeh</dc:creator>
      <pubDate>Thu, 27 Nov 2025 12:15:51 +0000</pubDate>
      <link>https://forem.com/ameh_mathias/onebody-api-documentation-2fm2</link>
      <guid>https://forem.com/ameh_mathias/onebody-api-documentation-2fm2</guid>
      <description>&lt;h2&gt;
  
  
  Base URL: &lt;code&gt;http://localhost:5000&lt;/code&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Register
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;POST &lt;code&gt;/api/auth/register&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Login
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;POST &lt;code&gt;/api/auth/login&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Create Artiste (Protected)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;POST &lt;code&gt;/api/artistes&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Response
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "success": true,
    "message": "Artiste created successfully",
    "data": {
        "artiste": {
            "id": 4,
            "name": "Samuel Adedayo",
            "desc": "A marvelous gospel singer",
            "image": "uploads\\artistes\\image-1764242219273-893646722.png",
            "updatedAt": "2025-11-27T11:17:00.716Z",
            "createdAt": "2025-11-27T11:17:00.716Z"
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Get all Artistes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GET &lt;code&gt;/api/artistes&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Response
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "success": true,
    "count": 4,
    "data": {
        "artistes": [
            {
                "id": 4,
                "name": "Samuel Adedayo",
                "image": "uploads\\artistes\\image-1764242219273-893646722.png",
                "desc": "A marvelous gospel singer",
                "createdAt": "2025-11-27T11:17:00.716Z",
                "updatedAt": "2025-11-27T11:17:00.716Z"
            },
            {
                "id": 3,
                "name": "Tito Pelumi",
                "image": "uploads\\artistes\\image-1764152676621-764537291.png",
                "desc": "A marvelous gospel singer",
                "createdAt": "2025-11-26T10:24:37.237Z",
                "updatedAt": "2025-11-26T10:24:37.237Z"
            },
            {
                "id": 2,
                "name": "Emmanuel Ajose",
                "image": "uploads\\artistes\\image-1764110414208-444407115.png",
                "desc": "A renowned gospel artiste ",
                "createdAt": "2025-11-25T22:40:14.275Z",
                "updatedAt": "2025-11-25T22:44:08.193Z"
            },
            {
                "id": 1,
                "name": "John Kenny",
                "image": "uploads\\artistes\\image-1764110357952-755066802.png",
                "desc": "A marvelous gospel singer",
                "createdAt": "2025-11-25T22:39:18.155Z",
                "updatedAt": "2025-11-25T22:39:18.155Z"
            }
        ]
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Get Artiste by id
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GET &lt;code&gt;/api/artistes/:id&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Response
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "success": true,
    "data": {
        "artiste": {
            "id": 2,
            "name": "Emmanuel Ajose",
            "image": "uploads\\artistes\\image-1764110414208-444407115.png",
            "desc": "A renowned gospel artiste ",
            "createdAt": "2025-11-25T22:40:14.275Z",
            "updatedAt": "2025-11-25T22:44:08.193Z",
            "albums": [
                {
                    "id": 2,
                    "name": "Here I Am",
                    "artisteId": 2,
                    "releaseYear": 2022,
                    "image": "uploads\\albums\\image-1764111099366-720471625.jpg",
                    "createdAt": "2025-11-25T22:51:39.424Z",
                    "updatedAt": "2025-11-25T22:51:39.424Z"
                }
            ],
            "songs": [
                {
                    "id": 2,
                    "name": "Orin Davadi",
                    "artisteId": 2,
                    "albumId": 1,
                    "lyrics": "Morning light is here\nDrowning out all my fears of the night\nOh delight in me\nLiving word show forth yourself \nAnd eternity is singing\nLike the waves breaking the shores of my heart\nRolling on and on\nNever stopping I’m in awe\n\nOh my heart is pleading Savior come\nSavior won’t you come and help me\nI am tired your mercy all that I need\nYes my heart is singing Savior help \nSavior won’t you help\nIn this life your strength is all that I need\nYour strength is all I need\nSavior come\nOh oh oh\n\nAs this drizzle falls softening testing the state of my heart\nOh your word is rain pouring down farmers are glad\nYet eternity is screaming buckets outs \nsoak it all up while it lasts\nLet my heart be sure even as the seasons return \n\nOh my heart is pleading Savior come\nSavior won’t you come oh\nI am tired your mercy all that I need\nYes my heart is singing Savior help \nSavior won’t you help\nIn this life your strength is all that I need\nYeah\n\nSavior wont you come\nYou strength is all that I need\nOh oh oh\nSavior won’t you come\nHelp me now help me now\nOh oh oh \n\n\nHere’s my offering before I give I know you loved\nmercy more\nLet your fire fall I will give it all all the same hmm\nStill eternity is dancing tambourines and cymbals of\ntime never stop oh\nFor my faith is strong \nI’ll be standing when you return\n \nOh my heart is pleading Savior come\nSavior won’t you come oh\nI am tired your mercy all that I need\nOh my heart is singing help \nSavior help me\nIn this life your strength is all that I need\nAll that I need yeah\nOh oh oh\nI need your help\nI need your help savior\nYou are all that I need\nOh oh\n\nMorning light is here\nDrowning out all my fears of the night\nOh delight in me\nLiving word show forth yourself \n",
                    "image": null,
                    "createdAt": "2025-11-25T23:05:06.873Z",
                    "updatedAt": "2025-11-25T23:05:06.873Z"
                }
            ]
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create Album (Protected)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;POST &lt;code&gt;/api/albums&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Response
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "success": true,
    "message": "Album created successfully",
    "data": {
        "album": {
            "id": 3,
            "name": "Mercy",
            "artisteId": 4,
            "releaseYear": 2025,
            "image": "uploads\\albums\\image-1764243719856-4963630.jpg",
            "updatedAt": "2025-11-27T11:42:00.414Z",
            "createdAt": "2025-11-27T11:42:00.414Z"
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Get all Album
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GET &lt;code&gt;/api/albums&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Response
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "success": true,
    "currentPage": 1,
    "totalPages": 1,
    "totalItems": 3,
    "data": {
        "albums": [
            {
                "id": 3,
                "name": "Mercy",
                "artisteId": 4,
                "releaseYear": 2025,
                "image": "uploads\\albums\\image-1764243719856-4963630.jpg",
                "createdAt": "2025-11-27T11:42:00.414Z",
                "updatedAt": "2025-11-27T11:42:00.414Z",
                "artiste": {
                    "id": 4,
                    "name": "Samuel Adedayo",
                    "image": "uploads\\artistes\\image-1764242219273-893646722.png"
                }
            },
            {
                "id": 1,
                "name": "Glorious God",
                "artisteId": 1,
                "releaseYear": 2024,
                "image": "uploads\\albums\\image-1764111430083-847458251.jpg",
                "createdAt": "2025-11-25T22:50:00.036Z",
                "updatedAt": "2025-11-25T22:57:10.117Z",
                "artiste": {
                    "id": 1,
                    "name": "John Kenny",
                    "image": "uploads\\artistes\\image-1764110357952-755066802.png"
                }
            },
            {
                "id": 2,
                "name": "Here I Am",
                "artisteId": 2,
                "releaseYear": 2022,
                "image": "uploads\\albums\\image-1764111099366-720471625.jpg",
                "createdAt": "2025-11-25T22:51:39.424Z",
                "updatedAt": "2025-11-25T22:51:39.424Z",
                "artiste": {
                    "id": 2,
                    "name": "Emmanuel Ajose",
                    "image": "uploads\\artistes\\image-1764110414208-444407115.png"
                }
            }
        ]
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Get Album by id
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GET &lt;code&gt;/api/albums/:id&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Response
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "success": true,
    "data": {
        "album": {
            "id": 2,
            "name": "Here I Am",
            "artisteId": 2,
            "releaseYear": 2022,
            "image": "uploads\\albums\\image-1764111099366-720471625.jpg",
            "createdAt": "2025-11-25T22:51:39.424Z",
            "updatedAt": "2025-11-25T22:51:39.424Z",
            "artiste": {
                "id": 2,
                "name": "Emmanuel Ajose",
                "image": "uploads\\artistes\\image-1764110414208-444407115.png",
                "desc": "A renowned gospel artiste "
            },
            "songs": [
                {
                    "id": 1,
                    "name": "Morning Light"
                },
                {
                    "id": 8,
                    "name": "Calling on my Saviour"
                }
            ]
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create Song (Protected)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;POST &lt;code&gt;/api/songs&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Response
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "success": true,
    "message": "Song created successfully",
    "data": {
        "song": {
            "id": 9,
            "name": "In HIs Presence",
            "artisteId": 1,
            "albumId": 3,
            "lyrics": "Morning light is here\nDrowning out all my fears of the night\nOh delight in me\nLiving word show forth yourself \nAnd eternity is singing\nLike the waves breaking the shores of my heart\nRolling on and on\nNever stopping I’m in awe\n\nOh my heart is pleading Savior come\nSavior won’t you come and help me\nI am tired your mercy all that I need\nYes my heart is singing Savior help \nSavior won’t you help\nIn this life your strength is all that I need\nYour strength is all I need\nSavior come\nOh oh oh\n\nAs this drizzle falls softening testing the state of my heart\nOh your word is rain pouring down farmers are glad\nYet eternity is screaming buckets outs \nsoak it all up while it lasts\nLet my heart be sure even as the seasons return \n\nOh my heart is pleading Savior come\nSavior won’t you come oh\nI am tired your mercy all that I need\nYes my heart is singing Savior help \nSavior won’t you help\nIn this life your strength is all that I need\nYeah\n\nSavior wont you come\nYou strength is all that I need\nOh oh oh\nSavior won’t you come\nHelp me now help me now\nOh oh oh \n\n\nHere’s my offering before I give I know you loved\nmercy more\nLet your fire fall I will give it all all the same hmm\nStill eternity is dancing tambourines and cymbals of\ntime never stop oh\nFor my faith is strong \nI’ll be standing when you return\n \nOh my heart is pleading Savior come\nSavior won’t you come oh\nI am tired your mercy all that I need\nOh my heart is singing help \nSavior help me\nIn this life your strength is all that I need\nAll that I need yeah\nOh oh oh\nI need your help\nI need your help savior\nYou are all that I need\nOh oh\n\nMorning light is here\nDrowning out all my fears of the night\nOh delight in me\nLiving word show forth yourself \n",
            "image": "uploads\\songs\\image-1764244937159-222340483.jpg",
            "createdAt": "2025-11-27T12:02:17.532Z",
            "updatedAt": "2025-11-27T12:02:17.532Z",
            "mainArtiste": {
                "id": 1,
                "name": "John Kenny",
                "image": "uploads\\artistes\\image-1764110357952-755066802.png"
            },
            "album": {
                "id": 3,
                "name": "Mercy",
                "image": "uploads\\albums\\image-1764243719856-4963630.jpg"
            },
            "featuredArtistes": [
                {
                    "id": 2,
                    "name": "Emmanuel Ajose",
                    "image": "uploads\\artistes\\image-1764110414208-444407115.png"
                },
                {
                    "id": 3,
                    "name": "Tito Pelumi",
                    "image": "uploads\\artistes\\image-1764152676621-764537291.png"
                }
            ]
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Get all Songs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GET &lt;code&gt;/api/songs&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Response
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "success": true,
    "currentPage": 1,
    "totalPages": 1,
    "totalItems": 4,
    "data": {
        "songs": [
            {
                "id": 9,
                "name": "In HIs Presence",
                "image": "uploads\\songs\\image-1764244937159-222340483.jpg",
                "createdAt": "2025-11-27T12:02:17.532Z"
            },
            {
                "id": 8,
                "name": "Calling on my Saviour",
                "image": null,
                "createdAt": "2025-11-27T10:48:44.576Z"
            },
            {
                "id": 2,
                "name": "Orin Davadi",
                "image": null,
                "createdAt": "2025-11-25T23:05:06.873Z"
            },
            {
                "id": 1,
                "name": "Morning Light",
                "image": null,
                "createdAt": "2025-11-25T23:03:20.744Z"
            }
        ]
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Get Song by id
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GET &lt;code&gt;/api/songs/:id&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Response
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "success": true,
    "data": {
        "song": {
            "id": 8,
            "name": "Calling on my Saviour",
            "artisteId": 3,
            "albumId": 2,
            "lyrics": "Morning light is here\nDrowning out all my fears of the night\nOh delight in me\nLiving word show forth yourself \nAnd eternity is singing\nLike the waves breaking the shores of my heart\nRolling on and on\nNever stopping I’m in awe\n\nOh my heart is pleading Savior come\nSavior won’t you come and help me\nI am tired your mercy all that I need\nYes my heart is singing Savior help \nSavior won’t you help\nIn this life your strength is all that I need\nYour strength is all I need\nSavior come\nOh oh oh\n\nAs this drizzle falls softening testing the state of my heart\nOh your word is rain pouring down farmers are glad\nYet eternity is screaming buckets outs \nsoak it all up while it lasts\nLet my heart be sure even as the seasons return \n\nOh my heart is pleading Savior come\nSavior won’t you come oh\nI am tired your mercy all that I need\nYes my heart is singing Savior help \nSavior won’t you help\nIn this life your strength is all that I need\nYeah\n\nSavior wont you come\nYou strength is all that I need\nOh oh oh\nSavior won’t you come\nHelp me now help me now\nOh oh oh \n\n\nHere’s my offering before I give I know you loved\nmercy more\nLet your fire fall I will give it all all the same hmm\nStill eternity is dancing tambourines and cymbals of\ntime never stop oh\nFor my faith is strong \nI’ll be standing when you return\n \nOh my heart is pleading Savior come\nSavior won’t you come oh\nI am tired your mercy all that I need\nOh my heart is singing help \nSavior help me\nIn this life your strength is all that I need\nAll that I need yeah\nOh oh oh\nI need your help\nI need your help savior\nYou are all that I need\nOh oh\n\nMorning light is here\nDrowning out all my fears of the night\nOh delight in me\nLiving word show forth yourself \n",
            "image": null,
            "createdAt": "2025-11-27T10:48:44.576Z",
            "updatedAt": "2025-11-27T10:48:44.576Z",
            "mainArtiste": {
                "id": 3,
                "name": "Tito Pelumi"
            },
            "album": {
                "id": 2,
                "name": "Here I Am"
            },
            "featuredArtistes": [
                {
                    "id": 1,
                    "name": "John Kenny"
                }
            ]
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Country Data API</title>
      <dc:creator>Ameh Mathias Ejeh</dc:creator>
      <pubDate>Sun, 26 Oct 2025 19:55:55 +0000</pubDate>
      <link>https://forem.com/ameh_mathias/country-data-api-2o2l</link>
      <guid>https://forem.com/ameh_mathias/country-data-api-2o2l</guid>
      <description>&lt;p&gt;A RESTful API that fetches country data from external APIs, stores it in a MySQL database, and provides CRUD operations with caching support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Fetch country data from REST Countries API&lt;/li&gt;
&lt;li&gt;Fetch exchange rates from Open Exchange Rates API&lt;/li&gt;
&lt;li&gt;Calculate estimated GDP for each country&lt;/li&gt;
&lt;li&gt;Cache data in MySQL database&lt;/li&gt;
&lt;li&gt;Filter and sort countries by region, currency, and GDP&lt;/li&gt;
&lt;li&gt;Generate summary images with top countries&lt;/li&gt;
&lt;li&gt;Full CRUD operations&lt;/li&gt;
&lt;li&gt;Comprehensive error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Clone the repository&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ameh0429/country-api.git
&lt;span class="nb"&gt;cd &lt;/span&gt;country-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Install dependencies&lt;/strong&gt;
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Set up environment variables&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file in the root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PORT=3000
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=yourpassword
DB_NAME=countries_db
DB_PORT=3306
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Create the MySQL database&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;countries_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Start the server&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For development with auto-reload:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The server will automatically create the necessary tables on startup.&lt;/p&gt;

&lt;h2&gt;
  
  
  API Endpoints
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Refresh Country Data
&lt;/h3&gt;

&lt;p&gt;Fetch fresh data from external APIs and update the database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /api/countries/refresh
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Countries data refreshed successfully"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total_countries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"last_refreshed_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-10-22T18:00:00.000Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Get All Countries
&lt;/h3&gt;

&lt;p&gt;Retrieve all countries with optional filters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/countries
GET /api/countries?region=Africa
GET /api/countries?currency=NGN
GET /api/countries?sort=gdp_desc
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Query Parameters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;region&lt;/code&gt; - Filter by region (e.g., Africa, Europe, Asia)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;currency&lt;/code&gt; - Filter by currency code (e.g., NGN, USD, EUR)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sort&lt;/code&gt; - Sort results:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;gdp_desc&lt;/code&gt; - Sort by GDP descending&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gdp_asc&lt;/code&gt; - Sort by GDP ascending&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;population_desc&lt;/code&gt; - Sort by population descending&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;population_asc&lt;/code&gt; - Sort by population ascending&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Nigeria"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"capital"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Abuja"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Africa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"population"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;206139589&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"currency_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NGN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"exchange_rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1600.23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"estimated_gdp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;25767448125.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"flag_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://flagcdn.com/ng.svg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"last_refreshed_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-10-22T18:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Get Single Country
&lt;/h3&gt;

&lt;p&gt;Retrieve a specific country by name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/countries/Nigeria
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Nigeria"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"capital"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Abuja"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Africa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"population"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;206139589&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NGN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exchange_rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1600.23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"estimated_gdp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;25767448125.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"flag_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://flagcdn.com/ng.svg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"last_refreshed_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-10-22T18:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Delete Country
&lt;/h3&gt;

&lt;p&gt;Delete a country record from the database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;DELETE /api/countries/Nigeria
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Country deleted successfully"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Get Status
&lt;/h3&gt;

&lt;p&gt;Get API statistics and last refresh timestamp.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/status
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total_countries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"last_refreshed_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-10-22T18:00:00.000Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Get Summary Image
&lt;/h3&gt;

&lt;p&gt;Retrieve the generated summary image showing top countries by GDP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/countries/image
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt; PNG image file&lt;/p&gt;

&lt;h2&gt;
  
  
  Error Responses
&lt;/h2&gt;

&lt;h3&gt;
  
  
  404 Not Found
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Country not found"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  400 Bad Request
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Validation failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"currency_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"is required"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  503 Service Unavailable
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"External data source unavailable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Could not fetch data from REST Countries API"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  500 Internal Server Error
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Data Schema
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Country Fields
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;id&lt;/td&gt;
&lt;td&gt;INT&lt;/td&gt;
&lt;td&gt;Auto&lt;/td&gt;
&lt;td&gt;Primary key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;name&lt;/td&gt;
&lt;td&gt;VARCHAR(255)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Country name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;capital&lt;/td&gt;
&lt;td&gt;VARCHAR(255)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Capital city&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;region&lt;/td&gt;
&lt;td&gt;VARCHAR(100)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Geographic region&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;population&lt;/td&gt;
&lt;td&gt;BIGINT&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Population count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;currency_code&lt;/td&gt;
&lt;td&gt;VARCHAR(10)&lt;/td&gt;
&lt;td&gt;Yes*&lt;/td&gt;
&lt;td&gt;Currency code (e.g., NGN)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;exchange_rate&lt;/td&gt;
&lt;td&gt;DECIMAL(20,6)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Exchange rate to USD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;estimated_gdp&lt;/td&gt;
&lt;td&gt;DECIMAL(30,2)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Calculated GDP estimate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flag_url&lt;/td&gt;
&lt;td&gt;TEXT&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;URL to country flag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;last_refreshed_at&lt;/td&gt;
&lt;td&gt;TIMESTAMP&lt;/td&gt;
&lt;td&gt;Auto&lt;/td&gt;
&lt;td&gt;Last update timestamp&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;*Note: &lt;code&gt;currency_code&lt;/code&gt; can be null if country has no currency data&lt;/p&gt;

&lt;h3&gt;
  
  
  GDP Calculation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;estimated_gdp = (population × random(1000-2000)) ÷ exchange_rate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A new random multiplier is generated on each refresh for each country.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;country-api/
├── src/
│   ├── config/
│   │   └── database.js          # Database connection and initialization
│   ├── controllers/
│   │   └── countryController.js # Request handlers
│   ├── models/
│   │   └── countryModel.js      # Database queries
│   ├── routes/
│   │   └── countryRoutes.js     # API routes
│   ├── services/
│   │   ├── externalApiService.js # External API calls
│   │   └── imageService.js      # Image generation
│   ├── middleware/
│   │   ├── validation.js        # Input validation
│   │   └── errorHandler.js      # Error handling
│   └── server.js                # Main application entry
├── cache/
│   └── summary.png              # Generated summary image
├── .env                         # Environment variables
├── .env.example                 # Environment template
├── package.json                 # Dependencies
└── README.md                    # Documentation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  External APIs Used
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;REST Countries API&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;URL: &lt;code&gt;https://restcountries.com/v2/all?fields=name,capital,region,population,flag,currencies&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Purpose: Fetch country information&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Open Exchange Rates API&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;URL: &lt;code&gt;https://open.er-api.com/v6/latest/USD&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Purpose: Fetch currency exchange rates&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Currency Handling Logic
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Multiple Currencies
&lt;/h3&gt;

&lt;p&gt;If a country has multiple currencies, only the &lt;strong&gt;first currency code&lt;/strong&gt; from the array is stored.&lt;/p&gt;

&lt;h3&gt;
  
  
  No Currencies
&lt;/h3&gt;

&lt;p&gt;If a country has no currencies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;currency_code&lt;/code&gt;: &lt;code&gt;null&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;exchange_rate&lt;/code&gt;: &lt;code&gt;null&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;estimated_gdp&lt;/code&gt;: &lt;code&gt;0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Country is still stored in database&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Currency Not Found in Exchange API
&lt;/h3&gt;

&lt;p&gt;If the currency code is not found in exchange rates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;exchange_rate&lt;/code&gt;: &lt;code&gt;null&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;estimated_gdp&lt;/code&gt;: &lt;code&gt;null&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Country is still stored in database&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Environment Variables
&lt;/h3&gt;

&lt;p&gt;All configuration is done via &lt;code&gt;.env&lt;/code&gt; file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PORT&lt;/code&gt;: Server port (default: 3000)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DB_HOST&lt;/code&gt;: country-service-amehmathiasejeh.f.aivencloud.com&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DB_USER&lt;/code&gt;: MySQL username&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DB_PASSWORD&lt;/code&gt;: MySQL password&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DB_NAME&lt;/code&gt;: defaultdb&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DB_PORT&lt;/code&gt;: 10648&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Error Handling
&lt;/h2&gt;

&lt;p&gt;The API includes comprehensive error handling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;External API failures&lt;/strong&gt;: Returns 503 with details&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database errors&lt;/strong&gt;: Returns 500&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation errors&lt;/strong&gt;: Returns 400 with field details&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not found errors&lt;/strong&gt;: Returns 404&lt;/li&gt;
&lt;li&gt;All errors return JSON responses&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Performance Considerations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Database indexing&lt;/strong&gt;: Indexes on &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;region&lt;/code&gt;, and &lt;code&gt;currency_code&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connection pooling&lt;/strong&gt;: MySQL connection pool with 10 connections&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching&lt;/strong&gt;: All country data is cached in database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeout handling&lt;/strong&gt;: 10-second timeout on external API calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;View the &lt;a href="https://github.com/ameh0429/country-api.git" rel="noopener noreferrer"&gt;Github Link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;App is live at &lt;a href="https://country-api-2.onrender.com" rel="noopener noreferrer"&gt;Country API&lt;/a&gt;&lt;/p&gt;

</description>
      <category>express</category>
      <category>mysql</category>
      <category>api</category>
      <category>node</category>
    </item>
    <item>
      <title>String Analysis RESTful API</title>
      <dc:creator>Ameh Mathias Ejeh</dc:creator>
      <pubDate>Sun, 26 Oct 2025 18:45:41 +0000</pubDate>
      <link>https://forem.com/ameh_mathias/string-analysis-restful-api-1fgp</link>
      <guid>https://forem.com/ameh_mathias/string-analysis-restful-api-1fgp</guid>
      <description>&lt;p&gt;A modular Node.js/Express API service that analyzes strings and stores their computed properties.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;String analysis with multiple computed properties&lt;/li&gt;
&lt;li&gt;SHA-256 based unique identification&lt;/li&gt;
&lt;li&gt;RESTful API endpoints&lt;/li&gt;
&lt;li&gt;Query filtering with standard parameters&lt;/li&gt;
&lt;li&gt;Natural language query parsing&lt;/li&gt;
&lt;li&gt;In-memory storage (easily replaceable with database)&lt;/li&gt;
&lt;li&gt;Comprehensive error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── index.js                          # Server entry point
├── app.js                            # Express app configuration
├── controllers/
│   └── stringController.js           # Request handlers
├── services/
│   ├── stringAnalyzer.js             # String analysis logic
│   ├── stringService.js              # Business logic layer
│   └── naturalLanguageParser.js      # NL query parser
├── repositories/
│   └── stringRepository.js           # Data access layer
├── routes/
│   └── stringRoutes.js               # Route definitions
└── middleware/
    ├── validation.js                 # Request validation
    └── errorHandler.js               # Error handling
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



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

&lt;/div&gt;



&lt;p&gt;Running the Server&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Production mode
npm start

// Development mode with auto-reload
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Server runs on &lt;code&gt;http://localhost:3000&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  API Endpoints
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create/Analyze String
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;POST &lt;code&gt;/strings&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "value": "ameh mathias"
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response (201 Created):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5mkzdie0813tsxzggmpp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5mkzdie0813tsxzggmpp.png" alt=" " width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Error Responses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;400 Bad Request&lt;/code&gt; - Invalid request body or missing "value" field&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;422 Unprocessable Entity&lt;/code&gt; - Invalid data type for "value" (must be string)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;409 Conflict&lt;/code&gt; - String already exists&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Get Specific String
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GET /strings/{string_value}&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: &lt;code&gt;GET /strings/ameh%20mathias&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Response (200 OK):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fusd9iiau08sbtj6xciwh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fusd9iiau08sbtj6xciwh.png" alt=" " width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Error Response:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;404 Not Found&lt;/code&gt; - String does not exist&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Get All Strings with Filtering
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GET &lt;code&gt;/strings
&lt;/code&gt;
Query Parameters:&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;is_palindrome&lt;/code&gt; - boolean (true/false)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;min_length&lt;/code&gt; - integer (minimum string length)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;max_length&lt;/code&gt; - integer (maximum string length)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;word_count&lt;/code&gt; - integer (exact word count)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;contains_character&lt;/code&gt; - string (single character)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: &lt;code&gt;GET /strings?is_palindrome=true&amp;amp;min_length=3&amp;amp;word_count=1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Response (200 OK):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5huezgl8av95nh21midx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5huezgl8av95nh21midx.png" alt=" " width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Error Response:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;400 Bad Request&lt;/code&gt; - Invalid query parameter values or types&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Natural Language Filtering
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GET &lt;code&gt;/strings/filter-by-natural-language?query={natural_language_query}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Supported Query Patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"all single word palindromic strings"&lt;/li&gt;
&lt;li&gt;"strings longer than 10 characters"&lt;/li&gt;
&lt;li&gt;"palindromic strings that contain the first vowel"&lt;/li&gt;
&lt;li&gt;"strings containing the letter z"&lt;/li&gt;
&lt;li&gt;"two word strings"&lt;/li&gt;
&lt;li&gt;"strings between 5 and 20 characters"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: &lt;code&gt;GET /strings/filter-by-natural-language?query=all%20single%20word%20palindromic%20strings&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Response (200 OK):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "data": [
        {
            "id": "31db973d2a73423e4a95ad7d5e9eed9d0a8e06c77ce7cb0c6f3b0d35b7f90625",
            "value": "ameh mathias",
            "properties": {
                "length": 12,
                "is_palindrome": false,
                "unique_characters": 8,
                "word_count": 2,
                "sha256_hash": "31db973d2a73423e4a95ad7d5e9eed9d0a8e06c77ce7cb0c6f3b0d35b7f90625",
                "character_frequency_map": {
                    "a": 3,
                    "m": 2,
                    "e": 1,
                    "h": 2,
                    " ": 1,
                    "t": 1,
                    "i": 1,
                    "s": 1
                }
            },
            "created_at": "2025-10-20T13:21:56.057Z"
        }
    ],
    "count": 1,
    "interpreted_query": {
        "original": "{natural_language_query}",
        "parsed_filters": {}
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Error Responses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;400 Bad Request&lt;/code&gt; - Unable to parse natural language query&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;422 Unprocessable Entity&lt;/code&gt; - Query parsed but resulted in conflicting filters&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Delete String
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;DELETE &lt;code&gt;/strings/{string_value}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: DELETE &lt;code&gt;/strings/ameh%20mathias&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Response:&lt;br&gt;
 &lt;code&gt;204 No Content&lt;/code&gt; (empty body)&lt;/p&gt;

&lt;p&gt;Error Response:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;404 Not Found&lt;/code&gt; - String does not exist&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  String Properties
&lt;/h3&gt;

&lt;p&gt;Each analyzed string includes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;length&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;Number of characters in the string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;is_palindrome&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;Whether string reads the same forwards/backwards (case-insensitive)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;unique_characters&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;Count of distinct characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;word_count&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;Number of words separated by whitespace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sha256_hash&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;SHA-256 hash for unique identification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;character_frequency_map&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;object&lt;/td&gt;
&lt;td&gt;Character-to-count mapping&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Testing Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Using Postman
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a string&lt;/span&gt;
POST http://localhost:3000/strings &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"value": "racecar"}'&lt;/span&gt;

&lt;span class="c"&gt;# Get a string&lt;/span&gt;
http://localhost:3000/strings/ameh%20mathias

&lt;span class="c"&gt;# Get palindromes&lt;/span&gt;
&lt;span class="s2"&gt;"http://localhost:3000/strings?is_palindrome=true"&lt;/span&gt;

&lt;span class="c"&gt;# Natural language query&lt;/span&gt;
&lt;span class="s2"&gt;"http://localhost:3000/strings/filter-by-natural-language?query=single%20word%20palindromic%20strings"&lt;/span&gt;

&lt;span class="c"&gt;# Delete a string&lt;/span&gt;
DELETE http://localhost:3000/strings/ameh%20mathias
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common status codes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;400&lt;/code&gt; - Bad Request (invalid input)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;404&lt;/code&gt; - Not Found (resource doesn't exist)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;409&lt;/code&gt; - Conflict (duplicate resource)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;422&lt;/code&gt; - Unprocessable Entity (invalid data type)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;500&lt;/code&gt; - Internal Server Error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;View the &lt;a href="https://github.com/ameh0429/string-analysis-api.git" rel="noopener noreferrer"&gt;Github Link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;App is live at &lt;a href="https://string-analysis-api.onrender.com" rel="noopener noreferrer"&gt;String Analysis API&lt;/a&gt;&lt;/p&gt;

</description>
      <category>express</category>
      <category>api</category>
      <category>node</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Profile API with Cat Facts</title>
      <dc:creator>Ameh Mathias Ejeh</dc:creator>
      <pubDate>Wed, 15 Oct 2025 21:56:30 +0000</pubDate>
      <link>https://forem.com/ameh_mathias/profile-api-with-cat-facts-mg8</link>
      <guid>https://forem.com/ameh_mathias/profile-api-with-cat-facts-mg8</guid>
      <description>&lt;p&gt;A simple RESTful API built with Node.js/Express that returns profile information along with dynamic cat facts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Profile endpoint at &lt;code&gt;/me&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Dynamic cat facts from external API&lt;/li&gt;
&lt;li&gt;UTC timestamps in ISO 8601 format&lt;/li&gt;
&lt;li&gt;Graceful error handling with fallback&lt;/li&gt;
&lt;li&gt;Configurable timeout (5 seconds)&lt;/li&gt;
&lt;li&gt;CORS enabled&lt;/li&gt;
&lt;li&gt;Environment variable configuration&lt;/li&gt;
&lt;li&gt;Request logging&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 18.x or higher&lt;/li&gt;
&lt;li&gt;npm&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project Setup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── app.js            # Main application file
├── package.json      # Project dependencies and scripts
├── .env.example      # Environment variables template
├── .env              # environment variables (git-ignored)
└── README.md         # Documentation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Clone or create the project directory&lt;/li&gt;
&lt;li&gt;Install dependencies
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Configure environment variables
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cp.env.example .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Edit &lt;code&gt;.env&lt;/code&gt; file with your information
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   PORT=8000
   USER_EMAIL=yourname@example.com
   USER_NAME=Your Full Name
   USER_STACK=Node.js/Express
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Start the server
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Production mode:
&lt;/h4&gt;



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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Development mode
&lt;/h4&gt;



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

&lt;/div&gt;



&lt;p&gt;The server will start on &lt;code&gt;http://localhost:8000&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  API Endpoints
&lt;/h2&gt;

&lt;p&gt;GET &lt;code&gt;/me&lt;/code&gt;&lt;br&gt;
Returns profile information with a dynamic cat fact.&lt;br&gt;
Response (Success - 200):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwxgifw8396549j1dizhx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwxgifw8396549j1dizhx.png" alt=" " width="800" height="306"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Response (Error - 503):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "status": "error",
  "user": {
    "email": "your.email@example.com",
    "name": "Your Full Name",
    "stack": "Node.js/Express"
  },
  "timestamp": "2025-10-15T14:30:45.123Z",
  "fact": "Cat fact temporarily unavailable. Did you know cats spend 70% of their lives sleeping?",
  "error": "Unable to fetch cat fact from external API"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GET &lt;code&gt;/health&lt;/code&gt;&lt;br&gt;
Health check endpoint.&lt;br&gt;
Response (200):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxj5ltpm5wbyf1i3zy8q0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxj5ltpm5wbyf1i3zy8q0.png" alt=" " width="800" height="234"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;Test the endpoint using postman:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Test profile endpoint
http://localhost:8000/me

// Test health check
http://localhost:8000/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Error Handling
&lt;/h2&gt;

&lt;p&gt;The API handles the following error scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Network errors:&lt;/strong&gt; Returns 503 with fallback message&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeout (5s):&lt;/strong&gt; Returns 503 with fallback message&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External API failure:&lt;/strong&gt; Returns 503 with fallback message&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invalid routes:&lt;/strong&gt; Returns 404 with error message&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server errors:&lt;/strong&gt; Returns 500 with error message&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Link
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;View the &lt;a href="https://hng-task-0.pxxl.click/me" rel="noopener noreferrer"&gt;Live Link&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;View the &lt;a href="https://github.com/ameh0429/hng-task-0.git" rel="noopener noreferrer"&gt;Github Link&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>api</category>
      <category>node</category>
      <category>backend</category>
    </item>
    <item>
      <title>Task Manager API</title>
      <dc:creator>Ameh Mathias Ejeh</dc:creator>
      <pubDate>Fri, 01 Aug 2025 09:46:17 +0000</pubDate>
      <link>https://forem.com/ameh_mathias/task-manager-api-2g62</link>
      <guid>https://forem.com/ameh_mathias/task-manager-api-2g62</guid>
      <description>&lt;p&gt;A RESTful API for managing tasks built with Node.js and Express.&lt;br&gt;
&lt;a href="https://github.com/ameh0429/task-mng-api.git" rel="noopener noreferrer"&gt;Repo&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;CRUD operations for tasks&lt;/li&gt;
&lt;li&gt;In-memory data storage&lt;/li&gt;
&lt;li&gt;Status filtering&lt;/li&gt;
&lt;li&gt;Pagination support&lt;/li&gt;
&lt;li&gt;Modern ES6+ JavaScript&lt;/li&gt;
&lt;li&gt;Comprehensive error handling&lt;/li&gt;
&lt;li&gt;Request logging middleware&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Task Entity
&lt;/h2&gt;

&lt;p&gt;Each task has the following properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt; (UUID) - Unique identifier&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;title&lt;/code&gt; (string) - Task title (required)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;description&lt;/code&gt; (string) - Task description (optional)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;status&lt;/code&gt; (enum) - One of: "pending", "in-progress", "completed"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;createdAt&lt;/code&gt; (timestamp) - Creation timestamp&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;updatedAt&lt;/code&gt; (timestamp) - Last update timestamp&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  API Endpoints
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Get All Tasks
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /api/tasks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Query parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;status&lt;/code&gt; - Filter by status (pending, in-progress, completed)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;page&lt;/code&gt; - Page number for pagination (default: 1)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;limit&lt;/code&gt; - Number of items per page (default: 10)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: &lt;code&gt;GET /api/tasks?status=completed&amp;amp;page=1&amp;amp;limit=5&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Get Task by ID
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /api/tasks/:id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create New Task
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /api/tasks
Content-Type: application/json

{
  "title": "Complete project",
  "description": "Finish the task manager API",
  "status": "pending"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update Task
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PUT /api/tasks/:id
Content-Type: application/json

{
  "title": "Updated title",
  "description": "Updated description",
  "status": "in-progress"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Delete Task
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DELETE /api/tasks/:id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installation &amp;amp; Setup
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;: Ensure you have Node.js installed&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Install dependencies&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run the server&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="c"&gt;# Production mode&lt;/span&gt;
   npm start

   &lt;span class="c"&gt;# Development mode with auto-restart&lt;/span&gt;
   npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Server will start on&lt;/strong&gt;: &lt;code&gt;http://localhost:3000&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;task-manager-api/
│   ├── controllers/
│   │   └── taskController.js
│   ├── middleware/
│   │   ├── errorHandler.js
│   │   └── logger.js
│   ├── models/
│   │   └── Task.js
│   ├── routes/
│   │   └── taskRoutes.js
│   ├── services/
│   │   └── taskService.js
│   ├── utils/
│   │   └── validation.js
│   └── server.js
├── .gitignore
├── .env
├── package.json
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create a task
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:3000/api/tasks &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"title": "Learn Node.js", "description": "Complete Node.js tutorial", "status": "pending"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  List all tasks
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:3000/api/tasks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Filter completed tasks with pagination
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"http://localhost:3000/api/tasks?status=completed&amp;amp;page=1&amp;amp;limit=5"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update a task
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; PUT http://localhost:3000/api/tasks/&lt;span class="o"&gt;{&lt;/span&gt;task-id&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"status": "completed"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Delete a task
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; DELETE http://localhost:3000/api/tasks/&lt;span class="o"&gt;{&lt;/span&gt;task-id&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Status Codes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;200&lt;/code&gt; - Success&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;201&lt;/code&gt; - Created&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;400&lt;/code&gt; - Bad Request&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;404&lt;/code&gt; - Not Found&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;500&lt;/code&gt; - Internal Server Error&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Error Response Format
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Error description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ERROR_CODE"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;The API includes sample data and comprehensive logging so you can immediately test all endpoints. This README provides detailed usage examples with curl commands for testing each endpoint.&lt;br&gt;
The code is production-ready with proper error handling, validation, and follows REST API design principles throughout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Improvements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Implement user route and authentication.&lt;/li&gt;
&lt;li&gt;Connect to a database for persistent storage.&lt;/li&gt;
&lt;li&gt;Generate API docs using Swagger UI&lt;/li&gt;
&lt;li&gt;Deploy the API to Render, Railway, Heroku or AWS&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Author
&lt;/h2&gt;

&lt;p&gt;Ameh Mathias Ejeh &lt;a href="https://www.linkedin.com/in/ameh-mathias-ejeh-7444042b4" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; • &lt;a href="https://github.com/ameh0429" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Todo List Backend API</title>
      <dc:creator>Ameh Mathias Ejeh</dc:creator>
      <pubDate>Mon, 28 Jul 2025 17:58:34 +0000</pubDate>
      <link>https://forem.com/ameh_mathias/todo-list-backend-api-79m</link>
      <guid>https://forem.com/ameh_mathias/todo-list-backend-api-79m</guid>
      <description>&lt;p&gt;A comprehensive, production-ready REST API for a Todo List application built with Node.js, Express, MongoDB, and ECMAScript Modules (ESM). Features include JWT authentication, task management with advanced filtering, email notifications, and user profile management.&lt;/p&gt;

&lt;h3&gt;
  
  
  Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JWT-based Authentication&lt;/strong&gt; - Secure user authentication with token-based sessions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complete Task Management&lt;/strong&gt; - CRUD operations with advanced filtering and search&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email Notifications&lt;/strong&gt; - Task creation confirmations and daily reminders&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Profile Management&lt;/strong&gt; - Update name, email, and password&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced Filtering&lt;/strong&gt; - Filter tasks by completion status, priority, due date, and search terms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pagination&lt;/strong&gt; - Efficient handling of large task lists&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Features&lt;/strong&gt; - Rate limiting, CORS, helmet protection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input Validation&lt;/strong&gt; - Comprehensive request validation using express-validator&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RESTful Design&lt;/strong&gt; - Clean, intuitive API endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Technology Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Runtime&lt;/strong&gt;: Node.js with ECMAScript Modules (ESM)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framework&lt;/strong&gt;: Express.js&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt;: MongoDB with Mongoose ODM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication&lt;/strong&gt;: JWT (JSON Web Tokens)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password Hashing&lt;/strong&gt;: bcryptjs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email Service&lt;/strong&gt;: Nodemailer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation&lt;/strong&gt;: express-validator&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduling&lt;/strong&gt;: node-cron (for daily reminders)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: cors, express-rate-limit&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Project Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;todo-backend-api/
├── server.js                 # Main server file
├── package.json              # Dependencies and scripts
├── .env                      # Environment variables
├── README.md                 # API documentation
├── controllers/
│   ├── authController.js     # Authentication logic
│   └── taskController.js     # Task management logic
├── models/
│   ├── User.js              # User data model
│   └── Task.js              # Task data model
├── middleware/
│   ├── authMiddleware.js    # JWT authentication middleware
│   ├── errorMiddleware.js   # Global error handling
│   └── validationMiddleware.js # Input validation rules
├── routes/
│   ├── authRoutes.js        # Authentication routes
│   └── taskRoutes.js        # Task management routes
├── services/
│   └── emailService.js      # Email notification service
└── utils/
    ├── generateToken.js     # JWT token generation
    └── responseHandler.js   # Standardized API responses
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API will be available at &lt;code&gt;http://todo-list-application.up.railway.app&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  API Documentation
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Base URL
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://todo-list-application.up.railway.app/api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Response Format
&lt;/h4&gt;

&lt;p&gt;All API responses follow this standardized format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "success": true,
  "message": "Operation completed successfully",
  "data": {
    // Response data (when applicable)
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Authentication Endpoints
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Register User
&lt;/h4&gt;

&lt;p&gt;Create a new user account.&lt;br&gt;
&lt;strong&gt;Endpoint&lt;/strong&gt;: &lt;code&gt;POST /api/register&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Request Body:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "John Doe",
  "email": "john@example.com",
  "password": "password123"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Success Response (201):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "success": true,
  "message": "User registered successfully",
  "data": {
    "user": {
      "id": "60f7b3b3b3b3b3b3b3b3b3b3",
      "name": "John Doe",
      "email": "john@example.com",
      "createdAt": "2023-11-20T10:30:00.000Z"
    },
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Error Response (400):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "success": false,
  "message": "User with this email already exists"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Login User
&lt;/h4&gt;

&lt;p&gt;Authenticate an existing user.&lt;br&gt;
&lt;strong&gt;Endpoint:&lt;/strong&gt; &lt;code&gt;POST /api/login&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Request Body:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "email": "john@example.com",
  "password": "password123"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Success Response (200):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "success": true,
  "message": "Login successful",
  "data": {
    "user": {
      "id": "60f7b3b3b3b3b3b3b3b3b3b3",
      "name": "John Doe",
      "email": "john@example.com",
      "createdAt": "2023-11-20T10:30:00.000Z"
    },
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Update Profile
&lt;/h4&gt;

&lt;p&gt;Update user's profile information.&lt;br&gt;
&lt;strong&gt;Endpoint:&lt;/strong&gt; &lt;code&gt;PUT /api/profile&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; Required (Bearer Token)&lt;br&gt;
&lt;strong&gt;Request Body:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "John Smith",
  "email": "johnsmith@example.com",
  "password": "newpassword123"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Success Response (200):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "success": true,
  "message": "Profile updated successfully",
  "data": {
    "user": {
      "id": "60f7b3b3b3b3b3b3b3b3b3b3",
      "name": "John Smith",
      "email": "johnsmith@example.com",
      "updatedAt": "2023-11-20T11:00:00.000Z"
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Task Management Endpoints
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Create Task
&lt;/h4&gt;

&lt;p&gt;Create a new task and send confirmation email.&lt;br&gt;
&lt;strong&gt;Endpoint:&lt;/strong&gt; &lt;code&gt;POST /api/tasks&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; Required (Bearer Token)&lt;br&gt;
&lt;strong&gt;Request Body:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "title": "Complete project documentation",
  "description": "Write comprehensive API documentation for the todo app",
  "dueDate": "2023-11-25T15:30:00.000Z",
  "priority": "High"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Success Response (201):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "success": true,
  "message": "Task created successfully",
  "data": {
    "task": {
      "_id": "60f7b3b3b3b3b3b3b3b3b3b3",
      "title": "Complete project documentation",
      "description": "Write comprehensive API documentation for the todo app",
      "dueDate": "2023-11-25T15:30:00.000Z",
      "priority": "High",
      "isCompleted": false,
      "userId": "60f7b3b3b3b3b3b3b3b3b3b3",
      "createdAt": "2023-11-20T10:30:00.000Z",
      "updatedAt": "2023-11-20T10:30:00.000Z"
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Get All Tasks
&lt;/h4&gt;

&lt;p&gt;Retrieve tasks with optional filtering, searching, and pagination.&lt;br&gt;
&lt;strong&gt;Endpoint:&lt;/strong&gt; &lt;code&gt;GET /api/tasks&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; Required (Bearer Token)&lt;br&gt;
&lt;strong&gt;Query Parameters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;isCompleted (boolean):&lt;/strong&gt; Filter by completion status&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;priority (string):&lt;/strong&gt; Filter by priority (Low, Medium, High)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;dueDate (string):&lt;/strong&gt; Filter by due date (YYYY-MM-DD format)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;search (string):&lt;/strong&gt; Search in title and description&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;page (integer):&lt;/strong&gt; Page number for pagination (default: 1)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;limit (integer):&lt;/strong&gt; Number of items per page (default: 10, max: 100)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;sortBy (string):&lt;/strong&gt; Sort field (default: createdAt)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;sortOrder (string):&lt;/strong&gt; Sort order - asc or desc (default: desc)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example Request:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /api/tasks?isCompleted=false&amp;amp;priority=High&amp;amp;page=1&amp;amp;limit=5&amp;amp;search=project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Success Response (200):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "success": true,
  "message": "Tasks retrieved successfully",
  "data": {
    "tasks": [
      {
        "_id": "60f7b3b3b3b3b3b3b3b3b3b3",
        "title": "Complete project documentation",
        "description": "Write comprehensive API documentation",
        "dueDate": "2023-11-25T15:30:00.000Z",
        "priority": "High",
        "isCompleted": false,
        "userId": "60f7b3b3b3b3b3b3b3b3b3b3",
        "createdAt": "2023-11-20T10:30:00.000Z",
        "updatedAt": "2023-11-20T10:30:00.000Z"
      }
    ],
    "pagination": {
      "currentPage": 1,
      "totalPages": 3,
      "totalTasks": 15,
      "hasNextPage": true,
      "hasPrevPage": false,
      "limit": 5
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Get Single Task
&lt;/h4&gt;

&lt;p&gt;Retrieve a specific task by ID.&lt;br&gt;
&lt;strong&gt;Endpoint:&lt;/strong&gt; &lt;code&gt;GET /api/tasks/:id&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; Required (Bearer Token)&lt;br&gt;
&lt;strong&gt;Success Response (200):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "success": true,
  "message": "Task retrieved successfully",
  "data": {
    "task": {
      "_id": "60f7b3b3b3b3b3b3b3b3b3b3",
      "title": "Complete project documentation",
      "description": "Write comprehensive API documentation",
      "dueDate": "2023-11-25T15:30:00.000Z",
      "priority": "High",
      "isCompleted": false,
      "userId": "60f7b3b3b3b3b3b3b3b3b3b3",
      "createdAt": "2023-11-20T10:30:00.000Z",
      "updatedAt": "2023-11-20T10:30:00.000Z"
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Update Task
&lt;/h4&gt;

&lt;p&gt;Update an existing task.&lt;br&gt;
&lt;strong&gt;Endpoint:&lt;/strong&gt; &lt;code&gt;PUT /api/tasks/:id&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; Required (Bearer Token)&lt;br&gt;
&lt;strong&gt;Request Body:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "title": "Updated task title",
  "description": "Updated description",
  "dueDate": "2023-11-26T15:30:00.000Z",
  "priority": "Medium",
  "isCompleted": true
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Success Response (200):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "success": true,
  "message": "Task updated successfully",
  "data": {
    "task": {
      "_id": "60f7b3b3b3b3b3b3b3b3b3b3",
      "title": "Updated task title",
      "description": "Updated description",
      "dueDate": "2023-11-26T15:30:00.000Z",
      "priority": "Medium",
      "isCompleted": true,
      "completedAt": "2023-11-20T12:00:00.000Z",
      "userId": "60f7b3b3b3b3b3b3b3b3b3b3",
      "createdAt": "2023-11-20T10:30:00.000Z",
      "updatedAt": "2023-11-20T12:00:00.000Z"
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Delete Task
&lt;/h4&gt;

&lt;p&gt;Delete a specific task.&lt;br&gt;
&lt;strong&gt;Endpoint:&lt;/strong&gt; &lt;code&gt;DELETE /api/tasks/:id&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; Required (Bearer Token)&lt;br&gt;
&lt;strong&gt;Success Response (200):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "success": true,
  "message": "Task deleted successfully"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Email Notifications
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Task Creation Confirmation
&lt;/h4&gt;

&lt;p&gt;Automatically sent when a new task is created. Contains task details and encouragement message.&lt;/p&gt;

&lt;h4&gt;
  
  
  Daily Reminders
&lt;/h4&gt;

&lt;p&gt;Automatically sent at 9:00 AM UTC to users who have tasks due the next day. Uses &lt;code&gt;node-cron&lt;/code&gt;for scheduling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Email Content Includes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;List of tasks due tomorrow&lt;/li&gt;
&lt;li&gt;Task titles, descriptions, and priorities&lt;/li&gt;
&lt;li&gt;Color-coded priority indicators&lt;/li&gt;
&lt;li&gt;Professional HTML formatting&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Authentication
&lt;/h3&gt;

&lt;p&gt;All protected endpoints require a JWT token in the Authorization header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authorization: Bearer &amp;lt;your-jwt-token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Token Structure:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expires in 7 days (configurable)&lt;/li&gt;
&lt;li&gt;Contains user ID in payload&lt;/li&gt;
&lt;li&gt;Signed with JWT_SECRET from environment variables&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Error Handling
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Common Error Responses
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Validation Error (400):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "success": false,
  "message": "Title must be between 1 and 100 characters"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Authentication Error (401):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "success": false,
  "message": "Access denied. No token provided."
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Not Found Error (404):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "success": false,
  "message": "Task not found"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Server Error (500):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "success": false,
  "message": "Server error during task creation"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Input Validation
&lt;/h3&gt;

&lt;h4&gt;
  
  
  User Registration
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;name&lt;/strong&gt;: 2-50 characters, trimmed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;email&lt;/strong&gt;: Valid email format, lowercase, unique&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;password&lt;/strong&gt;: Minimum 6 characters&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Task Creation/Update
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;title&lt;/strong&gt;: 1-100 characters, required, trimmed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;description&lt;/strong&gt;: Optional, max 500 characters, trimmed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;priority&lt;/strong&gt;: Must be "Low", "Medium", or "High"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;dueDate&lt;/strong&gt;: Must be valid ISO 8601 date, future dates only&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;isCompleted&lt;/strong&gt;: Boolean value&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Security Features
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Rate Limiting
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;100 requests per 15 minutes per IP address&lt;/li&gt;
&lt;li&gt;Applied to all &lt;code&gt;/api/*&lt;/code&gt; routes&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Password Security
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Hashed using bcryptjs with salt rounds of 12&lt;/li&gt;
&lt;li&gt;Never returned in API responses&lt;/li&gt;
&lt;li&gt;Comparison using secure bcrypt.compare()&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  CORS Protection
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Configurable allowed origins&lt;/li&gt;
&lt;li&gt;Credentials support enabled&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Database Schema
&lt;/h3&gt;

&lt;h4&gt;
  
  
  User Collection
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  _id: ObjectId,
  name: String (required, 2-50 chars),
  email: String (required, unique, lowercase),
  password: String (required, hashed, min 6 chars),
  createdAt: Date,
  updatedAt: Date
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Task Collection
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  _id: ObjectId,
  title: String (required, 1-100 chars),
  description: String (optional, max 500 chars),
  dueDate: Date (optional, future dates only),
  priority: String (enum: Low/Medium/High, default: Medium),
  isCompleted: Boolean (default: false),
  completedAt: Date (set when completed),
  userId: ObjectId (required, references User),
  createdAt: Date,
  updatedAt: Date
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Database Indexes
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;userId + createdAt&lt;/code&gt; (compound index for efficient task queries)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;userId + isCompleted&lt;/code&gt; (for filtering completed tasks)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;userId + priority&lt;/code&gt; (for priority filtering)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dueDate&lt;/code&gt; (for reminder email queries)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Deployment
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Environment Variables for Production
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NODE_ENV=production
PORT=3000
MONGODB_URI=mongodb://your-production-mongodb-uri
JWT_SECRET=your-very-secure-production-jwt-secret
JWT_EXPIRE=7d

# Production Email Service (replace with your SMTP provider)
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USER=your-email@gmail.com
EMAIL_PASS=your-app-specific-password
EMAIL_FROM=noreply@yourdomain.com

# Frontend URL for CORS
FRONTEND_URL=https://your-frontend-domain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Contact Book App</title>
      <dc:creator>Ameh Mathias Ejeh</dc:creator>
      <pubDate>Mon, 28 Jul 2025 15:28:28 +0000</pubDate>
      <link>https://forem.com/ameh_mathias/contact-book-app-1dhj</link>
      <guid>https://forem.com/ameh_mathias/contact-book-app-1dhj</guid>
      <description>&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;This is a console-based application that allows users to manage a basic contact list. It leverages Python's dictionary data structure to store contact names and their corresponding phone numbers. The application provides a menu-driven interface for adding new contacts, looking up existing ones, listing all stored contacts, and deleting contacts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Add Contact&lt;/strong&gt;: Allows users to input a contact's name and phone number, which are then stored in the contact book.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Look Up Contact&lt;/strong&gt;: Users can search for a contact by name, and if found, the application displays their phone number. It handles cases where the contact does not exist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;List All Contacts&lt;/strong&gt;: Displays all stored contact names and their phone numbers in an easy-to-read format. It also informs the user if the contact book is empty.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delete Contact&lt;/strong&gt;: Provides the ability to remove an existing contact from the book by name. It includes checks for an empty book and for contacts not found.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Menu-Driven Interface&lt;/strong&gt;: Guides the user through options with clear prompts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous Operation&lt;/strong&gt;: Runs in a loop, allowing multiple operations until the user chooses to exit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Use
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.x installed on your system.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Running the Application
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Save the code&lt;/strong&gt;: Save the provided Python code into a file named &lt;code&gt;contact_book.py&lt;/code&gt; (or any other &lt;code&gt;.py&lt;/code&gt; extension).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open a terminal/command prompt&lt;/strong&gt;: Navigate to the directory where you saved &lt;code&gt;contact_book.py&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the script&lt;/strong&gt;: Execute the command:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python contact_book.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Interacting with the App
&lt;/h3&gt;

&lt;p&gt;Upon running, you will see the main menu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--- Simple Contact Book Menu ---
1. Add a new contact
2. Look up a contact
3. List all contacts
4. Delete a contact
5. Exit
Enter your choice (1-5):
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;To Add a New Contact (Option 1):&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Enter &lt;code&gt;1&lt;/code&gt;and press Enter.&lt;/li&gt;
&lt;li&gt;Follow the prompts to enter the contact's name and phone number.&lt;/li&gt;
&lt;li&gt;A confirmation message will be displayed.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;To Look Up a Contact (Option 2):&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Enter &lt;code&gt;2&lt;/code&gt;and press Enter.&lt;/li&gt;
&lt;li&gt;Enter the name of the contact you wish to find.&lt;/li&gt;
&lt;li&gt;The app will display the contact's phone number or indicate if the contact is not found.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;To List All Contacts (Option 3):&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Enter &lt;code&gt;3&lt;/code&gt;and press Enter.&lt;/li&gt;
&lt;li&gt;The app will display all contacts stored in the book, or a message if the book is empty.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;To Delete a Contact (Option 4):&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Enter &lt;code&gt;4&lt;/code&gt;and press Enter.&lt;/li&gt;
&lt;li&gt;The app will first list current contacts for reference.&lt;/li&gt;
&lt;li&gt;Enter the name of the contact you wish to delete.&lt;/li&gt;
&lt;li&gt;A confirmation message will be displayed if successful, or a "not found" message if the contact doesn't exist.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;To Exit the App (Option 5):&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Enter &lt;code&gt;5&lt;/code&gt;and press Enter.&lt;/li&gt;
&lt;li&gt;The app will print a goodbye message and terminate.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Invalid Choice:&lt;/strong&gt; If you enter any number or text that is not a valid menu option, the app will prompt you to try again.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Code Structure
&lt;/h2&gt;

&lt;p&gt;The application's logic is contained within a single Python script (&lt;code&gt;contact_book.py&lt;/code&gt;) and is structured as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;contacts = {}&lt;/code&gt;: An empty Python dictionary initialized at the beginning. This &lt;code&gt;dictionary&lt;/code&gt;is the core data structure, storing &lt;code&gt;name: phone_number&lt;/code&gt; pairs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;while True:&lt;/code&gt; &lt;strong&gt;loop&lt;/strong&gt;: The primary loop that keeps the application running, continuously presenting the menu and processing user input until an explicit exit command.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Menu Display:&lt;/strong&gt; &lt;code&gt;print()&lt;/code&gt; statements inside the loop present the user with the available options.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Input:&lt;/strong&gt; &lt;code&gt;input()&lt;/code&gt; captures the user's menu choice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conditional Logic&lt;/strong&gt; (&lt;code&gt;if/elif/else&lt;/code&gt;):

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;if choice == '1':&lt;/code&gt; Handles adding a contact. It takes &lt;code&gt;name&lt;/code&gt;and &lt;code&gt;phone_number&lt;/code&gt; input and adds them to the &lt;code&gt;contacts&lt;/code&gt;dictionary using &lt;code&gt;contacts[name] = phone_number&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;elif choice == '2':&lt;/code&gt; Handles looking up a contact. It takes a &lt;code&gt;name&lt;/code&gt;as input and uses the &lt;code&gt;in&lt;/code&gt;operator (&lt;code&gt;if name in contacts:&lt;/code&gt;) to check for the key's existence before attempting to retrieve and print its value (&lt;code&gt;contacts[name]&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;elif choice == '3':&lt;/code&gt; Handles listing all contacts. It checks if the &lt;code&gt;contacts&lt;/code&gt;dictionary is empty (&lt;code&gt;len(contacts) == 0&lt;/code&gt; or &lt;code&gt;if not contacts:&lt;/code&gt;). If not empty, it iterates through all key-value pairs using &lt;code&gt;for name, number in contacts.items():&lt;/code&gt; and prints them.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;elif choice == '4':&lt;/code&gt; Handles deleting a contact. It first checks if the dictionary is empty. If not, it prompts for a name to delete, checks if the name exists using &lt;code&gt;in&lt;/code&gt;, and then uses &lt;code&gt;del contacts[name_to_delete]&lt;/code&gt; to remove the entry. Provides feedback for success or if the contact is not found.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;elif choice == '5':&lt;/code&gt; Handles exiting the app. It prints a goodbye message and uses &lt;code&gt;break&lt;/code&gt;to terminate the &lt;code&gt;while&lt;/code&gt;loop.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;else:&lt;/code&gt; Catches invalid menu choices.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Concepts Demonstrated
&lt;/h2&gt;

&lt;p&gt;This project effectively utilizes and reinforces the following fundamental Python programming concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data Structures&lt;/strong&gt;: Extensive use of Dictionaries (&lt;code&gt;dict&lt;/code&gt;) for storing and managing key-value pairs (contacts).

&lt;ul&gt;
&lt;li&gt;Dictionary methods like &lt;code&gt;.items()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Dictionary operations like adding/updating (&lt;code&gt;dict[key] = value&lt;/code&gt;), accessing (&lt;code&gt;dict[key]&lt;/code&gt;), and deleting (&lt;code&gt;del dict[key]&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Checking for key existence using the &lt;code&gt;in&lt;/code&gt;operator.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Variables&lt;/strong&gt;: Storing names, phone numbers, user choices, and the &lt;code&gt;contacts&lt;/code&gt; dictionary.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Input/Output&lt;/strong&gt;: Using &lt;code&gt;input()&lt;/code&gt; for user interaction and &lt;code&gt;print()&lt;/code&gt; for displaying information.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Logic &amp;amp; Control Flow:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;while&lt;/code&gt;&lt;strong&gt;Loop&lt;/strong&gt;: For the continuous operation of the application.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;if/elif/else&lt;/code&gt; &lt;strong&gt;Statements&lt;/strong&gt;: For decision-making based on user menu choices and dictionary conditions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;for&lt;/code&gt;&lt;strong&gt;Loop&lt;/strong&gt;: For iterating through dictionary items to display all contacts.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;break&lt;/code&gt;&lt;strong&gt;Keyword&lt;/strong&gt;: For gracefully exiting the main application loop.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;String Formatting&lt;/strong&gt;: Using f-strings (&lt;code&gt;f"..."&lt;/code&gt;) for clear and dynamic output messages.&lt;/li&gt;

&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Number Guessing Game</title>
      <dc:creator>Ameh Mathias Ejeh</dc:creator>
      <pubDate>Fri, 11 Jul 2025 22:28:42 +0000</pubDate>
      <link>https://forem.com/ameh_mathias/number-guessing-game-107e</link>
      <guid>https://forem.com/ameh_mathias/number-guessing-game-107e</guid>
      <description>&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;This is a simple console-based number guessing game implemented in Python. The computer "thinks" of a random number within a specified range (1 to 100), and the player's goal is to guess that number. The game provides hints ("Too high!" or "Too low!") after each guess, helping the player narrow down the possibilities. The game tracks and displays the number of attempts it took to guess the correct number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Random Number Generation: The computer selects a random integer between 1 and 100 (inclusive) as the secret number.&lt;/li&gt;
&lt;li&gt;Interactive Guessing: Players can repeatedly enter their guesses.&lt;/li&gt;
&lt;li&gt;Hints System: Provides feedback to the player after each guess, indicating whether their guess was too high or too low.&lt;/li&gt;
&lt;li&gt;Guess Counter: Keeps track of the number of attempts the player makes.&lt;/li&gt;
&lt;li&gt;Win Condition: The game ends once the player correctly guesses the secret number.&lt;/li&gt;
&lt;li&gt;Summary: Displays the total number of guesses taken and the secret number upon winning.&lt;/li&gt;
&lt;li&gt;Input Validation: Handles non-numeric input gracefully, prompting the user to enter a valid number.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Play
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.x installed on your system.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Running the Game
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Save the code&lt;/strong&gt;: Save the provided Python code into a file named &lt;code&gt;guessing_game.py&lt;/code&gt; (or any other &lt;code&gt;.py&lt;/code&gt; extension).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open a terminal/command prompt&lt;/strong&gt;: Navigate to the directory where you saved &lt;code&gt;guessing_game.py&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the script&lt;/strong&gt;: Execute the command:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python guessing_game.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Game Instructions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Upon starting, the game will welcome you and inform you that it has chosen a number between 1 and 100.&lt;/li&gt;
&lt;li&gt;You will be prompted to Enter your guess: .&lt;/li&gt;
&lt;li&gt;Type a whole number between 1 and 100 and press Enter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hints&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;If your guess is too high, the game will say: "Too high! Try a lower number!"&lt;/li&gt;
&lt;li&gt;If your guess is too low, the game will say: "Too low! Try a higher number!"&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Winning&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;If your guess is correct, the game will congratulate you and tell you how many guesses it took to find the number.&lt;/li&gt;
&lt;li&gt;The game will then end.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Invalid Input&lt;/strong&gt;: If you enter anything that is not a whole number (e.g., text, decimals), the game will prompt you to enter a valid number and will not count that as a guess.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqhcnkey61czty7102hm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqhcnkey61czty7102hm.png" alt=" " width="439" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Structure
&lt;/h2&gt;

&lt;p&gt;The game's logic is contained within a single Python script (&lt;code&gt;guessing_game.py&lt;/code&gt;) and follows this structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;import random&lt;/code&gt;: Imports the &lt;code&gt;random&lt;/code&gt; module, which is essential for generating the &lt;code&gt;secret_number&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;secret_number = random.randint(1, 100)&lt;/code&gt;: This line generates the random target number for the game. For testing purposes during development, you might temporarily print(&lt;code&gt;secret_number&lt;/code&gt;) but it should be removed or commented out for actual gameplay.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;guesses = 0&lt;/code&gt;: An integer variable initialized to zero, used to count the player's attempts.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;while True:&lt;/code&gt; &lt;strong&gt;loop&lt;/strong&gt;: This is the main game loop. It continues indefinitely until the player guesses correctly and a break statement is executed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;try-except ValueError&lt;/code&gt; &lt;strong&gt;block&lt;/strong&gt;: Encapsulates the user input process.

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;try:&lt;/code&gt; Attempts to get user input (&lt;code&gt;input()&lt;/code&gt;) and convert it to an integer (&lt;code&gt;int()&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;except ValueError&lt;/code&gt;: Catches errors if the user enters non-numeric text, printing an error message and allowing the loop to continue without crashing.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;guesses += 1&lt;/code&gt;: Increments the guesses counter with each valid attempt.&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;if/elif/else&lt;/code&gt; &lt;strong&gt;statements&lt;/strong&gt;: These conditional statements form the core game logic:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;if user_guess &amp;gt; secret_number&lt;/code&gt;: Provides the "Too high!" hint.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;elif user_guess &amp;lt; secret_number&lt;/code&gt;: Provides the "Too low!" hint.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;else&lt;/code&gt;: This block is executed when &lt;code&gt;user_guess == secret_number&lt;/code&gt;. It congratulates the player, prints the total &lt;code&gt;guesses&lt;/code&gt;, and then uses &lt;code&gt;break&lt;/code&gt;to exit the &lt;code&gt;while&lt;/code&gt;loop, ending the game.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fioh3fadpk51ewoiboy81.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fioh3fadpk51ewoiboy81.png" alt=" " width="764" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Concepts Demonstrated
&lt;/h2&gt;

&lt;p&gt;This project effectively demonstrates and reinforces the following Python programming concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Modules&lt;/strong&gt;: Importing and using the &lt;code&gt;random&lt;/code&gt;module.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Variables&lt;/strong&gt;: Storing the &lt;code&gt;secret_number&lt;/code&gt;, &lt;code&gt;user_guess&lt;/code&gt;, and &lt;code&gt;guesses&lt;/code&gt;count.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input/Output&lt;/strong&gt;: Using &lt;code&gt;input()&lt;/code&gt; to get user guesses and &lt;code&gt;print()&lt;/code&gt; to provide game feedback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logic &amp;amp; Control Flow&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;while Loop&lt;code&gt;:&lt;/code&gt;For repetitive guessing until a condition is met.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;if/elif/else&lt;/code&gt; &lt;strong&gt;Statements&lt;/strong&gt;: For decision-making based on guess comparison.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;break&lt;/code&gt;&lt;strong&gt;Keyword&lt;/strong&gt;: For exiting the game loop early upon a win.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Type Conversion&lt;/strong&gt;: Using &lt;code&gt;int()&lt;/code&gt; to convert string input to an integer.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Counters&lt;/strong&gt;: Incrementing a variable to track progress.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Error Handling&lt;/strong&gt;: Basic &lt;code&gt;try-except&lt;/code&gt; for &lt;code&gt;ValueError&lt;/code&gt; to handle invalid input.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Check out the repo &lt;a href="https://github.com/ameh0429/Number-Guessing-Game.git" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>python</category>
      <category>developer</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Basic To-Do List App</title>
      <dc:creator>Ameh Mathias Ejeh</dc:creator>
      <pubDate>Fri, 11 Jul 2025 14:01:11 +0000</pubDate>
      <link>https://forem.com/ameh_mathias/basic-to-do-list-app-documentation-11jb</link>
      <guid>https://forem.com/ameh_mathias/basic-to-do-list-app-documentation-11jb</guid>
      <description>&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;This is a simple console-based To-Do List application built in Python. It allows users to manage their daily tasks by providing options to add new tasks, view existing tasks, and remove completed or unwanted tasks. The application runs in a continuous loop until the user decides to exit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Add Task:&lt;/strong&gt; Allows the user to input a new task and add it to their to-do list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;View Tasks:&lt;/strong&gt; Displays all current tasks in a numbered list, making it easy to reference. It also informs the user if the list is empty.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove Task:&lt;/strong&gt; Enables the user to remove a task by specifying its corresponding number from the displayed list. Includes error handling for invalid input (non-numeric input or out-of-range numbers).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exit Application:&lt;/strong&gt; Provides an option to gracefully terminate the program.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User-Friendly Interface:&lt;/strong&gt; Presents a clear menu and provides feedback for all operations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Use
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.x installed on your system.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Running the Application
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Save the code:&lt;/strong&gt; Save the provided Python code into a file named &lt;code&gt;todo_app.py&lt;/code&gt; (or any other &lt;code&gt;.py&lt;/code&gt; extension).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open a terminal/command prompt:&lt;/strong&gt; Navigate to the directory where you saved &lt;code&gt;todo_app.py&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the script:&lt;/strong&gt; Execute the command:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python todo_app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Interacting with the App
&lt;/h3&gt;

&lt;p&gt;Upon running, you will see a main menu:&lt;br&gt;
Welcome to the To-Do App!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Add a task
2. View tasks
3. Remove a task
4. Exit the app
Enter your choice (1-4):
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  To Add a Task:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Enter &lt;code&gt;1&lt;/code&gt;and press Enter.&lt;/li&gt;
&lt;li&gt;You will be prompted: &lt;code&gt;Enter the task you want to add:&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Type your task (e.g., &lt;code&gt;Go grocery shopping&lt;/code&gt;) and press Enter.&lt;/li&gt;
&lt;li&gt;The app will confirm the task has been added.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Each time you choose '1', you add one task.&lt;/p&gt;

&lt;h4&gt;
  
  
  To View Tasks:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Enter &lt;code&gt;2&lt;/code&gt;and press Enter.&lt;/li&gt;
&lt;li&gt;The app will display your current tasks as a numbered list (e.g., &lt;code&gt;1. Go grocery shopping&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;If no tasks are present, it will inform you.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  To Remove a Task:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Enter &lt;code&gt;3&lt;/code&gt;and press Enter.&lt;/li&gt;
&lt;li&gt;The app will first show you your current tasks with their numbers.&lt;/li&gt;
&lt;li&gt;You will be prompted: &lt;code&gt;Enter the task number you want to remove:&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;Enter the number corresponding to the task you wish to remove (e.g., &lt;code&gt;1&lt;/code&gt;) and press Enter.&lt;/li&gt;
&lt;li&gt;The app will confirm the task has been removed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Handling:&lt;/strong&gt; If you enter invalid input (e.g., text, a number outside the range of tasks), the app will provide an error message and return to the main menu.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  To Exit the App:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Enter &lt;code&gt;4&lt;/code&gt;and press Enter.&lt;/li&gt;
&lt;li&gt;The app will print a goodbye message and close.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Code Structure
&lt;/h2&gt;

&lt;p&gt;The application's logic is primarily contained within a single Python script (todo_app.py) and is structured as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;to_do_items = []&lt;/code&gt;: An empty Python &lt;code&gt;list&lt;/code&gt;is initialized at the beginning of the script. This list serves as the central data storage for all active tasks.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;while True&lt;/code&gt;: &lt;strong&gt;loop&lt;/strong&gt;: The core of the application is an infinite &lt;code&gt;while&lt;/code&gt;loop. This ensures the menu is continuously displayed and the user can perform multiple actions until they explicitly choose to exit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Menu Display:&lt;/strong&gt; Inside the loop, &lt;code&gt;print()&lt;/code&gt;statements are used to present the user with a clear set of options.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Input:&lt;/strong&gt; The &lt;code&gt;input()&lt;/code&gt; function captures the user's choice (1-4).&lt;/li&gt;
&lt;li&gt;Conditional Logic (&lt;code&gt;if/elif/else&lt;/code&gt;): A series of &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;elif&lt;/code&gt; (else if), and &lt;code&gt;else&lt;/code&gt;statements are used to process the user's choice:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;a&lt;/strong&gt;. &lt;code&gt;if choice == "1"&lt;/code&gt;: Handles adding a task. It prompts for the task string and uses &lt;code&gt;list.append()&lt;/code&gt; to add it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnuf5o74kfx4pyoiy2d6z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnuf5o74kfx4pyoiy2d6z.png" alt=" " width="440" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;b&lt;/strong&gt;. &lt;code&gt;elif choice == "2"&lt;/code&gt;: Handles viewing tasks. It checks if the list is empty &lt;code&gt;(len())&lt;/code&gt; and, if not, uses a for loop with &lt;code&gt;enumerate(..., start=1)&lt;/code&gt; to display tasks with user-friendly 1-based numbering.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl8rtu3461wf6aj2zm39z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl8rtu3461wf6aj2zm39z.png" alt=" " width="508" height="154"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;c&lt;/strong&gt;. &lt;code&gt;elif choice == "3"&lt;/code&gt;: Handles removing a task.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It first displays the current tasks.&lt;/li&gt;
&lt;li&gt;It uses a &lt;code&gt;try-except&lt;/code&gt; block to gracefully handle &lt;code&gt;ValueError&lt;/code&gt; if the user enters non-numeric input.&lt;/li&gt;
&lt;li&gt;It converts the user's 1-based input to a 0-based list index.&lt;/li&gt;
&lt;li&gt;A critical if statement &lt;code&gt;(0 &amp;lt;= actual_index_to_remove &amp;lt; len(to_do_items))&lt;/code&gt; validates the index to prevent &lt;code&gt;IndexError&lt;/code&gt; for out-of-range numbers.&lt;/li&gt;
&lt;li&gt;If the index is valid, &lt;code&gt;list.pop()&lt;/code&gt; is used to remove the task at that position.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0b7k4w0nfl7xcqpl1ahj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0b7k4w0nfl7xcqpl1ahj.png" alt=" " width="663" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;d&lt;/strong&gt;. &lt;code&gt;elif choice == "4"&lt;/code&gt;: Handles exiting the app by printing a goodbye message and using the break keyword to terminate the while loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;e&lt;/strong&gt;. &lt;code&gt;else&lt;/code&gt;: Catches any invalid numerical or text input and prompts the user to try again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7zs5njp5ja34x8a1hhch.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7zs5njp5ja34x8a1hhch.png" alt=" " width="402" height="128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Concepts Demonstrated
&lt;/h2&gt;

&lt;p&gt;This project effectively utilizes and reinforces several fundamental Python programming concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Variables&lt;/strong&gt;: Storing user choices, tasks, and the &lt;code&gt;to_do_items&lt;/code&gt; list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Structures&lt;/strong&gt;: Extensive use of Lists (&lt;code&gt;to_do_items&lt;/code&gt;) for storing and managing collections of tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input/Output&lt;/strong&gt;: Using &lt;code&gt;input()&lt;/code&gt; to get data from the user and &lt;code&gt;print()&lt;/code&gt; to display information and feedback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logic &amp;amp; Control Flow&lt;/strong&gt;:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;a&lt;/strong&gt;. &lt;code&gt;while Loops&lt;/code&gt;: For maintaining the application's running state.&lt;br&gt;
&lt;strong&gt;b&lt;/strong&gt;. &lt;code&gt;if/elif/else Statements&lt;/code&gt;: For decision-making based on user choices and list conditions.&lt;br&gt;
c. &lt;code&gt;for Loops&lt;/code&gt;: For iterating over the &lt;code&gt;to_do_items&lt;/code&gt; list to display tasks.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Functions/Methods&lt;/strong&gt;:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;a&lt;/strong&gt;. Built-in functions like &lt;code&gt;len()&lt;/code&gt;, &lt;code&gt;int()&lt;/code&gt;, &lt;code&gt;enumerate()&lt;/code&gt;.&lt;br&gt;
&lt;strong&gt;b&lt;/strong&gt;. List methods like append() and pop().&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;String Formatting&lt;/strong&gt;: Using f-strings (&lt;code&gt;f"..."&lt;/code&gt;) for clear and dynamic messages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Handling&lt;/strong&gt;: Basic &lt;code&gt;try-except&lt;/code&gt; blocks to manage potential runtime errors (like &lt;code&gt;ValueError&lt;/code&gt; for invalid input) and conditional checks for &lt;code&gt;IndexError&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;. Check out the repo &lt;a href="https://github.com/ameh0429/Basic-to-do-list-app.git" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>development</category>
    </item>
    <item>
      <title>The Importance of Leveraging AI to Learn</title>
      <dc:creator>Ameh Mathias Ejeh</dc:creator>
      <pubDate>Tue, 24 Jun 2025 13:14:00 +0000</pubDate>
      <link>https://forem.com/ameh_mathias/the-importance-of-leveraging-ai-to-learn-4c2n</link>
      <guid>https://forem.com/ameh_mathias/the-importance-of-leveraging-ai-to-learn-4c2n</guid>
      <description>&lt;p&gt;&lt;strong&gt;Artificial Intelligence (AI)&lt;/strong&gt; refers to the development of computer systems capable of performing tasks that typically require human intelligence, such as reasoning, learning, decision-making, and perception. There's no doubt that AI is here to stay, and it is essential to leverage its capabilities appropriately in the learning process.&lt;/p&gt;

&lt;p&gt;As a junior developer who is just beginning to learn how to code, it might not be wise to rely solely on AI to generate code. If you do, how will you truly learn? To effectively leverage AI, it's important to use it as a learning companion rather than a shortcut. Let it guide you, challenge your understanding, and support your growth, instead of simply handing you complete answers.&lt;/p&gt;

&lt;p&gt;The quality of AI-generated responses depends heavily on the quality of the prompts you give it. To get meaningful and useful responses, follow these &lt;strong&gt;six building blocks&lt;/strong&gt; of a good prompt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Task&lt;/strong&gt;: Begin with a clear action verb and define the specific output you’re expecting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Context&lt;/strong&gt;: Provide relevant background information or clarify the scenario to help the AI understand your needs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Exemplars&lt;/strong&gt;: Share examples to guide the AI in terms of format, tone, or content style.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Persona&lt;/strong&gt;: Specify the role the AI should take—such as a coach, teacher, or technical expert—to align its tone and approach.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Format&lt;/strong&gt;: Clearly define the desired output format—whether it’s a bullet list, essay, explanation, or report.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tone&lt;/strong&gt;: Decide on the emotional or professional tone you want—formal, friendly, motivational, etc.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By following this prompting framework, you'll greatly enhance your ability to communicate with AI and receive more accurate, detailed, and helpful responses. Use AI wisely—not as a replacement for learning, but as a powerful tool to support and accelerate your educational journey&lt;/p&gt;

</description>
      <category>ai</category>
      <category>development</category>
      <category>programming</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Building a User Feedback Collector with Node.js and Express.js</title>
      <dc:creator>Ameh Mathias Ejeh</dc:creator>
      <pubDate>Tue, 24 Jun 2025 11:07:38 +0000</pubDate>
      <link>https://forem.com/ameh_mathias/building-a-user-feedback-collector-with-nodejs-and-expressjs-5akl</link>
      <guid>https://forem.com/ameh_mathias/building-a-user-feedback-collector-with-nodejs-and-expressjs-5akl</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This project collects user feedback via a form and save it in a local file. A user feedback collector is a fundamental feature in many applications, it is a tool or system, used to gather feedback from users about their experience with a product or service. It helps project teams understand what users think, feel, or need, and is essential for making informed improvements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Basic knowledge of JavaScript, Node.js and Express.&lt;/li&gt;
&lt;li&gt;Node.js, git and npm installed on your computer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Accepts feedback with &lt;strong&gt;name&lt;/strong&gt;, &lt;strong&gt;email&lt;/strong&gt;, &lt;strong&gt;message&lt;/strong&gt;, and an &lt;strong&gt;image&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Validates all input fields, including email format and message length.&lt;/li&gt;
&lt;li&gt;Accepts only &lt;strong&gt;.jpeg, .jpg, .png&lt;/strong&gt; images up to &lt;strong&gt;10MB&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Saves uploaded files to the &lt;code&gt;/uploads&lt;/code&gt; folder.&lt;/li&gt;
&lt;li&gt;Persists feedback in a local &lt;code&gt;feedbacks.json&lt;/code&gt; file.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TC-Feedback-Collector/
├──controllers/       // Handles request logic
├──middleware/        // Custom middleware functions
├──model/
├──routes/            // API route definition
├──service/
├──utility/
├──app.js             // main application file
├──.gitignore         // gitignore file
├──package.json       // Project dependencies
└──README.md         //documentation file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Project setup
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Clone the repository and navigate to the project directory
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/Jerryemmanuel01/TC-Feedback-Collector.git
cd TC-Feedback-Collector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Initialize the project as a node.js project.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm init -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a &lt;code&gt;package.json&lt;/code&gt; file&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install dependencies
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install express multer nodemon path
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;express&lt;/strong&gt;: Simplifies the process of building web servers and APIs.&lt;br&gt;
&lt;strong&gt;multer&lt;/strong&gt;: Allows users to upload files via forms (e.g., images, documents).&lt;br&gt;
&lt;strong&gt;nodemon&lt;/strong&gt;: Automatically restarts the Node.js application when file changes are detected.&lt;br&gt;
&lt;strong&gt;path&lt;/strong&gt;: Provides utilities for working with file and directory paths in a way that is platform-independent.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Setting up the Server
&lt;/h3&gt;

&lt;p&gt;This is the main server file &lt;code&gt;app.js&lt;/code&gt;. It sets up an Express.js server, serves static files (uploaded images), routes feedback submissions to &lt;code&gt;/feedback&lt;/code&gt; and also parses incoming JSON and URL-encoded form data.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Setting up &lt;code&gt;saveFeedback&lt;/code&gt; file
&lt;/h3&gt;

&lt;p&gt;The code defines a function that handles saving user feedback to a local JSON file (feedbacks.json) on the server.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Setting up the middleware
&lt;/h3&gt;

&lt;p&gt;The middleware folder contains two files; &lt;code&gt;fileUpload&lt;/code&gt; and &lt;code&gt;validateFeedback&lt;/code&gt; files.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;fileupload.js&lt;/strong&gt;: This code sets up file upload handling in a Node.js application using the Multer middleware. It securely handles file uploads by saving them in a dedicated folder, naming them uniquely and enforcing file type and size restrictions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;validateFeedback.js&lt;/strong&gt;:  This code performs server-side validation on the feedback submission request before it's processed further. It ensures the submitted data is well-formed and meets specific rules&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Creating the routes
&lt;/h3&gt;

&lt;p&gt;This code sets up an Express.js route for handling user feedback submissions. The POST endpoint (/) validates incoming feedback data and saves the feedback to storage (JSON file)&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Run the Application
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Testing the Application using Postman
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/feedback&lt;/code&gt; is the main endpoint that accepts a feedback submission.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enter the url
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:5000/feedback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Set the body type to &lt;code&gt;form-data
&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add the necessary key-value pairs&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;name&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;Samson&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;email&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:testing@gmail.com"&gt;testing@gmail.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;message&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;Just testing this route&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;file&lt;/td&gt;
&lt;td&gt;File&lt;/td&gt;
&lt;td&gt;Image.jpg&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Send the request. If all validations pass, you’ll get a success response like:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "status": 201,
  "message": "Feedback saved successfully.",
  "error": false
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;This project is a simple yet effective solution for gathering structured user feedback along with image uploads. It demonstrates key backend development practices, including form validation, file handling with Multer, and structured routing in Express.js. &lt;/p&gt;

&lt;h2&gt;
  
  
  Contributors
&lt;/h2&gt;

&lt;p&gt;&lt;a href="//husseinabdulmalik0@gmail.com"&gt;Abdulmalik Hussein&lt;/a&gt;&lt;br&gt;
&lt;a href="//bright.bediako.dev@gmail.com"&gt;Bright Bediako&lt;/a&gt;&lt;br&gt;
&lt;a href="//adeniyiadebimpe04@gmail.com"&gt;Adeniyi Adebimpe&lt;/a&gt; &lt;br&gt;
&lt;a href="//davidadejumo10@gmail.com"&gt;Adejumo David &lt;/a&gt; &lt;br&gt;
&lt;a href="//jerryemmanuelolisa@gmail.com"&gt;Dominic Jerryemmanuel&lt;/a&gt;&lt;br&gt;
&lt;a href="//adegbengaoluwatosin61@gmail.com"&gt;Adekoya Adegbenga&lt;/a&gt; &lt;br&gt;
&lt;a href="//amehmathiasejeh40@gmail.com"&gt;Ameh Mathias Ejeh&lt;/a&gt;&lt;br&gt;
&lt;a href="//bahseet2022@gmail.com"&gt;Basit Adebayo&lt;/a&gt;&lt;br&gt;
&lt;a href="//danielkasambala51@gmail.com"&gt;Daniel Kasambala&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>npm</category>
    </item>
  </channel>
</rss>
