使用 Notion 数据库进行 Next.js 应用全栈开发

发表于 1年以前  | 总阅读数:1034 次

前言

在上一篇中,我们使用了 strapi 和 Next.js 开发了一个简易微博,但是我没有部署上线,因为我知道这个小应用只能个人体验,若是我们的个人项目想要部署上线,难道还得花钱买服务器吗,“任何不能令人满意的东西,不值得我们屈尊“,今天我就得带大家来白嫖一下 Notion 数据库,让我们的个人应用轻松上线。

本文涉及代码都在 Github 仓库中

Notion 是什么?

Notion 是一款极其出色的个人笔记软件,它将“万物皆对象”的思维运用到笔记中,让使用者可以天马行空地去创造、拖拽、链接。Notion 适合各种场景,无论是生活、工作还是学习,各种东西都可以在这里记录;它可以帮助用户记录日程表、每日计划、待办事项、日记等,到了时间系统会自动进行提醒。

但对我们程序员来说,Notion 除了是笔记软件,还可以是数据库。

在开始之前,我强烈建议不要在生产环境中使用 Notion 数据库。

需求分析

我先来介绍一下我的需求,之前写过一篇《【实战】Next.js + 云函数开发一个面试刷题网站》,采用了"腾讯云云开发"中的云数据库和 Vercel 部署了一个面试刷题网站,但现在"腾讯云云开发"目前收费了,价格是 19.9 元/月,所以我打算将数据库从腾讯云迁移到 Notion,并且使用 Next.js 服务端渲染,最后部署同样使用 Vercel。这样做的优点是:

  • 整个上线网站我都不用花钱
  • SSR 渲染,搜索引擎可以收录网站中更多的页面。

创建 Notion 数据库

如果没有账户的同学,请大家自行注册前往 https://www.notion.so/ 注册账号,Notion 目前没有中文版本,大家可以装这个油猴插件[1],汉化一下。

首先我们在 Notion 中,创建一个 full page table 页面,来作为我们的数据库保存数据。

1 . 打开 Notion,添加一个 page,输入名称,并在模板中选择 DATABASE 下的 Table。

新建页面创建 DATABASE

2 . 点击 properties,添加我们题目表所需要的一些数据项:

Notion 添加 properties 字段

题目表

根据之前的数据结构,添加字段,这是题目表的数据 ts 类型接口


export interface Question {
    _id: string;
    category: string; // 分类
    title: string; // 标题
    desc: string; // 简介
    options: string; // 选项,JSON转成字符串
    explanation: string; //  解析
    level: number; //  难度
    tagId: number; //  标签
}

Notion properties 字段类型

可以看到 Notion 数据字段也分为文本数字下拉多选文件等等,我们在右侧选择字段类型,所有添加完成后,我们再手动录入一条数据,以便于后续测试数据。

标签表


export interface Tag {
    id: number;
    tagName: string;
    image: string;
}

Notion 创建标签表

我们使用相同步骤,建立标签表,并且添加数据到表中。

建立表关联

题目表和标签表是多对一的关系,一个标签下有多道题目,一个题目只有一个标签

Notion 建立表关联

在题目表添加属性tag,选择 Relation,让后选择“标签”表

Notion 建立表关联

这样题目表和标签表就建立了关系

创建 Notion 集成

在使用 Notion API 之前,我们需要创建一个 Notion 的应用集成,获取 API Key。 打开 https://www.notion.so/my-integrations,打开 Notion 集成页面,登录自己的账号,点击 New integration 创建一个新的应用:

创建 Notion 集成

名称可以自己起,上传一个 LOGO,然后关联一个 Notion 的工作空间:

创建 Notion 集成,填写信息

点击提交,这个应用就创建好了,在跳转的新页面里,把Internal Integration Token复制下来,不要泄露,否则拿到这个 key 的人都能操作你的笔记啦。

复制 Notion Token

接下来在 Notion Page 页面,点击更多,创建 Connections,连接到我们刚才创建的应用

Notion 页面关联集成应用

这样就可以使用 API 来操作笔记啦。

初始化项目

1 . 创建一个 next 项目

npx create-next-app fe-app --typescript
cd fe-app

2 . 安装 TailwindCSS


yarn add -D tailwindcss postcss autoprefixer @tailwindcss/typography
npx tailwindcss init -p

3 . 编辑 tailwind.config.js 配置文件

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
      "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
      extend: {},
  },
  plugins: [
      require('@tailwindcss/typography')
  ],
}

4 . 修改 styles/global.css 为 tailwindcss 的初始化指令

@tailwind base;
@tailwind components;
@tailwind utilities;

5 . 设置 Node 环境变量,新建一个 .env.local 文件


NOTION_ACCESS_TOKEN=
NOTION_DATABASE_QUESTION_ID=
NOTION_DATABASE_TAG_ID=

TOKEN 为刚才复制的 TOKEN,数据库 ID 为 NOTION 页面 URL 上的 ID

复制 Notion 数据库ID

链接数据库

我们需要安装 Notion Javascript 客户端,来获取表数据信息。

yarn add @notionhq/client

查询列表

新建一个 lib/NotionServer.ts, 将数据库请求的方法封装在这个 class 中

import { Client } from "@notionhq/client";

const auth = process.env.NOTION_ACCESS_TOKEN;

const database = process.env.NOTION_DATABASE_QUESTION_ID ?? "";

type Question = any;

export default class NotionService {
  client: Client;

  constructor() {
    this.client = new Client({ auth });
  }

  async query(): Promise<Question[]> {
    const response = await this.client.databases.query({
      database_id: database,
    });

    return response.results;
  }
}

新建pages/api/question.ts, 与约定式路由一样,任何在pages/api 目录下的文件都可以作为/api/*接口访问,以下代码就直接调用了刚才创建的 NotionServer:


import type { NextApiRequest, NextApiResponse } from "next";
import NotionServer from "../../lib/NotionServer";

type Data = any;

const notionServer = new NotionServer();

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>
) {
  const data = await notionServer.query();
  res.status(200).json(data);
}

此时我们访问 http://localhost:3000/api/question ,会看到如下数据

Notion api 返回数据结构

这显然不是我们想要的数据,在 Notion 中,表中的每一个单元格数据,都有类型和针对这个类型的描述字段。 我们想要的格式是这样的,比如:

{
  "title":"输出什么?"
  "desc":"题目描述"
}

title 字段叫标题,而且肯定是单行文本类型,是明确的,所以我们需要简化一下字段,在 NotionServer.ts 中加入一个transformer 方法,用于数据转换:


class NotionService {
  ...

  async query(): Promise<Question[]> {
    const response = await this.client.databases.query({
      database_id: database,
    });

    return response.results.map((item) => NotionService.transformer(item));
  }

  private static transformer(page: any): Question {
    let data: any = {};

    for (const key in page.properties) {
      switch (page.properties[key].type) {
        case "relation":
          data[key] = page.properties[key].relation[0].id;
          break;

        case "title":
        case "rich_text":
          data[key] =
            page.properties[key][page.properties[key].type][0].text.content;
          break;

        default:
          data[key] = page.properties[key];
          break;
      }
    }

    return data;
  }
}

转换后,刷新页面,便得到了我们想要的数据。

转换后的 json 数据

查询详情

根据 page_id 查询数据详情可以使用如下代码

async detail(id: string): Promise<Question> {
  const response = await this.client.pages.retrieve({
    page_id: id,
  });

  return NotionService.transformer(response);
}

同样查询结果需要转换一下,将 Notion 复杂结果转换为我们需要的简单数据结构。

添加/修改数据

添加和修改数据,我们可以使用下面这个 2 个方法

this.client.pages.create(paramters)
this.client.pages.update(paramters)

其中 paramters 的值,我们可以通过 ts 类型看,其实跟查询返回的数据一致,我只需要拷贝查询结果的其中一条数据,删除掉字段中的 id,替换掉里面的内容为真实数据即可。

notionhq/client TS 类型提示

下面是添加题目函数的代码:

async create(question: Question): Promise<any> {
  const response = await this.client.pages.create({
    parent: {
      database_id: database,
    },
    properties: {
      desc: {
        type: "rich_text",
        rich_text: [
          {
            type: "text",
            text: {
              content: question.desc,
            },
          },
        ],
      },
      options: {
        type: "rich_text",
        rich_text: [
          {
            type: "text",
            text: {
              content: question.options,
            },
          },
        ],
      },
      explanation: {
        type: "rich_text",
        rich_text: [
          {
            type: "text",
            text: {
              content: question.explanation,
            },
          },
        ],
      },
      tag: {
        type: "relation",
        relation: [
          {
            id: question.tag,
          },
        ],
      },
      title: {
        type: "title",
        title: [
          {
            type: "text",
            text: {
              content: question.title,
            },
          },
        ],
      },
    },
  });
  return response;
}

编辑数据与添加数据方法相似,只需要拷贝查询结果的数据,将需要修改的部分修改掉即可,使用 Notion 作为数据库的还有一个好处,就是可以将 Notion 作为 CMS 系统来用,有时添加和修改不是必须的功能,我们可以直接在 Notion 后台进行数据管理。

数据库迁移

因此我们可以从云开发数据库中将数据导出,通过 api 导入到 Notion 数据库中。

首先在新建一个 pages/api/create.ts 用于创建题目的接口。

import type { NextApiRequest, NextApiResponse } from "next";
import NotionServer from "../../lib/NotionServer";

type Data = any;

const notionServer = new NotionServer();

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>
) {
  if (req.method !== "POST") {
    res.status(405).send({ message: "Only POST requests allowed" });
    return;
  }

  const data = await notionServer.create(req.body);

  res.status(200).json(data);
}

接下来就可以将导出的 json,通过执行本地 node fetch 的方式将数据全部导入到 Notion 中


const fs = require("fs");
const path = require("path");
const fetch = require("node-fetch");

(async () => {
  const content = fs.readFileSync(
    path.resolve(__dirname, "./fe.json"),
    "utf8"
  );

  const tags = [
    {
      _id: "6b428f57-0831-4280-ac1c-8d016c8d038b",
      id: 17,
      image: "/static/logo/fun.svg",
      tagName: "趣味题",
    },
    ...
  ];

  const data = content.split("\n");

  for (let index = 0; index < data.length; index++) {
    if (data[index].trim() === "") continue;
    let item = JSON.parse(data[index]);
    item.tag = tags.find((tag) => tag.id === item.tagId)?._id;

    const res = await fetch("http://localhost:3000/api/create", {
      method: "post",
      headers: {
        "Content-Type": "application/json; charset=utf-8",
      },
      body: JSON.stringify(item),
    }).then((res) => res.json());

    console.log("第" + index + "题" + item.title + res.id);
  }
  console.log("end");
})();

上面代码中 tags 我是选创建了一个 tag 查询接口,因为 tags 数据不多,所以我直接将数据复制了下来,用 nodejs 执行一下以上代码,我们便可以在控制台中看到数据导入的进度。

数据迁移到 Notion 完成

执行完成,总共 968 题, 我们来 Notion 中看看导入的效果。

Notion 查看数据表

当然我也将全部题目公开在 Notion 上面了,大家可以通过这个链接查看。

删除数据

在 Notion 中,不能使用 api 的方式删除数据,所以我们需要换一种思维来解决,在表中创建一个字段In stock 来标识,用于假删除数据,也就是将“删除数据逻辑”修改为“修改数据逻辑”,将这条数据修改为归档类型,所以之前查询列表接口需要加上 In stock 值为 false,查询未归档的数据。

下面是假删除数据的代码:

async remove(pageId: string) {
    const response = await this.client.pages.update({
      page_id: pageId,
      properties: {
        "In stock": {
          checkbox: true,
        },
      },
    });
    return response;
  }

Next.js SSR

到此,我们完成了数据表的建设,以及数据库的迁移。完成了对 NotionService 的封装,有了 NotionService ,我们不但可以完成接口,而且还可以在 Next.js 中直接获取数据,用于服务端渲染。

以下是首页中服务端获取数据的部分实现代码

import React from "react";
import { GetServerSidePropsContext,InferGetServerSidePropsType } from "next";
import NotionServer from "@/lib/NotionServer";

export default function Interview({
  data,
  tags,
  q,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
  // 使用 props 数据,之间进行 render
  return (...)
}
// 在Nodejs 环境中执行
export async function getServerSideProps(context: GetServerSidePropsContext) {
  const { q = "", tagid, cursor } = context.query;
  const notionServer = new NotionServer();

  const tags = await notionServer.queryTags();

  const { data, has_more } = await notionServer.query({
    title: q as string,
    tagid: tagid as string,
  });
  // 获取到的数据,传递给组件props
  return {
    props: {
      data,
      tags,
      has_more,
      q,
    },
  };
}

这部分主要跟之前文章中的内容大致相同,所以我就不过多介绍了,我们直接来看下效果。

Notion 和 Next.js 开发现效果

大家可以使用 https://notion.runjs.cool/ 访问体验,也可以与之前的腾讯云开发做对比 https://runjs.cool/

Notion 和腾讯云接口对比

Notion 小数据量接口平均 800ms,列表接口包含了答案详情,所以在 1s 以上,腾讯云每个接口都在 300ms 以下。

踩坑

虽然全文下来,大致上没什么难度,但是我在实践过程中也遇到了一些坑。

1 . Notion 中,文本字段是一个数组,上面transformer代码中,我们取值都是取了[0].text.content,但 content 的长度限制是 2000,所以我们之前的一些 Text 字段,超过 2000 需要拆分成数组存储,读取的时候再将数组拼接成一个字符串。

至于如何拆分,大家可以思考下,也直接看我的代码仓库[2]

2 . 传统数据分页传 pagepage_size 2 个字段,但在 Notion 中采用了 start_cursor 指针的方式,前端需要使用滚动翻页的方式。

小结

本文我们利用 Next.js 和 Notion API 编写了一个前端刷题网站,整个流程是:

  • 创建数据库页面、配置属性、获取数据库 id。
  • 从 Notion 官网创建应用,获取 API KEY。
  • 使用 @notionhq/client 对 Notion 数据进行操作,编写接口
  • 在 Page 中,使用 getServerSideProps 进行数据获取,让后使用 react 进行渲染页面。

使用 Notion 作为数据库有利有弊

最大的优点: 免费,打通 Notion,让笔记和网站得以同步;

缺点:Notion API 的结构比较复杂,接口固定,不能实现定义的功能,所以这里适合做一些尝试性的项目。

思考:若是我们的网站也需要实现 CMS 自定义设计表单的功能,我相信 Notion 的数据结构值得我们参考。

好了,以上就是利用 Notion API 和 Next.js 进行应用全栈开发的过程,你学会了吗?若对你有帮助,记得帮我点赞。

本文由哈喽比特于1年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/afwYU8bl7IHZWbkcN97i4g

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 目录