fresh-remult-todo:使用Deno的Fresh和Postgres构建全栈CRUD应用


Fresh是 Deno 的全栈框架,可以轻松使用 TypeScript 开发应用程序。让我们用它来构建一个具有完整 CRUD 功能的简单 Todo 应用程序。
这个(非常)简短教程的结果代码可以在这个 GitHub 存储库中找到。
要开始使用,请确保您已安装 Deno CLI版本 1.23.0 或更高版本。

第 1 步 - 创建一个新的新项目

deno run -A -r https://fresh.deno.dev todos
cd todos
deno task start

您现在可以在浏览器中打开http://localhost:8000来查看该页面。

第 2 步 - 使用全栈 CRUD 框架加快速度
为了快速高效地构建 CRUD 操作,我将使用Remult,这是一个为 Node.js 构建的 CRUD 框架,但也适用于 Deno。

(注:我是 Remult 的创造者之一。)

让我们将 Remult 添加到项目中import_map.json:

{
  "imports": {
    // ...
    "remult": "https://cdn.skypack.dev/remult?dts",
    "remult/remult-fresh": "https://cdn.skypack.dev/remult/remult-fresh?dts"
  }
}

Remult 作为Fresh中间件被引导,让我们在下面创建一个_middleware.ts文件routes/,代码如下:
// routes/_middleware.ts

import { remultFresh } from "remult/remult-fresh";

export const remultServer = remultFresh({ }, Response);

export const handler = remultServer.handle;

第 3 步 - 创建任务模型
model在根目录下创建一个文件夹,并task.ts使用以下代码创建一个文件:
// model/task.ts

import { Entity, Fields } from "remult";

@Entity("tasks", {
    allowApiCrud: true
})
export class Task {
    @Fields.uuid()
    id!: string;

    @Fields.string()
    title = '';

    @Fields.boolean()
    completed = false;
}

为了remult给Task模型提供一个 CRUD API,我们需要在服务器的entities数组中注册它remult:
// routes/_middleware.ts

import { remultFresh } from "remult/remult-fresh";
import { Task } from "../model/task.ts";

export const remultServer = remultFresh({
  entities: [Task]
}, Response);

//...

此时 CRUD API 已准备就绪。(在http://localhost:8000/api/tasks进行测试。)

第 4 步 - 创建一个岛用于显示和编辑待办事项
由于待办事项列表将非常具有交互性,因此我将使用 Freshisland来显示和编辑列表。但是,我将使用 Fresh 的服务器端渲染在页面加载时获取初始任务列表。

islands/todos.tsx使用以下代码创建一个文件:
// islands/todos.tsx

/** @jsx h */
import { h } from
"preact";
import { Remult } from
"remult";
import { useState } from
"preact/hooks";
import { Task } from
"../model/task.ts";

const remult = new Remult();
const taskRepo = remult.repo(Task);

export default function Todos({ data }: { data: Task[] }) {
  const [tasks, setTasks] = useState<Task[]>(data);

  const addTask = () => {
    setTasks([...tasks, new Task()]);
  };

  return (
    <div>
      {tasks.map((task) => {
        const handleChange = (values: Partial<Task>) => {
          setTasks(tasks.map((t) => t === task ? { ...task, ...values } : t));
        };

        const saveTask = async () => {
          const savedTask = await taskRepo.save(task);
          setTasks(tasks.map((t) => t === task ? savedTask : t));
        };

        const deleteTask = async () => {
          await taskRepo.delete(task);
          setTasks(tasks.filter((t) => t !== task));
        };

        return (
          <div key={task.id}>
            <input
              type=
"checkbox"
              checked={task.completed}
              onClick={(e) => handleChange({ completed: !task.completed })}
            />
            <input
              value={task.title}
              onInput={(e) => handleChange({ title: e.currentTarget.value })}
            />
            <button onClick={saveTask}>Save</button>
            <button onClick={deleteTask}>Delete</button>
          </div>
        );
      })}
      <button onClick={addTask}>Add Task</button>
    </div>
  );
}

修改默认routes/index.ts文件,在数据库中查询 SSR 处理程序中的任务列表,并在渲染页面时将其传递给Todosisland:
// routes/index.ts

/** @jsx h */
import { h } from
"preact";
import { Handlers, PageProps } from
"$fresh/server.ts";
import Todos from
"../islands/todos.tsx";
import { Task } from
"../model/task.ts";
import { remultServer } from
"./_middleware.ts";

export const handler: Handlers<Task[]> = {
  async GET(req, ctx) {
    const remult = await remultServer.getRemult(req);
    return ctx.render(await remult.repo(Task).find());
  },
};

export default function Home({ data }: PageProps<Task[]>) {
  return (
    <div>
      <Todos data={data} />
    </div>
  );
}

困难的部分结束了
此时,我们有一个待办事项列表,我们可以创建、更新和删除任务。

继续创建一些任务,当您保存它们时,您会注意到在项目文件夹中创建db了一个包含tasks.json文件的文件夹。这个JSON 数据库是remult的默认数据库,非常适合快速原型设计。

第 5 步 - 连接到 Postgres 并部署
我们需要一个新的 Postgres 实例来连接。我使用Deno Deploy 文档中的这些说明在Supabase上设置了一个实例。

有几种方法可以将应用程序部署到 Deno Deploy。我选择将我的代码推送到 GitHub,并使用Deno Deploy 仪表板将 Deno Deploy 项目连接到 GitHub 存储库。

不要忘记DATABASE_URL在 Deno Deploy 项目设置中添加一个环境变量,其值为 Supabase 提供的连接字符串。

现在让我们告诉remult使用DATABASE_URL环境变量连接到 Postgres。
// routes/_middleware.ts

import { remultFresh } from "remult/remult-fresh";
import { Task } from
"../model/task.ts";
import { createPostgresConnection } from
"https://deno.land/x/remult/postgres.ts";

export const remultServer = remultFresh({
  entities: [Task],
  dataProvider: async () => {
    const dbUrl = Deno.env.get(
"DATABASE_URL");
    if (dbUrl) {
      return createPostgresConnection({ connectionString: dbUrl });
    }
    return await undefined;
  },
}, Response);

export const handler = remultServer.handle;

.env您可以通过在项目的根文件夹中创建一个包含以下内容的文件来在本地测试 Postgres 连接: DATABASE_URL='<YOUR_CONNECTION_STRING>'.

一旦您提交并将最后一次更改推送到您的 GitHub 存储库,您的 todo 应用程序将被部署并连接到您的 Postgres 数据库