الوثائقأطر عمل Fullstack
من لارافيل إلى نكسي
في Nexy، يصبح مسار Laravel وطريقة التحكم الخاصة به ملفًا واحدًا ضمن src/routes/: يصبح عرض Blade صفحة .nexy، و $request->validate نموذجًا Pydantic، وEloquent هو ORM تختاره، مثل SQLModel.
يعرض كل قسم أدناه إحدى عادات Laravel، ثم نفس الشيء في Nexy.
طريق
use Illuminate\Support\Facades\Route; Route::get('/hello', fn () => ['message' => 'Hello']);لا يوجد ملف مسارات: مسار الملف هو عنوان URL، واسم الوظيفة هو الطريقة.
async def GET(): return {"message": "Hello"}معلمات الطريق و where
Route::get('/users/{id}', fn (int $id) => ['id' => $id])->whereNumber('id');{id} هو مقطع [id]، والتعليق int هو القيد: /api/users/abc يجيب 404.
async def GET(id: int): return {"id": id}الشرط المائل الاختياري {slug?} أو {path} المتطابق (->where('path', '.*')) هو [...path].py شامل.
وحدات التحكم
class NoteController extends Controller{ public function show(int $id) { return ['id' => $id]; } public function destroy(int $id) { return ['deleted' => $id]; }}Route::get('/notes/{id}', [NoteController::class, 'show']);Route::delete('/notes/{id}', [NoteController::class, 'destroy']);أساليب التحكم هي وظائف الملف، والتي تحمل اسم الفعل:
async def GET(id: int): return {"id": id} async def DELETE(id: int): return {"deleted": id}بالنسبة للفئات التي تحتوي على خدمات محقونة، كما هو الحال في وحدة تحكم Laravel، راجع modular router.
سلاسل الاستعلام
public function __invoke(Request $request){ return ['q' => $request->query('q', ''), 'page' => (int) $request->query('page', 1)];}async def GET(q: str = "", page: int = 1): return {"q": q, "page": page}التحقق من الصحة وطلبات النموذج
public function store(Request $request){ $data = $request->validate([ 'name' => 'required|string|max:100', 'price' => 'required|numeric|gt:0', ]); return response()->json($data, 201);}القواعد نموذج Pydantic . الجسم الذي ينكسر يجيب 422 بالمجال والقاعدة كما يفعل Laravel:
from pydantic import BaseModel, Field from nexy.http import JSONResponse class NewItem(BaseModel): name: str = Field(max_length=100) price: float = Field(gt=0) async def POST(item: NewItem): return JSONResponse(item.model_dump(), status_code=201)توجيه المجموعات باستخدام البادئة والبرامج الوسيطة
Route::prefix('admin')->middleware('admin')->group(function () { Route::get('/stats', fn () => ['users' => 3]);});public function handle(Request $request, Closure $next){ abort_unless($request->header('X-Role') === 'admin', 403, 'Admins only'); return $next($request);}المجلد هو البادئة، و dependencies.py هو الوسيط للمجموعة:
async def GET(): return {"users": 3}from nexy.http import Header, HTTPException def require_admin(x_role: str = Header("")) -> None: if x_role != "admin": raise HTTPException(status_code=403, detail="Admins only") dependencies = [require_admin]الوسيطة العالمية
public function handle(Request $request, Closure $next){ $response = $next($request); $response->headers->set('X-App', 'demo'); return $response;}A middleware.py في جذر المسارات يلتف كل طلب:
from nexy.http import Request async def middleware(request: Request, call_next): response = await call_next(request) response.headers["x-app"] = "demo" return responseوجهات نظر بليد
Route::get('/users', fn () => view('users', ['users' => [['name' => 'Ada'], ['name' => 'Alan']]]));@extends('layouts.app') @section('content')<ul> @foreach ($users as $user) <li>{{ $user['name'] }}</li> @endforeach</ul>@endsectionالمسار والعرض هما ملف واحد .nexy:
users = [{"name": "Ada"}, {"name": "Alan"}]<ul> {% for user in users %} <li>{{ user.name }}</li> {% endfor %}</ul>| بليد | Jinja2 في .nexy |
|---|---|
{{ $name }} (هرب) |
{{ name | e }}: .nexy لا يفلت افتراضيًا |
{!! $html !!} |
{{ html | safe }} |
@if ($user) ... @endif |
{% if user %} ... {% endif %} |
@foreach ($items as $item) |
{% for item in items %} |
@php ... @endphp |
Python في المقدمة |
التخطيطات و @yield
<!DOCTYPE html><html> <body>@yield('content')</body></html>A layout.nexy يغلف كل صفحة من المجلد الخاص به، مع عدم وجود @extends في الصفحات:
children: prop[NexyElement]<!DOCTYPE html><html> <body>{{ children | safe }}</body></html>مكونات الشفرة
@props(['title'])<article> <h2>{{ $title }}</h2> {{ $slot }}</article><x-card title="Latest"><p>This week</p></x-card>
title: prop[str]<article> <h2>{{ title }}</h2> <Slot /></article>from "@/components/Card.nexy" import Card<Card title="Latest"> <p>This week</p></Card>@ هو src/: nx new يكتب useAliases = {"@": "src"} في nexyconfig.py.
النماذج، @csrf وإعادة التوجيه
<form method="POST" action="{{ route('notes.store') }}"> @csrf <input name="title"> <button type="submit">Add</button></form>public function store(Request $request){ Note::create($request->validate(['title' => 'required'])); return back();}الإجراء هو طريقة التحكم التي ينشرها النموذج. معلماته هي الحقول، ويذهب الزائر back() بعلامة 303 عند النجاح:
from nexy.decorators import Action NOTES: list[str] = [] @Actionasync def add_note(title: str) -> None: NOTES.append(title)from src.actions.notes import NOTES<form method="post" action="{{ __actions_path['add_note'] }}"> <input name="title"> <button type="submit">Add</button></form><ul> {% for note in NOTES %} <li>{{ note }}</li> {% endfor %}</ul>الفصيحة والهجرات
class Post extends Model{ protected $fillable = ['title', 'published'];} $posts = Post::where('published', true)->get();Nexy ليس لديه ORM خاص به. nx new يمكن أن يكون سقالة SQLModel، SQLAlchemy أو سلحفاة-ORM؛ مع SQLModel:
from sqlmodel import Field, SQLModel class Post(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) title: str published: bool = Falsefrom sqlmodel import Session, select from src.configs.db import enginefrom src.models.post_model import Post async def GET(): with Session(engine) as session: posts = session.exec(select(Post).where(Post.published)).all() return {"titles": [post.title for post in posts]}| حرفي | نكسي |
|---|---|
php artisan make:migration |
nx migrate -m "add posts" |
php artisan migrate |
nx migrate --upgrade |
انظر قواعد البيانات.
abort(404) ومشاهد الخطأ
Route::get('/posts/{slug}', function (string $slug) { abort_unless(isset(POSTS[$slug]), 404); return view('post', ['post' => POSTS[$slug]]);});notFound() هو abort(404)، و notfound.nexy هو resources/views/errors/404.blade.php:
from nexy import notFound POSTS = {"hello": "Hello, world"} slug: prop[str]if slug not in POSTS: notFound()<h1>{{ POSTS[slug] }}</h1><h1>Not found</h1>الجلسات
public function store(Request $request){ session(['user' => $request->input('name')]); return ['user' => session('user')];}from nexy.core.models import NexyConfigModel class NexyConfig(NexyConfigModel): useAliases = {"@": "src"} useSession = {"secret_key": "change-me"}from pydantic import BaseModel from nexy.http import Request class Login(BaseModel): name: str async def POST(request: Request, body: Login): request.session["user"] = body.name return {"user": body.name}صفحة تقرأ الجلسة مع useSession().
طوابير و dispatch
SendWelcomeEmail::dispatch($user); return response()->json(['queued' => true], 202);بالنسبة للعمل الذي يمكن تشغيله بعد الإجابة، فإن مهام الخلفية الخاصة بـ FastAPI كافية:
from nexy.http import BackgroundTasks, JSONResponse SENT: list[str] = [] def send_welcome_email(email: str) -> None: SENT.append(email) async def POST(email: str, tasks: BackgroundTasks): tasks.add_task(send_welcome_email, email) return JSONResponse({"queued": True}, status_code=202)بالنسبة لإعادة المحاولة والعاملين على أجهزة أخرى، استخدم قائمة انتظار Python مثل Celery أو RQ أو arq.
التخزين المؤقت للصفحة
Route::get('/blog', BlogController::class)->middleware('cache.headers:public;max_age=60');
revalidate = 60<h1>Blog</h1>يتم تخزين الصفحة مؤقتًا لكل عنوان URL ويتم عرضها مرة أخرى في الخلفية بعد 60 ثانية.
.env و config/
| لارافيل | نكسي |
|---|---|
.env + config/database.php |
.env مع DATABASE_URL، قرأه src/configs/db.py |
config/cors.php |
useCORS في nexyconfig.py |
APP_KEY للجلسات |
useSession = {"secret_key": ...} |
public/ |
public/، خدم في / |
Livewire والقصور الذاتي
تقوم المكونات التي يحركها الخادم في Livewire بتعيين الإجراءات التي يتم استدعاؤها من الصفحة. تحدد صفحات Inertia React أو Vue مسارات .tsx أو .vue، أو إلى جزر داخل صفحة .nexy. انظر الإجراءات و الجزر.
الاختبارات
public function test_hello(): void{ $this->getJson('/api/hello')->assertExactJson(['message' => 'Hello']);}from fastapi.testclient import TestClient from nexy.app import app def test_hello(): assert TestClient(app).get("/api/hello").json() == {"message": "Hello"}الجري
| لارافيل | نكسي |
|---|---|
laravel new |
nx new |
php artisan serve |
nx dev |
php artisan route:list |
/docs |
php artisan test |
nx test |
npm run build |
nx build |