📚 Mundarija#
- Express.js vs NestJS: Qaysi birini tanlash?
- Express.js: Tez va Moslashuvchan
- NestJS: Korxona Darajadagi Arxitektura
- Express.js da To‘liq Loyiha
- NestJS da To‘liq Loyiha
- Database Integratsiyasi
- Authentication va Authorization
- Error Handling va Logging
- Testing (Unit, Integration, E2E)
- Deployment va Performance
⚖️ Express.js vs NestJS: Qaysi birini tanlash?#
| Xususiyat | Express.js | NestJS |
|---|---|---|
| Arxitektura | Minimalist, middleware-based | Modular, Angular-like |
| TypeScript | Qo‘shimcha sozlash kerak | Built-in |
| DI (Dependency Injection) | ❌ Yo‘q | ✅ Ha |
| Modullar | ❌ Yo‘q | ✅ Ha |
| Decorators | ❌ Yo‘q | ✅ Ha |
| GraphQL | Qo‘shimcha | Built-in |
| Microservices | Qo‘shimcha | Built-in |
| WebSockets | Qo‘shimcha | Built-in |
| Testing | Qo‘lda | Built-in |
| O‘rganish qiyinligi | Oson | O‘rta/Qiyin |
| Ishlash tezligi | Juda tez | Tez |
| Ishlatilishi | Kichik va o‘rta loyihalar | Korxona loyihalari |
🚀 Express.js: Tez va Moslashuvchan#
Express.js minimalistik framework bo‘lib, sizga kerakli funksiyalarni o‘zingiz tanlash imkoniyatini beradi.
📦 O‘rnatish va Sozlash#
# Loyiha yaratish
mkdir express-backend
cd express-backend
npm init -y
# Kerakli paketlar
npm install express cors helmet morgan dotenv compression
npm install -D typescript @types/node @types/express nodemon ts-node
# Validation va sanitization
npm install express-validator
🏗️ Express.js Strukturasi#
express-backend/
├── src/
│ ├── config/
│ │ ├── database.ts
│ │ └── env.ts
│ ├── middleware/
│ │ ├── auth.ts
│ │ ├── error.ts
│ │ └── validation.ts
│ ├── models/
│ │ └── user.model.ts
│ ├── services/
│ │ └── user.service.ts
│ ├── controllers/
│ │ └── user.controller.ts
│ ├── routes/
│ │ └── user.routes.ts
│ ├── utils/
│ │ ├── logger.ts
│ │ └── jwt.ts
│ └── app.ts
├── .env
├── tsconfig.json
└── package.json
📄 Express.js Asosiy Fayllar#
1. Server (app.ts)
// src/app.ts
import express, { Application } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import compression from 'compression';
import dotenv from 'dotenv';
import { errorHandler } from './middleware/error';
import { logger } from './utils/logger';
import userRoutes from './routes/user.routes';
dotenv.config();
const app: Application = express();
// Middleware
app.use(helmet());
app.use(cors({
origin: process.env.CORS_ORIGIN?.split(',') || '*',
credentials: true
}));
app.use(compression());
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
app.use(morgan('combined', { stream: { write: (message) => logger.info(message.trim()) } }));
// Routes
app.use('/api/users', userRoutes);
app.use('/api/health', (req, res) => res.json({ status: 'OK', timestamp: new Date() }));
// Error handling
app.use(errorHandler);
export default app;
2. Server Start (index.ts)
// src/index.ts
import app from './app';
import { connectDB } from './config/database';
import { logger } from './utils/logger';
const PORT = process.env.PORT || 5000;
const startServer = async () => {
try {
await connectDB();
logger.info('Database connected successfully');
const server = app.listen(PORT, () => {
logger.info(`🚀 Server running on port ${PORT}`);
logger.info(`📁 Environment: ${process.env.NODE_ENV}`);
});
// Graceful shutdown
process.on('SIGTERM', () => {
logger.info('SIGTERM signal received: closing HTTP server');
server.close(() => {
logger.info('HTTP server closed');
process.exit(0);
});
});
} catch (error) {
logger.error('Failed to start server:', error);
process.exit(1);
}
};
startServer();
🏗️ Express.js da MVC Pattern#
Model (user.model.ts)
// src/models/user.model.ts
import mongoose, { Schema, Document } from 'mongoose';
import bcrypt from 'bcrypt';
export interface IUser extends Document {
email: string;
password: string;
name: string;
role: 'user' | 'admin' | 'moderator';
isActive: boolean;
comparePassword(candidatePassword: string): Promise<boolean>;
}
const UserSchema = new Schema<IUser>(
{
email: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true,
match: /^\S+@\S+\.\S+$/
},
password: {
type: String,
required: true,
minlength: 6,
select: false
},
name: {
type: String,
required: true,
trim: true
},
role: {
type: String,
enum: ['user', 'admin', 'moderator'],
default: 'user'
},
isActive: {
type: Boolean,
default: true
}
},
{
timestamps: true,
toJSON: {
transform: (_, ret) => {
delete ret.password;
delete ret.__v;
return ret;
}
}
}
);
// Password hash
UserSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
});
// Compare password
UserSchema.methods.comparePassword = async function(candidatePassword: string): Promise<boolean> {
return bcrypt.compare(candidatePassword, this.password);
};
export const User = mongoose.model<IUser>('User', UserSchema);
Service (user.service.ts)
// src/services/user.service.ts
import { User, IUser } from '../models/user.model';
import { AppError } from '../utils/appError';
import { generateToken, verifyToken } from '../utils/jwt';
import { logger } from '../utils/logger';
export class UserService {
// CREATE
async createUser(userData: Partial<IUser>) {
try {
const existingUser = await User.findOne({ email: userData.email });
if (existingUser) {
throw new AppError('User already exists', 409);
}
const user = new User(userData);
await user.save();
return { user, error: null };
} catch (error: any) {
logger.error('Create user error:', error);
return { user: null, error: error.message };
}
}
// READ - all
async getAllUsers(page: number = 1, limit: number = 10) {
try {
const skip = (page - 1) * limit;
const [users, total] = await Promise.all([
User.find().skip(skip).limit(limit).sort({ createdAt: -1 }),
User.countDocuments()
]);
return {
data: users,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit)
},
error: null
};
} catch (error: any) {
logger.error('Get users error:', error);
return { data: null, pagination: null, error: error.message };
}
}
// READ - by id
async getUserById(id: string) {
try {
const user = await User.findById(id);
if (!user) {
throw new AppError('User not found', 404);
}
return { user, error: null };
} catch (error: any) {
logger.error('Get user by id error:', error);
return { user: null, error: error.message };
}
}
// UPDATE
async updateUser(id: string, updateData: Partial<IUser>) {
try {
const user = await User.findByIdAndUpdate(
id,
{ ...updateData },
{ new: true, runValidators: true }
);
if (!user) {
throw new AppError('User not found', 404);
}
return { user, error: null };
} catch (error: any) {
logger.error('Update user error:', error);
return { user: null, error: error.message };
}
}
// DELETE (soft delete)
async deleteUser(id: string) {
try {
const user = await User.findByIdAndUpdate(
id,
{ isActive: false },
{ new: true }
);
if (!user) {
throw new AppError('User not found', 404);
}
return { user, error: null };
} catch (error: any) {
logger.error('Delete user error:', error);
return { user: null, error: error.message };
}
}
// LOGIN
async login(email: string, password: string) {
try {
const user = await User.findOne({ email }).select('+password');
if (!user) {
throw new AppError('Invalid credentials', 401);
}
const isMatch = await user.comparePassword(password);
if (!isMatch) {
throw new AppError('Invalid credentials', 401);
}
const token = generateToken({
id: user._id,
email: user.email,
role: user.role
});
return { user, token, error: null };
} catch (error: any) {
logger.error('Login error:', error);
return { user: null, token: null, error: error.message };
}
}
}
Controller (user.controller.ts)
// src/controllers/user.controller.ts
import { Request, Response, NextFunction } from 'express';
import { UserService } from '../services/user.service';
import { AppError } from '../utils/appError';
import { validate } from '../middleware/validation';
const userService = new UserService();
export class UserController {
// CREATE
static async createUser(req: Request, res: Response, next: NextFunction) {
try {
const { user, error } = await userService.createUser(req.body);
if (error) return next(new AppError(error, 400));
res.status(201).json({
success: true,
message: 'User created successfully',
data: user
});
} catch (error: any) {
next(error);
}
}
// READ - all
static async getAllUsers(req: Request, res: Response, next: NextFunction) {
try {
const page = parseInt(req.query.page as string) || 1;
const limit = parseInt(req.query.limit as string) || 10;
const { data, pagination, error } = await userService.getAllUsers(page, limit);
if (error) return next(new AppError(error, 400));
res.json({
success: true,
data,
pagination
});
} catch (error: any) {
next(error);
}
}
// READ - by id
static async getUserById(req: Request, res: Response, next: NextFunction) {
try {
const { user, error } = await userService.getUserById(req.params.id);
if (error) return next(new AppError(error, 404));
res.json({
success: true,
data: user
});
} catch (error: any) {
next(error);
}
}
// UPDATE
static async updateUser(req: Request, res: Response, next: NextFunction) {
try {
const { user, error } = await userService.updateUser(req.params.id, req.body);
if (error) return next(new AppError(error, 400));
res.json({
success: true,
message: 'User updated successfully',
data: user
});
} catch (error: any) {
next(error);
}
}
// DELETE
static async deleteUser(req: Request, res: Response, next: NextFunction) {
try {
const { user, error } = await userService.deleteUser(req.params.id);
if (error) return next(new AppError(error, 400));
res.json({
success: true,
message: 'User deleted successfully',
data: user
});
} catch (error: any) {
next(error);
}
}
// LOGIN
static async login(req: Request, res: Response, next: NextFunction) {
try {
const { email, password } = req.body;
const { user, token, error } = await userService.login(email, password);
if (error) return next(new AppError(error, 401));
res.json({
success: true,
message: 'Login successful',
data: { user, token }
});
} catch (error: any) {
next(error);
}
}
}
Routes (user.routes.ts)
// src/routes/user.routes.ts
import { Router } from 'express';
import { UserController } from '../controllers/user.controller';
import { authenticate, authorize } from '../middleware/auth';
import { validate } from '../middleware/validation';
import { userValidation } from '../validations/user.validation';
const router = Router();
// Public routes
router.post('/login', UserController.login);
router.post('/register', validate(userValidation.register), UserController.createUser);
// Protected routes
router.use(authenticate);
router.get('/', authorize(['admin', 'moderator']), UserController.getAllUsers);
router.get('/:id', UserController.getUserById);
router.put('/:id', validate(userValidation.update), UserController.updateUser);
router.delete('/:id', authorize(['admin']), UserController.deleteUser);
export default router;
🏛️ NestJS: Korxona Darajadagi Arxitektura#
NestJS Angular uslubidagi arxitektura bilan kuchli va modulli backend yaratish imkonini beradi.
📦 O‘rnatish va Sozlash#
# NestJS CLI o‘rnatish
npm install -g @nestjs/cli
# Loyiha yaratish
nest new nest-backend
cd nest-backend
# Kerakli paketlar
npm install @nestjs/mongoose mongoose
npm install @nestjs/jwt @nestjs/passport passport passport-jwt passport-local
npm install @nestjs/config class-validator class-transformer
npm install @nestjs/swagger
# Development uchun
npm install -D @types/passport @types/passport-jwt @types/passport-local
🏗️ NestJS Strukturasi#
nest-backend/
├── src/
│ ├── modules/
│ │ ├── auth/
│ │ │ ├── dto/
│ │ │ │ ├── login.dto.ts
│ │ │ │ └── register.dto.ts
│ │ │ ├── strategies/
│ │ │ │ ├── jwt.strategy.ts
│ │ │ │ └── local.strategy.ts
│ │ │ ├── guards/
│ │ │ │ ├── jwt-auth.guard.ts
│ │ │ │ └── roles.guard.ts
│ │ │ ├── auth.controller.ts
│ │ │ ├── auth.service.ts
│ │ │ └── auth.module.ts
│ │ ├── users/
│ │ │ ├── dto/
│ │ │ │ ├── create-user.dto.ts
│ │ │ │ └── update-user.dto.ts
│ │ │ ├── schemas/
│ │ │ │ └── user.schema.ts
│ │ │ ├── users.controller.ts
│ │ │ ├── users.service.ts
│ │ │ └── users.module.ts
│ │ └── common/
│ │ ├── decorators/
│ │ ├── filters/
│ │ ├── interceptors/
│ │ ├── middleware/
│ │ ├── guards/
│ │ └── pipes/
│ ├── config/
│ │ └── configuration.ts
│ ├── main.ts
│ └── app.module.ts
├── test/
├── .env
├── tsconfig.json
└── package.json
📄 NestJS Asosiy Fayllar#
1. Main (main.ts)
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AllExceptionsFilter } from './modules/common/filters/all-exceptions.filter';
import { logger } from './modules/common/utils/logger';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Global middleware
app.enableCors({
origin: process.env.CORS_ORIGIN?.split(',') || '*',
credentials: true
});
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
transform: true,
forbidNonWhitelisted: true
}));
app.useGlobalFilters(new AllExceptionsFilter());
app.setGlobalPrefix('api');
// Swagger documentation
const config = new DocumentBuilder()
.setTitle('NestJS Backend API')
.setDescription('Professional backend with NestJS')
.setVersion('1.0')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api/docs', app, document);
const port = process.env.PORT || 5000;
await app.listen(port, () => {
logger.info(`🚀 Server running on port ${port}`);
logger.info(`📘 API docs: http://localhost:${port}/api/docs`);
});
}
bootstrap();
2. Module (app.module.ts)
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MongooseModule } from '@nestjs/mongoose';
import { AuthModule } from './modules/auth/auth.module';
import { UsersModule } from './modules/users/users.module';
import configuration from './config/configuration';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [configuration]
}),
MongooseModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
uri: configService.get<string>('database.uri'),
useNewUrlParser: true,
useUnifiedTopology: true
}),
inject: [ConfigService]
}),
AuthModule,
UsersModule
]
})
export class AppModule {}
3. Schema (user.schema.ts)
// src/modules/users/schemas/user.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, HydratedDocument } from 'mongoose';
import * as bcrypt from 'bcrypt';
export type UserDocument = HydratedDocument<User>;
@Schema({
timestamps: true,
toJSON: {
transform: (doc, ret) => {
delete ret.password;
delete ret.__v;
return ret;
}
}
})
export class User extends Document {
@Prop({
required: true,
unique: true,
lowercase: true,
trim: true,
match: /^\S+@\S+\.\S+$/
})
email: string;
@Prop({ required: true, minlength: 6, select: false })
password: string;
@Prop({ required: true, trim: true })
name: string;
@Prop({
type: String,
enum: ['user', 'admin', 'moderator'],
default: 'user'
})
role: string;
@Prop({ default: true })
isActive: boolean;
}
export const UserSchema = SchemaFactory.createForClass(User);
// Pre-save hook for password hashing
UserSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
});
// Method to compare password
UserSchema.methods.comparePassword = async function(candidatePassword: string): Promise<boolean> {
return bcrypt.compare(candidatePassword, this.password);
};
4. DTO (create-user.dto.ts)
// src/modules/users/dto/create-user.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsString, MinLength, IsEnum, IsOptional } from 'class-validator';
export class CreateUserDto {
@ApiProperty({ example: 'user@example.com' })
@IsEmail()
email: string;
@ApiProperty({ example: 'password123' })
@IsString()
@MinLength(6)
password: string;
@ApiProperty({ example: 'John Doe' })
@IsString()
name: string;
@ApiProperty({ enum: ['user', 'admin', 'moderator'], required: false })
@IsEnum(['user', 'admin', 'moderator'])
@IsOptional()
role?: string;
}
5. Service (users.service.ts)
// src/modules/users/users.service.ts
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User, UserDocument } from './schemas/user.schema';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { PaginationDto } from './dto/pagination.dto';
@Injectable()
export class UsersService {
constructor(
@InjectModel(User.name) private userModel: Model<UserDocument>
) {}
// CREATE
async create(createUserDto: CreateUserDto): Promise<User> {
const existingUser = await this.userModel.findOne({ email: createUserDto.email });
if (existingUser) {
throw new ConflictException('User already exists');
}
const user = new this.userModel(createUserDto);
return user.save();
}
// READ - all
async findAll(paginationDto: PaginationDto) {
const { page = 1, limit = 10 } = paginationDto;
const skip = (page - 1) * limit;
const [users, total] = await Promise.all([
this.userModel.find().skip(skip).limit(limit).sort({ createdAt: -1 }),
this.userModel.countDocuments()
]);
return {
data: users,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit)
}
};
}
// READ - by id
async findById(id: string): Promise<User> {
const user = await this.userModel.findById(id);
if (!user) {
throw new NotFoundException('User not found');
}
return user;
}
// READ - by email
async findByEmail(email: string): Promise<User | null> {
return this.userModel.findOne({ email }).select('+password');
}
// UPDATE
async update(id: string, updateUserDto: UpdateUserDto): Promise<User> {
const user = await this.userModel.findByIdAndUpdate(
id,
{ ...updateUserDto },
{ new: true, runValidators: true }
);
if (!user) {
throw new NotFoundException('User not found');
}
return user;
}
// DELETE
async remove(id: string): Promise<User> {
const user = await this.userModel.findByIdAndUpdate(
id,
{ isActive: false },
{ new: true }
);
if (!user) {
throw new NotFoundException('User not found');
}
return user;
}
}
6. Controller (users.controller.ts)
// src/modules/users/users.controller.ts
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, Query } from '@nestjs/common';
import { ApiTags, ApiBearerAuth, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { PaginationDto } from './dto/pagination.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { RolesGuard } from '../auth/guards/roles.guard';
import { Roles } from '../auth/decorators/roles.decorator';
import { Public } from '../auth/decorators/public.decorator';
@ApiTags('Users')
@ApiBearerAuth()
@Controller('users')
@UseGuards(JwtAuthGuard, RolesGuard)
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
@Roles('admin')
@ApiOperation({ summary: 'Create a new user' })
@ApiResponse({ status: 201, description: 'User created successfully' })
@ApiResponse({ status: 409, description: 'User already exists' })
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Get()
@Roles('admin', 'moderator')
@ApiOperation({ summary: 'Get all users with pagination' })
findAll(@Query() paginationDto: PaginationDto) {
return this.usersService.findAll(paginationDto);
}
@Get(':id')
@ApiOperation({ summary: 'Get user by ID' })
@ApiResponse({ status: 404, description: 'User not found' })
findById(@Param('id') id: string) {
return this.usersService.findById(id);
}
@Patch(':id')
@ApiOperation({ summary: 'Update user' })
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.usersService.update(id, updateUserDto);
}
@Delete(':id')
@Roles('admin')
@ApiOperation({ summary: 'Delete user (soft delete)' })
remove(@Param('id') id: string) {
return this.usersService.remove(id);
}
}
7. Module (users.module.ts)
// src/modules/users/users.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { User, UserSchema } from './schemas/user.schema';
@Module({
imports: [
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }])
],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService]
})
export class UsersModule {}
🔐 Authentication Module#
Auth Service (auth.service.ts)
// src/modules/auth/auth.service.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
import { RegisterDto } from './dto/register.dto';
import { LoginDto } from './dto/login.dto';
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService
) {}
async register(registerDto: RegisterDto) {
const user = await this.usersService.create(registerDto);
const token = this.generateToken(user);
return { user, token };
}
async login(loginDto: LoginDto) {
const user = await this.usersService.findByEmail(loginDto.email);
if (!user) {
throw new UnauthorizedException('Invalid credentials');
}
const isPasswordValid = await user.comparePassword(loginDto.password);
if (!isPasswordValid) {
throw new UnauthorizedException('Invalid credentials');
}
const token = this.generateToken(user);
return { user, token };
}
private generateToken(user: any) {
const payload = {
sub: user._id,
email: user.email,
role: user.role
};
return this.jwtService.sign(payload);
}
}
JWT Strategy (jwt.strategy.ts)
// src/modules/auth/strategies/jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get('jwt.secret')
});
}
async validate(payload: any) {
return {
id: payload.sub,
email: payload.email,
role: payload.role
};
}
}
Roles Guard (roles.guard.ts)
// src/modules/auth/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from '../decorators/roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>(
ROLES_KEY,
[context.getHandler(), context.getClass()]
);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some(role => user?.role === role);
}
}
💾 Database Integratsiyasi#
MongoDB (Mongoose) va PostgreSQL (TypeORM)#
MongoDB Connection
// src/config/database.ts
import mongoose from 'mongoose';
import { logger } from '../utils/logger';
export const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.MONGODB_URI!);
logger.info(`MongoDB Connected: ${conn.connection.host}`);
return conn;
} catch (error) {
logger.error('MongoDB connection error:', error);
process.exit(1);
}
};
PostgreSQL (TypeORM)#
// src/config/typeorm.config.ts
import { DataSource } from 'typeorm';
import { User } from '../entities/user.entity';
import { config } from 'dotenv';
config();
export const AppDataSource = new DataSource({
type: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || '5432'),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
entities: [User],
migrations: ['src/migrations/*.ts'],
synchronize: process.env.NODE_ENV === 'development',
logging: process.env.NODE_ENV === 'development'
});
📊 Error Handling va Logging#
Global Error Handler#
// src/utils/appError.ts
export class AppError extends Error {
constructor(
public message: string,
public statusCode: number = 500,
public isOperational: boolean = true
) {
super(message);
Error.captureStackTrace(this, this.constructor);
}
}
// src/middleware/error.ts
import { Request, Response, NextFunction } from 'express';
import { AppError } from '../utils/appError';
import { logger } from '../utils/logger';
export const errorHandler = (
err: any,
req: Request,
res: Response,
next: NextFunction
) => {
let error = { ...err };
error.message = err.message;
// Log error
logger.error(`${err.statusCode || 500} - ${err.message} - ${req.originalUrl}`);
logger.error(err.stack);
// Mongoose validation error
if (err.name === 'ValidationError') {
const message = Object.values(err.errors).map((val: any) => val.message).join(', ');
error = new AppError(message, 400);
}
// Mongoose duplicate key
if (err.code === 11000) {
const field = Object.keys(err.keyPattern)[0];
const message = `${field} already exists`;
error = new AppError(message, 409);
}
// JWT error
if (err.name === 'JsonWebTokenError') {
error = new AppError('Invalid token', 401);
}
const statusCode = error.statusCode || 500;
res.status(statusCode).json({
success: false,
message: error.message || 'Internal server error',
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
};
Logging Service#
// src/utils/logger.ts
import winston from 'winston';
import path from 'path';
const logFormat = winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json(),
winston.format.printf(({ timestamp, level, message, stack }) => {
return `${timestamp} [${level.toUpperCase()}]: ${message} ${stack || ''}`;
})
);
export const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: logFormat,
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}),
new winston.transports.File({
filename: path.join('logs', 'error.log'),
level: 'error'
}),
new winston.transports.File({
filename: path.join('logs', 'combined.log')
})
],
exitOnError: false
});
// Morgan integration for HTTP logging
export const morganStream = {
write: (message: string) => logger.info(message.trim())
};
🧪 Testing (Unit, Integration, E2E)#
Unit Test (Jest)#
// src/modules/users/users.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { getModelToken } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { UsersService } from './users.service';
import { User } from './schemas/user.schema';
describe('UsersService', () => {
let service: UsersService;
let model: Model<User>;
const mockUser = {
_id: 'someId',
email: 'test@example.com',
name: 'Test User',
role: 'user',
isActive: true
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: getModelToken(User.name),
useValue: {
find: jest.fn(),
findById: jest.fn(),
create: jest.fn(),
findByIdAndUpdate: jest.fn(),
countDocuments: jest.fn()
}
}
]
}).compile();
service = module.get<UsersService>(UsersService);
model = module.get<Model<User>>(getModelToken(User.name));
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('findAll', () => {
it('should return paginated users', async () => {
const paginationDto = { page: 1, limit: 10 };
const mockUsers = [mockUser];
jest.spyOn(model, 'find').mockReturnValue({
skip: jest.fn().mockReturnThis(),
limit: jest.fn().mockReturnThis(),
sort: jest.fn().mockReturnThis(),
exec: jest.fn().mockResolvedValue(mockUsers)
} as any);
jest.spyOn(model, 'countDocuments').mockReturnValue({
exec: jest.fn().mockResolvedValue(1)
} as any);
const result = await service.findAll(paginationDto);
expect(result.data).toEqual(mockUsers);
expect(result.pagination.total).toBe(1);
});
});
});
Integration Test#
// test/integration/user.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../../src/app.module';
describe('UsersController (e2e)', () => {
let app: INestApplication;
let authToken: string;
let userId: string;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule]
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterAll(async () => {
await app.close();
});
it('should register a new user', async () => {
const response = await request(app.getHttpServer())
.post('/api/auth/register')
.send({
email: 'test@example.com',
password: 'password123',
name: 'Test User'
})
.expect(201);
expect(response.body.data).toHaveProperty('token');
authToken = response.body.data.token;
userId = response.body.data.user._id;
});
it('should login', async () => {
const response = await request(app.getHttpServer())
.post('/api/auth/login')
.send({
email: 'test@example.com',
password: 'password123'
})
.expect(200);
expect(response.body.data).toHaveProperty('token');
authToken = response.body.data.token;
});
it('should get user profile', async () => {
const response = await request(app.getHttpServer())
.get('/api/users/me')
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
expect(response.body.data.email).toBe('test@example.com');
});
it('should update user', async () => {
const response = await request(app.getHttpServer())
.patch(`/api/users/${userId}`)
.set('Authorization', `Bearer ${authToken}`)
.send({ name: 'Updated Name' })
.expect(200);
expect(response.body.data.name).toBe('Updated Name');
});
});
🚀 Deployment va Performance#
Environment Configuration#
# .env
NODE_ENV=production
PORT=5000
# Database
MONGODB_URI=mongodb://localhost:27017/myapp
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=postgres
DB_PASSWORD=password
DB_DATABASE=myapp
# JWT
JWT_SECRET=your-secret-key
JWT_EXPIRES_IN=7d
# CORS
CORS_ORIGIN=https://your-frontend.com
# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
# Logging
LOG_LEVEL=info
Performance Optimization#
// src/middleware/performance.ts
import compression from 'compression';
import rateLimit from 'express-rate-limit';
import { Express } from 'express';
export const setupPerformance = (app: Express) => {
// Compression
app.use(compression());
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per window
message: 'Too many requests from this IP',
standardHeaders: true,
legacyHeaders: false
});
app.use('/api', limiter);
// Heavy endpoints rate limit
const heavyLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 10,
message: 'Too many heavy requests'
});
app.use('/api/upload', heavyLimiter);
};
// Redis Caching
import Redis from 'ioredis';
export class CacheService {
private client: Redis;
constructor() {
this.client = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
retryStrategy: (times) => {
const delay = Math.min(times * 50, 2000);
return delay;
}
});
}
async get<T>(key: string): Promise<T | null> {
const data = await this.client.get(key);
return data ? JSON.parse(data) : null;
}
async set(key: string, value: any, ttl?: number): Promise<void> {
const data = JSON.stringify(value);
if (ttl) {
await this.client.setex(key, ttl, data);
} else {
await this.client.set(key, data);
}
}
async del(key: string): Promise<void> {
await this.client.del(key);
}
async invalidatePattern(pattern: string): Promise<void> {
const keys = await this.client.keys(pattern);
if (keys.length > 0) {
await this.client.del(...keys);
}
}
}
// Cache Interceptor
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class CacheInterceptor implements NestInterceptor {
constructor(private cacheService: CacheService, private ttl?: number) {}
async intercept(
context: ExecutionContext,
next: CallHandler
): Promise<Observable<any>> {
const request = context.switchToHttp().getRequest();
const key = `cache:${request.method}:${request.url}`;
const cached = await this.cacheService.get(key);
if (cached) {
return new Observable((observer) => {
observer.next({ data: cached, fromCache: true });
observer.complete();
});
}
return next.handle().pipe(
tap(async (response) => {
if (response?.data) {
await this.cacheService.set(key, response.data, this.ttl);
}
})
);
}
}
Dockerization#
# Dockerfile
FROM node:20-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
COPY yarn.lock ./
# Install dependencies
RUN yarn install --frozen-lockfile --production
# Copy source code
COPY . .
# Build TypeScript
RUN yarn build
# Expose port
EXPOSE 5000
# Start application
CMD ["yarn", "start:prod"]
# docker-compose.yml
version: '3.8'
services:
api:
build:
context: .
dockerfile: Dockerfile
container_name: node-backend
restart: unless-stopped
ports:
- "5000:5000"
environment:
- NODE_ENV=production
env_file:
- .env
depends_on:
- mongodb
- postgres
- redis
networks:
- app-network
mongodb:
image: mongo:6.0
container_name: mongodb
restart: unless-stopped
ports:
- "27017:27017"
environment:
- MONGO_INITDB_ROOT_USERNAME=admin
- MONGO_INITDB_ROOT_PASSWORD=password
volumes:
- mongodb_data:/data/db
networks:
- app-network
postgres:
image: postgres:16
container_name: postgres
restart: unless-stopped
ports:
- "5432:5432"
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
- POSTGRES_DB=myapp
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- app-network
redis:
image: redis:7-alpine
container_name: redis
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- app-network
nginx:
image: nginx:alpine
container_name: nginx
restart: unless-stopped
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- api
networks:
- app-network
networks:
app-network:
driver: bridge
volumes:
mongodb_data:
postgres_data:
redis_data:
📊 Monitoring va Logging#
Winston + Elasticsearch#
// src/utils/logger.ts (Extended)
import winston from 'winston';
import { ElasticsearchTransport } from 'winston-elasticsearch';
const esTransport = new ElasticsearchTransport({
level: 'info',
clientOpts: {
node: process.env.ELASTICSEARCH_URL || 'http://localhost:9200'
},
index: 'application-logs',
transformer: (logData) => {
const transformed = {
'@timestamp': new Date().toISOString(),
severity: logData.level,
message: logData.message,
...logData.meta
};
return transformed;
}
});
export const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}),
new winston.transports.File({
filename: 'logs/error.log',
level: 'error'
}),
new winston.transports.File({
filename: 'logs/combined.log'
}),
esTransport
]
});
🎯 Xulosa#
Express.js vs NestJS: Qaysi birini tanlash?#
| Agar... | Express.js | NestJS |
|---|---|---|
| Kichik loyiha | ✅ | ❌ |
| Tez prototip | ✅ | ❌ |
| Microservices | ⚠️ | ✅ |
| GraphQL | ⚠️ | ✅ |
| Korxona loyihasi | ❌ | ✅ |
| TypeScript ni sevasiz | ❌ | ✅ |
| DI pattern ni sevasiz | ❌ | ✅ |
Tavsiya Qilingan Stack#
Stack: NestJS + MongoDB/PostgreSQL + Redis + TypeScript
Authentication: JWT + OAuth2
Testing: Jest + Supertest + E2E
Deployment: Docker + Kubernetes + AWS/GCP
Monitoring: Prometheus + Grafana + ELK Stack
🔗 Ushbu qo‘llanmani do‘stlaringiz bilan ulashing va backend mahoratingizni oshiring!
#NodeJS #ExpressJS #NestJS #Backend #API #TypeScript #MongoDB #PostgreSQL #WebDevelopment

No comments yet.