Skip to content
BackendNodeJs

8. Express + TypeScript – Type-Safe API कैसे बनाएं 2026

April 25, 2026 21 min read

नमस्ते दोस्तों! 🙏
स्वागत है The Easy Master पर!

क्या तुमने कभी सोचा है – Express.js API TypeScript ke saath type-safe kaise banayein? Runtime errors se bachna है, autocomplete चाहिए, production mein bugs kam चाहिए।

TypeScript Express.js ko type-safe बनाता है – req, res, next – सबके types define कर सकते हो।

Express TypeScript type-safe API Hindi में समझना बहुत जरूरी है क्योंकि:

  • Type safety – runtime errors 70% kam
  • Better autocomplete – IDE suggestions
  • Self-documenting – code hi documentation
  • Refactoring – safe code changes
  • Interview mein pakka TypeScript + Express questions puche jayenge

Aaj kya seekhoge?

TopicKya Seekhega?
SetupTypeScript + Express project
TypesRequest, Response, NextFunction
Custom TypesInterfaces for req.body, req.query
Middleware TypesTyped middleware
Error HandlingTyped error handler
ControllersClass-based with types
DTOsData transfer objects
Real ProjectComplete type-safe API

Kya tumhe pata hai?
TypeScript ts-node-dev se run करो – auto-restart + type checking – development super fast!

तो चलिए शुरू करते हैं – Express TypeScript type-safe API Hindi सीखने का सफर! 🚀

1. Why TypeScript with Express? – Benefits

JavaScript vs TypeScript:

Code
// ❌ JavaScript – No type safety
app.get('/api/users/:id', (req, res) => {
  const id = req.params.id;
  // id is string, but we need number!
  // No idea what's in req.body
  // No autocomplete!
  res.json({ id });
});
Code
// ✅ TypeScript – Type safe!
interface User {
  id: number;
  name: string;
  email: string;
}

app.get('/api/users/:id', (req: Request, res: Response) => {
  const id = parseInt(req.params.id); // TypeScript knows it's string
  // Full autocomplete for req, res
  // IDE shows all available methods
  res.json({ id } as User);
});

Benefits Summary:

BenefitExplanation
Type SafetyWrong types = compile error
AutocompleteIDE suggestions
RefactoringSafe code changes
Self-documentingCode as documentation
Early Error DetectionCatch errors before runtime
Better Team CollaborationClear interfaces

Express TypeScript type-safe API Hindi में हम production-ready setup बनाएंगे।

2. Project Setup – Installation

Step 1: Create Project

Code
mkdir express-ts-api
cd express-ts-api
npm init -y

Step 2: Install Dependencies

Code
# Production dependencies
npm install express

# TypeScript dependencies
npm install -D typescript @types/node @types/express

# Development tools
npm install -D ts-node nodemon ts-node-dev

Step 3: Create Source Folder

Code
mkdir src
touch src/index.ts

Step 4: package.json Scripts

Code
{
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "ts-node-dev --respawn --transpile-only src/index.ts",
    "dev:watch": "nodemon --exec ts-node src/index.ts",
    "type-check": "tsc --noEmit"
  }
}

3. TypeScript Configuration – tsconfig.json

tsconfig.json:

Code
{
  "compilerOptions": {
    // Language and Environment
    "target": "ES2022",
    "lib": ["ES2022"],
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    
    // Modules
    "module": "commonjs",
    "moduleResolution": "node",
    "rootDir": "./src",
    "outDir": "./dist",
    "resolveJsonModule": true,
    
    // Emit
    "sourceMap": true,
    "removeComments": true,
    "noEmitOnError": true,
    
    // Interop Constraints
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "allowSyntheticDefaultImports": true,
    
    // Type Checking
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

4. Basic Server – Types with Express

Basic Express Server with Types:

Code
// src/index.ts
import express, { Express, Request, Response, NextFunction } from 'express';

const app: Express = express();
const PORT: number = parseInt(process.env.PORT || '3000', 10);

// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Routes
app.get('/', (req: Request, res: Response) => {
  res.json({ 
    message: 'Welcome to TypeScript Express API!',
    timestamp: new Date().toISOString()
  });
});

app.get('/health', (req: Request, res: Response) => {
  res.status(200).json({ 
    status: 'OK', 
    uptime: process.uptime(),
    timestamp: new Date().toISOString()
  });
});

// 404 handler
app.use('*', (req: Request, res: Response) => {
  res.status(404).json({ 
    error: `Cannot ${req.method} ${req.originalUrl}` 
  });
});

// Global error handler
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  console.error(err.stack);
  res.status(500).json({ 
    error: process.env.NODE_ENV === 'production' 
      ? 'Internal server error' 
      : err.message 
  });
});

// Start server
app.listen(PORT, () => {
  console.log(`🚀 Server running on http://localhost:${PORT}`);
  console.log(`📝 Environment: ${process.env.NODE_ENV || 'development'}`);
});

5. Request Types – Params, Query, Body

Defining Custom Request Types:

Code
// src/types/user.types.ts
export interface UserParams {
  id: string;
}

export interface UserQuery {
  page?: string;
  limit?: string;
  search?: string;
  sortBy?: string;
  order?: 'asc' | 'desc';
}

export interface CreateUserBody {
  name: string;
  email: string;
  password: string;
  age?: number;
}

export interface UpdateUserBody {
  name?: string;
  email?: string;
  age?: number;
}

export interface UserResponse {
  id: number;
  name: string;
  email: string;
  age?: number;
  createdAt: Date;
  updatedAt: Date;
}

Using Request Types:

Code
// src/routes/users.ts
import { Router, Request, Response } from 'express';
import { 
  UserParams, 
  UserQuery, 
  CreateUserBody, 
  UpdateUserBody,
  UserResponse 
} from '../types/user.types';

const router = Router();

// In-memory database
let users: UserResponse[] = [];
let nextId = 1;

// GET /api/users - with query params
router.get('/', (req: Request<{}, {}, {}, UserQuery>, res: Response) => {
  const { page = '1', limit = '10', search, sortBy = 'id', order = 'asc' } = req.query;
  
  let filteredUsers = [...users];
  
  if (search) {
    filteredUsers = filteredUsers.filter(u => 
      u.name.toLowerCase().includes(search.toLowerCase()) ||
      u.email.toLowerCase().includes(search.toLowerCase())
    );
  }
  
  // Sort
  filteredUsers.sort((a, b) => {
    const aVal = a[sortBy as keyof UserResponse];
    const bVal = b[sortBy as keyof UserResponse];
    if (order === 'asc') {
      return aVal > bVal ? 1 : -1;
    } else {
      return aVal < bVal ? 1 : -1;
    }
  });
  
  // Pagination
  const pageNum = parseInt(page);
  const limitNum = parseInt(limit);
  const start = (pageNum - 1) * limitNum;
  const paginatedUsers = filteredUsers.slice(start, start + limitNum);
  
  res.json({
    success: true,
    data: paginatedUsers,
    pagination: {
      page: pageNum,
      limit: limitNum,
      total: filteredUsers.length,
      totalPages: Math.ceil(filteredUsers.length / limitNum)
    }
  });
});

// GET /api/users/:id
router.get('/:id', (req: Request<UserParams>, res: Response) => {
  const id = parseInt(req.params.id);
  const user = users.find(u => u.id === id);
  
  if (!user) {
    return res.status(404).json({ error: 'User not found' });
  }
  
  res.json({ success: true, data: user });
});

// POST /api/users
router.post('/', (req: Request<{}, {}, CreateUserBody>, res: Response) => {
  const { name, email, password, age } = req.body;
  
  // Validation
  if (!name || !email || !password) {
    return res.status(400).json({ error: 'Name, email, and password are required' });
  }
  
  // Check duplicate email
  const existingUser = users.find(u => u.email === email);
  if (existingUser) {
    return res.status(409).json({ error: 'Email already exists' });
  }
  
  const newUser: UserResponse = {
    id: nextId++,
    name,
    email,
    age,
    createdAt: new Date(),
    updatedAt: new Date()
  };
  
  users.push(newUser);
  
  // Don't return password
  res.status(201).json({ success: true, data: newUser });
});

// PUT /api/users/:id
router.put('/:id', (req: Request<UserParams, {}, UpdateUserBody>, res: Response) => {
  const id = parseInt(req.params.id);
  const { name, email, age } = req.body;
  
  const userIndex = users.findIndex(u => u.id === id);
  
  if (userIndex === -1) {
    return res.status(404).json({ error: 'User not found' });
  }
  
  users[userIndex] = {
    ...users[userIndex],
    name: name || users[userIndex].name,
    email: email || users[userIndex].email,
    age: age !== undefined ? age : users[userIndex].age,
    updatedAt: new Date()
  };
  
  res.json({ success: true, data: users[userIndex] });
});

// DELETE /api/users/:id
router.delete('/:id', (req: Request<UserParams>, res: Response) => {
  const id = parseInt(req.params.id);
  const userIndex = users.findIndex(u => u.id === id);
  
  if (userIndex === -1) {
    return res.status(404).json({ error: 'User not found' });
  }
  
  users.splice(userIndex, 1);
  res.status(204).send();
});

export default router;

6. Response Types – Typed Responses

Response Type Helpers:

Code
// src/types/response.types.ts
export interface ApiResponse<T = any> {
  success: boolean;
  message?: string;
  data?: T;
  error?: {
    code: string;
    message: string;
    details?: any;
  };
  timestamp: string;
}

export interface PaginatedResponse<T> {
  success: boolean;
  data: T[];
  pagination: {
    page: number;
    limit: number;
    total: number;
    totalPages: number;
    hasNext: boolean;
    hasPrev: boolean;
  };
  timestamp: string;
}

// src/utils/response.ts
import { Response } from 'express';
import { ApiResponse, PaginatedResponse } from '../types/response.types';

export class ResponseHandler {
  static success<T>(res: Response, data: T, message?: string, statusCode: number = 200): Response {
    const response: ApiResponse<T> = {
      success: true,
      message,
      data,
      timestamp: new Date().toISOString()
    };
    return res.status(statusCode).json(response);
  }
  
  static created<T>(res: Response, data: T, message: string = 'Resource created successfully'): Response {
    return this.success(res, data, message, 201);
  }
  
  static noContent(res: Response): Response {
    return res.status(204).send();
  }
  
  static error(res: Response, message: string, statusCode: number = 500, code?: string, details?: any): Response {
    const response: ApiResponse = {
      success: false,
      error: {
        code: code || 'INTERNAL_ERROR',
        message,
        details
      },
      timestamp: new Date().toISOString()
    };
    return res.status(statusCode).json(response);
  }
  
  static badRequest(res: Response, message: string, details?: any): Response {
    return this.error(res, message, 400, 'BAD_REQUEST', details);
  }
  
  static unauthorized(res: Response, message: string = 'Unauthorized'): Response {
    return this.error(res, message, 401, 'UNAUTHORIZED');
  }
  
  static forbidden(res: Response, message: string = 'Forbidden'): Response {
    return this.error(res, message, 403, 'FORBIDDEN');
  }
  
  static notFound(res: Response, message: string = 'Resource not found'): Response {
    return this.error(res, message, 404, 'NOT_FOUND');
  }
  
  static conflict(res: Response, message: string, details?: any): Response {
    return this.error(res, message, 409, 'CONFLICT', details);
  }
  
  static paginated<T>(
    res: Response, 
    data: T[], 
    page: number, 
    limit: number, 
    total: number
  ): Response {
    const totalPages = Math.ceil(total / limit);
    const response: PaginatedResponse<T> = {
      success: true,
      data,
      pagination: {
        page,
        limit,
        total,
        totalPages,
        hasNext: page < totalPages,
        hasPrev: page > 1
      },
      timestamp: new Date().toISOString()
    };
    return res.json(response);
  }
}

Using Response Handler:

Code
// src/routes/users.ts (updated)
import { Router, Request } from 'express';
import { ResponseHandler } from '../utils/response';
import { UserParams, CreateUserBody, UserResponse } from '../types/user.types';

const router = Router();

router.get('/', (req: Request, res) => {
  const users: UserResponse[] = getUsers();
  ResponseHandler.success(res, users, 'Users fetched successfully');
});

router.get('/:id', (req: Request<UserParams>, res) => {
  const user = users.find(u => u.id === parseInt(req.params.id));
  
  if (!user) {
    return ResponseHandler.notFound(res, 'User not found');
  }
  
  ResponseHandler.success(res, user);
});

router.post('/', (req: Request<{}, {}, CreateUserBody>, res) => {
  const { name, email, password } = req.body;
  
  if (!name || !email || !password) {
    return ResponseHandler.badRequest(res, 'Name, email, and password are required');
  }
  
  const existingUser = users.find(u => u.email === email);
  if (existingUser) {
    return ResponseHandler.conflict(res, 'Email already exists');
  }
  
  const newUser = createUser(req.body);
  ResponseHandler.created(res, newUser);
});

7. Middleware Types – Typed Middleware

Typed Middleware Examples:

Code
// src/middleware/auth.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { ResponseHandler } from '../utils/response';

export interface AuthRequest extends Request {
  user?: {
    id: number;
    email: string;
    role: string;
  };
}

export const authenticate = (
  req: AuthRequest, 
  res: Response, 
  next: NextFunction
): void => {
  const authHeader = req.headers.authorization;
  
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    ResponseHandler.unauthorized(res, 'No token provided');
    return;
  }
  
  const token = authHeader.split(' ')[1];
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET!) as {
      id: number;
      email: string;
      role: string;
    };
    req.user = decoded;
    next();
  } catch (error) {
    ResponseHandler.unauthorized(res, 'Invalid or expired token');
  }
};

export const authorize = (...roles: string[]) => {
  return (req: AuthRequest, res: Response, next: NextFunction): void => {
    if (!req.user) {
      ResponseHandler.unauthorized(res, 'Authentication required');
      return;
    }
    
    if (!roles.includes(req.user.role)) {
      ResponseHandler.forbidden(res, 'Insufficient permissions');
      return;
    }
    
    next();
  };
};
Code
// src/middleware/validation.ts
import { Request, Response, NextFunction } from 'express';
import { validationResult, ValidationChain } from 'express-validator';
import { ResponseHandler } from '../utils/response';

export const validate = (validations: ValidationChain[]) => {
  return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    await Promise.all(validations.map(validation => validation.run(req)));
    
    const errors = validationResult(req);
    if (errors.isEmpty()) {
      return next();
    }
    
    const formattedErrors = errors.array().map(err => ({
      field: err.param,
      message: err.msg
    }));
    
    ResponseHandler.badRequest(res, 'Validation failed', formattedErrors);
  };
};

// src/middleware/logger.ts
import { Request, Response, NextFunction } from 'express';

export const logger = (req: Request, res: Response, next: NextFunction): void => {
  const start = Date.now();
  
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
  
  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`[${req.method}] ${req.url} - ${res.statusCode} - ${duration}ms`);
  });
  
  next();
};

Using Typed Middleware:

Code
// src/routes/protected.ts
import { Router } from 'express';
import { authenticate, authorize, AuthRequest } from '../middleware/auth';
import { ResponseHandler } from '../utils/response';

const router = Router();

// All routes need authentication
router.use(authenticate);

router.get('/profile', (req: AuthRequest, res) => {
  ResponseHandler.success(res, req.user, 'Profile fetched');
});

router.get('/admin', 
  authorize('admin'), 
  (req: AuthRequest, res) => {
    ResponseHandler.success(res, { message: 'Admin access granted' });
  }
);

router.get('/moderator', 
  authorize('admin', 'moderator'), 
  (req: AuthRequest, res) => {
    ResponseHandler.success(res, { message: 'Moderator access granted' });
  }
);

8. Custom Types – Interfaces & Types

Complete Type Definitions:

Code
// src/types/index.ts
export * from './user.types';
export * from './product.types';
export * from './order.types';
export * from './response.types';
Code
// src/types/product.types.ts
export interface Product {
  id: number;
  name: string;
  description: string;
  price: number;
  category: string;
  inStock: boolean;
  createdAt: Date;
  updatedAt: Date;
}

export interface CreateProductBody {
  name: string;
  description: string;
  price: number;
  category: string;
  inStock?: boolean;
}

export interface UpdateProductBody {
  name?: string;
  description?: string;
  price?: number;
  category?: string;
  inStock?: boolean;
}

export interface ProductQuery {
  category?: string;
  minPrice?: string;
  maxPrice?: string;
  inStock?: string;
  search?: string;
  page?: string;
  limit?: string;
  sortBy?: keyof Product;
  order?: 'asc' | 'desc';
}
Code
// src/types/env.types.ts
export interface EnvConfig {
  PORT: number;
  NODE_ENV: 'development' | 'production' | 'staging' | 'test';
  DATABASE_URL: string;
  JWT_SECRET: string;
  JWT_EXPIRES_IN: string;
  CORS_ORIGIN: string;
  RATE_LIMIT_WINDOW_MS: number;
  RATE_LIMIT_MAX: number;
}

export const getEnv = (): EnvConfig => ({
  PORT: parseInt(process.env.PORT || '3000', 10),
  NODE_ENV: (process.env.NODE_ENV as EnvConfig['NODE_ENV']) || 'development',
  DATABASE_URL: process.env.DATABASE_URL!,
  JWT_SECRET: process.env.JWT_SECRET!,
  JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '7d',
  CORS_ORIGIN: process.env.CORS_ORIGIN || '*',
  RATE_LIMIT_WINDOW_MS: parseInt(process.env.RATE_LIMIT_WINDOW_MS || '900000', 10),
  RATE_LIMIT_MAX: parseInt(process.env.RATE_LIMIT_MAX || '100', 10)
});

9. Error Handling – Typed Error Handler

Custom Error Classes:

Code
// src/utils/errors.ts
export class AppError extends Error {
  public readonly statusCode: number;
  public readonly isOperational: boolean;
  public readonly code?: string;
  public readonly details?: any;
  
  constructor(
    message: string, 
    statusCode: number = 500, 
    code?: string, 
    details?: any
  ) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true;
    this.code = code;
    this.details = details;
    
    Error.captureStackTrace(this, this.constructor);
  }
}

export class BadRequestError extends AppError {
  constructor(message: string = 'Bad request', details?: any) {
    super(message, 400, 'BAD_REQUEST', details);
  }
}

export class UnauthorizedError extends AppError {
  constructor(message: string = 'Unauthorized') {
    super(message, 401, 'UNAUTHORIZED');
  }
}

export class ForbiddenError extends AppError {
  constructor(message: string = 'Forbidden') {
    super(message, 403, 'FORBIDDEN');
  }
}

export class NotFoundError extends AppError {
  constructor(resource: string = 'Resource') {
    super(`${resource} not found`, 404, 'NOT_FOUND');
  }
}

export class ConflictError extends AppError {
  constructor(message: string = 'Conflict', details?: any) {
    super(message, 409, 'CONFLICT', details);
  }
}

export class ValidationError extends AppError {
  constructor(details: any) {
    super('Validation failed', 400, 'VALIDATION_ERROR', details);
  }
}

Typed Error Handler:

Code
// src/middleware/errorHandler.ts
import { Request, Response, NextFunction } from 'express';
import { AppError } from '../utils/errors';
import { ResponseHandler } from '../utils/response';
import { getEnv } from '../types/env.types';

const env = getEnv();

export const errorHandler = (
  err: Error | AppError,
  req: Request,
  res: Response,
  next: NextFunction
): void => {
  // Log error
  console.error('Error:', {
    name: err.name,
    message: err.message,
    stack: env.NODE_ENV === 'development' ? err.stack : undefined,
    path: req.path,
    method: req.method,
    ip: req.ip
  });
  
  // Handle known operational errors
  if (err instanceof AppError) {
    ResponseHandler.error(
      res, 
      err.message, 
      err.statusCode, 
      err.code, 
      env.NODE_ENV === 'development' ? err.details : undefined
    );
    return;
  }
  
  // Handle JWT errors
  if (err.name === 'JsonWebTokenError') {
    ResponseHandler.unauthorized(res, 'Invalid token');
    return;
  }
  
  if (err.name === 'TokenExpiredError') {
    ResponseHandler.unauthorized(res, 'Token expired');
    return;
  }
  
  // Handle validation errors
  if (err.name === 'ValidationError') {
    ResponseHandler.badRequest(res, err.message);
    return;
  }
  
  // Handle unknown errors
  ResponseHandler.error(
    res,
    env.NODE_ENV === 'production' ? 'Internal server error' : err.message,
    500,
    'INTERNAL_ERROR'
  );
};

10. Controllers – Class-based with Types

Base Controller:

Code
// src/controllers/base.controller.ts
import { Request, Response } from 'express';
import { ResponseHandler } from '../utils/response';
import { AppError } from '../utils/errors';

export abstract class BaseController {
  protected sendSuccess<T>(res: Response, data: T, message?: string, statusCode?: number): Response {
    return ResponseHandler.success(res, data, message, statusCode);
  }
  
  protected sendCreated<T>(res: Response, data: T, message?: string): Response {
    return ResponseHandler.created(res, data, message);
  }
  
  protected sendNoContent(res: Response): Response {
    return ResponseHandler.noContent(res);
  }
  
  protected sendError(res: Response, error: Error | AppError): Response {
    if (error instanceof AppError) {
      return ResponseHandler.error(res, error.message, error.statusCode, error.code, error.details);
    }
    return ResponseHandler.error(res, error.message);
  }
  
  protected handleAsync(fn: Function) {
    return (req: Request, res: Response, next: any) => {
      Promise.resolve(fn(req, res, next)).catch(next);
    };
  }
}

User Controller:

Code
// src/controllers/user.controller.ts
import { Request, Response } from 'express';
import { BaseController } from './base.controller';
import { UserService } from '../services/user.service';
import { CreateUserBody, UpdateUserBody, UserQuery } from '../types/user.types';
import { NotFoundError, BadRequestError } from '../utils/errors';

export class UserController extends BaseController {
  constructor(private userService: UserService) {
    super();
  }
  
  getAllUsers = this.handleAsync(async (req: Request<{}, {}, {}, UserQuery>, res: Response) => {
    const { page, limit, search, sortBy, order } = req.query;
    const result = await this.userService.findAll({
      page: page ? parseInt(page) : 1,
      limit: limit ? parseInt(limit) : 10,
      search,
      sortBy,
      order
    });
    this.sendSuccess(res, result.data, 'Users fetched successfully', 200);
  });
  
  getUserById = this.handleAsync(async (req: Request<{ id: string }>, res: Response) => {
    const id = parseInt(req.params.id);
    const user = await this.userService.findById(id);
    
    if (!user) {
      throw new NotFoundError('User');
    }
    
    this.sendSuccess(res, user);
  });
  
  createUser = this.handleAsync(async (req: Request<{}, {}, CreateUserBody>, res: Response) => {
    const user = await this.userService.create(req.body);
    this.sendCreated(res, user, 'User created successfully');
  });
  
  updateUser = this.handleAsync(async (req: Request<{ id: string }, {}, UpdateUserBody>, res: Response) => {
    const id = parseInt(req.params.id);
    const user = await this.userService.update(id, req.body);
    
    if (!user) {
      throw new NotFoundError('User');
    }
    
    this.sendSuccess(res, user, 'User updated successfully');
  });
  
  deleteUser = this.handleAsync(async (req: Request<{ id: string }>, res: Response) => {
    const id = parseInt(req.params.id);
    const deleted = await this.userService.delete(id);
    
    if (!deleted) {
      throw new NotFoundError('User');
    }
    
    this.sendNoContent(res);
  });
}

User Service:

Code
// src/services/user.service.ts
import { UserRepository } from '../repositories/user.repository';
import { CreateUserBody, UpdateUserBody, UserResponse } from '../types/user.types';
import { ConflictError } from '../utils/errors';
import bcrypt from 'bcrypt';

export class UserService {
  constructor(private userRepository: UserRepository) {}
  
  async findAll(options: {
    page: number;
    limit: number;
    search?: string;
    sortBy?: string;
    order?: 'asc' | 'desc';
  }): Promise<{ data: UserResponse[]; total: number }> {
    return this.userRepository.findAll(options);
  }
  
  async findById(id: number): Promise<UserResponse | null> {
    return this.userRepository.findById(id);
  }
  
  async create(data: CreateUserBody): Promise<UserResponse> {
    const existingUser = await this.userRepository.findByEmail(data.email);
    if (existingUser) {
      throw new ConflictError('Email already exists');
    }
    
    const hashedPassword = await bcrypt.hash(data.password, 10);
    return this.userRepository.create({ ...data, password: hashedPassword });
  }
  
  async update(id: number, data: UpdateUserBody): Promise<UserResponse | null> {
    return this.userRepository.update(id, data);
  }
  
  async delete(id: number): Promise<boolean> {
    return this.userRepository.delete(id);
  }
}

11. DTOs – Data Transfer Objects

DTOs for Data Transformation:

Code
// src/dtos/user.dto.ts
import { UserResponse, CreateUserBody } from '../types/user.types';

export class UserDTO {
  static toResponse(user: any): UserResponse {
    return {
      id: user.id,
      name: user.name,
      email: user.email,
      age: user.age,
      createdAt: user.createdAt,
      updatedAt: user.updatedAt
    };
  }
  
  static toResponseList(users: any[]): UserResponse[] {
    return users.map(user => this.toResponse(user));
  }
  
  static toCreate(data: CreateUserBody): any {
    return {
      name: data.name,
      email: data.email,
      password: data.password,
      age: data.age,
      createdAt: new Date(),
      updatedAt: new Date()
    };
  }
  
  static toUpdate(data: Partial<CreateUserBody>): any {
    return {
      ...data,
      updatedAt: new Date()
    };
  }
}

12. Real-world Project – Complete API

Project Structure:

Code
express-ts-api/
├── src/
│   ├── config/
│   │   └── database.ts
│   ├── controllers/
│   │   ├── base.controller.ts
│   │   ├── user.controller.ts
│   │   └── product.controller.ts
│   ├── dtos/
│   │   ├── user.dto.ts
│   │   └── product.dto.ts
│   ├── middleware/
│   │   ├── auth.ts
│   │   ├── errorHandler.ts
│   │   ├── logger.ts
│   │   └── validation.ts
│   ├── models/
│   │   ├── User.ts
│   │   └── Product.ts
│   ├── repositories/
│   │   ├── user.repository.ts
│   │   └── product.repository.ts
│   ├── routes/
│   │   ├── index.ts
│   │   ├── users.ts
│   │   └── products.ts
│   ├── services/
│   │   ├── user.service.ts
│   │   └── product.service.ts
│   ├── types/
│   │   ├── index.ts
│   │   ├── user.types.ts
│   │   ├── product.types.ts
│   │   ├── response.types.ts
│   │   └── env.types.ts
│   ├── utils/
│   │   ├── errors.ts
│   │   ├── response.ts
│   │   └── logger.ts
│   └── index.ts
├── .env
├── .gitignore
├── package.json
├── tsconfig.json
└── README.md

Main Server File (src/index.ts):

Code
import express, { Express } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import compression from 'compression';
import rateLimit from 'express-rate-limit';
import { errorHandler } from './middleware/errorHandler';
import { logger } from './middleware/logger';
import { getEnv } from './types/env.types';
import routes from './routes';

const env = getEnv();
const app: Express = express();

// Security middleware
app.use(helmet());
app.use(cors({ origin: env.CORS_ORIGIN, credentials: true }));
app.use(compression());

// Rate limiting
const limiter = rateLimit({
  windowMs: env.RATE_LIMIT_WINDOW_MS,
  max: env.RATE_LIMIT_MAX,
  message: 'Too many requests, please try again later.'
});
app.use('/api', limiter);

// Logging
app.use(morgan('dev'));
app.use(logger);

// Body parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));

// Routes
app.use('/api', routes);

// Health check
app.get('/health', (req, res) => {
  res.status(200).json({
    status: 'OK',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    environment: env.NODE_ENV
  });
});

// 404 handler
app.use('*', (req, res) => {
  res.status(404).json({
    success: false,
    error: {
      code: 'NOT_FOUND',
      message: `Cannot ${req.method} ${req.originalUrl}`
    },
    timestamp: new Date().toISOString()
  });
});

// Global error handler
app.use(errorHandler);

// Start server
app.listen(env.PORT, () => {
  console.log(`🚀 Server running on http://localhost:${env.PORT}`);
  console.log(`📝 Environment: ${env.NODE_ENV}`);
  console.log(`🔒 CORS Origin: ${env.CORS_ORIGIN}`);
});

export default app;

13. Common Mistakes + Solutions

Mistake 1: Not installing @types packages

Code
# ❌ Missing types
npm install express

# ✅ Install types too
npm install express @types/express

Mistake 2: Using any type everywhere

TypeScript
// ❌ Defeats TypeScript purpose
app.get('/api/users', (req: any, res: any) => {
  res.json({});
});

// ✅ Proper types
app.get('/api/users', (req: Request, res: Response) => {
  res.json({});
});

Mistake 3: Not handling async errors

Code
// ❌ Async errors not caught
app.get('/api/users', async (req: Request, res: Response) => {
  const users = await db.find(); // If error, crashes!
  res.json(users);
});

// ✅ Use async handler
const asyncHandler = (fn: Function) => (req: Request, res: Response, next: NextFunction) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

app.get('/api/users', asyncHandler(async (req: Request, res: Response) => {
  const users = await db.find();
  res.json(users);
}));

Mistake 4: Not configuring strict mode

Code
// ❌ Loose type checking
{
  "compilerOptions": {
    "strict": false
  }
}

// ✅ Strict mode
{
  "compilerOptions": {
    "strict": true
  }
}

14. Quick Cheat Sheet

Installation:

Code
npm init -y
npm install express
npm install -D typescript @types/node @types/express ts-node ts-node-dev
npx tsc --init

Basic Types:

Code
import { Request, Response, NextFunction } from 'express';

// Request with params
router.get('/:id', (req: Request<{ id: string }>, res: Response) => {});

// Request with query
router.get('/', (req: Request<{}, {}, {}, { page: string }>, res: Response) => {});

// Request with body
router.post('/', (req: Request<{}, {}, CreateUserBody>, res: Response) => {});

Custom Response Type:

Code
interface ApiResponse<T = any> {
  success: boolean;
  data?: T;
  error?: string;
}

tsconfig.json Essentials:

Code
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "./dist",
    "rootDir": "./src"
  }
}

15. FAQ

Q1: Express TypeScript type-safe API Hindi में सबसे important kya hai?
strict: true in tsconfig.json – ye full type safety enable करता है।

Q2: @types/express kyun chahiye?
Express pure JavaScript में लिखा है – types package TypeScript को Express ke types बताता है।

Q3: req.params का type kaise define karein?
Request<{ id: string }> – generic में params type pass करो।

Q4: req.query का type kaise define karein?
Request<{}, {}, {}, { page: string }> – 4th generic query के लिए है।

Q5: req.body का type kaise define karein?
Request<{}, {}, CreateUserBody> – 3rd generic body के लिए है।

Q6: Custom middleware mein types kaise use karein?
AuthRequest extends Request बनाओ – user property add करो।

Q7: Async errors TypeScript में कैसे handle karein?
asyncHandler wrapper function बनाओ – Promise.resolve().catch(next)

Q8: any type kab use karein?
Almost never! unknown use करो – या proper type define करो।

Q9: TypeScript production mein compile kaise karein?
npm run build (tsc) – dist folder mein compiled JS आएगा।

Q10: ts-node-dev vs nodemon – kya use karein?
ts-node-dev – faster restart + type checking। nodemon – slower but more configurable।

16. Conclusion

बहुत बढ़िया दोस्तों! आज हमने Express TypeScript type-safe API Hindi को पूरी detail में समझा।

Quick Recap:

ConceptKey Takeaway
SetupTypeScript + Express + @types
Request TypesParams, Query, Body generics
Response TypesTyped response handler
MiddlewareAuthRequest extends Request
Error HandlingCustom AppError class
ControllersClass-based with types

Mera personal experience:

TypeScript + Express use करने के बाद production bugs 70% kam ho gaye। Autocomplete से development speed 2x हो गई। Ab bina TypeScript के Express API नहीं बनाता।

Tum bhi ye steps follow karo:

  1. ✅ TypeScript + Express project setup करो
  2. ✅ tsconfig.json में strict: true set करो
  3. ✅ Request/Response types define करो
  4. ✅ Custom error handler बनाओ
  5. ✅ Async handler wrapper use करो

अब तुम्हारी बारी है!

नीचे comment में बताओ:

  1. क्या तुम TypeScript use karoge Express के साथ?
  2. तुम्हें कौन सा type sabse useful laga?
  3. अगला topic क्या चाहिए? (GraphQL with TypeScript? TypeORM? Prisma with TypeScript?)

The Easy Master पर बने रहो। Happy Type-Safe Coding! 🚀💻

Resources

Additional Resources

TheEasyMaster

Author at The Easy Master.

Related posts

Leave a Reply

Your email address will not be published. Required fields are marked *