Flask笔记十三:写一个简单的 JSON API Blueprint

📅 2026/6/27 21:31:16
Flask笔记十三:写一个简单的 JSON API Blueprint
上一篇我们把SECRET_KEY、数据库地址挪到了环境变量。网页端已经能看备忘录列表了但还会遇到这类需求手机脚本想 拉 JSON不想解析 HTML前端页面用 JavaScript 异步刷新 列表定时任务、小工具 HTTP 调一下 就能查数据这一篇做一件事在同一个项目里加第三个 Blueprintapi返回 JSON并且 复用第十篇的note_service不复制 SQL。例子仍是通用的Note备忘录不涉及任何真实业务。1. 学完后你能做什么新建apiBlueprintURL 统一带/api/前缀用jsonify返回 JSON正确设置 HTTP 状态码200 / 400 / 401 / 404列表接口 共用list_notes_for_user知道入门阶段 要不要做 Token 鉴权2. HTML 页面和 JSON 接口差在哪HTML 页面homeJSON 接口api返回render_template(...)jsonify({...})给谁用浏览器直接打开脚本、前端 JS、Postman错误提示flash 跳转{ok: false, msg: ...} 状态码查询逻辑note_service同一个note_service关键视图层换输出格式service 层不变。3. 目录长什么样在第十一、十二篇基础上加app/├── __init__.py├── home/├── admin/├── api/ # 新增│ ├── __init__.py│ └── views.py├── note_service.py├── auth_utils.py└── templates/三个 Blueprint 并列/notes/ → homeHTML 列表/admin/notes/ → adminHTML 后台列表/api/notes/ → apiJSON 列表4. 创建 api Blueprintapp/api/__init__.pyfrom flask import Blueprintapi Blueprint(api, __name__)import app.api.viewsapp/__init__.py里注册from app.api import api as api_blueprintapp.register_blueprint(api_blueprint, url_prefix/api)之后api.route(/notes/)的实际地址是/api/notes/。5. 第一个接口备忘录列表 JSONapp/api/views.pyfrom flask import jsonify, request, sessionfrom app.api import apifrom app.auth_utils import login_requiredfrom app.note_service import list_notes_for_userdef _note_to_dict(row):return {id: row.id,title: row.title,content: row.content or ,addtime: row.addtime.strftime(%Y-%m-%d %H:%M:%S) if row.addtime else ,}api.route(/notes/, methods[GET])login_requireddef notes_list():q (request.args.get(q) or ).strip()page request.args.get(page, 1, typeint)per_page request.args.get(per_page, 10, typeint)per_page min(max(per_page, 1), 50)page_data list_notes_for_user(session[user_id],qq,pagepage,per_pageper_page,)return jsonify({ok: True,items: [_note_to_dict(n) for n in page_data.items],page: page_data.page,pages: page_data.pages,total: page_data.total,has_next: page_data.has_next,has_prev: page_data.has_prev,})测试curl -b cookies.txt http://127.0.0.1:5000/api/notes/?q会议page1查询仍在note_serviceapi 视图只做读参数 → 调函数 → 转成 dict →jsonify。6. 单条查询与 404from app.note_service import get_note_for_userapi.route(/notes/int:note_id/, methods[GET])login_requireddef note_detail(note_id):row get_note_for_user(note_id, session[user_id])if not row:return jsonify({ok: False, msg: 记录不存在或无权访问}), 404return jsonify({ok: True, item: _note_to_dict(row)})JSON API 里 404 用状态码表达客户端靠状态码分支更简单。7. POST 新增一条JSON 请求体HTML 表单用request.formAPI 常用 JSON bodyfrom app import dbfrom app.models import Noteapi.route(/notes/, methods[POST])login_requireddef note_create():data request.get_json(silentTrue) or {}title (data.get(title) or ).strip()content (data.get(content) or ).strip()if not title:return jsonify({ok: False, msg: 标题不能为空}), 400note Note(titletitle,contentcontent,user_idsession[user_id],)db.session.add(note)db.session.commit()return jsonify({ok: True, item: _note_to_dict(note)}), 201状态码含义200成功GET201创建成功400参数错误401未登录404资源不存在8. 未登录时JSON 不要 redirectHTML 登录失败会redirect到登录页。API 客户端 跟不上 302应直接返回 JSONfrom functools import wrapsfrom flask import session, redirect, url_for, request, jsonifydef login_required(view):wraps(view)def wrapped(*args, **kwargs):if not session.get(user_id):if request.path.startswith(/api/):return jsonify({ok: False, msg: 请先登录}), 401return redirect(url_for(home.login, nextrequest.path))return view(*args, **kwargs)return wrapped同一装饰器 既能保护 HTML 页也能保护 API。9. 前端页面怎么调自己的 APIbutton typebutton idbtn-refresh-json用 API 刷新/buttonul idjson-note-list/ulscriptdocument.getElementById(btn-refresh-json).addEventListener(click, function () {fetch(/api/notes/?page1).then(function (res) {if (!res.ok) throw new Error(HTTP res.status);return res.json();}).then(function (data) {var ul document.getElementById(json-note-list);ul.innerHTML ;(data.items || []).forEach(function (n) {var li document.createElement(li);li.textContent n.title — n.addtime;ul.appendChild(li);});}).catch(function (err) {alert(加载失败 err.message);});});/script页面仍用 Jinja 渲染数据可以服务端渲染也可以 fetch API——查询规则都在note_service不会两套逻辑打架。10. 入门阶段要不要 Token 鉴权场景建议浏览器里 JS 调 同站/api/Session Cookie 够用手机 App、外部脚本需要 API Key 或 JWT后面专题完全公开的只读数据可不加登录但要限流入门阶段先 Session login_required把 JSON 格式和状态码写对。11. 统一 JSON 形状{ok: true, items: [...], page: 1}{ok: false, msg: 标题不能为空}成功ok: true 数据字段失败ok: falsemsg 合适的 HTTP 状态码12. 流程示意GET /api/notes/?q会议│▼login_required → 未登录401 JSON│▼list_notes_for_user(user_id, q会议, ...)│▼jsonify({ok: true, items: [...]})home.note_listHTML ──┐api.notes_listJSON ──┼── list_notes_for_userexport 脚本 ──┘13. 新手常踩的 6 个坑API 里render_template— 应只返回jsonify错误也返回 200 — 应return jsonify(...), 401忘记Content-Type: application/json— POST 用request.get_json()API 里复制 SQL — 都走note_service跨域一头雾水 — 同站调用不需要 CORSdatetime直接丢进 JSON — 先转成字符串14. 小结记住五件事第三个 Blueprintapi—url_prefix/apijsonify 状态码 — 错误用 4xx成功 200/201复用note_service— 不复制查询未登录返回 JSON 401 — 不要 redirect入门用 Session — Token 留给外部调用需求时十三篇连下来你已经会结构、数据库、WTForms CRUD、搜索、模板继承、Migrate、Flash、登录 Session、service 分层、admin Blueprint、环境变量、JSON API。