Express Server Setup
Express Server Setup
Basic Server
import express, { Request, Response, NextFunction } from 'express';
import { env } from './config/env.js';
const app = express();
app.use(express.json());
app.get('/health', (req: Request, res: Response) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
app.listen(env.PORT, () => {
console.log(`Server running on port ${env.PORT}`);
});
Typed Route Handlers
interface User {
id: number;
name: string;
email: string;
}
let users: User[] = [];
// GET /api/users
app.get('/api/users', (req: Request, res: Response) => {
res.json(users);
});
// GET /api/users/:id
app.get('/api/users/:id', (req: Request, res: Response) => {
const id = parseInt(req.params.id, 10);
const user = users.find(u => u.id === id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
// POST /api/users
app.post('/api/users', (req: Request, res: Response) => {
const { name, email } = req.body;
const user: User = {
id: users.length + 1,
name,
email
};
users.push(user);
res.status(201).json(user);
});
Typed Request and Response
Typed Request and Response
Custom Request Types
// Typed request with body
interface CreateUserRequest extends Request {
body: {
name: string;
email: string;
role?: 'admin' | 'user';
};
}
// Typed request with params
interface GetUserRequest extends Request {
params: {
id: string;
};
}
// Typed request with query
interface SearchUsersRequest extends Request {
query: {
q?: string;
page?: string;
limit?: string;
};
}
app.get('/api/users', (req: SearchUsersRequest, res: Response) => {
const { q, page = '1', limit = '10' } = req.query;
// types are safe here
});
Typed Response
interface ApiResponse<T> {
data: T;
meta?: {
page: number;
total: number;
limit: number;
};
}
interface ErrorResponse {
error: string;
code: string;
details?: unknown;
}
app.get('/api/users', (req: Request, res: Response<ApiResponse<User[]>>) => {
res.json({ data: users });
});
app.get('/api/users/:id', (req: Request, res: Response<ApiResponse<User> | ErrorResponse>) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).json({ error: 'Not found', code: 'USER_NOT_FOUND' });
}
res.json({ data: user });
});
Typed Middleware
Typed Middleware
Authentication Middleware
import { JwtPayload, verify } from 'jsonwebtoken';
interface AuthenticatedRequest extends Request {
user?: JwtPayload & { id: string; role: string };
}
function authenticate(
req: AuthenticatedRequest,
res: Response,
next: NextFunction
): void {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
res.status(401).json({ error: 'No token provided' });
return;
}
try {
const decoded = verify(token, env.JWT_SECRET) as JwtPayload & {
id: string;
role: string;
};
req.user = decoded;
next();
} catch {
res.status(401).json({ error: 'Invalid token' });
}
}
// Usage
app.get('/api/profile', authenticate, (req: AuthenticatedRequest, res: Response) => {
res.json({ user: req.user });
});
Validation Middleware
import { z } from 'zod';
const CreateUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
role: z.enum(['admin', 'user']).optional()
});
type CreateUserInput = z.infer<typeof CreateUserSchema>;
function validate(schema: z.ZodSchema) {
return (req: Request, res: Response, next: NextFunction): void => {
const result = schema.safeParse(req.body);
if (!result.success) {
res.status(400).json({
error: 'Validation failed',
details: result.error.flatten()
});
return;
}
req.body = result.data;
next();
};
}
app.post('/api/users', validate(CreateUserSchema), (req: Request<{}, {}, CreateUserInput>, res: Response) => {
const user = { id: users.length + 1, ...req.body };
users.push(user);
res.status(201).json(user);
});
Error Handling Middleware
Error Handling Middleware
Typed Error Classes
class AppError extends Error {
constructor(
public statusCode: number,
public code: string,
message: string,
public details?: unknown
) {
super(message);
this.name = 'AppError';
}
}
function errorHandler(
err: Error | AppError,
req: Request,
res: Response,
next: NextFunction
): void {
if (err instanceof AppError) {
res.status(err.statusCode).json({
error: err.message,
code: err.code,
details: err.details
});
return;
}
console.error('Unhandled error:', err);
res.status(500).json({
error: 'Internal server error',
code: 'INTERNAL_ERROR'
});
}
// Usage in routes
app.get('/api/users/:id', (req: Request, res: Response, next: NextFunction) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return next(new AppError(404, 'USER_NOT_FOUND', 'User not found'));
}
res.json(user);
});
app.use(errorHandler);