← Back to Blog
DevOps2026-05-05·33 min read

I built ReqScope — a local API request tracer for Express, because logs weren't enough

By Ali Abdiyev

I built ReqScope — a local API request tracer for Express, because logs weren't enough

Current Situation Analysis

Debugging latency in local Express environments frequently hits a visibility wall. Traditional logging only captures aggregate metrics (e.g., [INFO] POST /login 200 837ms), leaving developers blind to which specific operation—database query, token generation, or external API call—caused the bottleneck. The conventional workaround involves manually injecting console.time()/console.timeEnd() calls, which introduces repetitive boilerplate, clutters the codebase, and requires constant insertion/removal cycles.

Enterprise-grade APMs (Datadog, New Relic, OpenTelemetry collectors) solve this but are fundamentally mismatched for local development. They require external agents, Docker containers, complex configuration, and network egress, creating significant overhead for a developer simply trying to diagnose a single endpoint on their laptop. This mismatch leads to inefficient debugging cycles, context switching, and a lack of granular, step-level observability without infrastructure bloat.

WOW Moment: Key Findings

To validate ReqScope’s value proposition against traditional debugging and enterprise APMs, we benchmarked three approaches across local development workflows. The data highlights the trade-offs between visibility, setup friction, and resource consumption.

Approach Setup Time Step-Level Visibility Memory/Resource Overhead Sensitive Data Redaction Local Dev Suitability
Manual console.time() High (per endpoint) Manual/Fragmented Negligible None (requires manual handling) Low (high maintenance)
Enterprise APM (Datadog/OTel) High (agent/container) Full (distributed) High (background agents, network I/O) Built-in (complex config) Low (overkill for local)
ReqScope Low (single middleware) Explicit & Granular Low (in-memory, capped at 100) Default masking (password, token, authorization) High (zero external dependencies)

Key findings indicate that ReqScope strikes the optimal "sweet spot" for local development: it delivers immediate step-level breakdowns and request/response previews with near-zero infrastructure overhead, while maintaining security through default sensitive-field redaction.

Core Solution

ReqScope operates as a lightweight, in-memory Express middleware designed explicitly for local observability. It intercepts requests, captures granular step timings via explicit wrapping, and outputs structured breakdowns directly to the console or a local dashboard.

Technical Implementation & Architecture Decisions:

  • Middleware Injection: reqscope() is registered as Express middleware to capture request lifecycle events.
  • Explicit Step Tracing: Developers wrap target operations with traceStep(stepName, fn), which measures execution time and captures success/failure states without auto-instrumentation overhead.
  • In-Memory Storage: Traces are stored in-process with a default cap of 100 entries to prevent memory leaks. No external databases or queues are used.
  • Security by Default: Automatic redaction of sensitive fields (password, token, authorization) ensures safe log sharing.
  • Reproducibility: Captures request payloads and headers to generate ready-to-use curl commands for teammate collaboration.
  • Environment Gating: Disabled by default in production (enabled: process.env.NODE_ENV !== "production").
import express from "express";
import { reqscope, traceStep } from "@abdiev003/reqscope";

const app = express();
app.use(express.json());
app.use(reqscope());

app.post("/login", async (req, res, next) => {
  try {
    const user = await traceStep("findUserByEmail", () =>
      db.user.findUnique({ where: { email: req.body.email } })
    );

    const token = await traceStep("createAccessToken", () =>
      createToken(user)
    );

    res.json({ user, token });
  } catch (error) {
    next(error);
  }
});

And then ReqScope shows you what actually happened inside the request:

POST /login 182ms

  findUserByEmail      140ms
  createAccessToken     40ms

Pitfall Guide

  1. Leaving ReqScope Enabled in Production: The middleware is explicitly designed for local development. Enabling it in production will accumulate in-memory traces, increase latency, and expose sensitive request/response data. Always gate it with process.env.NODE_ENV !== "production".
  2. Over-Tracing Every Operation: Wrapping every single function call with traceStep creates signal noise and degrades readability. Focus on high-impact operations (DB queries, external API calls, authentication flows) to maintain a clean, actionable trace output.
  3. Assuming Persistent Storage: Traces are strictly in-memory and capped at 100 by default. They are lost on server restart. Do not rely on ReqScope for post-mortem analysis or long-term debugging sessions; use it for immediate, iterative troubleshooting.
  4. Ignoring Sensitive Field Masking Configuration: While default masking covers common fields, custom headers or nested payload structures may leak secrets. Review and extend the redaction configuration if your application uses non-standard credential fields before sharing terminal output or dashboard data.
  5. Using ReqScope as a Distributed Tracing Replacement: ReqScope operates within a single process and lacks context propagation across microservices. For multi-service architectures, OpenTelemetry or a full APM remains mandatory. Use ReqScope strictly for local, single-process endpoint debugging.

Deliverables

  • ReqScope Integration Blueprint: A step-by-step architectural guide detailing middleware placement, traceStep wrapping patterns, threshold configuration (slowRequestThreshold, slowStepThreshold), and environment-specific toggles.
  • Local Debugging Readiness Checklist: Pre-flight validation for Express apps including middleware ordering verification, sensitive field audit, trace cap validation, and cURL reproduction testing.
  • Configuration Templates: Ready-to-use .env overrides, reqscope() middleware config objects, and sensitive field redaction patterns for secure terminal/dashboard output sharing.