Skip to content

Express Route Handler TypeScript Error: No Overload Matches

Problem Statement

When using TypeScript with Express.js, developers often encounter the following type error in route handlers:

ts
No overload matches this call.
  The last overload gave the following error.
    Argument of type '(req: Request, res: Response) => Promise<express.Response<any, Record<string, any>> | undefined>' is not assignable to parameter of type 'Application<Record<string, any>>'.
      Type '(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>) => Promise<...>' is missing the following properties from type 'Application<Record<string, any>>': init, defaultConfiguration, engine, set, and 63 more.

This error typically occurs in code like this:

ts
import express from 'express';
import { Request, Response } from 'express';

const router = express.Router();

router.post('/endpoint', async (req: Request, res: Response) => {
  // Error appears here
});

The core issues causing this mismatch are:

  1. Return value mismatches between your route handler and TypeScript type definitions
  2. Using outdated @types/express versions with newer Express patterns
  3. Incorrectly returning Express Response objects from handlers

Solution 1: Avoid Returning Response Objects (Best Practice)

The most robust solution is to modify how you handle responses in your route handlers:

ts
router.post('/create-checkout-session', async (req: Request, res: Response) => {
  try {
    // Business logic here
    
    // Send response without returning it
    res.status(200).json({ success: true });
    return; // Explicit return to exit handler
  } catch (error) {
    // Handle error without returning the response
    res.status(500).json({ error: 'Operation failed' });
    return;
  }
});

Why this works:

  • Express doesn't use return values from route handlers
  • New @types/express versions enforce void return types
  • Explicit returns prevent accidental double response sending

Solution 2: Upgrade Approach for Middleware Chaining

If you're returning response objects to chain middleware, refactor your approach:

ts
// ❌ Problematic pattern:
return res.status(200).json(data);

// ✅ Correct pattern:
res.status(200).json(data);
return; // Exit handler but don't return response object

// Example with middleware chaining:
const validateInput = (req: Request, res: Response, next: NextFunction) => {
  if (!req.body) {
    res.status(400).json({ error: 'Invalid input' });
    return; // Exit early
  }
  next(); // Proceed to next handler
}

router.post('/endpoint', validateInput, mainHandler);

Solution 3: Update Type Definitions (Temporary Fix)

If you must maintain existing return patterns temporarily:

bash
npm install -D @types/express@4

Caveats:

  • Only recommended for legacy code migrations
  • Blocks TypeScript improvements in newer versions
  • Doesn't fix the underlying architectural issue

Why the Error Occurs

The error happens when:

  1. Response objects are returned: Route handlers should not return Response objects
  2. Type definitions changed: @types/express v5+ enforces proper handler signatures
  3. Async handlers complicate types: Async functions implicitly return Promises

Best Practices for Express+TypeScript

  1. Handler signatures should always return void:

    ts
    // Correct
    (req: Request, res: Response): void => {}
    
    // Also correct for async
    async (req: Request, res: Response): Promise<void> => {}
  2. Always end requests explicitly:

    ts
    res.send()   // Send empty response
    res.json()   // Send JSON response
    res.end()    // End without data
    res.redirect() // Redirect request
  3. Avoid returning anything except early exits:

    ts
    // ✅ Good
    if (error) {
      res.status(500).send('Error');
      return;
    }
    
    // ❌ Bad
    if (error) {
      return res.status(500).send('Error');
    }
  4. Keep dependencies updated:

    json
    "dependencies": {
      "express": "^5.0.0",
      "@types/express": "^5.0.0",
      "typescript": "^5.0.0"
    }

Migration Tip

When upgrading @types/express, search your codebase for return res. patterns and remove the return statements. This ensures compatibility with modern type definitions.

Conclusion

The "No overload matches this call" error occurs when TypeScript detects a mismatch between your route handler's return type and Express's expected function signature. The optimal solution is to refactor your handlers to avoid returning Response objects, instead using res.send(), res.json(), or similar methods to send responses, then exit early with return when needed.

While downgrading @types/express provides a quick fix, it obscures the underlying issue and prevents you from benefiting from improved type safety in newer versions. Adopting the recommended handler patterns ensures compatibility with both current and future versions of Express and TypeScript.