13.2 Express + TypeScript — Request/Response Types and Middleware
Installing Express + TypeScript
npm install express
npm install --save-dev @types/express typescript @types/node
Request/Response Generic Types
Express's Request and Response support generic types.
// Request<Params, ResBody, ReqBody, ReqQuery>
// Response<ResBody>
import { Request, Response } from 'express'
// Full type specification
interface UserParams {
id: string
}
interface CreateUserBody {
name: string
email: string
role?: 'admin' | 'user'
}
interface UserQuery {
page?: string
limit?: string
sort?: 'name' | 'email' | 'createdAt'
}
interface UserResponse {
id: string
name: string
email: string
}
// Type-safe handler
app.get<UserParams, UserResponse>(
'/users/:id',
async (req: Request<UserParams>, res: Response<UserResponse>) => {
const { id } = req.params // string (type-safe)
const user = await getUserById(id)
res.json(user)
}
)
app.post<{}, UserResponse, CreateUserBody, UserQuery>(
'/users',
async (req, res) => {
const { name, email, role } = req.body // Type-safe
const { page } = req.query // string | undefined
const user = await createUser({ name, email, role })
res.status(201).json(user)
}
)
Extending Request Types (Module Augmentation)
// src/types/express.d.ts
import 'express'
declare module 'express-serve-static-core' {
interface Request {
user?: AuthUser
requestId: string
startTime: number
logger: Logger
}
}
Middleware Types
import { Request, Response, NextFunction } from 'express'
// Regular middleware
const requestLogger = (req: Request, res: Response, next: NextFunction) => {
const start = Date.now()
console.log(`→ ${req.method} ${req.path}`)
res.on('finish', () => {
const duration = Date.now() - start
console.log(`← ${res.statusCode} ${duration}ms`)
})
next()
}
// Error middleware (4 parameters)
const errorHandler = (
error: Error,
req: Request,
res: Response,
next: NextFunction
) => {
console.error(error)
res.status(500).json({ error: error.message })
}
Authentication Middleware
// middleware/auth.ts
import { Request, Response, NextFunction } from 'express'
import jwt from 'jsonwebtoken'
interface JwtPayload {
userId: string
role: 'admin' | 'user'
}
export async function authMiddleware(
req: Request,
res: Response,
next: NextFunction
) {
try {
const token = req.headers.authorization?.replace('Bearer ', '')
if (!token) throw new Error('No token')
const payload = jwt.verify(token, process.env.JWT_SECRET!) as JwtPayload
req.user = await getUserById(payload.userId)
next()
} catch {
res.status(401).json({ error: 'Authentication failed' })
}
}
// Permission check middleware factory
export function requireRole(...roles: string[]) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user || !roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Access denied' })
}
next()
}
}
Router Type Pattern
// routes/user.routes.ts
import { Router } from 'express'
import { authMiddleware, requireRole } from '../middleware/auth'
import { UserController } from '../controllers/user.controller'
import { validateBody } from '../middleware/validate'
import { CreateUserSchema, UpdateUserSchema } from '../schemas/user.schema'
const router = Router()
const controller = new UserController()
router.get('/', controller.getAll)
router.get('/:id', controller.getById)
router.post('/', authMiddleware, validateBody(CreateUserSchema), controller.create)
router.put('/:id', authMiddleware, validateBody(UpdateUserSchema), controller.update)
router.delete('/:id', authMiddleware, requireRole('admin'), controller.delete)
export { router as userRouter }
Type-Safe Error Handling
// utils/errors.ts
export class AppError extends Error {
constructor(
message: string,
public statusCode: number = 500,
public code?: string
) {
super(message)
this.name = 'AppError'
}
}
export class ValidationError extends AppError {
constructor(
message: string,
public fieldErrors?: Record<string, string[]>
) {
super(message, 422, 'VALIDATION_ERROR')
}
}
export class NotFoundError extends AppError {
constructor(resource: string) {
super(`${resource} not found.`, 404, 'NOT_FOUND')
}
}
// middleware/error.ts
import { Request, Response, NextFunction } from 'express'
import { AppError } from '../utils/errors'
export function errorHandler(
error: unknown,
req: Request,
res: Response,
next: NextFunction
) {
if (error instanceof AppError) {
return res.status(error.statusCode).json({
error: error.message,
code: error.code,
})
}
if (error instanceof Error) {
console.error('Unexpected error:', error)
return res.status(500).json({ error: 'An internal server error occurred.' })
}
res.status(500).json({ error: 'Unknown error' })
}
Pro Tips
Auto-validate request body with Zod
// middleware/validate.ts
import { Request, Response, NextFunction } from 'express'
import { ZodSchema } from 'zod'
export function validateBody<T>(schema: ZodSchema<T>) {
return (req: Request, res: Response, next: NextFunction) => {
const result = schema.safeParse(req.body)
if (!result.success) {
return res.status(422).json({
error: 'Validation failed',
details: result.error.flatten().fieldErrors,
})
}
req.body = result.data // Replace with parsed data
next()
}
}