المجموعة FARM هي مجموعة معاصرة لتطوير الويب التي تجمع ثلاث تقنيات قوية: FastAPI، React، و MongoDB. هذه الحلولة الكاملة توفر للمطورين مجموعة من الأدوات القوية لبناء تطبيقات الويب القابلة للتحجيم والفعالة والأداء العالي.

في هذه المقالة، سأقدم لكم مقدمة عن كل من هذه التقنيات الرئيسية، ومن ثم سنبني مشروعا باستخدام مجموعة FARM و Docker لترون كيفية عمل جميع الأشياء معا.

هذه المقالة مبنية على دورة أنشأتها في قناة freeCodeCamp.org على YouTube. شاهدوها هنا:

مقدمة عن مجموعة FARM

يشير FARM في مجموعة FARM إلى:

  • F: FastAPI (الواجهة الخلفية)

  • R: React (الواجهة الأمامية)

  • M: MongoDB (قاعدة البيانات)

تم تصميم مجموعة FARM ليستفيد من قوت كل جزء، مما يسمح للمطورين ببناء تطبيقات مميزة بخصائص كبيرة وخلال تجربة تطوير سلسة.

عناصر مجموعة FARM

  1. FastAPI: FastAPI هو إطار ويب بيثوني حديث وأداء عالٍ لبناء API. يتم تصميمه ليكون سهلاً الاستخدام، سريعاً في التعديل، وجاهزاً للبيئات الإنتاجية. FastAPI مبني على Starlette للأجزاء الويبية و Pydantic للأجزاء البيانية، مما يجعله خيار قوي لبناء خدمات الواجهة الخلفية المروجة.

  2. React: يتم تطوير React وإدارته من قبل فيسبوك ، وهو مكتب جافاScript مشهور لبناء الواجهات المستخدمة. يسمح للمطورين بإنشاء مكونات واجهة المستخدم قابلة للتكرار والتحديث والتصرف بكفاءة أثناء تغيير البيانات. تقوم هياكلها المبنية على المكونات والDOM الافتراضي بجعله خيارًا جيدًا لبناء تطبيقات الواجهة الأولية الحيوية والمستجيبة.
  3. MongoDB: يوجد MongoDB وهي قاعدة بيانات متناولة الوثائق ول

  1. الأمور الجيدة في استخدام FARM Stack:

  2. التنمية: جميع المكونات من قاعدة FARM صممت للتنمية. يمكن ل FastAPI معالجة ال solicitudes متجاوزة بالكفاءة، وتطبيقات React تستطيع إدارة واجهات المستخدم معقدة، ويمكن ل MongoDB توزيع البيانات عبر مخزون متعدد من السيرفرات.

  3. المجتمع والإيكوسيما: تتمتع جميع هذه التكنولوجيات الثلاث بمجتمعات كبيرة ونشطة وبنظم متعددة من المكتبات والأدوات.

  4. المرونة: تعد مجموعة FARM مرنة بما يكفي لاستيعاب أنواع مختلفة من تطبيقات الويب ، من تطبيقات CRUD البسيطة إلى الأنظمة المعقدة والغنية بالبيانات.

من خلال الجمع بين هذه التقنيات ، توفر مجموعة FARM حلاً شاملاً لبناء تطبيقات الويب الحديثة. يتيح للمطورين إنشاء خلفيات سريعة وقابلة للتوسع باستخدام FastAPI ، وواجهات أمامية بديهية ومتجاوبة باستخدام React ، وتخزين بيانات مرن وفعال باستخدام MongoDB. تعتبر هذه المجموعة مناسبة بشكل خاص للتطبيقات التي تتطلب تحديثات فورية ، ونماذج بيانات معقدة ، وأداء عالي.

نظرة عامة على المشروع: تطبيق المهام

في دورة الفيديو ، سأتحدث بشكل أكثر عن كل تقنية فردية في مجموعة FARM. ولكن في هذه المقالة ، سنبدأ مباشرة في مشروع لتجميع كل شيء معًا.

سنقوم بإنشاء تطبيق للمهام لمساعدتنا في فهم مجموعة FARM. قبل أن نبدأ في إنشاء التطبيق ، دعنا نناقش المزيد حول الميزات وهندسة البرمجيات.

ميزات تطبيق المهام

سيتضمن تطبيق المهام الخاص بمجموعة FARM الميزات التالية:

  1. قوائم المهام المتعددة:

    • يمكن للمستخدمين إنشاء وعرض وتحديث وحذف قوائم المهام المتعددة.

    • تحتوي كل قائمة على اسم وتحتوي على عناصر مهام متعددة.

  2. عناصر المعالجة:

    • خلال كل قائمة، يمكن للمستخدمين إضافة وعرض وتحديث وحذف عناصر المعالجة.

    • تحتوي كل عنصر على وسم، وحالة مشخصة/غير مشخصة، وتنتمي إلى قائمة محددة.

  3. تحديثات فورية:

    • تحدث الواجهة الرسومية بشكل فوري عندما تتم إجراء تغييرات على القوائم أو العناصر.
  4. تصميم متجاوب:

    • سيكون التطبيق متجاوب ويعمل بشكل جيد على الحاسب الشخصي والأجهزة النقالة.

هيكلة النظام

سيتبع التطبيق المعالجة الخاص بنا نظ

  1. الواجهة الأمامية (React):

    • توفر واجهة المستخدم للتفاعل مع قوائم المهام والعناصر.

    • تتواصل مع الخلفية عبر استدعاءات واجهة برمجة تطبيقات RESTful.

  2. الخلفية (FastAPI):

    • تتعامل مع طلبات واجهة برمجة التطبيقات من الواجهة الأمامية.

    • تنفذ المنطق التجاري لإدارة قوائم المهام والعناصر.

    • تتفاعل مع قاعدة بيانات MongoDB لاستمرارية البيانات.

  3. قاعدة البيانات (MongoDB):

    • تخزن قوائم المهام والعناصر.

    • توفر استعلامًا فعالًا وتحديثًا لبيانات المهام.

  4. دوكر:

    • يجعل كل مكون (الواجهة الأمامية، الخلفية، قاعدة البيانات) محايدًا للحاويات لسهولة التطوير والنشر.

تصميم نموذج البيانات

يتكون نموذج بياناتنا في MongoDB من هيكلين رئيسيين:

  1. قائمة المهام:
   {
     "_id": ObjectId,
     "name": String,
     "items": [
       {
         "id": String,
         "label": String,
         "checked": Boolean
       }
     ]
   }
  1. ملخص القائمة (للعرض في قائمة جميع قوائم المهام):
   {
     "_id": ObjectId,
     "name": String,
     "item_count": Integer
   }

تصميم نقطة النهاية للواجهة البرمجية

سيكشف الخادم الخلفي FastAPI الخاص بنا عن نقاط نهاية RESTful التالية:

  1. قوائم المهام:

    • GET /api/lists: استرداد جميع قوائم المهام (عرض الملخص)

    • POST /api/lists: إنشاء قائمة مهام جديدة

    • GET /api/lists/{list_id}: استرداد قائمة مهام محددة مع جميع عناصرها

    • DELETE /api/lists/{list_id}: حذف قائمة مهام محددة

  2. عناصر المهام:

    • POST /api/lists/{list_id}/items: إضافة عنصر جديد إلى قائمة معينة

    • PATCH /api/lists/{list_id}/checked_state: تحديث حالة الإشارة للعنصر

    • DELETE /api/lists/{list_id}/items/{item_id}: إزالة عنصر محدد من القائمة

سيوفر هذا المشروع قاعدة متماسكة في تطوير مكوك FARM وتحجيم Docker، التي يمكنك التوسع عليها لتطوير تطبيقات أكثر تعقيدا في المستقبل.

لنبدأ بمشروعنا.

تعليمات المشروع

إعداد المشروع وتطوير الخلفية

الخطوة 1: إعداد هيكل المشروع

إنشاء دليل جديد لمشروعك:

   mkdir farm-stack-todo
   cd farm-stack-todo

إنشاء دلائل فرعية للخلفية والواجهة:

   mkdir backend frontend

الخطوة 2: إعداد بيئة الخلفية

انتقل إلى دليل الخلفية:

   cd backend

إنشاء بيئة إفتراضية وتفعيلها:

   python -m venv venv
   source venv/bin/activate  # On Windows, use: venv\Scripts\activate

إنشاء الملفات التالية في الدليل الخلفي:

    • Dockerfile

      • pyproject.toml

في جهاز ترمينالك، قم بتثبيت الحزم المطلوبة:

pip install "fastapi[all]" "motor[srv]" beanie aiostream

إنشاء ملف requirements.txt:

pip freeze > requirements.txt

بعد إنشاء ملف requirements.txt (إما من خلال pip-compile أو يدوياً),يمكنك تثبيت الإعتمادات باستخدام:

   pip install -r requirements.txt

أضف المحتوى التالي إلى Dockerfile:

   FROM python:3

   WORKDIR /usr/src/app
   COPY requirements.txt ./

   RUN pip install --no-cache-dir --upgrade -r ./requirements.txt

   EXPOSE 3001

   CMD [ "python", "./src/server.py" ]

أضف المحتوى التالي إلى pyproject.toml:

   [tool.pytest.ini_options]
   pythonpath = "src"

الخطوة 4: إنشاء هيكل الخلفية

إنشاء دليل src داخل الدليل الخلفي:

   mkdir src

إنشاء الملفات التالية داخل دليل src:

الخطوة 5: تنفيذ الطبقة الواحدة للوصول للبيانات (DAL)

فتح src/dal.py وإضافة المحتوى التالي:

from bson import ObjectId
from motor.motor_asyncio import AsyncIOMotorCollection
from pymongo import ReturnDocument

from pydantic import BaseModel

from uuid import uuid4

class ListSummary(BaseModel):
  id: str
  name: str
  item_count: int

  @staticmethod
  def from_doc(doc) -> "ListSummary":
      return ListSummary(
          id=str(doc["_id"]),
          name=doc["name"],
          item_count=doc["item_count"],
      )

class ToDoListItem(BaseModel):
  id: str
  label: str
  checked: bool

  @staticmethod
  def from_doc(item) -> "ToDoListItem":
      return ToDoListItem(
          id=item["id"],
          label=item["label"],
          checked=item["checked"],
      )

class ToDoList(BaseModel):
  id: str
  name: str
  items: list[ToDoListItem]

  @staticmethod
  def from_doc(doc) -> "ToDoList":
      return ToDoList(
          id=str(doc["_id"]),
          name=doc["name"],
          items=[ToDoListItem.from_doc(item) for item in doc["items"]],
      )

class ToDoDAL:
  def __init__(self, todo_collection: AsyncIOMotorCollection):
      self._todo_collection = todo_collection

  async def list_todo_lists(self, session=None):
      async for doc in self._todo_collection.find(
          {},
          projection={
              "name": 1,
              "item_count": {"$size": "$items"},
          },
          sort={"name": 1},
          session=session,
      ):
          yield ListSummary.from_doc(doc)

  async def create_todo_list(self, name: str, session=None) -> str:
      response = await self._todo_collection.insert_one(
          {"name": name, "items": []},
          session=session,
      )
      return str(response.inserted_id)

  async def get_todo_list(self, id: str | ObjectId, session=None) -> ToDoList:
      doc = await self._todo_collection.find_one(
          {"_id": ObjectId(id)},
          session=session,
      )
      return ToDoList.from_doc(doc)

  async def delete_todo_list(self, id: str | ObjectId, session=None) -> bool:
      response = await self._todo_collection.delete_one(
          {"_id": ObjectId(id)},
          session=session,
      )
      return response.deleted_count == 1

  async def create_item(
      self,
      id: str | ObjectId,
      label: str,
      session=None,
  ) -> ToDoList | None:
      result = await self._todo_collection.find_one_and_update(
          {"_id": ObjectId(id)},
          {
              "$push": {
                  "items": {
                      "id": uuid4().hex,
                      "label": label,
                      "checked": False,
                  }
              }
          },
          session=session,
          return_document=ReturnDocument.AFTER,
      )
      if result:
          return ToDoList.from_doc(result)

  async def set_checked_state(
      self,
      doc_id: str | ObjectId,
      item_id: str,
      checked_state: bool,
      session=None,
  ) -> ToDoList | None:
      result = await self._todo_collection.find_one_and_update(
          {"_id": ObjectId(doc_id), "items.id": item_id},
          {"$set": {"items.$.checked": checked_state}},
          session=session,
          return_document=ReturnDocument.AFTER,
      )
      if result:
          return ToDoList.from_doc(result)

  async def delete_item(
      self,
      doc_id: str | ObjectId,
      item_id: str,
      session=None,
  ) -> ToDoList | None:
      result = await self._todo_collection.find_one_and_update(
          {"_id": ObjectId(doc_id)},
          {"$pull": {"items": {"id": item_id}}},
          session=session,
          return_document=ReturnDocument.AFTER,
      )
      if result:
          return ToDoList.from_doc(result)

هذا يختم جزء 1 من الدرس، حيث قمنا بإنشاء هيكل المشروع وتنفيذ الطبقة الوصول للبيانات لتطبيق تودو الأدوات FARM. في الجزء التالي، سننفذ خادم FastAPI ونخلق نقاط الوصول للAPI.

تنفيذ خادم FastAPI

الخطوة 6: تنفيذ خادم FastAPI

فتح src/server.py وإضافة المحتوى التالي:

from contextlib import asynccontextmanager
from datetime import datetime
import os
import sys

from bson import ObjectId
from fastapi import FastAPI, status
from motor.motor_asyncio import AsyncIOMotorClient
from pydantic import BaseModel
import uvicorn

from dal import ToDoDAL, ListSummary, ToDoList

COLLECTION_NAME = "todo_lists"
MONGODB_URI = os.environ["MONGODB_URI"]
DEBUG = os.environ.get("DEBUG", "").strip().lower() in {"1", "true", "on", "yes"}


@asynccontextmanager
async def lifespan(app: FastAPI):
    # بدء التشغيل:
    client = AsyncIOMotorClient(MONGODB_URI)
    database = client.get_default_database()

    # التأكد من توفر قاعدة البيانات:
    pong = await database.command("ping")
    if int(pong["ok"]) != 1:
        raise Exception("Cluster connection is not okay!")

    todo_lists = database.get_collection(COLLECTION_NAME)
    app.todo_dal = ToDoDAL(todo_lists)

    # إعادة التسليم إلى تطبيق FastAPI:
    yield

    # إيقاف التشغيل:
    client.close()


app = FastAPI(lifespan=lifespan, debug=DEBUG)


@app.get("/api/lists")
async def get_all_lists() -> list[ListSummary]:
    return [i async for i in app.todo_dal.list_todo_lists()]


class NewList(BaseModel):
    name: str


class NewListResponse(BaseModel):
    id: str
    name: str


@app.post("/api/lists", status_code=status.HTTP_201_CREATED)
async def create_todo_list(new_list: NewList) -> NewListResponse:
    return NewListResponse(
        id=await app.todo_dal.create_todo_list(new_list.name),
        name=new_list.name,
    )


@app.get("/api/lists/{list_id}")
async def get_list(list_id: str) -> ToDoList:
    """Get a single to-do list"""
    return await app.todo_dal.get_todo_list(list_id)


@app.delete("/api/lists/{list_id}")
async def delete_list(list_id: str) -> bool:
    return await app.todo_dal.delete_todo_list(list_id)


class NewItem(BaseModel):
    label: str


class NewItemResponse(BaseModel):
    id: str
    label: str


@app.post(
    "/api/lists/{list_id}/items/",
    status_code=status.HTTP_201_CREATED,
)
async def create_item(list_id: str, new_item: NewItem) -> ToDoList:
    return await app.todo_dal.create_item(list_id, new_item.label)


@app.delete("/api/lists/{list_id}/items/{item_id}")
async def delete_item(list_id: str, item_id: str) -> ToDoList:
    return await app.todo_dal.delete_item(list_id, item_id)


class ToDoItemUpdate(BaseModel):
    item_id: str
    checked_state: bool


@app.patch("/api/lists/{list_id}/checked_state")
async def set_checked_state(list_id: str, update: ToDoItemUpdate) -> ToDoList:
    return await app.todo_dal.set_checked_state(
        list_id, update.item_id, update.checked_state
    )


class DummyResponse(BaseModel):
    id: str
    when: datetime


@app.get("/api/dummy")
async def get_dummy() -> DummyResponse:
    return DummyResponse(
        id=str(ObjectId()),
        when=datetime.now(),
    )


def main(argv=sys.argv[1:]):
    try:
        uvicorn.run("server:app", host="0.0.0.0", port=3001, reload=DEBUG)
    except KeyboardInterrupt:
        pass


if __name__ == "__main__":
    main()

هذه التنفيذة تقوم بإعداد خادم FastAPI مع واجهة البرمجة المتوسطة CORS، والاتصال بمونغو DB، وتحديد نقاط الوصول للAPI لتطبيق تودو الخاص بنا.

الخطوة 7: إعداد متغيرات البيئة

إنشاء ملف .env في الدليل الجذر بالمحتوى التالي. تأكد من إضافة إسم قاعدة البيانات (“todo”) في نهاية “.mongodb.net/”.

MONGODB_URI='mongodb+srv://beau:codecamp@cluster0.ji7hu.mongodb.net/todo?retryWrites=true&w=majority&appName=Cluster0'

الخطوة 8: إنشاء ملف docker-compose

في دليل المشروع الجذر (farm-stack-todo),إنشاء ملف يُدعى compose.yml بالمحتوى التالي:

name: todo-app
services:
  nginx:
    image: nginx:1.17
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
    ports:
      - 8000:80
    depends_on:
      - backend
      - frontend
  frontend:
    image: "node:22"
    user: "node"
    working_dir: /home/node/app
    environment:
      - NODE_ENV=development
      - WDS_SOCKET_PORT=0
    volumes:
      - ./frontend/:/home/node/app
    expose:
      - "3000"
    ports:
      - "3000:3000"
    command: "npm start"
  backend:
    image: todo-app/backend
    build: ./backend
    volumes:
      - ./backend/:/usr/src/app
    expose:
      - "3001"
    ports:
      - "8001:3001"
    command: "python src/server.py"
    environment:
      - DEBUG=true
    env_file:
      - path: ./.env
        required: true

الخطوة 9: إعداد تكوين Nginx

إنشاء دليل يُدعى nginx في الجذر الخاص بمشروعك:

mkdir nginx

إنشاء ملف باسم nginx.conf داخل مجلد nginx بالمحتويات التالية:

server {
    listen 80;
    server_name farm_intro;

    location / {
        proxy_pass http://frontend:3000;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    location /api {
        proxy_pass http://backend:3001/api;
    }
}

هذا يعتمد على الجزء 2 من التورية حيث قمنا بتنفيذ الخوادم السريعة FastAPI والتكيف بالمادات البيئية، وإنشاء ملف docker-compose وتكوين Nginx. في الجزء القادم، سنركز على إنشاء الواجهة الأمامية React لتطبيق التوصيل FARM الذي يحتاج إلى قائمة من الأعمال.

إنشاء واجهة الأمامية React

الخطوة 10: إنشاء تطبيق React

تحرك إلى مجلد الجزء الأمامي.

cd ../frontend

إنشاء تطبيق React جديد بوسيلة Create React App:

npx create-react-app .

تثبيط تعويذات إضافية:

   npm install axios react-icons

الخطوة 11: تكوين المكون الرئيسي App

أستبدل محتويات src/App.js بالتالي:

import { useEffect, useState } from "react";
import axios from "axios";
import "./App.css";
import ListToDoLists from "./ListTodoLists";
import ToDoList from "./ToDoList";

function App() {
  const [listSummaries, setListSummaries] = useState(null);
  const [selectedItem, setSelectedItem] = useState(null);

  useEffect(() => {
    reloadData().catch(console.error);
  }, []);

  async function reloadData() {
    const response = await axios.get("/api/lists");
    const data = await response.data;
    setListSummaries(data);
  }

  function handleNewToDoList(newName) {
    const updateData = async () => {
      const newListData = {
        name: newName,
      };

      await axios.post(`/api/lists`, newListData);
      reloadData().catch(console.error);
    };
    updateData();
  }

  function handleDeleteToDoList(id) {
    const updateData = async () => {
      await axios.delete(`/api/lists/${id}`);
      reloadData().catch(console.error);
    };
    updateData();
  }

  function handleSelectList(id) {
    console.log("Selecting item", id);
    setSelectedItem(id);
  }

  function backToList() {
    setSelectedItem(null);
    reloadData().catch(console.error);
  }

  if (selectedItem === null) {
    return (
      <div className="App">
        <ListToDoLists
          listSummaries={listSummaries}
          handleSelectList={handleSelectList}
          handleNewToDoList={handleNewToDoList}
          handleDeleteToDoList={handleDeleteToDoList}
        />
      </div>
    );
  } else {
    return (
      <div className="App">
        <ToDoList listId={selectedItem} handleBackButton={backToList} />
      </div>
    );
  }
}

export default App;

الخطوة 12: إنشاء مكون ListTodoLists

إنشاء ملف جديد src/ListTodoLists.js بالمحتويات التالية:

import "./ListTodoLists.css";
import { useRef } from "react";
import { BiSolidTrash } from "react-icons/bi";

function ListToDoLists({
  listSummaries,
  handleSelectList,
  handleNewToDoList,
  handleDeleteToDoList,
}) {
  const labelRef = useRef();

  if (listSummaries === null) {
    return <div className="ListToDoLists loading">Loading to-do lists ...</div>;
  } else if (listSummaries.length === 0) {
    return (
      <div className="ListToDoLists">
        <div className="box">
        <label>
          New To-Do List:&nbsp;
          <input id={labelRef} type="text" />
        </label>
        <button
          onClick={() =>
            handleNewToDoList(document.getElementById(labelRef).value)
          }
        >
          New
        </button>
        </div>
        <p>There are no to-do lists!</p>
      </div>
    );
  }
  return (
    <div className="ListToDoLists">
      <h1>All To-Do Lists</h1>
      <div className="box">
        <label>
          New To-Do List:&nbsp;
          <input id={labelRef} type="text" />
        </label>
        <button
          onClick={() =>
            handleNewToDoList(document.getElementById(labelRef).value)
          }
        >
          New
        </button>
      </div>
      {listSummaries.map((summary) => {
        return (
          <div
            key={summary.id}
            className="summary"
            onClick={() => handleSelectList(summary.id)}
          >
            <span className="name">{summary.name} </span>
            <span className="count">({summary.item_count} items)</span>
            <span className="flex"></span>
            <span
              className="trash"
              onClick={(evt) => {
                evt.stopPropagation();
                handleDeleteToDoList(summary.id);
              }}
            >
              <BiSolidTrash />
            </span>
          </div>
        );
      })}
    </div>
  );
}

export default ListToDoLists;

إنشاء ملف src/ListTodoLists.css بالمحتويات التالية:

.ListToDoLists .summary {
    border: 1px solid lightgray;
    padding: 1em;
    margin: 1em;
    cursor: pointer;
    display: flex;
}

.ListToDoLists .count {
    padding-left: 1ex;
    color: blueviolet;
    font-size: 92%;
}

الخطوة 13: إنشاء مكون ToDoList

إنشاء ملف جديد src/ToDoList.js بالمحتويات التالية:

import "./ToDoList.css";
import { useEffect, useState, useRef } from "react";
import axios from "axios";
import { BiSolidTrash } from "react-icons/bi";

function ToDoList({ listId, handleBackButton }) {
  let labelRef = useRef();
  const [listData, setListData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      const response = await axios.get(`/api/lists/${listId}`);
      const newData = await response.data;
      setListData(newData);
    };
    fetchData();
  }, [listId]);

  function handleCreateItem(label) {
    const updateData = async () => {
      const response = await axios.post(`/api/lists/${listData.id}/items/`, {
        label: label,
      });
      setListData(await response.data);
    };
    updateData();
  }

  function handleDeleteItem(id) {
    const updateData = async () => {
      const response = await axios.delete(
        `/api/lists/${listData.id}/items/${id}`
      );
      setListData(await response.data);
    };
    updateData();
  }

  function handleCheckToggle(itemId, newState) {
    const updateData = async () => {
      const response = await axios.patch(
        `/api/lists/${listData.id}/checked_state`,
        {
          item_id: itemId,
          checked_state: newState,
        }
      );
      setListData(await response.data);
    };
    updateData();
  }

  if (listData === null) {
    return (
      <div className="ToDoList loading">
        <button className="back" onClick={handleBackButton}>
          Back
        </button>
        Loading to-do list ...
      </div>
    );
  }
  return (
    <div className="ToDoList">
      <button className="back" onClick={handleBackButton}>
        Back
      </button>
      <h1>List: {listData.name}</h1>
      <div className="box">
        <label>
          New Item:&nbsp;
          <input id={labelRef} type="text" />
        </label>
        <button
          onClick={() =>
            handleCreateItem(document.getElementById(labelRef).value)
          }
        >
          New
        </button>
      </div>
      {listData.items.length > 0 ? (
        listData.items.map((item) => {
          return (
            <div
              key={item.id}
              className={item.checked ? "item checked" : "item"}
              onClick={() => handleCheckToggle(item.id, !item.checked)}
            >
              <span>{item.checked ? "✅" : "⬜️"} </span>
              <span className="label">{item.label} </span>
              <span className="flex"></span>
              <span
                className="trash"
                onClick={(evt) => {
                  evt.stopPropagation();
                  handleDeleteItem(item.id);
                }}
              >
                <BiSolidTrash />
              </span>
            </div>
          );
        })
      ) : (
        <div className="box">There are currently no items.</div>
      )}
    </div>
  );
}

export default ToDoList;

إنشاء ملف src/ToDoList.css بالمحتويات التالية:

.ToDoList .back {
    margin: 0 1em;
    padding: 1em;
    float: left;
}

.ToDoList .item {
    border: 1px solid lightgray;
    padding: 1em;
    margin: 1em;
    cursor: pointer;
    display: flex;
}

.ToDoList .label {
    margin-left: 1ex;
}

.ToDoList .checked .label {
    text-decoration: line-through;
    color: lightgray;
}

الخطوة 14: 更新 الملف ال CSS الرئيسي

أستبدل محتويات src/index.css بالتالي:

html, body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-size: 12pt;
}

input, button {
  font-size: 1em;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

.box {
    border: 1px solid lightgray;
    padding: 1em;
    margin: 1em;
}

.flex {
  flex: 1;
}

هذا يعتمد على الجزء 3 من التورية حيث قمنا بإنشاء واجهة الأمامية React لتطبيق التوصيل FARM الذي يحتاج إلى قائمة من الأعمال. لقد أنشأنا المكون الرئيسي App ومكون ListTodoLists لعرض جميع القوائم التوصيلية ومكون ToDoList للقوائم التوصيلية الفردية.

إجراء واختبار التطبيق

خطوة 18: تشغيل التطبيق باستخدام Docker Compose

  1. تأكد من أن لديك Docker و Docker Compose مثبتة على نظامك

  2. افتح تطرير في الدليل الجذر لمشروعك (farm-stack-todo)

  3. بناء وتشغيل الكونتينرات:

docker-compose up --build
  1. بمجرد أن تكون الكونتينرات مشغولة وتعمل، فافتح متصفح الويب الخاص بك واذهب إلى http://localhost:8000

خطوة 19: إيقاف التطبيق

  1. إذا كنت تشغل التطبيق دون Docker:

    • أوقف خادم تطوير React بالضغط على Ctrl+C في تطريره

    • أوقف خادم FastAPI بالضغط على Ctrl+C في تطريره

    • أوقف خادم MongoDB بالضغط على Ctrl+C في تطريره

  2. إذا كنت تشغل التطبيق مع Docker Compose:

    • اضغط Ctrl+C في التيرمينال الذي قمت فيه بتشغيل docker-compose up

    • قم بتنفيذ الأمر التالي لإيقاف وحذف الوعاءات:

     docker-compose down

“`

تهانينا! لقد قمت ببناء واختبار تطبيق FARM stack todo بنجاح. هذا التطبيق يظهر تكامل FastAPI، React، و MongoDB في تطبيق ويب كامل الطرفة.

إليك بعض الخطوات المحتملة التي يمكنك تطويرها لتحسين تطبيقك:

  1. أضف توثيق المستخدم والتخويل

  2. قم بتنفيذ التحقق من البيانات وتصليح الأخطاء

  3. أضف المزيد من الخصائص مثل تواريخ الاستحقاق، الأولويات أو فئات العناصر todo

  4. حسن الجمال بتحسين تصميم UI/UX

  5. اكتب اختبارات الوحدة والتكامل للواجهة الأمامية والخلفية

  6. إعداد التكامل المستمر والنشر (CI / CD) لتطبيقك

تذكر أن تحافظ على تحديث الاعتماديات الخاصة بك وتتبع أفضل الممارسات للأمان والأداء أثناء مواصلة تطوير تطبيقك.

الاستنتاج والخطوات التالية

تهانينا على إكمال هذا البرنامج التعليمي الشامل لمجموعة FARM! من خلال بناء هذا التطبيق للمهام، اكتسبت خبرة عملية في بعض التقنيات الأكثر قوة وشهرة في تطوير الويب الحديث. لقد تعلمت كيفية إنشاء واجهة برمجة تطبيقات خلفية قوية باستخدام FastAPI، وبناء واجهة أمامية ديناميكية ومستجيبة باستخدام React، والحفاظ على البيانات باستخدام MongoDB، وتحويل تطبيقك بأكمله إلى حاوية باستخدام Docker. هذا المشروع قد أظهر كيف يعمل هذه التقنيات معاً بسلاسة لإنشاء تطبيق ويب متكامل وقابل للتوسعة.