بناء واجهات REST API للإنتاج باستخدام NestJS و TypeORM و PostgreSQL

NestJS هو أشهر إطار عمل Node.js للمؤسسات — وهذا لسبب وجيه. مبنيّ بـ TypeScript، مستوحى من بنية Angular، ويعمل على Express (أو Fastify) في الخلفية. يجلب NestJS الهيكلية والقابلية للاختبار والتوسع لتطوير الخوادم. مع TypeORM و PostgreSQL، يشكّل حزمة جاهزة للإنتاج تستخدمها شركات مثل Adidas و Roche و Autodesk.
ماذا ستبني؟
نظام إدارة المستخدمين (User Management API) متكامل يتضمّن:
- بنية NestJS معيارية مع متحكمات وخدمات ومستودعات
- قاعدة بيانات PostgreSQL مع كيانات TypeORM وترحيلات
- مصادقة بـ JWT (تسجيل، دخول، مسارات محمية)
- التحقق من المدخلات باستخدام class-validator و DTOs
- معالجة أخطاء منظّمة مع مرشحات الاستثناءات
- ترقيم الصفحات والتصفية والترتيب
- إعدادات مبنية على البيئة مع nestjs/config@
- اختبارات وحدات واختبارات شاملة
المتطلبات المسبقة
قبل البدء، تأكد من توفر التالي:
- Node.js 20+ مثبّت
- PostgreSQL 15+ يعمل محلياً أو عبر Docker
- مدير حزم npm أو yarn
- فهم أساسي لـ TypeScript ومفاهيم REST
- محرر أكواد (يُنصح بـ VS Code)
جديد على TypeScript؟ يجب أن تكون مرتاحاً مع الأصناف (Classes)، المزخرفات (Decorators)، الواجهات (Interfaces)، والأنماط العامة (Generics). دليل TypeScript Handbook الرسمي نقطة بداية ممتازة.
لماذا NestJS؟
قبل الغوص في التفاصيل، دعنا نفهم ما يميّز NestJS:
| الميزة | NestJS | Express | Fastify | Hono |
|---|---|---|---|---|
| البنية | منظّمة (وحدات) | بسيطة | بسيطة | بسيطة |
| TypeScript | دعم أصلي | إضافة | إضافة | دعم أصلي |
| حاوية DI | مدمجة | لا يوجد | لا يوجد | لا يوجد |
| CLI | توليد كامل | لا يوجد | لا يوجد | لا يوجد |
| الاختبارات | أدوات مدمجة | يدوي | يدوي | يدوي |
| صعوبة التعلم | متوسطة | منخفضة | منخفضة | منخفضة |
يستبدل NestJS مرونة Express ببنية منظّمة ومنهجية تؤتي ثمارها كلما كبر مشروعك. إذا عملت مع Angular أو Spring Boot، ستجد الأنماط مألوفة.
الخطوة 1: إعداد المشروع
ثبّت NestJS CLI عالمياً وأنشئ مشروعاً جديداً:
npm i -g @nestjs/cli
nest new user-apiاختر npm عند السؤال. ثم انتقل إلى مجلد المشروع:
cd user-apiثبّت الاعتماديات المطلوبة:
npm install @nestjs/typeorm typeorm pg
npm install @nestjs/config
npm install @nestjs/jwt @nestjs/passport passport passport-jwt
npm install class-validator class-transformer
npm install bcrypt
npm install -D @types/passport-jwt @types/bcryptهيكل المشروع يبدو هكذا:
user-api/
├── src/
│ ├── app.module.ts # الوحدة الجذرية
│ ├── app.controller.ts # المتحكم الجذري
│ ├── app.service.ts # الخدمة الجذرية
│ └── main.ts # نقطة الدخول
├── test/
│ └── app.e2e-spec.ts # اختبارات شاملة
├── nest-cli.json
├── tsconfig.json
└── package.json
الخطوة 2: إعداد قاعدة البيانات
أنشئ ملف .env في جذر المشروع:
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_USER=postgres
DATABASE_PASSWORD=postgres
DATABASE_NAME=user_api
JWT_SECRET=your-super-secret-key-change-in-production
JWT_EXPIRES_IN=1hحدّث app.module.ts لإعداد TypeORM ومتغيرات البيئة:
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
type: 'postgres',
host: config.get('DATABASE_HOST'),
port: config.get<number>('DATABASE_PORT'),
username: config.get('DATABASE_USER'),
password: config.get('DATABASE_PASSWORD'),
database: config.get('DATABASE_NAME'),
autoLoadEntities: true,
synchronize: process.env.NODE_ENV !== 'production',
}),
}),
],
})
export class AppModule {}لا تستخدم synchronize: true أبداً في الإنتاج. هذا الخيار يعدّل بنية قاعدة البيانات تلقائياً عند كل تشغيل، مما قد يسبب فقدان البيانات. استخدم الترحيلات (Migrations) بدلاً من ذلك — سنعدّها لاحقاً.
الخطوة 3: إنشاء كيان المستخدم
أنشئ وحدة المستخدمين باستخدام CLI:
nest g module users
nest g controller users
nest g service usersأنشئ كيان المستخدم:
// src/users/entities/user.entity.ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ length: 100 })
name: string;
@Column({ unique: true })
email: string;
@Column({ select: false })
password: string;
@Column({ default: 'user' })
role: string;
@Column({ default: true })
isActive: boolean;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}سجّل الكيان في وحدة المستخدمين:
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { User } from './entities/user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}الخطوة 4: كائنات نقل البيانات والتحقق
أنشئ كائنات نقل البيانات (DTOs) للتحقق من الطلبات الواردة:
// src/users/dto/create-user.dto.ts
import { IsEmail, IsString, MinLength, IsOptional } from 'class-validator';
export class CreateUserDto {
@IsString()
@MinLength(2)
name: string;
@IsEmail()
email: string;
@IsString()
@MinLength(8)
password: string;
@IsOptional()
@IsString()
role?: string;
}// src/users/dto/update-user.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';
export class UpdateUserDto extends PartialType(CreateUserDto) {}أنشئ DTO للاستعلام مع ترقيم الصفحات والتصفية:
// src/users/dto/query-user.dto.ts
import { IsOptional, IsInt, Min, IsString } from 'class-validator';
import { Type } from 'class-transformer';
export class QueryUserDto {
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
page?: number = 1;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
limit?: number = 10;
@IsOptional()
@IsString()
search?: string;
@IsOptional()
@IsString()
role?: string;
}فعّل التحقق عالمياً في main.ts:
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
app.enableCors();
app.setGlobalPrefix('api/v1');
await app.listen(3000);
console.log(`Application running on: ${await app.getUrl()}`);
}
bootstrap();الخطوة 5: خدمة المستخدمين (منطق الأعمال)
نفّذ الخدمة مع عمليات CRUD وترقيم الصفحات وتشفير كلمات المرور:
// src/users/users.service.ts
import {
Injectable,
NotFoundException,
ConflictException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Like } from 'typeorm';
import * as bcrypt from 'bcrypt';
import { User } from './entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { QueryUserDto } from './dto/query-user.dto';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private readonly usersRepository: Repository<User>,
) {}
async create(createUserDto: CreateUserDto): Promise<User> {
const existing = await this.usersRepository.findOne({
where: { email: createUserDto.email },
});
if (existing) {
throw new ConflictException('البريد الإلكتروني مسجّل مسبقاً');
}
const hashedPassword = await bcrypt.hash(createUserDto.password, 12);
const user = this.usersRepository.create({
...createUserDto,
password: hashedPassword,
});
const saved = await this.usersRepository.save(user);
delete saved.password;
return saved;
}
async findAll(query: QueryUserDto) {
const { page, limit, search, role } = query;
const skip = (page - 1) * limit;
const where: any = {};
if (role) where.role = role;
if (search) where.name = Like(`%${search}%`);
const [users, total] = await this.usersRepository.findAndCount({
where,
skip,
take: limit,
order: { createdAt: 'DESC' },
});
return {
data: users,
meta: {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
},
};
}
async findOne(id: string): Promise<User> {
const user = await this.usersRepository.findOne({ where: { id } });
if (!user) {
throw new NotFoundException(`المستخدم بالمعرّف "${id}" غير موجود`);
}
return user;
}
async findByEmail(email: string): Promise<User> {
return this.usersRepository.findOne({
where: { email },
select: ['id', 'name', 'email', 'password', 'role', 'isActive'],
});
}
async update(id: string, updateUserDto: UpdateUserDto): Promise<User> {
const user = await this.findOne(id);
if (updateUserDto.password) {
updateUserDto.password = await bcrypt.hash(updateUserDto.password, 12);
}
Object.assign(user, updateUserDto);
return this.usersRepository.save(user);
}
async remove(id: string): Promise<void> {
const user = await this.findOne(id);
await this.usersRepository.remove(user);
}
}الخطوة 6: متحكم المستخدمين (طبقة HTTP)
أنشئ المتحكم مع أساليب HTTP المناسبة ورموز الحالة:
// src/users/users.controller.ts
import {
Controller,
Get,
Post,
Put,
Delete,
Body,
Param,
Query,
ParseUUIDPipe,
HttpCode,
HttpStatus,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { QueryUserDto } from './dto/query-user.dto';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
@HttpCode(HttpStatus.CREATED)
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Get()
findAll(@Query() query: QueryUserDto) {
return this.usersService.findAll(query);
}
@Get(':id')
findOne(@Param('id', ParseUUIDPipe) id: string) {
return this.usersService.findOne(id);
}
@Put(':id')
update(
@Param('id', ParseUUIDPipe) id: string,
@Body() updateUserDto: UpdateUserDto,
) {
return this.usersService.update(id, updateUserDto);
}
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
remove(@Param('id', ParseUUIDPipe) id: string) {
return this.usersService.remove(id);
}
}الخطوة 7: المصادقة بـ JWT
أنشئ وحدة المصادقة:
nest g module auth
nest g controller auth
nest g service authأنشئ استراتيجية JWT:
// src/auth/strategies/jwt.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
import { UsersService } from '../../users/users.service';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private configService: ConfigService,
private usersService: UsersService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get('JWT_SECRET'),
});
}
async validate(payload: { sub: string; email: string }) {
const user = await this.usersService.findOne(payload.sub);
if (!user || !user.isActive) {
throw new UnauthorizedException();
}
return user;
}
}نفّذ خدمة المصادقة:
// src/auth/auth.service.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcrypt';
import { UsersService } from '../users/users.service';
import { CreateUserDto } from '../users/dto/create-user.dto';
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
) {}
async register(createUserDto: CreateUserDto) {
const user = await this.usersService.create(createUserDto);
const token = this.generateToken(user.id, user.email);
return { user, access_token: token };
}
async login(email: string, password: string) {
const user = await this.usersService.findByEmail(email);
if (!user || !(await bcrypt.compare(password, user.password))) {
throw new UnauthorizedException('بيانات الاعتماد غير صالحة');
}
delete user.password;
const token = this.generateToken(user.id, user.email);
return { user, access_token: token };
}
private generateToken(userId: string, email: string): string {
return this.jwtService.sign({ sub: userId, email });
}
}أنشئ متحكم المصادقة:
// src/auth/auth.controller.ts
import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';
import { AuthService } from './auth.service';
import { CreateUserDto } from '../users/dto/create-user.dto';
class LoginDto {
email: string;
password: string;
}
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('register')
register(@Body() createUserDto: CreateUserDto) {
return this.authService.register(createUserDto);
}
@Post('login')
@HttpCode(HttpStatus.OK)
login(@Body() loginDto: LoginDto) {
return this.authService.login(loginDto.email, loginDto.password);
}
}اربط وحدة المصادقة:
// src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtStrategy } from './strategies/jwt.strategy';
import { UsersModule } from '../users/users.module';
@Module({
imports: [
UsersModule,
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
secret: config.get('JWT_SECRET'),
signOptions: { expiresIn: config.get('JWT_EXPIRES_IN', '1h') },
}),
}),
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}الخطوة 8: حماية المسارات بالحراس
أنشئ حارس مصادقة JWT:
// src/auth/guards/jwt-auth.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}أنشئ حارس الأدوار للتفويض:
// src/auth/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) =>
Reflect.metadata(ROLES_KEY, roles);
@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.includes(user.role);
}
}الآن احمِ متحكم المستخدمين:
// src/users/users.controller.ts (محدّث)
import {
Controller,
Get,
Put,
Delete,
Body,
Param,
Query,
ParseUUIDPipe,
HttpCode,
HttpStatus,
UseGuards,
} from '@nestjs/common';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { RolesGuard, Roles } from '../auth/guards/roles.guard';
import { UsersService } from './users.service';
import { UpdateUserDto } from './dto/update-user.dto';
import { QueryUserDto } from './dto/query-user.dto';
@Controller('users')
@UseGuards(JwtAuthGuard, RolesGuard)
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
findAll(@Query() query: QueryUserDto) {
return this.usersService.findAll(query);
}
@Get(':id')
findOne(@Param('id', ParseUUIDPipe) id: string) {
return this.usersService.findOne(id);
}
@Put(':id')
update(
@Param('id', ParseUUIDPipe) id: string,
@Body() updateUserDto: UpdateUserDto,
) {
return this.usersService.update(id, updateUserDto);
}
@Delete(':id')
@Roles('admin')
@HttpCode(HttpStatus.NO_CONTENT)
remove(@Param('id', ParseUUIDPipe) id: string) {
return this.usersService.remove(id);
}
}الخطوة 9: مرشحات الاستثناءات ومعالجة الأخطاء
أنشئ مرشح استثناءات عام لاستجابات خطأ متسقة:
// src/common/filters/http-exception.filter.ts
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '@nestjs/common';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const message =
exception instanceof HttpException
? exception.getResponse()
: 'خطأ داخلي في الخادم';
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
...(typeof message === 'string' ? { message } : message),
});
}
}سجّله عالمياً في main.ts:
// أضف إلى دالة bootstrap في main.ts
import { AllExceptionsFilter } from './common/filters/http-exception.filter';
app.useGlobalFilters(new AllExceptionsFilter());الخطوة 10: ترحيلات قاعدة البيانات
أعدّ TypeORM CLI للترحيلات. أنشئ typeorm.config.ts في جذر المشروع:
// typeorm.config.ts
import { DataSource } from 'typeorm';
import * as dotenv from 'dotenv';
dotenv.config();
export default new DataSource({
type: 'postgres',
host: process.env.DATABASE_HOST,
port: parseInt(process.env.DATABASE_PORT, 10),
username: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
entities: ['src/**/*.entity.ts'],
migrations: ['src/migrations/*.ts'],
});أضف سكريبتات الترحيل إلى package.json:
{
"scripts": {
"migration:generate": "typeorm migration:generate -d typeorm.config.ts",
"migration:run": "typeorm migration:run -d typeorm.config.ts",
"migration:revert": "typeorm migration:revert -d typeorm.config.ts"
}
}أنشئ وشغّل أول ترحيل:
npx ts-node -r tsconfig-paths/register ./node_modules/.bin/typeorm migration:generate src/migrations/CreateUsers -d typeorm.config.ts
npx ts-node -r tsconfig-paths/register ./node_modules/.bin/typeorm migration:run -d typeorm.config.tsالخطوة 11: الاختبارات
يتضمّن NestJS أدوات اختبار ممتازة. اكتب اختبار وحدة لخدمة المستخدمين:
// src/users/users.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { UsersService } from './users.service';
import { User } from './entities/user.entity';
import { NotFoundException } from '@nestjs/common';
const mockUser: Partial<User> = {
id: '550e8400-e29b-41d4-a716-446655440000',
name: 'John Doe',
email: 'john@example.com',
role: 'user',
isActive: true,
};
describe('UsersService', () => {
let service: UsersService;
let repository: Repository<User>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: getRepositoryToken(User),
useValue: {
findOne: jest.fn(),
findAndCount: jest.fn(),
create: jest.fn(),
save: jest.fn(),
remove: jest.fn(),
},
},
],
}).compile();
service = module.get<UsersService>(UsersService);
repository = module.get(getRepositoryToken(User));
});
describe('findOne', () => {
it('should return a user by ID', async () => {
jest.spyOn(repository, 'findOne').mockResolvedValue(mockUser as User);
const result = await service.findOne(mockUser.id);
expect(result).toEqual(mockUser);
});
it('should throw NotFoundException for invalid ID', async () => {
jest.spyOn(repository, 'findOne').mockResolvedValue(null);
await expect(service.findOne('invalid-id')).rejects.toThrow(
NotFoundException,
);
});
});
describe('findAll', () => {
it('should return paginated users', async () => {
jest
.spyOn(repository, 'findAndCount')
.mockResolvedValue([[mockUser as User], 1]);
const result = await service.findAll({ page: 1, limit: 10 });
expect(result.data).toHaveLength(1);
expect(result.meta.total).toBe(1);
});
});
});اكتب اختبار شامل (e2e):
// test/users.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('Users (e2e)', () => {
let app: INestApplication;
let authToken: string;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
app.setGlobalPrefix('api/v1');
await app.init();
// تسجيل والحصول على الرمز
const res = await request(app.getHttpServer())
.post('/api/v1/auth/register')
.send({
name: 'Test User',
email: 'test@example.com',
password: 'password123',
});
authToken = res.body.access_token;
});
it('/api/v1/users (GET) - يجب أن يتطلب مصادقة', () => {
return request(app.getHttpServer())
.get('/api/v1/users')
.expect(401);
});
it('/api/v1/users (GET) - يجب أن يعيد المستخدمين مع المصادقة', () => {
return request(app.getHttpServer())
.get('/api/v1/users')
.set('Authorization', `Bearer ${authToken}`)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.meta).toBeDefined();
});
});
afterAll(async () => {
await app.close();
});
});شغّل الاختبارات:
# اختبارات الوحدات
npm run test
# الاختبارات الشاملة
npm run test:e2e
# تغطية الاختبارات
npm run test:covالخطوة 12: هيكل المشروع النهائي
يجب أن يبدو مشروعك المكتمل هكذا:
user-api/
├── src/
│ ├── auth/
│ │ ├── guards/
│ │ │ ├── jwt-auth.guard.ts
│ │ │ └── roles.guard.ts
│ │ ├── strategies/
│ │ │ └── jwt.strategy.ts
│ │ ├── auth.controller.ts
│ │ ├── auth.module.ts
│ │ └── auth.service.ts
│ ├── common/
│ │ └── filters/
│ │ └── http-exception.filter.ts
│ ├── migrations/
│ │ └── ...CreateUsers.ts
│ ├── users/
│ │ ├── dto/
│ │ │ ├── create-user.dto.ts
│ │ │ ├── update-user.dto.ts
│ │ │ └── query-user.dto.ts
│ │ ├── entities/
│ │ │ └── user.entity.ts
│ │ ├── users.controller.ts
│ │ ├── users.module.ts
│ │ ├── users.service.ts
│ │ └── users.service.spec.ts
│ ├── app.module.ts
│ └── main.ts
├── test/
│ └── users.e2e-spec.ts
├── .env
├── typeorm.config.ts
└── package.json
اختبار واجهة API
شغّل التطبيق:
npm run start:devاختبر نقاط النهاية باستخدام curl:
# تسجيل مستخدم جديد
curl -X POST http://localhost:3000/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"name": "أحمد", "email": "ahmed@example.com", "password": "securepass123"}'
# تسجيل الدخول
curl -X POST http://localhost:3000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "ahmed@example.com", "password": "securepass123"}'
# جلب كل المستخدمين (مع الرمز)
curl http://localhost:3000/api/v1/users \
-H "Authorization: Bearer YOUR_TOKEN_HERE"
# جلب المستخدمين مع ترقيم الصفحات
curl "http://localhost:3000/api/v1/users?page=1&limit=5&search=ahmed" \
-H "Authorization: Bearer YOUR_TOKEN_HERE"استكشاف الأخطاء وإصلاحها
"Cannot find module 'pg'" — ثبّت مشغّل PostgreSQL: npm install pg
"relation users does not exist" — شغّل الترحيلات أو فعّل synchronize: true مؤقتاً أثناء التطوير.
"Unauthorized" في كل المسارات — تحقق أن JWT_SECRET متطابق بين .env وتوليد الرمز. تأكد أن الرمز لم تنته صلاحيته.
التحقق لا يعمل — تأكد من تسجيل ValidationPipe عالمياً في main.ts وأن مزخرفات class-validator موجودة على خصائص DTO.
أخطاء التبعية الدائرية — استخدم forwardRef() عندما تعتمد وحدتان على بعضهما: @Inject(forwardRef(() => UsersService)).
الخطوات التالية
الآن بعد أن لديك أساس متين، فكّر في توسيع واجهة API بـ:
- توثيق Swagger — أضف
@nestjs/swaggerلتوثيق API تلقائي - تحديد المعدّل — استخدم
@nestjs/throttlerللحماية من الإساءة - التخزين المؤقت — نفّذ تخزين Redis مع
@nestjs/cache-manager - رفع الملفات — أضف
@nestjs/platform-expressمع Multer - WebSockets — أضف ميزات الوقت الفعلي مع
@nestjs/websockets - GraphQL — استبدل REST بـ
@nestjs/graphqlو Apollo Server - الخدمات المصغّرة — قسّم إلى خدمات مصغّرة باستخدام
@nestjs/microservices
الخلاصة
لقد بنيت واجهة REST API جاهزة للإنتاج باستخدام NestJS و TypeORM و PostgreSQL. تعلّمت كيف تعزّز البنية المعيارية لـ NestJS تنظيم الكود النظيف، وكيف يبسّط TypeORM عمليات قاعدة البيانات، وكيفية تنفيذ مصادقة JWT مع التحكم في الوصول بناءً على الأدوار.
قد تبدو بنية NestJS المنهجية ثقيلة للمشاريع الصغيرة، لكنها تتألق كلما نما تطبيقك — نظام حقن التبعيات، أدوات الاختبار المدمجة، والتصميم المعياري يجعلون إضافة الميزات سهلة دون تراكم الديون التقنية. لهذا يظل NestJS الخيار الأول لتطبيقات Node.js في المؤسسات.
ناقش مشروعك معنا
نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.
دعنا نجد أفضل الحلول لاحتياجاتك.
مقالات ذات صلة

بناء واجهات REST API باستخدام Go و Fiber: دليل عملي للمبتدئين
تعلّم كيف تبني واجهات REST API سريعة وجاهزة للإنتاج باستخدام لغة Go وإطار Fiber. يغطي هذا الدليل خطوة بخطوة إعداد المشروع، التوجيه، معالجة JSON، الربط بقاعدة البيانات عبر GORM، الوسطاء، معالجة الأخطاء، والاختبارات — من الصفر إلى واجهة API عاملة.

بناء تطبيق متكامل باستخدام Drizzle ORM و Next.js 15: قاعدة بيانات آمنة الأنواع من الصفر إلى الإنتاج
تعلّم كيفية بناء تطبيق متكامل آمن الأنواع باستخدام Drizzle ORM مع Next.js 15. يغطي هذا الدليل العملي تصميم المخططات والترحيلات وServer Actions وعمليات CRUD والنشر مع PostgreSQL.

بناء واجهات REST API جاهزة للإنتاج باستخدام FastAPI و PostgreSQL و Docker
تعلّم كيف تبني وتختبر وتنشر واجهة REST API احترافية باستخدام إطار FastAPI في بايثون مع PostgreSQL و SQLAlchemy و Alembic و Docker Compose — من الصفر حتى النشر.