Skip to main content
This page documents incidents related to product search and listing functionality in the Node.js service.

Overview

Product search incidents typically involve:
  • Missing dependencies for search functionality
  • Pagination calculation errors
  • Query parameter handling issues

Incidents

Summary

Severity: P2 - High
Service: node-service
Date: 2026-02-28
Environment: Staging
The product search endpoint returned 500 errors due to a missing fuse.js dependency. The module was used in code but not added to package.json.

Problem

Calling GET /api/products/search?q=widget resulted in:Error message:
Error: Cannot find module 'fuse.js'
Require stack:
- /app/src/routes/products.js

Root Cause

The search functionality was implemented using the fuse.js library for fuzzy search, but the dependency was never added to package.json. The developer likely installed it locally during development but forgot to save it to dependencies.Problematic code (src/routes/products.js:34-58):
router.get("/search", async (req, res) => {
  try {
    const Fuse = require("fuse.js");  // Module not in package.json!
    const { q } = req.query;

    if (!q) {
      return res.status(400).json({ error: "Search query 'q' is required" });
    }

    const products = await Product.findAll();
    const fuse = new Fuse(
      products.map((p) => formatProductResponse(p)),
      {
        keys: ["name", "description"],
        threshold: 0.4,
      }
    );

    const results = fuse.search(q).map((r) => r.item);
    res.json({ products: results, count: results.length });
  } catch (error) {
    console.error("Search error:", error);
    res.status(500).json({ error: "Search failed" });
  }
});

Resolution

  1. Install and save the dependency:
npm install fuse.js --save
  1. Verify package.json includes the dependency:
{
  "dependencies": {
    "fuse.js": "^7.0.0",
    // ... other dependencies
  }
}
  1. Commit the updated package.json and package-lock.json

Prevention

  • Always use npm install --save (or npm install which saves by default in modern npm) when adding dependencies
  • Run npm ci in CI/CD pipelines to catch missing dependencies before deployment
  • Add a pre-commit hook to verify all require() statements have corresponding dependencies:
// Script to check dependencies
const fs = require('fs');
const path = require('path');

function findMissingDeps(dir) {
  const packageJson = require('./package.json');
  const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
  
  // Scan source files for require() statements
  // Compare with package.json
  // Report missing dependencies
}
  • Use tools like depcheck to find missing or unused dependencies:
npm install -g depcheck
depcheck

Summary

Severity: P2 - High
Service: node-service
Date: 2026-02-28
Environment: Production
Product listing pagination returned wrong results. Page 1 showed items 11-20 instead of 1-10, skipping the first page of products entirely.

Problem

The offset calculation was incorrect, causing page numbers to be off by one:
  • GET /api/products?page=1&limit=10 returned products 11-20 (expected 1-10)
  • GET /api/products?page=2&limit=10 returned products 21-30 (expected 11-20)

Root Cause

The paginate() utility function calculated offset incorrectly using page * limit instead of (page - 1) * limit.Problematic code (src/utils/formatters.js:34-45):
function paginate(page, limit) {
  const parsedPage = parseInt(page, 10) || 1;
  const parsedLimit = parseInt(limit, 10) || 10;

  const offset = parsedPage * parsedLimit;  // Wrong calculation!

  return {
    limit: parsedLimit,
    offset: offset,
    page: parsedPage,
  };
}
Why this is wrong:
  • Page 1: offset = 1 * 10 = 10 (skips first 10 items)
  • Page 2: offset = 2 * 10 = 20 (skips first 20 items)

Resolution

Fix the offset calculation to use zero-based indexing:Fixed code:
function paginate(page, limit) {
  const parsedPage = parseInt(page, 10) || 1;
  const parsedLimit = parseInt(limit, 10) || 10;

  const offset = (parsedPage - 1) * parsedLimit;  // Correct calculation

  return {
    limit: parsedLimit,
    offset: offset,
    page: parsedPage,
  };
}
How it works now:
  • Page 1: offset = (1 - 1) * 10 = 0 (starts at first item)
  • Page 2: offset = (2 - 1) * 10 = 10 (starts at 11th item)
  • Page 3: offset = (3 - 1) * 10 = 20 (starts at 21st item)

Prevention

  • Write unit tests for pagination logic:
describe('paginate', () => {
  it('should return correct offset for page 1', () => {
    const result = paginate(1, 10);
    expect(result.offset).toBe(0);
  });
  
  it('should return correct offset for page 2', () => {
    const result = paginate(2, 10);
    expect(result.offset).toBe(10);
  });
  
  it('should handle default values', () => {
    const result = paginate();
    expect(result.page).toBe(1);
    expect(result.limit).toBe(10);
    expect(result.offset).toBe(0);
  });
});
  • Add integration tests that verify actual data returned:
it('should return first page of products', async () => {
  const response = await request(app)
    .get('/api/products?page=1&limit=10');
  
  expect(response.body.products[0].id).toBe(1);  // First product
  expect(response.body.products.length).toBe(10);
});
  • Use established pagination libraries or patterns:
// Using Sequelize's built-in pagination
const { rows, count } = await Product.findAndCountAll({
  offset: (page - 1) * limit,
  limit: limit
});

Best Practices

Dependency Management

Always save dependencies:
# Production dependency
npm install package-name

# Development dependency
npm install --save-dev package-name
Audit dependencies regularly:
# Check for security vulnerabilities
npm audit

# Find missing/unused dependencies
npx depcheck
Lock dependency versions:
  • Commit package-lock.json to version control
  • Use npm ci in production/CI instead of npm install

Pagination Pattern

Standard pagination helper:
function paginate(page = 1, limit = 10) {
  const parsedPage = Math.max(1, parseInt(page, 10) || 1);
  const parsedLimit = Math.min(100, Math.max(1, parseInt(limit, 10) || 10));
  
  return {
    limit: parsedLimit,
    offset: (parsedPage - 1) * parsedLimit,
    page: parsedPage,
  };
}
Complete pagination response:
router.get("/products", async (req, res) => {
  const { page, limit } = paginate(req.query.page, req.query.limit);
  
  const { rows, count } = await Product.findAndCountAll({
    offset,
    limit,
    order: [["createdAt", "DESC"]]
  });
  
  res.json({
    products: rows,
    pagination: {
      page,
      limit,
      total: count,
      totalPages: Math.ceil(count / limit),
      hasNext: page * limit < count,
      hasPrev: page > 1
    }
  });
});

Search Implementation

Efficient search with database:
// Use database full-text search when possible
const products = await Product.findAll({
  where: {
    [Op.or]: [
      { name: { [Op.iLike]: `%${query}%` } },
      { description: { [Op.iLike]: `%${query}%` } }
    ]
  }
});
Fuzzy search with fuse.js:
// Only for small datasets or complex fuzzy matching
const fuse = new Fuse(products, {
  keys: ['name', 'description'],
  threshold: 0.3,  // 0 = exact match, 1 = match anything
  includeScore: true
});

const results = fuse.search(query).map(r => r.item);

Build docs developers (and LLMs) love