科技前沿 科普Web 基础教程建站系列

数据库基础:从 Excel 到 Supabase

📅 2026-07-15

如果你用过 Excel,你已经理解数据库 90% 的核心概念。唯一的区别是:Excel 的格子你自己点,数据库的格子用代码读写。

Excel 思维 vs 数据库思维

Excel 做数据管理:

姓名 文章标题 点赞数
张三 数据库入门 12
李四 CSS Grid 详解 8

在 Supabase(PostgreSQL)里,这个表格对应一张"表"(table):

articles 表
─────────────────────────────────
id  | title        | views | category
─────┼──────────────┼───────┼─────────
1   | 数据库入门    | 12    | 科技前沿
2   | CSS Grid 详解 | 8     | 科技前沿

Excel 有你手动算 SUM 和筛选,数据库用 SQL 查。白物集不直接写 SQL,改用 @supabase/supabase-js(Supabase 的 JavaScript 客户端库),它在背后帮你生成 SQL。

三步:连接、查询、写入

白物集在后端(Express API)连接 Supabase 的代码就三行:

import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(supabaseUrl, supabaseKey);

这跟 Excel 打开文件的逻辑一模一样——你得先指定"哪个文件"(supabaseUrl),再提供"打开权限"(supabaseKey)。只不过 Excel 文件存在本地,Supabase 存在云端。

连接之后,查询文章列表:

let query = supabase
  .from('articles')
  .select('id, title, category, published_at')
  .eq('status', 'published')
  .order('published_at', { ascending: false })
  .range(0, 19);

拆开看,每步都是 Excel 里你手动做的事情:

代码 Excel 对应操作
.from('articles') 切换到「articles」这个工作表
.select('id, title, ...') 选中 A、B、C 三列
.eq('status', 'published') 筛选:状态 = "已发布"
.order('published_at', descending) 按发布日期降序排序
.range(0, 19) 只看前 20 行

写入数据也一样直观:

const { data, error } = await supabase
  .from('articles')
  .insert({
    title: '数据库基础',
    category: '科技前沿',
    status: 'published',
  })
  .select('id, title');

这等于在 Excel 表格的最后一行下面新建一行,填入三个单元格的值,然后 Excel 返回给你新行的内容。

白物集踩坑:不传 content_html,页面显示 "内容加载中"

这是白物集踩过最多次的坑。POST /api/v1/content/ingest 不会自动把 content_md 转成 content_html,而文章详情页的模板逻辑是:

article.content_html || '<p>内容加载中...</p>'

如果只传了 content_mdcontent_html 为空字符串,页面上就是「内容加载中」。修复办法:POST 前用 Python 的 markdown 库手动转一次:

import markdown
html = markdown.markdown(md_clean, extensions=['fenced_code', 'tables'])

同时 payload 里传 content_mdcontent_html 两个字段。这个坑的本质是:数据库只存你传进去的数据,不会帮你算任何东西。Excel 的公式是自动重算的,数据库不会。

表和表的关系

Excel 可以把 A 工作表的某列引用到 B 工作表的某行。数据库也一样。

白物集的 article_likes 表就关联到 articles 表:

CREATE TABLE article_likes (
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  article_id BIGINT NOT NULL REFERENCES articles(id) ON DELETE CASCADE,
  user_ip TEXT NOT NULL,
  UNIQUE(article_id, user_ip)
);

REFERENCES articles(id) 表示:每一行 article_likes 数据都必须指向一条真实存在的文章。这等于在 Excel 里做了「数据验证 → 序列 → 引用另一个工作表」,只是数据库强制执行——你塞不进去一个不存在的文章 ID。

查询关联数据时,先查文章,再查它的点赞数:

// 查文章
const { data: article } = await supabase
  .from('articles')
  .select('id, title')
  .eq('id', articleId)
  .single();

// 查点赞数
const { count } = await supabase
  .from('article_likes')
  .select('*', { count: 'exact', head: true })
  .eq('article_id', articleId);

第一次写这段代码时踩了一个性能陷阱:每查一篇文章就单独查一次点赞数。首页列表有 20 篇文章,就要发 21 次请求。后来改成用 select 的关联查询(或者在文章查询时直接返回计数),但为了演示简单的两步查询,上面的分步写法更容易理解。

数据库和 Excel 的本质区别

最后说一个很多人没意识到的区别:Excel 是你一个人用,数据库是服务器用。

你在 Excel 里改了数据,保存,发给别人。数据库不是——前端不直接连数据库,中间隔着一层 API。

白物集的架构是:

用户浏览器 → Astro SSR → Express API → Supabase

前端通过 fetch('https://ai.golfr20.cn/api/articles') 拿到数据,前端代码里不从 Supabase 客户端读取。所以 supabase.ts 中的匿名 key 即使暴露在浏览器端,也只能读公开数据,写操作受到行级安全(RLS,Row Level Security)控制。

这个架构和安全意识,是数据库和 Excel 最根本的分水岭。

下一篇讲白物集 API 架构拆解:19 个路由的分层设计——怎么从一条 Express 路由,长成一个可维护的后端系统。

← Vercel 开源 Native SDK:原生桌面开发新路径 → Apple 频道 uv——比 pip 快 10 倍的 Python 包管理器 →
🍎 Apple 深度分析
本文基于 Apple 公开资料及行业分析撰写。观点仅供参考与学习交流。