Skip to content

jeffreyheping/PyNuxt

Repository files navigation

PyNuxt - Tagged Posts

FastAPI + Jinja2 + HTMX — 纯 Python 全栈 SSR

项目起源

本项目是从 py4web/tagged_posts 移植而来,旨在验证使用 零 JavaScript 的方式移植完整应用的可能性。

技术栈对比

方面 py4web 原版 PyNuxt 版本
前端框架 Vue.js Jinja2 + HTMX
页面渲染 SPA SSR
JavaScript 必须 零 JavaScript
认证方式 Session JWT + Cookie
标签过滤 JavaScript 纯服务端
文件路由 py4web 机制 Nuxt 风格
架构模式 单一应用 BFF 分离

功能特性

已实现功能

  • 用户注册与登录(Cookie + JWT 认证)
  • 发布帖子(支持 #tag 格式标签)
  • 自动提取帖子内容中的标签
  • 标签过滤(纯服务端 HTMX 方案)
  • 删除自己的帖子
  • 待办事项管理
  • 零 JavaScript(HTMX 无刷新交互)

核心特点

  1. 零 JavaScript:纯服务端渲染,HTMX 处理所有交互
  2. Cookie 认证:JWT Token 存 Cookie,支持多端(Web、APP)
  3. BFF 架构:前端 BFF 调用后端 API,后端专注数据层
  4. 文件系统路由:类似 Nuxt.js,自动映射页面路由

架构

浏览器 ──HTMX──► 前端服务 (:3000) ──HTTP──► 后端服务 (:8012)
                   │                           │
                   ├── Jinja2 整页渲染         ├── REST API (JSON)
                   ├── Jinja2 片段渲染         └── SQLAlchemy ORM
                   └── Cookie 认证            └── JWT 认证

前端目录结构(借鉴 Nuxt)

frontend/
├── pages/              # 文件系统路由
│   ├── index.html     # 首页 - 标签帖子
│   └── login.html     # 登录页
├── layouts/           # 布局
│   └── default.html
├── components/        # 组件
│   ├── post_list.html
│   ├── post_item.html
│   ├── tag_filter.html
│   └── ...
└── static/
    └── js/
        └── htmx.min.js   # HTMX(唯一的 JS 依赖)

⚠️ 重要:后端代码冻结

backend/ 目录下的代码直接复制自 app+3,一行不许改动。

数据库文件存放在项目根目录 data.db

所有打磨和优化都在 frontend/ 目录下进行。

快速开始

# 安装依赖
pip install -r requirements.txt --break-system-packages

# 启动后端(端口 8012)
cd backend
uvicorn main:app --port 8012 --reload

# 启动前端(端口 3000,新终端)
cd frontend
uvicorn main:app --port 3000 --reload

或一键启动(PowerShell):

.\start-all.ps1    # 启动前后端
.\stop-all.ps1     # 停止

访问 http://localhost:3000

文件系统路由

等同 Nuxt 的 pages/ 约定:pages/ 下放 .html 文件,自动映射为页面路由,无需手动注册。

/                       -> pages/index.html
/about                  -> pages/about.html
/users/list             -> pages/users/list.html
/posts/123              -> pages/posts/[id].html          (动态路由)
/users/jeff/profile     -> pages/users/[uid]/profile.html (嵌套动态)

动态路由:文件名或目录名用 [param] 形式,自动捕获为路由参数,注入模板上下文:

<!-- pages/posts/[id].html -->
<h1>Post ID: {{ id }}</h1>

匹配优先级(从高到低):

  1. 精确文件匹配:/aboutpages/about.html
  2. 精确目录匹配:/users/listpages/users/list.html
  3. 动态文件匹配:/posts/123pages/posts/[id].html
  4. 动态目录匹配:/users/jeff/profilepages/users/[uid]/profile.html

实现原理(借鉴 app-6):

  • main.py 用 catch-all 路由 /{path:path} 捕获所有非 API 请求
  • 路由引擎递归匹配路径:先尝试静态精确匹配,再尝试 [param] 动态匹配
  • 动态参数注入模板上下文,模板中直接用 {{ param }} 访问
  • 通过 jinja2.Environment + FileSystemLoader 渲染,模板按相对路径解析
  • Jinja2 的 {% extends %} / {% include %} 直接从 loader 根目录解析,跨目录引用正常工作
  • HTMX API 路由(/api/*)注册在 catch-all 之前,优先匹配

项目结构

PyNuxt/
├── backend/              # ⚠️ 复制自 app+3,不许改动
│   ├── main.py
│   ├── config.py
│   ├── database.py
│   ├── models.py
│   ├── schemas.py
│   └── routers/
│       ├── todos.py
│       ├── auth.py       # JWT 认证
│       └── posts.py      # 帖子 API
├── frontend/             # 🎯 打磨优化目标
│   ├── main.py           # 入口(API 路由 + install_file_routing)
│   ├── config.py         # 集中配置(等同 nuxt.config)
│   ├── bff_core.py       # BFF 业务子类(PostBFF, TodoBFF, AuthBFF)
│   ├── pages/            # 文件系统路由(等同 Nuxt pages/)
│   │   ├── index.html   # 首页 - 标签帖子
│   │   └── login.html   # 登录页
│   ├── layouts/
│   │   └── default.html
│   ├── components/
│   │   ├── post_list.html
│   │   ├── post_item.html
│   │   ├── tag_filter.html
│   │   ├── todo_list.html
│   │   └── ...
│   └── pynuxt/           # 🔧 框架包(不用改)
│       ├── routing.py    # 文件系统路由引擎
│       ├── bff.py        # BFF 基座(HTTP 客户端 + 模板渲染)
│       ├── auth.py       # 认证工具(Cookie/JWT)
│       └── errors.py     # 错误处理
│   │
│   └── static/
│       ├── css/style.css
│       └── js/
│           └── htmx.min.js  # 唯一的 JS 依赖
├── data.db               # SQLite 数据库(根目录)
├── start-all.ps1
├── stop-all.ps1
├── requirements.txt
└── README.md

pynuxt 框架包

框架代码与业务代码分离,借鉴 Nuxt 自身的设计:

模块 职责 等同 Nuxt
pynuxt/routing.py 文件系统路由引擎 @nuxt/router
pynuxt/bff.py BFF 基座(HTTP + 渲染) server/api 引擎
pynuxt/helpers.py HTMX 辅助 composables/useHtmx()

用户代码只做两件事:

  1. 继承 BFFBase 写业务方法(bff_core.py
  2. pages/ 下放模板,自动映射路由

后端 API

接口 方法 说明
认证
/api/auth/register POST 注册用户
/api/auth/login POST 登录
/api/auth/me GET 获取当前用户
帖子
/api/posts GET 获取帖子列表(支持 ?tags= 过滤)
/api/posts POST 创建帖子
/api/posts/{id} DELETE 删除帖子
/api/posts/tags GET 获取所有标签
待办
/api/todos GET/POST 获取/创建待办
/api/todos/{id} GET/PUT/DELETE 获取/更新/删除
/api/todos/{id}/done PUT 切换完成状态

前端 HTMX API

接口 方法 说明
/api/posts GET/POST 获取/创建帖子 HTML
/api/posts/tags GET 获取标签列表 HTML
/api/todos GET/POST 获取/创建待办 HTML
/api/todos/{id} GET/PUT/DELETE 获取/更新/删除待办 HTML
/api/auth/login POST 登录表单提交
/api/auth/register POST 注册表单提交

Jinja2 模板规则

  • FileSystemLoader 根目录frontend/(config.py 中 TEMPLATE_DIRS = ["."]
  • 跨目录引用:从根出发,如 {% extends "layouts/default.html" %}
  • 同目录引用:直接用文件名,如 {% import "post_item.html" as item %}
  • 组件传参:用 {% macro %} 宏 + {% import %} 引入
  • 页面模板:放 pages/,通过 env.get_template() 按相对路径渲染

移植过程记录

挑战与解决方案

  1. Jinja2 与 HTMX 的协同

    • 问题:需要一种既能服务端渲染又能保持前端交互最小化的模板方案
    • 解决:Jinja2 负责服务端 HTML 渲染,HTMX 处理无刷新交互,完全不需要 JavaScript
  2. 标签过滤的 JavaScript 依赖

    • 问题:初始实现使用 JavaScript 管理标签状态
    • 解决:改用纯服务端 HTMX 方案,标签点击通过 HTMX 请求服务端渲染
  3. JWT Token 管理

    • 问题:需要支持多端(Web、APP),但又不想写 JavaScript
    • 解决:JWT Token 存 Cookie,浏览器自动发送,无需 JavaScript 操作

零 JavaScript 验证

以下功能已实现零 JavaScript:

  • ✅ 标签过滤(纯 HTMX)
  • ✅ 帖子列表加载(HTMX)
  • ✅ 待办事项管理(HTMX)
  • ✅ 登录/登出(Cookie 自动携带)
  • ✅ 页面导航(传统链接)

唯一的 JavaScript 依赖:HTMX 库本身(htmx.min.js

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors