TypeScript

مشروع Node.js مع TypeScript — API كامل 2026

📅 2026-10-09⏱ 10 دقائق قراءة
في المقال السابق، بنيت تطبيق 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، واكتب: ```bash 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`:** ```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`: ```json { "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`:** ```typescript // المهمة الواحدة 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`:** ```typescript 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`:** ```typescript 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`:** ```typescript 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`:** ```typescript 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`:** ```typescript 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`:** ```typescript 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: تشغيل المشروع ```bash npm run dev ``` **النتيجة:** ```bash 🚀 السيرفر يعمل على http://localhost:5000 📋 API: http://localhost:5000/api/todos ``` **🎉 مبروك! لديك API كامل يعمل!** ## اختبار API ### 1. جلب كل المهام ```bash curl http://localhost:5000/api/todos ``` **النتيجة:** ```json { "success": true, "data": [] } ``` ### 2. إضافة مهمة ```bash curl -X POST http://localhost:5000/api/todos \ -H "Content-Type: application/json" \ -d '{"text": "تعلم TypeScript"}' ``` **النتيجة:** ```json { "success": true, "data": { "id": "abc-123", "text": "تعلم TypeScript", "completed": false, "createdAt": "2026-10-09T..." }, "message": "تم إنشاء المهمة بنجاح" } ``` ### 3. تعديل مهمة ```bash curl -X PUT http://localhost:5000/api/todos/abc-123 \ -H "Content-Type: application/json" \ -d '{"completed": true}' ``` ### 4. حذف مهمة ```bash curl -X DELETE http://localhost:5000/api/todos/abc-123 ``` ## فهم كيف يعمل TypeScript هنا ### 1. الأنواع في الطلبات ```typescript req: Request<{ id: string }, {}, UpdateTodoDTO> ``` **الفائدة:** `req.params.id` هو `string`، و `req.body` هو `UpdateTodoDTO`. ### 2. الأنواع في الاستجابات ```typescript res: Response<ApiResponse<Todo>> ``` **الفائدة:** كل استجابة تطابق `ApiResponse<Todo>`. ### 3. الأدوية (Generics) ```typescript interface ApiResponse<T> { success: boolean; data?: T; } ``` **الفائدة:** نفس الـ Interface يعمل مع `Todo[]`, `Todo`, `null`. ## تمارين إضافية ### تمرين 1: إضافة البحث أضف `GET /api/todos/search?q=text`. **الحل:** ```typescript // في 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`. **الحل:** ```typescript getPaginated(page: number, limit: number): Todo[] { const start = (page - 1) * limit; return this.todos.slice(start, start + limit); } ``` ### تمرين 3: تصفية حسب completed أضف `?completed=true`. **الحل:** ```typescript getFiltered(completed?: boolean): Todo[] { if (completed === undefined) return this.todos; return this.todos.filter((t) => t.completed === completed); } ``` ### تمرين 4: التحقق من البيانات أضف مكتبة `zod` للتحقق. **الحل:** ```bash npm install zod ``` ```typescript 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:** ```bash npm install prisma @prisma/client npx prisma init --datasource-provider sqlite ``` ثم: ```prisma model Todo { id String @id @default(uuid()) text String completed Boolean @default(false) createdAt DateTime @default(now()) } ``` ## حل المشاكل الشائعة ### 🔴 المشكلة 1: `Cannot find module 'express'` **السبب:** المكتبة غير مثبتة. **الحل:** ```bash npm install express @types/express ``` ### 🔴 المشكلة 2: `Port 5000 already in use` **السبب:** منفذ مشغول. **الحل:** استخدم منفذاً آخر: ```typescript const PORT = process.env.PORT || 5001; ``` ### 🔴 المشكلة 3: `req.body is undefined` **السبب:** لم تضف `express.json()`. **الحل:** ```typescript app.use(express.json()); ``` ### 🔴 المشكلة 4: `Type 'X' is not assignable to type 'Y'` **السبب:** نوع خاطئ في الطلب أو الاستجابة. **الحل:** تأكد من تطابق الأنواع. ## جدول الأوامر الأساسية <table> <thead> <tr> <th>الأمر</th> <th>الوظيفة</th> </tr> </thead> <tbody> <tr> <td><code>npm run dev</code></td> <td>تشغيل السيرفر</td> </tr> <tr> <td><code>npm run build</code></td> <td>بناء المشروع</td> </tr> <tr> <td><code>npm start</code></td> <td>تشغيل الإنتاج</td> </tr> <tr> <td><code>curl http://localhost:5000/api/todos</code></td> <td>اختبار API</td> </tr> </tbody> </table> ## قائمة تحقق نهائية <table> <thead> <tr> <th>المهمة</th> <th>الحالة</th> </tr> </thead> <tbody> <tr> <td>إنشاء المشروع وتثبيت المكتبات</td> <td>⬜</td> </tr> <tr> <td>إعداد tsconfig.json</td> <td>⬜</td> </tr> <tr> <td>تعريف الأنواع (types/todo.ts)</td> <td>⬜</td> </tr> <tr> <td>إنشاء Service</td> <td>⬜</td> </tr> <tr> <td>إنشاء Controllers</td> <td>⬜</td> </tr> <tr> <td>إنشاء Routes</td> <td>⬜</td> </tr> <tr> <td>إضافة Error Handler</td> <td>⬜</td> </tr> <tr> <td>إعداد app.ts و server.ts</td> <td>⬜</td> </tr> <tr> <td>تشغيل السيرفر بنجاح</td> <td>⬜</td> </tr> <tr> <td>اختبار API بـ curl</td> <td>⬜</td> </tr> <tr> <td>حل تمرين واحد على الأقل</td> <td>⬜</td> </tr> </tbody> </table> ## 🎉 مبروك! أكملت مسار TypeScript! لقد وصلت إلى نهاية المسار. أنت الآن تعرف: - ✅ **أساسيات TypeScript:** الأنواع، الواجهات، الدوال. - ✅ **المفاهيم المتقدمة:** الفئات، الأدوية، الأنواع المتقدمة. - ✅ **مشروع React:** واجهة أمامية تفاعلية. - ✅ **مشروع Node.js:** API خلفي كامل. ## ماذا بعد؟ الآن أنت جاهز لـ: 1. **Next.js + TypeScript** — إطار عمل كامل. 2. **قواعد البيانات** — PostgreSQL, MongoDB. 3. **المصادقة** — JWT, OAuth. 4. **النشر** — Vercel, Railway, AWS. 5. **الاختبارات** — Jest, Vitest. 6. **بناء معرض أعمالك** — ارفع مشاريعك على GitHub. ## الخلاصة في هذا المشروع، طبقت: - ✅ **Node.js و Express** لبناء API. - ✅ **TypeScript** للأنواع والأمان. - ✅ **تنظيم الكود** في طبقات. - ✅ **معالجة الأخطاء** بشكل احترافي. - ✅ **الأدوية (Generics)** في `ApiResponse<T>`. - ✅ **الواجهات** لكل نوع بيانات. - ✅ **اختبار API** بـ curl. **هذا المشروع هو أساس كل Backend احترافي.** احتفظ بالكود، وطور فيه بنفسك! **🎉 هل أكملت مسار TypeScript بالكامل؟ شاركنا في التعليقات!**