TS with Node.js (Express)
TypeScript secures your Express APIs like palace gate security — every request is verified.
Express.js is the most popular web framework for Node.js, and TypeScript makes it significantly safer. Every route handler receives a Request and Response object — and TypeScript lets you type BOTH the incoming data (params, body, query) and the outgoing data (response body). This turns your API into a type-safe fortress, like the security at the Nizam's palace gates.
Imagine the security checkpost at the Nizam's palace in Hyderabad. Every person entering the palace must present proper identification (params — who are you?). They must state their purpose of visit (body — what are you here for?). The guards verify the information (middleware — check the credentials). And they decide which area to grant access to (response — what data to send back).
TypeScript is the guard who checks everything at every gate. The visitor doesn't just say "I'm here to see the Nizam" — they present a typed request: Request<{ visitorId: string }, { name: string }> with a specific purpose: { reason: "meeting", department: "finance" }. The guard knows EXACTLY what information to expect, and TypeScript ensures that the information matches the expected types. If something is wrong, the guard catches it at the gate — before the visitor enters the palace.
The core of Express type safety is the Request and Response types, which accept generic parameters to specify the shape of your data.
import express, {
Request,
Response,
NextFunction
} from "express";
const app = express();
app.use(express.json()); // body parser
interface User {
id: string;
name: string;
email: string;
}
// GET route with typed params
app.get<
{ id: string }, // Params
User // Response body
>(
"/users/:id",
(req, res) => {
// req.params.id is string ✓
const user: User = {
id: req.params.id,
name: "Imran",
email: "imran@example.com"
};
res.json(user);
// res.json() expects User ✓
}
);
Complete Example with All Type Parameters
interface CreateUserBody {
name: string;
email: string;
}
interface User {
id: string;
name: string;
email: string;
}
app.post<
{}, // Params (none)
User, // Response body
CreateUserBody, // Request body
{} // Query (none)
>(
"/api/users",
(req, res) => {
// req.body.name is string ✓
// req.body.email is string ✓
const user: User = {
id: "123",
name: req.body.name,
email: req.body.email,
};
res.status(201).json(user);
}
);Express middleware and route handlers also benefit from TypeScript typing. Let's look at common patterns.
Typed Router
const router = express.Router();
router.get("/users/:id", (
req: Request<{ id: string }>,
res: Response<User>
) => {
// ...
});
router.post("/users", (
req: Request<
{},
User,
CreateUserBody
>,
res: Response<User>
) => {
// ...
});
app.use("/api", router);
Custom Middleware with Typed Request
// Extend Request via declaration merging
declare module "express" {
interface Request {
user?: User;
}
}
// Auth middleware
function authMiddleware(
req: Request,
res: Response,
next: NextFunction
): void {
const token = req.headers.authorization;
if (!token) {
res.status(401).json({
error: "Unauthorized"
});
return;
}
// After verification:
req.user = { id: "1", name: "Imran" };
next();
}
// Usage in route
app.get(
"/profile",
authMiddleware,
(req, res) => {
// req.user is available!
res.json(req.user);
}
);
Error Handling Middleware
// Must have all 4 parameters!
function errorHandler(
err: Error,
req: Request,
res: Response,
next: NextFunction
): void {
console.error(err.stack);
res.status(500).json({
error: err.message
});
}
app.use(errorHandler);Express + TypeScript has several common pitfalls. Let's walk through the most important ones.
Trap 1: Missing express.json() middleware
Without app.use(express.json()), req.body remains undefined. TypeScript types req.body based on your generic parameter, but the actual value at runtime is undefined. Always add body-parsing middleware.
Trap 2: Incomplete type parameters in Request
// ❌ All generic params are optional,
// leaving them out gives any
app.get("/users/:id", (req, res) => {
req.params.id; // any!
req.body; // any!
});
// ✅ Specify the ones you use
app.get<{ id: string }>(
"/users/:id",
(req, res) => {
req.params.id; // string ✓
}
);
Trap 3: Forgetting NextFunction in error middleware
Express identifies error middleware by the 4-parameter signature. Without the 4th parameter (next), Express treats it as regular middleware, and errors pass through unhandled.
Trap 4: Not extending Request for user/auth properties
When you add properties like req.user in middleware, TypeScript doesn't know about them unless you use declaration merging. Create a types.d.ts file to extend the Express Request interface.
Here's your complete cheatsheet for Express + TypeScript!
Request Type Parameters:
Request<P, ResBody, ReqBody, Q>
// P = Params (default: {})
// ResBody = Response body (default: any)
// ReqBody = Request body (default: any)
// Q = Query string (default: qs.ParsedQs)
Basic Route Patterns:
app.get<{ id: string }, User>("/path", handler)
app.post<{}, User, CreateBody>("/path", handler)
app.put<{ id: string }, User, UpdateBody>("/path", handler)
app.delete<{ id: string }>("/path", handler)
Middleware & Error Handling:
app.use(express.json());
app.use(authMiddleware);
app.use(errorHandler); // 4 params!
Key Rules:
- Always specify at least the params type parameter for routes with URL parameters
- Use
app.use(express.json())before any route that receives JSON bodies - Use declaration merging to extend Request for custom properties like req.user
- Error middleware MUST have all 4 parameters (err, req, res, next)
- Use Router for modular route organization
The Golden Rule: "Express + TypeScript is like palace gate security — every request is checked at every gate. The params are the visitor's ID, the body is their purpose, the middleware is the security clearance, and the response is the access granted. Type the parameters, and your API becomes a secure, well-guarded palace, bhai!"
Key Takeaways
- Express Request has 4 generic type parameters: Params, ResBody, ReqBody, Query
- Always use app.use(express.json()) to parse JSON request bodies
- Type route handler parameters with Request
for full safety
- Error-handling middleware must have exactly 4 parameters: err, req, res, next
- Use declaration merging to extend Express Request for custom properties like req.user
- Use express.Router() for modular route organization with typed handlers
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login