المجموعة FARM هي مجموعة معاصرة لتطوير الويب التي تجمع ثلاث تقنيات قوية: FastAPI، React، و MongoDB. هذه الحلولة الكاملة توفر للمطورين مجموعة من الأدوات القوية لبناء تطبيقات الويب القابلة للتحجيم والفعالة والأداء العالي.
في هذه المقالة، سأقدم لكم مقدمة عن كل من هذه التقنيات الرئيسية، ومن ثم سنبني مشروعا باستخدام مجموعة FARM و Docker لترون كيفية عمل جميع الأشياء معا.
هذه المقالة مبنية على دورة أنشأتها في قناة freeCodeCamp.org على YouTube. شاهدوها هنا:
مقدمة عن مجموعة FARM
يشير FARM في مجموعة FARM إلى:
-
F: FastAPI (الواجهة الخلفية)
-
R: React (الواجهة الأمامية)
-
M: MongoDB (قاعدة البيانات)
تم تصميم مجموعة FARM ليستفيد من قوت كل جزء، مما يسمح للمطورين ببناء تطبيقات مميزة بخصائص كبيرة وخلال تجربة تطوير سلسة.
عناصر مجموعة FARM
-
FastAPI: FastAPI هو إطار ويب بيثوني حديث وأداء عالٍ لبناء API. يتم تصميمه ليكون سهلاً الاستخدام، سريعاً في التعديل، وجاهزاً للبيئات الإنتاجية. FastAPI مبني على Starlette للأجزاء الويبية و Pydantic للأجزاء البيانية، مما يجعله خيار قوي لبناء خدمات الواجهة الخلفية المروجة.
- React: يتم تطوير React وإدارته من قبل فيسبوك ، وهو مكتب جافاScript مشهور لبناء الواجهات المستخدمة. يسمح للمطورين بإنشاء مكونات واجهة المستخدم قابلة للتكرار والتحديث والتصرف بكفاءة أثناء تغيير البيانات. تقوم هياكلها المبنية على المكونات والDOM الافتراضي بجعله خيارًا جيدًا لبناء تطبيقات الواجهة الأولية الحيوية والمستجيبة.
- MongoDB: يوجد MongoDB وهي قاعدة بيانات متناولة الوثائق ول
-
الأمور الجيدة في استخدام FARM Stack:
-
التنمية: جميع المكونات من قاعدة FARM صممت للتنمية. يمكن ل FastAPI معالجة ال solicitudes متجاوزة بالكفاءة، وتطبيقات React تستطيع إدارة واجهات المستخدم معقدة، ويمكن ل MongoDB توزيع البيانات عبر مخزون متعدد من السيرفرات.
-
المجتمع والإيكوسيما: تتمتع جميع هذه التكنولوجيات الثلاث بمجتمعات كبيرة ونشطة وبنظم متعددة من المكتبات والأدوات.
-
المرونة: تعد مجموعة FARM مرنة بما يكفي لاستيعاب أنواع مختلفة من تطبيقات الويب ، من تطبيقات CRUD البسيطة إلى الأنظمة المعقدة والغنية بالبيانات.
من خلال الجمع بين هذه التقنيات ، توفر مجموعة FARM حلاً شاملاً لبناء تطبيقات الويب الحديثة. يتيح للمطورين إنشاء خلفيات سريعة وقابلة للتوسع باستخدام FastAPI ، وواجهات أمامية بديهية ومتجاوبة باستخدام React ، وتخزين بيانات مرن وفعال باستخدام MongoDB. تعتبر هذه المجموعة مناسبة بشكل خاص للتطبيقات التي تتطلب تحديثات فورية ، ونماذج بيانات معقدة ، وأداء عالي.
نظرة عامة على المشروع: تطبيق المهام
في دورة الفيديو ، سأتحدث بشكل أكثر عن كل تقنية فردية في مجموعة FARM. ولكن في هذه المقالة ، سنبدأ مباشرة في مشروع لتجميع كل شيء معًا.
سنقوم بإنشاء تطبيق للمهام لمساعدتنا في فهم مجموعة FARM. قبل أن نبدأ في إنشاء التطبيق ، دعنا نناقش المزيد حول الميزات وهندسة البرمجيات.
ميزات تطبيق المهام
سيتضمن تطبيق المهام الخاص بمجموعة FARM الميزات التالية:
-
قوائم المهام المتعددة:
-
يمكن للمستخدمين إنشاء وعرض وتحديث وحذف قوائم المهام المتعددة.
-
تحتوي كل قائمة على اسم وتحتوي على عناصر مهام متعددة.
-
-
عناصر المعالجة:
-
خلال كل قائمة، يمكن للمستخدمين إضافة وعرض وتحديث وحذف عناصر المعالجة.
-
تحتوي كل عنصر على وسم، وحالة مشخصة/غير مشخصة، وتنتمي إلى قائمة محددة.
-
-
تحديثات فورية:
- تحدث الواجهة الرسومية بشكل فوري عندما تتم إجراء تغييرات على القوائم أو العناصر.
-
تصميم متجاوب:
- سيكون التطبيق متجاوب ويعمل بشكل جيد على الحاسب الشخصي والأجهزة النقالة.
هيكلة النظام
سيتبع التطبيق المعالجة الخاص بنا نظ
-
الواجهة الأمامية (React):
-
توفر واجهة المستخدم للتفاعل مع قوائم المهام والعناصر.
-
تتواصل مع الخلفية عبر استدعاءات واجهة برمجة تطبيقات RESTful.
-
-
الخلفية (FastAPI):
-
تتعامل مع طلبات واجهة برمجة التطبيقات من الواجهة الأمامية.
-
تنفذ المنطق التجاري لإدارة قوائم المهام والعناصر.
-
تتفاعل مع قاعدة بيانات MongoDB لاستمرارية البيانات.
-
-
قاعدة البيانات (MongoDB):
-
تخزن قوائم المهام والعناصر.
-
توفر استعلامًا فعالًا وتحديثًا لبيانات المهام.
-
-
دوكر:
- يجعل كل مكون (الواجهة الأمامية، الخلفية، قاعدة البيانات) محايدًا للحاويات لسهولة التطوير والنشر.
تصميم نموذج البيانات
يتكون نموذج بياناتنا في MongoDB من هيكلين رئيسيين:
- قائمة المهام:
{
"_id": ObjectId,
"name": String,
"items": [
{
"id": String,
"label": String,
"checked": Boolean
}
]
}
- ملخص القائمة (للعرض في قائمة جميع قوائم المهام):
{
"_id": ObjectId,
"name": String,
"item_count": Integer
}
تصميم نقطة النهاية للواجهة البرمجية
سيكشف الخادم الخلفي FastAPI الخاص بنا عن نقاط نهاية RESTful التالية:
-
قوائم المهام:
-
GET /api/lists: استرداد جميع قوائم المهام (عرض الملخص)
-
POST /api/lists: إنشاء قائمة مهام جديدة
-
GET /api/lists/{list_id}: استرداد قائمة مهام محددة مع جميع عناصرها
-
DELETE /api/lists/{list_id}: حذف قائمة مهام محددة
-
-
عناصر المهام:
-
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:
<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:
<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:
<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
-
تأكد من أن لديك Docker و Docker Compose مثبتة على نظامك
-
افتح تطرير في الدليل الجذر لمشروعك (farm-stack-todo)
-
بناء وتشغيل الكونتينرات:
docker-compose up --build
- بمجرد أن تكون الكونتينرات مشغولة وتعمل، فافتح متصفح الويب الخاص بك واذهب إلى http://localhost:8000
خطوة 19: إيقاف التطبيق
-
إذا كنت تشغل التطبيق دون Docker:
-
أوقف خادم تطوير React بالضغط على Ctrl+C في تطريره
-
أوقف خادم FastAPI بالضغط على Ctrl+C في تطريره
-
أوقف خادم MongoDB بالضغط على Ctrl+C في تطريره
-
-
إذا كنت تشغل التطبيق مع Docker Compose:
-
اضغط Ctrl+C في التيرمينال الذي قمت فيه بتشغيل docker-compose up
-
قم بتنفيذ الأمر التالي لإيقاف وحذف الوعاءات:
-
docker-compose down
“`
تهانينا! لقد قمت ببناء واختبار تطبيق FARM stack todo بنجاح. هذا التطبيق يظهر تكامل FastAPI، React، و MongoDB في تطبيق ويب كامل الطرفة.
إليك بعض الخطوات المحتملة التي يمكنك تطويرها لتحسين تطبيقك:
-
أضف توثيق المستخدم والتخويل
-
قم بتنفيذ التحقق من البيانات وتصليح الأخطاء
-
أضف المزيد من الخصائص مثل تواريخ الاستحقاق، الأولويات أو فئات العناصر todo
-
حسن الجمال بتحسين تصميم UI/UX
-
اكتب اختبارات الوحدة والتكامل للواجهة الأمامية والخلفية
-
إعداد التكامل المستمر والنشر (CI / CD) لتطبيقك
تذكر أن تحافظ على تحديث الاعتماديات الخاصة بك وتتبع أفضل الممارسات للأمان والأداء أثناء مواصلة تطوير تطبيقك.
الاستنتاج والخطوات التالية
تهانينا على إكمال هذا البرنامج التعليمي الشامل لمجموعة FARM! من خلال بناء هذا التطبيق للمهام، اكتسبت خبرة عملية في بعض التقنيات الأكثر قوة وشهرة في تطوير الويب الحديث. لقد تعلمت كيفية إنشاء واجهة برمجة تطبيقات خلفية قوية باستخدام FastAPI، وبناء واجهة أمامية ديناميكية ومستجيبة باستخدام React، والحفاظ على البيانات باستخدام MongoDB، وتحويل تطبيقك بأكمله إلى حاوية باستخدام Docker. هذا المشروع قد أظهر كيف يعمل هذه التقنيات معاً بسلاسة لإنشاء تطبيق ويب متكامل وقابل للتوسعة.
Source:
https://www.freecodecamp.org/news/use-the-farm-stack-to-develop-full-stack-apps/