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:
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:
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:
- Return value mismatches between your route handler and TypeScript type definitions
- Using outdated
@types/express
versions with newer Express patterns - Incorrectly returning Express
Response
objects from handlers
Recommended Solutions
Solution 1: Avoid Returning Response Objects (Best Practice)
The most robust solution is to modify how you handle responses in your route handlers:
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 enforcevoid
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:
// ❌ 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:
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:
- Response objects are returned: Route handlers should not return
Response
objects - Type definitions changed:
@types/express
v5+ enforces proper handler signatures - Async handlers complicate types: Async functions implicitly return Promises
Best Practices for Express+TypeScript
Handler signatures should always return
void
:ts// Correct (req: Request, res: Response): void => {} // Also correct for async async (req: Request, res: Response): Promise<void> => {}
Always end requests explicitly:
tsres.send() // Send empty response res.json() // Send JSON response res.end() // End without data res.redirect() // Redirect request
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'); }
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.