مشروع Node.js مع TypeScript — API كامل 2026
مشروع عملي لبناء API كامل بـ Node.js و Express و TypeScript — خطوة بخطوة مع الكود الكامل والشرح
في المقال السابق، بنيت تطبيق React كاملاً بـ TypeScript. الآن سننتقل إلى Backend — سنبني API كامل باستخدام Node.js و Express و TypeScript.
هذا المشروع سيعلمك:
- إعداد مشروع Node.js مع TypeScript.
- بناء API مع Express.
- تعريف الأنواع للطلبات والاستجابات.
- معالجة الأخطاء.
- تنظيم الكود في طبقات (Routes, Controllers, Services).
ما سنبنيه
API لإدارة المهام (Todos) بوظائف:
- GET /api/todos — جلب كل المهام.
- GET /api/todos/:id — جلب مهمة واحدة.
- POST /api/todos — إضافة مهمة جديدة.
- PUT /api/todos/:id — تعديل مهمة.
- DELETE /api/todos/:id — حذف مهمة.
هيكل المشروع
سننشئ:
node-todo-api/
├── src/
│ ├── types/
│ │ └── todo.ts
│ ├── controllers/
│ │ └── todoController.ts
│ ├── services/
│ │ └── todoService.ts
│ ├── routes/
│ │ └── todoRoutes.ts
│ ├── middleware/
│ │ └── errorHandler.ts
│ ├── app.ts
│ └── server.ts
├── package.json
├── tsconfig.json
└── .env
الخطوة 1: إنشاء المشروع
افتح Terminal، واكتب:
mkdir node-todo-api
cd node-todo-api
npm init -y
npm install express cors
npm install -D typescript @types/node @types/express @types/cors ts-node nodemon
شرح المكتبات:
express: إطار عمل للـ API.cors: للسماح بالطلبات من نطاقات مختلفة.typescript: لغة TypeScript.@types/*: تعريفات الأنواع.ts-node: تشغيل TypeScript مباشرة.nodemon: إعادة تشغيل السيرفر تلقائياً.
الخطوة 2: إعداد TypeScript
أنشئ ملف tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
الخطوة 3: تحديث package.json
افتح package.json وعدّل scripts:
{
"name": "node-todo-api",
"version": "1.0.0",
"scripts": {
"dev": "nodemon --exec ts-node src/server.ts",
"build": "tsc",
"start": "node dist/server.js"
},
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5"
},
"devDependencies": {
"typescript": "^5.0.0",
"@types/node": "^20.0.0",
"@types/express": "^4.17.17",
"@types/cors": "^2.8.13",
"ts-node": "^10.9.1",
"nodemon": "^3.0.1"
}
}
الخطوة 4: تعريف الأنواع (Types)
أنشئ ملف src/types/todo.ts:
// المهمة الواحدة
export interface Todo {
id: string;
text: string;
completed: boolean;
createdAt: string;
}
// طلب إضافة مهمة
export interface CreateTodoDTO {
text: string;
}
// طلب تعديل مهمة
export interface UpdateTodoDTO {
text?: string;
completed?: boolean;
}
// استجابة API
export interface ApiResponse<T> {
success: boolean;
data?: T;
message?: string;
error?: string;
}
شرح:
Todo: شكل المهمة الكاملة.CreateTodoDTO: البيانات المطلوبة لإضافة مهمة.UpdateTodoDTO: البيانات المطلوبة للتعديل (كلها اختيارية).ApiResponse<T>: استجابة عامة (Generic) لأي نوع.
الخطوة 5: طبقة الخدمات (Service)
هذه الطبقة تحتوي على منطق العمل (Business Logic).
أنشئ ملف src/services/todoService.ts:
import { Todo, CreateTodoDTO, UpdateTodoDTO } from "../types/todo";
import { randomUUID } from "crypto";
class TodoService {
private todos: Todo[] = [];
// جلب كل المهام
getAll(): Todo[] {
return this.todos;
}
// جلب مهمة واحدة
getById(id: string): Todo | undefined {
return this.todos.find((todo) => todo.id === id);
}
// إضافة مهمة
create(dto: CreateTodoDTO): Todo {
const newTodo: Todo = {
id: randomUUID(),
text: dto.text,
completed: false,
createdAt: new Date().toISOString(),
};
this.todos.push(newTodo);
return newTodo;
}
// تعديل مهمة
update(id: string, dto: UpdateTodoDTO): Todo | null {
const index = this.todos.findIndex((todo) => todo.id === id);
if (index === -1) return null;
this.todos[index] = {
...this.todos[index],
...dto,
};
return this.todos[index];
}
// حذف مهمة
delete(id: string): boolean {
const index = this.todos.findIndex((todo) => todo.id === id);
if (index === -1) return false;
this.todos.splice(index, 1);
return true;
}
}
export const todoService = new TodoService();
شرح:
class TodoService: فئة تحتوي على منطق العمل.private todos: مصفوفة داخلية (سنستبدلها بقاعدة بيانات لاحقاً).getAll, getById, create, update, delete: الطرق الأساسية.export const todoService: كائن واحد (Singleton).
الخطوة 6: طبقة المتحكمات (Controllers)
هذه الطبقة تتعامل مع الطلبات والاستجابات.
أنشئ ملف src/controllers/todoController.ts:
import { Request, Response, NextFunction } from "express";
import { todoService } from "../services/todoService";
import { CreateTodoDTO, UpdateTodoDTO, ApiResponse, Todo } from "../types/todo";
// جلب كل المهام
export const getAllTodos = (
req: Request,
res: Response<ApiResponse<Todo[]>>,
next: NextFunction
): void => {
try {
const todos = todoService.getAll();
res.json({
success: true,
data: todos,
});
} catch (error) {
next(error);
}
};
// جلب مهمة واحدة
export const getTodoById = (
req: Request<{ id: string }>,
res: Response<ApiResponse<Todo>>,
next: NextFunction
): void => {
try {
const todo = todoService.getById(req.params.id);
if (!todo) {
res.status(404).json({
success: false,
error: "المهمة غير موجودة",
});
return;
}
res.json({
success: true,
data: todo,
});
} catch (error) {
next(error);
}
};
// إضافة مهمة
export const createTodo = (
req: Request<{}, {}, CreateTodoDTO>,
res: Response<ApiResponse<Todo>>,
next: NextFunction
): void => {
try {
const { text } = req.body;
if (!text || text.trim() === "") {
res.status(400).json({
success: false,
error: "النص مطلوب",
});
return;
}
const newTodo = todoService.create({ text: text.trim() });
res.status(201).json({
success: true,
data: newTodo,
message: "تم إنشاء المهمة بنجاح",
});
} catch (error) {
next(error);
}
};
// تعديل مهمة
export const updateTodo = (
req: Request<{ id: string }, {}, UpdateTodoDTO>,
res: Response<ApiResponse<Todo>>,
next: NextFunction
): void => {
try {
const updated = todoService.update(req.params.id, req.body);
if (!updated) {
res.status(404).json({
success: false,
error: "المهمة غير موجودة",
});
return;
}
res.json({
success: true,
data: updated,
message: "تم التحديث بنجاح",
});
} catch (error) {
next(error);
}
};
// حذف مهمة
export const deleteTodo = (
req: Request<{ id: string }>,
res: Response<ApiResponse<null>>,
next: NextFunction
): void => {
try {
const deleted = todoService.delete(req.params.id);
if (!deleted) {
res.status(404).json({
success: false,
error: "المهمة غير موجودة",
});
return;
}
res.json({
success: true,
message: "تم الحذف بنجاح",
});
} catch (error) {
next(error);
}
};
شرح:
Request<Params, ResBody, ReqBody>: أنواع Express.Response<ApiResponse<T>>: الاستجابة بنوع عام.next: لدالة معالجة الأخطاء.
الخطوة 7: طبقة المسارات (Routes)
أنشئ ملف src/routes/todoRoutes.ts:
import { Router } from "express";
import {
getAllTodos,
getTodoById,
createTodo,
updateTodo,
deleteTodo,
} from "../controllers/todoController";
const router = Router();
router.get("/", getAllTodos);
router.get("/:id", getTodoById);
router.post("/", createTodo);
router.put("/:id", updateTodo);
router.delete("/:id", deleteTodo);
export default router;
الخطوة 8: Middleware لمعالجة الأخطاء
أنشئ ملف src/middleware/errorHandler.ts:
import { Request, Response, NextFunction } from "express";
import { ApiResponse } from "../types/todo";
export const errorHandler = (
err: Error,
req: Request,
res: Response<ApiResponse<null>>,
next: NextFunction
): void => {
console.error("Error:", err.message);
res.status(500).json({
success: false,
error: "حدث خطأ في السيرفر",
});
};
export const notFoundHandler = (
req: Request,
res: Response<ApiResponse<null>>
): void => {
res.status(404).json({
success: false,
error: "المسار غير موجود",
});
};
الخطوة 9: تطبيق Express
أنشئ ملف src/app.ts:
import express, { Application } from "express";
import cors from "cors";
import todoRoutes from "./routes/todoRoutes";
import { errorHandler, notFoundHandler } from "./middleware/errorHandler";
const app: Application = express();
// Middleware
app.use(cors());
app.use(express.json());
// Routes
app.use("/api/todos", todoRoutes);
// Health check
app.get("/api/health", (req, res) => {
res.json({ status: "OK", timestamp: new Date().toISOString() });
});
// Error handlers (بعد كل المسارات)
app.use(notFoundHandler);
app.use(errorHandler);
export default app;
الخطوة 10: نقطة البداية (Server)
أنشئ ملف src/server.ts:
import app from "./app";
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`🚀 السيرفر يعمل على http://localhost:${PORT}`);
console.log(`📋 API: http://localhost:${PORT}/api/todos`);
});
الخطوة 11: تشغيل المشروع
npm run dev
النتيجة:
🚀 السيرفر يعمل على http://localhost:5000
📋 API: http://localhost:5000/api/todos
🎉 مبروك! لديك API كامل يعمل!
اختبار API
1. جلب كل المهام
curl http://localhost:5000/api/todos
النتيجة:
{ "success": true, "data": [] }
2. إضافة مهمة
curl -X POST http://localhost:5000/api/todos \
-H "Content-Type: application/json" \
-d '{"text": "تعلم TypeScript"}'
النتيجة:
{
"success": true,
"data": {
"id": "abc-123",
"text": "تعلم TypeScript",
"completed": false,
"createdAt": "2026-10-09T..."
},
"message": "تم إنشاء المهمة بنجاح"
}
3. تعديل مهمة
curl -X PUT http://localhost:5000/api/todos/abc-123 \
-H "Content-Type: application/json" \
-d '{"completed": true}'
4. حذف مهمة
curl -X DELETE http://localhost:5000/api/todos/abc-123
فهم كيف يعمل TypeScript هنا
1. الأنواع في الطلبات
req: Request<{ id: string }, {}, UpdateTodoDTO>
الفائدة: req.params.id هو string، و req.body هو UpdateTodoDTO.
2. الأنواع في الاستجابات
res: Response<ApiResponse<Todo>>
الفائدة: كل استجابة تطابق ApiResponse<Todo>.
3. الأدوية (Generics)
interface ApiResponse<T> {
success: boolean;
data?: T;
}
الفائدة: نفس الـ Interface يعمل مع Todo[], Todo, null.
تمارين إضافية
تمرين 1: إضافة البحث
أضف GET /api/todos/search?q=text.
الحل:
// في Service
search(query: string): Todo[] {
return this.todos.filter((t) =>
t.text.toLowerCase().includes(query.toLowerCase())
);
}
// في Controller
export const searchTodos = (req, res, next) => {
const query = req.query.q as string || "";
const results = todoService.search(query);
res.json({ success: true, data: results });
};
تمرين 2: Pagination
أضف ?page=1&limit=10.
الحل:
getPaginated(page: number, limit: number): Todo[] {
const start = (page - 1) * limit;
return this.todos.slice(start, start + limit);
}
تمرين 3: تصفية حسب completed
أضف ?completed=true.
الحل:
getFiltered(completed?: boolean): Todo[] {
if (completed === undefined) return this.todos;
return this.todos.filter((t) => t.completed === completed);
}
تمرين 4: التحقق من البيانات
أضف مكتبة zod للتحقق.
الحل:
npm install zod
import { z } from "zod";
const CreateTodoSchema = z.object({
text: z.string().min(1).max(200),
});
// في Controller
const result = CreateTodoSchema.safeParse(req.body);
if (!result.success) {
res.status(400).json({
success: false,
error: result.error.errors[0].message,
});
return;
}
تمرين 5: قاعدة بيانات حقيقية
استبدل المصفوفة بـ SQLite أو MongoDB.
الحل باستخدام Prisma + SQLite:
npm install prisma @prisma/client
npx prisma init --datasource-provider sqlite
ثم:
model Todo {
id String @id @default(uuid())
text String
completed Boolean @default(false)
createdAt DateTime @default(now())
}
حل المشاكل الشائعة
🔴 المشكلة 1: Cannot find module 'express'
السبب: المكتبة غير مثبتة.
الحل:
npm install express @types/express
🔴 المشكلة 2: Port 5000 already in use
السبب: منفذ مشغول.
الحل: استخدم منفذاً آخر:
const PORT = process.env.PORT || 5001;
🔴 المشكلة 3: req.body is undefined
السبب: لم تضف express.json().
الحل:
app.use(express.json());
🔴 المشكلة 4: Type 'X' is not assignable to type 'Y'
السبب: نوع خاطئ في الطلب أو الاستجابة.
الحل: تأكد من تطابق الأنواع.
جدول الأوامر الأساسية
| الأمر | الوظيفة |
|---|---|
npm run dev | تشغيل السيرفر |
npm run build | بناء المشروع |
npm start | تشغيل الإنتاج |
curl http://localhost:5000/api/todos | اختبار API |
قائمة تحقق نهائية
| المهمة | الحالة |
|---|---|
| إنشاء المشروع وتثبيت المكتبات | ⬜ |
| إعداد tsconfig.json | ⬜ |
| تعريف الأنواع (types/todo.ts) | ⬜ |
| إنشاء Service | ⬜ |
| إنشاء Controllers | ⬜ |
| إنشاء Routes | ⬜ |
| إضافة Error Handler | ⬜ |
| إعداد app.ts و server.ts | ⬜ |
| تشغيل السيرفر بنجاح | ⬜ |
| اختبار API بـ curl | ⬜ |
| حل تمرين واحد على الأقل | ⬜ |
🎉 مبروك! أكملت مسار TypeScript!
لقد وصلت إلى نهاية المسار. أنت الآن تعرف:
- ✅ أساسيات TypeScript: الأنواع، الواجهات، الدوال.
- ✅ المفاهيم المتقدمة: الفئات، الأدوية، الأنواع المتقدمة.
- ✅ مشروع React: واجهة أمامية تفاعلية.
- ✅ مشروع Node.js: API خلفي كامل.
ماذا بعد؟
الآن أنت جاهز لـ:
- Next.js + TypeScript — إطار عمل كامل.
- قواعد البيانات — PostgreSQL, MongoDB.
- المصادقة — JWT, OAuth.
- النشر — Vercel, Railway, AWS.
- الاختبارات — Jest, Vitest.
- بناء معرض أعمالك — ارفع مشاريعك على GitHub.
الخلاصة
في هذا المشروع، طبقت:
- ✅ Node.js و Express لبناء API.
- ✅ TypeScript للأنواع والأمان.
- ✅ تنظيم الكود في طبقات.
- ✅ معالجة الأخطاء بشكل احترافي.
- ✅ الأدوية (Generics) في
ApiResponse<T>. - ✅ الواجهات لكل نوع بيانات.
- ✅ اختبار API بـ curl.
هذا المشروع هو أساس كل Backend احترافي. احتفظ بالكود، وطور فيه بنفسك!
🎉 هل أكملت مسار TypeScript بالكامل؟ شاركنا في التعليقات!
📚 مقالات ذات صلة
مشروع React مع TypeScript — تطبيق مهام كامل 2026
مشروع عملي لبناء تطبيق مهام (Todo App) بـ React و TypeScript — خطوة بخطوة مع الكود الكامل والشرح
الأنواع المتقدمة في TypeScript — دليل شامل 2026
دليل عملي مفصل للأنواع المتقدمة في TypeScript — Union, Intersection, Conditional, Mapped, Template Literal، مع تمارين وحلول
الأدوية (Generics) في TypeScript — دليل شامل 2026
دليل عملي مفصل للأدوية في TypeScript — الدوال العامة، الواجهات العامة، القيود، مع تمارين وحلول