Supabase + Deno + Nextjs 极简案例 | Indie Hacker 成长指南(玖)

科技   科技   2023-12-30 22:01   美国  

前文链接:https://bodhi.wtf/10842

本文链接:https://bodhi.wtf/13751

联系作者:https://x.com/0xleeduckgo

Wechat: 197626581

在上一篇中,我们阐述了这一全新的技术栈的几个关键入门知识,包括什么是 Supabase、Supabase 中的边缘函数及其案例、Supabase 中的 Cron 及其案例。

在本文中,我们将通过案例实践快速上手 Supabase + Deno + NextJS 这一超强技术栈。

案例资料如下:

仓库地址:

https://github.com/NonceGeek/noncegeek-project-space

项目在线浏览: 

https://projects.noncegeek.com/

0x01 后端设计与实现

1.1 表的设计与实现

在 Supabase 中,我们可以通过所见即所得的方式创建表:

image-20231230100254748

也可以通过 SQL 语句来实现。一个很方便的地方在于,我们可以通过查看 Defination 的方式来复制已有的表的创建语句:

image-20231230100453346
create table
  public.noncegeek_project_space (
    id bigint generated by default as identity,
    name text not null,
    description text not null,
    status text not null,
    url text not null,
    github text not null,
    tags json not null,
    created_at timestamp with time zone not null default now(),
    author json null,
    whitepaper text null,
    constraint noncegeek_project_space_pkey primary key (id)
  ) tablespace pg_default;

在 SQL Editor 中运行上述 SQL 语句即可创建表:

image-20231230100736778

然后在表中插入一些模拟数据(Mock)。

1.2 Deno 简介与重要链接

Prompt: Give me a introduction about deno.

Answer by GPT-4:

Deno 是一个简单、现代且安全的 JavaScript 和 TypeScript 运行时,它使用 V8 并用 Rust 构建。Deno 由 Node.js 的原始创建者 Ryan Dahl 创建,旨在解决他在 Node.js 中发现的一些缺点。以下是其一些核心特征和方面:

安全性:Deno 默认情况下是安全的,这意味着除非明确授予,否则它无法访问文件系统、网络或环境。这使得运行未知来源的脚本更加安全。

TypeScript 支持:Deno 开箱即用地支持 TypeScript。无需安装 TypeScript 编译器等额外工具。这使得开发人员能够轻松高效地编写类型安全的代码。

单个可执行文件:Deno 作为单个可执行文件提供,其中包括运行时和标准库。这使得安装和使用变得容易。

模块系统:Deno 使用 URL 直接导入模块,就像浏览器一样。它不使用node_modules目录或package.json文件。这可以简化依赖管理并使其更加透明。

内置实用程序:Deno 附带了一组用于测试、格式化和运行代码的内置实用程序,这可以减少对外部工具的需求并简化开发工作流程。

与 Web 标准的兼容性:Deno 努力与 DOM 和 Fetch API 等 Web 标准兼容,使 Web 开发人员更容易编写服务器端代码。

Deno 旨在为现代程序员提供高效且安全的脚本环境。它对安全性的关注,加上其原生 TypeScript 支持和易于使用的模块系统,为某些用例提供了令人信服的 Node.js 替代方案。

💡 简单的理解,Deno 是 Node.js 的一个加强版本。

Prompt:Give me all the important links about deno.

Answer by GPT-4:

以下是有关 Deno 的原始 Markdown 格式的重要链接:

  1. Deno 官方网站:Deno,下一代 JavaScript 运行时 - 有关 Deno 及其功能的最权威、最新的信息【8†来源 】。

  2. Deno 文档:有关综合指南、API 参考和示例,请访问:

  • Deno 手册 - Deno 官方手册和文档【11†来源】。
  • Deno 标准模块 和 Deno 第三方模块 - Deno 模块的官方存储库【13†来源】。
  • Deno 问题 - 用于报告和跟踪 Deno 问题【13†来源】。
  • Deno 安装指南:在不同操作系统上安装 Deno 的各种指南和教程:

    • Deno 安装 - Deno 安装脚本(中文)【9†源码】。
    • Linode 指南 - 安装和使用 Deno JavaScript 运行时指南【10†来源】。
    • Telerik 指南 - Deno 入门综合指南【11† 来源】。
  • 运行时 API:有关 Deno 中可用 API 的详细信息:

    • Deno Runtime APIs - Deno 实现的 Web 平台 API 以及 Deno 命名空间中特定 API 的集合【19†来源】。
    • Deno.run API - 有关 Deno.run API 及其用法的信息【21†来源】。
    • Deno.loadavg API - 有关系统负载平均值的 Deno.loadavg API 的详细信息【22†来源】。
  • 社区和支持:保持更新并与 Deno 社区互动:

    • Deno 博客 - 用于公告和更新的官方 Deno 博客【24†来源】。
  • 学习资源和教程:利用这些资源增强您的知识和技能:

    • Telerik 教程 - Deno 入门的综合指南和技巧【11 †来源】【13†来源】【16†来源】。
    • Martin McK Guide - 理解 Deno 的简单指南【14†来源】【17†来源】。
    • freeCodeCamp Deno 手册 - 包含代码示例的 TypeScript 运行时教程【15†来源】【18†来源】。
  • 比较分析和用例:了解 Deno 与其他运行时及其用例的比较:

    • InfoWorld 文章 - Deno 1.0 到来挑战 Node.js【23†来源】。
    • OpenReplay 博客 - Deno 与其他运行时的比较【27†来源】。
  • 其他指南和教程

    • Packt Publishing - 讨论 Deno 为黄金时段使用做好的准备【25†来源】。
    • ReactGo 教程 - Deno 初学者指南【26†来源】。
    • 在 Deno 中构建 URL 缩短器 - freeCodeCamp 上的实用项目教程【28†来源】。

    1.3 在 Deno 中编写 Edge Functions

    Deno 安装指南:

    https://docs.deno.com/runtime/manual/getting_started/installation

    DeployCtl 安装指南:

    https://docs.deno.com/deploy/manual/deployctl#install-deployctl

    我们编写一个 Deno Edge Functions,保存为query_noncegeek_project_space.ts,这段代码的作用是从刚才创建的表中拿到所有数据。

    import { Application, Router, send } from "https://deno.land/x/oak/mod.ts";
    import { oakCors } from "https://deno.land/x/cors/mod.ts";
    import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.5.0'
    import { Configuration, OpenAIApi } from 'https://esm.sh/openai@3.1.0'

    console.log("Hello from Query!")



    const router = new Router();

    router
      .get("/"async (context) => {

        const supabase = createClient(
        // Supabase API URL - env var exported by default.
        Deno.env.get('SUPABASE_URL') ?? '',
        // Supabase API ANON KEY - env var exported by default.
        Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '',
          // Create client with Auth context of the user that called the function.
          // This way your row-level-security (RLS) policies are applied.
          // { global: { headers: { Authorization: req.headers.get('Authorization')! } } }
        )
        // 1. create a item in bodhi_user_search.
        const { data, error } = await supabase.
          from('noncegeek_project_space')
          .select();
        context.response.body = data
      })


    const app = new Application();
    app.use(oakCors()); // Enable CORS for All Routes
    app.use(router.routes());

    console.info("CORS-enabled web server listening on port 8000");
    await app.listen({ port: 8000 });

    通过deployctl进行部署:

    deployctl deploy --project=query-nps ./query_noncegeek_project_space.ts

    WechatIMG558

    配置环境变量,在https://dash.deno.com相应的项目的Settings里:

    WechatIMG557

    因为是Get方法,所以直接在浏览器中就可访问:

    https://query-nps.deno.dev/

    WechatIMG556

    💡POST 方法可以在终端中执行curl命令。

    0x02 前端设计与实现

    前端实现用的是scaffold-eth-2模板,基于Next.js

    image-20231230210543438

    项目包含hardhatnextjs两个部分,hardhat部署合约用,nextjs是主程序。

    2.1 调取 Deno 函数

    我们今天主要关心在packages/nextjs/pages/index.tsx中是如何deno函数的。

    // 定义 projects 与 setProjects
    const [projects, setProjects] = useState([]);

    // init 函数,通过异步的方式从 deno 接口拿到数据,并通过`setProjects`赋值给`projects`
    const init = async () => {
        const response = await fetch("https://query-noncegeek-space.deno.dev", {
          method: "GET",
          headers: {
            "Content-Type""application/json",
          },
          // mode: "no-cors",
        });
        const data = await response.json();
        console.log("data:" + JSON.stringify(data));
        setProjects(data);
      };

    2.2 页面渲染

    通过projects.map遍历projects列表,将其渲染到页面上。

                <div className="join mb-6">
                  <div className="grid gap-5 mt-5 md:grid-cols-2 lg:grid-cols-2">
                    {projects.map(({ id, name, description, status, url, github, tags, author, whitepaper }) => (
                      <div
                        key={id}
                        className="max-w-sm bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700"
                      >
                        <div className="p-5">
                          <h5 className="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">{name}</h5>
                          <p className="mb-3 font-normal text-gray-700 dark:text-gray-400 h-20 overflow-y-auto">
                            {description}
                          </
    p>
                          <a
                            href={url}
                            target="_blank"
                            rel="noreferrer"
                            className="inline-flex justify-center items-center px-3 py-2 text-sm font-medium text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 w-32"
                          >
                            Launch App🚀
                          </a>
                          &nbsp;&nbsp;&nbsp;
                          <a
                            href={github}
                            target="_blank"
                            rel="noreferrer"
                            className="inline-flex justify-center items-center px-3 py-2 text-sm font-medium text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 w-32"
                          >
                            Github
                          </
    a>
                          <br></br>
                          <br></
    br>
                          <a
                            href={author.twitter}
                            target="_blank"
                            rel="noreferrer"
                            className="inline-flex justify-center items-center px-3 py-2 text-sm font-medium text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 w-32"
                          >
                            👀View the Author
                          </a>
                          &nbsp;&nbsp;&nbsp;
                          <a
                            href={whitepaper}
                            target="_blank"
                            rel="noreferrer"
                            className="inline-flex justify-center items-center px-3 py-2 text-sm font-medium text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 w-32"
                          >
                            📄Whitepaper on Bodhi
                          </
    a>
                          <br></br>
                          <br></
    br>
                          <div>
                            <b>Tags: </b>
                            {tags.map((tag, index) => (
                              <span key={index} className="inline-flex items-center px-3 py-1 text-xs font-semibold leading-none text-white bg-orange-600 rounded-full">
                                {tag}
                              </
    span>
                            ))}
                            
                          </div>
                          <br></
    br>
                          <div>
                            <b>Status: </b>
                            <span className="inline-flex items-center px-3 py-1 text-xs font-semibold leading-none text-white bg-orange-600 rounded-full">
                              {status}
                            </
    span>
                          </div>
                        </
    div>
                      </div>
                    ))}
                  </
    div>
                </div>

    最终效果:

    image-20231230211303949

    历史文章:

    Supabase 极速入门之定时任务 | Indie Hacker 成长指南(捌)

    独立开发者工具梳理 | Indie Hacker 成长指南(柒)

    全链上创业组织 | Indie Hacker 成长指南(陆)

    运行社区的 4 大挑战 | Indie Hacker 成长指南(伍)

    产品上线前如何解决资金问题?|  Indie Hacker 成长指南(肆)

    Waitlist 经验谈:独立开发者企业的工具栈 | Indie Hacker 成长指南(叄)

    如何围绕你的生活设计你的事业 |  Indie Hacker 成长指南(贰)

    打破 MVP 魔咒 | Indie Hacker 成长指南(壹)



    李大狗LDG
    面向「炫酷」编程
     最新文章