Back to all articles
Web EngineeringCloud & Systems18 min read

Modern API Design with Node.js & TypeScript: Building for High Concurrency

Designing resilient backend APIs that never stumble during viral traffic spikes. Learn how to architect clean Node.js and TypeScript services with input validation, graceful error handling, and rate limiting.

P
Plexid EngineeringPlexid Engineering
•Sep 28, 2026
Share:
Modern API Design with Node.js & TypeScript: Building for High Concurrency

The High-Concurrency Challenge for Modern Backends

In modern software development, writing an API that functions correctly on a developer's localhost machine is relatively straightforward. A few lines of Express or Fastify code, a basic database query, and you have a working endpoint.

However, the real test of backend engineering begins when your service transitions to production:

  • When a marketing campaign in Delhi NCR or Mumbai generates 5,000 requests per second.
  • When slow cellular networks cause thousands of HTTP connections to linger open for several seconds.
  • When third-party payment gateways experience transient timeout errors.
  • When malicious crawlers flood your login routes with automated requests.
  • In unoptimized backends, these stresses cause the dreaded Event Loop Lag, memory leaks, unhandled promise rejections, and complete server outages.

    Building scalable APIs requires strict discipline: compile-time type safety, runtime input validation, non-blocking asynchronous workflows, intelligent caching, circuit breakers, and graceful failure isolation.

    In this technical blueprint, we share the battle-tested API architecture patterns we use at Plexid to build services that handle tens of thousands of concurrent users with sub-50ms response times.


    Core Principles of Production-Grade API Engineering

    CODE
    Client Request
          |
          v
    [1. Rate Limiting & DDoS Shield] (Prevent abuse & bot hammering)
          |
          v
    [2. Zod Runtime Schema Validation] (Reject malformed payloads with 400 Bad Request)
          |
          v
    [3. Idempotency Key Verification] (Ensure duplicate requests don't double charge)
          |
          v
    [4. Authentication & RBAC Middleware] (Verify JWT / Session tokens)
          |
          v
    [5. Controller Layer] (Orchestrate input & output, no raw business logic)
          |
          v
    [6. Service / Domain Layer] (Pure business logic, unit-testable)
          |
          v
    [7. Data Access Layer (ORM / Query Builder)] (Read-replicas & Redis caching)

    Principle 1: End-to-End Type Safety with TypeScript and Zod

    One of the most dangerous anti-patterns in backend JavaScript is trusting incoming HTTP request bodies. If a client sends an unexpected data type (such as passing a string where an integer is expected, or omitting an email field), your database queries can throw runtime errors or execute unintended logic.

    Using TypeScript alone is insufficient because TypeScript types are completely stripped away at runtime. You need runtime schema validation synchronized with compile-time types.

    The Zod Validation Pattern:

    typescript
    import { z } from 'zod';
    import { Request, Response, NextFunction } from 'express';
    
    // 1. Define the runtime validation schema
    export const CreateOrderSchema = z.object({
      body: z.object({
        productId: z.string().uuid("Invalid product identifier"),
        quantity: z.number().int().positive().max(100, "Maximum 100 units per order"),
        shippingPincode: z.string().regex(/^[1-9][0-9]{5}$/, "Invalid 6-digit Indian PIN code"),
        customerPhone: z.string().regex(/^[6-9]d{9}$/, "Invalid 10-digit Indian mobile number")
      })
    });
    
    // Infer TypeScript type directly from the schema (Single Source of Truth!)
    export type CreateOrderInput = z.infer<typeof CreateOrderSchema>['body'];
    
    // 2. Reusable validation middleware
    export const validate = (schema: z.AnyZodObject) => 
      async (req: Request, res: Response, next: NextFunction) => {
        try {
          await schema.parseAsync({
            body: req.body,
            query: req.query,
            params: req.params,
          });
          return next();
        } catch (error) {
          if (error instanceof z.ZodError) {
            return res.status(400).json({
              status: 'fail',
              errors: error.errors.map(err => ({ field: err.path.join('.'), message: err.message }))
            });
          }
          return next(error);
        }
      };

    By enforcing this middleware before your controller executes, you guarantee that your business logic only ever receives pristine, valid data. Invalid requests are rejected in under 2 milliseconds without touching your database.


    Principle 2: Protecting the Node.js Single-Threaded Event Loop

    Node.js executes JavaScript on a single thread using an event loop. While asynchronous I/O operations (like database queries and network calls) are offloaded to background worker threads, CPU-intensive operations are executed directly on the main event loop.

    If your API handler performs heavy synchronous operations—such as:

  • Parsing a 10MB JSON string
  • Encrypting passwords using synchronous crypto functions
  • Complex image resizing or heavy array filtering
  • The event loop becomes completely blocked. While that single calculation is running, no other incoming request can be accepted or processed, causing latency to spike for every user connected to your service.

    Best Practices for Event Loop Health:

  • Always Use Asynchronous Crypto: Never use bcrypt.hashSync or crypto.pbkdf2Sync in request handlers. Always use their asynchronous promise-based variants (await bcrypt.hash).
  • Offload Heavy Calculations to Worker Threads: For intensive tasks like PDF invoice generation or CSV bulk exports, offload the work to background queue processors (using BullMQ with Redis) rather than executing them inside the HTTP request cycle.
  • Monitor Event Loop Delay: Use tools like clinic.js or Prometheus metrics to monitor nodejs_eventloop_lag_seconds. In a healthy service, event loop lag should remain below 20 milliseconds.

  • Principle 3: Idempotency Keys for Financial & Critical Mutations

    On mobile cellular connections across India, mobile networks frequently drop connection right as an order is submitted. The customer taps "Pay Now", the bank debits their account, but the mobile network disconnects before receiving the API confirmation.

    The customer panics and taps "Pay Now" a second time. Without idempotency protection, the backend processes a duplicate charge and creates two identical orders.

    The Idempotency Key Architecture:

    typescript
    // Idempotency Middleware using Redis
    export const idempotencyMiddleware = async (req: Request, res: Response, next: NextFunction) => {
      const idempotencyKey = req.headers['x-idempotency-key'] as string;
      if (!idempotencyKey) return next();
    
      const cacheKey = `idempotency:${idempotencyKey}`;
      const cachedResponse = await redis.get(cacheKey);
    
      if (cachedResponse) {
        // Return the exact cached response from the previous successful execution
        const parsed = JSON.parse(cachedResponse);
        return res.status(parsed.status).json(parsed.data);
      }
    
      // Intercept the response to cache it upon completion
      const originalJson = res.json.bind(res);
      res.json = (body: any) => {
        redis.setex(cacheKey, 86400, JSON.stringify({ status: res.statusCode, data: body }));
        return originalJson(body);
      };
    
      next();
    };

    Principle 4: Circuit Breaker Pattern for External HTTP Calls

    Modern applications rely on external third-party APIs for SMS OTPs, GST lookups, and payment gateway webhooks.

    If an external payment gateway experiences an outage and begins taking 30 seconds to fail, your backend servers will keep hundreds of HTTP connections open waiting for responses. Within minutes, your server exhausts its socket pool and crashes completely.

    The Fix: Circuit Breakers (using Opossum)

    A circuit breaker monitors external calls. If an external service fails more than 50% of the time over a 10-second window, the circuit trips open.

    Subsequent calls fail immediately with a clean fallback message, preventing your application from freezing or exhausting server memory. Once the external service recovers, the circuit automatically closes and normal traffic resumes.


    Principle 5: Graceful Shutdowns & Health Checks

    When deploying new code or restarting cloud containers in Kubernetes or AWS ECS, the cloud orchestrator sends a SIGTERM signal to your Node.js process.

    If your application abruptly exits upon receiving SIGTERM, any active database transactions are violently severed, leading to corrupted data states or lost customer orders.

    Implementing Graceful Shutdown in Node.js:

    typescript
    // Graceful shutdown handler
    function setupGracefulShutdown(server: any, dbConnection: any) {
      const shutdown = async (signal: string) => {
        console.log("Received " + signal + ". Starting graceful shutdown...");
        
        // 1. Stop accepting new incoming HTTP connections
        server.close(async () => {
          console.log('HTTP server closed.');
    
          // 2. Close active database connections cleanly
          try {
            await dbConnection.close();
            console.log('Database connections closed.');
            process.exit(0);
          } catch (err) {
            console.error('Error during database close:', err);
            process.exit(1);
          }
        });
    
        // 3. Force exit if shutdown hangs longer than 10 seconds
        setTimeout(() => {
          console.error('Graceful shutdown timed out. Forcing process exit.');
          process.exit(1);
        }, 10000);
      };
    
      process.on('SIGTERM', () => shutdown('SIGTERM'));
      process.on('SIGINT', () => shutdown('SIGINT'));
    }

    High-Concurrency Benchmarking Blueprint: Testing with Autocannon

    Never deploy an API to production without subjecting it to real-world stress testing. Using autocannon, you can simulate thousands of concurrent connections from your terminal:

    bash
    # Run a 30-second benchmark simulating 500 concurrent connections
    npx autocannon -c 500 -d 30 -m POST \
      --headers "content-type=application/json" \
      --body '{"productId":"d840ef90-502a-45c1-a5d6-8480db80a37e","quantity":1,"shippingPincode":"800001","customerPhone":"9876543210"}' \
      http://localhost:5000/api/v1/orders

    Key metrics to evaluate:

  • p99 Latency: 99% of requests should complete in under 100 milliseconds.
  • Zero 5xx Errors: Even under 500 concurrent connections, the server should never drop requests or return unhandled exceptions.
  • Steady Memory Footprint: Memory usage in Node.js should remain stable without escalating indefinitely (indicating no memory leaks).

  • Production API Architecture Checklist

    Architecture ComponentImplementation StandardRecommended Solution
    Language & TypingStrict TypeScript (No any types permitted)TypeScript 5.x
    Input ValidationRuntime validation schemas with auto-type generationZod / TypeBox
    IdempotencyX-Idempotency-Key support for payment and order routesRedis Key Expiry (24 hours)
    Circuit BreakersFault isolation on all external HTTP dependenciesOpossum Circuit Breaker
    Rate LimitingTiered rate limiting by IP and authenticated user tokenexpress-rate-limit + Redis store
    Background ProcessingDecoupled Redis queues for asynchronous operationsBullMQ / AWS SQS
    Logging & TracingStructured JSON logging with unique correlation IDsPino / Winston

    Conclusion: Engineering Discipline at Scale

    Building high-throughput backend APIs is not about writing clever, cryptic code. It is about applying consistent engineering discipline: validating inputs rigorously, guarding the event loop, handling failures gracefully, and writing clean, type-safe services that your team can maintain and scale with confidence.

    When your backend architecture is built right from day one, traffic spikes become causes for celebration rather than midnight engineering emergencies.

    Found this guide helpful?
    Share:
    P
    Plexid EngineeringCore Engineering

    Engineers at Plexid specialize in high-concurrency microservices, real-time telephony AI pipelines, distributed web architectures, and sub-second Next.js web applications.

    // READ NEXT

    Related Architecture Publications

    View all
    Web Engineering18 min read

    Hiring a development partner shouldn't feel like a high-stakes gamble. Learn the critical questions, contract clauses, and red flags to protect your budget, timeline, and intellectual property.

    Plexid EngineeringRead
    Cloud & Systems18 min read

    With India's Digital Personal Data Protection (DPDP) Act in full force, data security is no longer optional. Discover the essential technical safeguards every Indian business must implement.

    Plexid EngineeringRead

    Building high-concurrency systems or custom software?

    Our engineering team can design, scope, and deliver your next web or mobile system.