Astro 和 Redis 入门Astro 和 Redis 入门
Astro 和 Redis 入门 Astro 是一种针对构建快速 内容网站而优化的新工具。虽然它是新的,但团队已经发布了 1.0 版本,并且 API 现在已经稳定。主要卖点是默认情况下零 JavaScript 以及“自带框架”。我们将在这里使用 Svelte 来帮助我们开始使用 Astro 和 Redis。那就是用无服务器数据库构建一个基本的笔记应用程序。尽管我们使用 Svelte,但您应该能够在
Astro 和 Redis 入门
Astro 是一种针对构建快速 内容网站而优化的新工具。虽然它是新的,但团队已经发布了 1.0 版本,并且 API 现在已经稳定。主要卖点是默认情况下零 JavaScript 以及“自带框架”。我们将在这里使用 Svelte 来帮助我们开始使用 Astro 和 Redis。那就是用无服务器数据库构建一个基本的笔记应用程序。尽管我们使用 Svelte,但您应该能够在没有事先 Svelte 知识的情况下进行操作。也就是说,如果您愿意,您可以稍后将组件换成React、Vue 或其他支持的框架。
我们将严重依赖浏览器 API,事实上,仅在单个组件中提供 JavaScript。我们将在 Cloudflare 上托管应用程序并使用 Workers,但稍后会介绍。
要开始使用 Astro 和 Redis,请在本地克隆起始代码模板:
pnpm create astro -- --template rodneylab/upstash-astro
进入全屏模式 退出全屏模式
出现提示时,接受依赖项的安装并初始化 git 存储库。选择推荐的 TypeScript 选项。将目录更改为新项目文件夹。默认情况下没有框架支持,我们添加了一个集成以便能够使用 Svelte:
pnpm astro add svelte
进入全屏模式 退出全屏模式
如果您从提示中选择默认选项,Svelte 设置是自动的。接下来,要启动开发服务器,请运行pnpm dev
命令。 CLI 将为您提供 URL(类似于http://localhost:3000)。在浏览器中打开 URL。
天文路由
我们来看看src/pages/index.astro
。这是http://localhost:3000/
主页的 Astro 标记。 Astro 使用基于文件的路由。这意味着我们在src/pages
目录中创建的 Astro 文件将输出到具有匹配路径的 HTML 页面。我们还将在src/pages/api.ts
的 TypeScript 中编写一个 API 端点。 API 文件也遵循基于文件的路由模式,因此我们将在http://localhost:3000/api
访问该端点。
您可能会注意到我们在浏览器中有一个 favicon,但在index.astro
标记中没有link
标记。那是因为我们使用的是src/layouts/Layout.astro
的布局文件。在index.astro
中,我们将页面主要内容包装在Layout
组件中。这种模式为我们节省了在大型项目中重复的布局代码。
在src/components
文件夹中,我们有 Svelte 组件。我们将能够将这些导入到index.astro
中,就像我们对 Layout 组件所做的那样。
开始使用 Astro 和 Redis:Astro 文件
Astro 文件有两个部分。第一部分,script,是我们可以添加任何 JavaScript 或 TypeScript 逻辑的地方。我们将此部分包装在“---
”分隔符中。第二部分是模板,看起来很像 HTML。如果您已经了解 JSX,您会很熟悉。
一些现代 Astro 功能包括:
-
开箱即用的 TypeScript 支持,
-
部分补水和
-
顶级等待(在
.astro
文件中)。
初始注释
让我们在index.astro
的脚本部分定义一些注释,然后将它们传递给模板部分的 Svelte 组件。
更新src/pages/index.astro
中的代码(您可以将style
标记留在底部,因为它在整个教程中都是如此):
---
import HeadingBar from '~components/HeadingBar.svelte';
import NoteBody from '~components/NoteBody.svelte';
import NotesList from '~components/NotesList.svelte';
import Layout from '~layouts/Layout.astro';
import type { Note } from '~types/note';
const editMode = false;
const notes: Note[] = [
{
id: '1',
title: 'Very first note',
text: 'First note’s text',
modified: new Date().toISOString(),
},
{
id: '2',
title: 'Another note',
text: 'This note’s text',
modified: new Date().toISOString(),
},
];
const selectedNote = notes[0];
const title = 'Upstash Astro Notes';
---
<Layout title={title}>
<main class="wrapper">
<h1>{title}</h1>
<div class="container">
<header class="heading">
{selectedNote ? <HeadingBar note={selectedNote} {editMode} /> : null}
</header>
<aside class="list">
<NotesList client:load {notes} selectedId={selectedNote?.id} {editMode} />
</aside>
<section class="note">
<NoteBody note={selectedNote} />
</section>
</div>
</main>
</Layout>
进入全屏模式 退出全屏模式
您应该会看到显示的第一个注释。目前还不能做很多其他事情,我们稍后会设置数据库。现在,这是您完成的第一个 Astro 代码!我们看到了一些 JSX 的影响:
{selectedNote ? <HeadingBar note={selectedNote} {editMode} /> : null}
进入全屏模式 退出全屏模式
这里我们使用 JavaScript 三元运算符来检查是否存在selectedNote
,如果没有则不渲染。注意我们如何使用HeadingBar
组件,传入 props。这包括一个现代快捷方式,让我们可以使用速记{editMode}
,我们可以在其中写出editMode={editMode}
。
我们之前提到,Astro 默认发布零 JavaScript。事实上,我们刚刚添加的HeadingBar
组件遵循这个默认值。当我们确实希望 JavaScript 在组件中运行时,我们可以通过在其属性中包含client:load
指令来启用它(就像在NotesList
组件上一样)。检查Astro 文档以获取其他指令,例如水合物一旦可见。接下来我们将启动我们的 Redis 数据库,这样我们就可以摆脱静态的手动注释。
Astro 和 Redis 入门:无服务器 Redis
我们将使用 Upstash 来提供我们的 Redis 数据库。创建一个 Upstash 帐户如果您还没有,则只需登录即可。从控制台创建一个新数据库。我们需要来自控制台的 Astro 项目的两个环境变量。向下滚动到数据库_详细信息_页面的_REST API_部分。我们需要:
-
UPSTASH_REDIS_REST_URL
-
UPSTASH_REDIS_REST_TOKEN
将项目根目录中的.env.EXAMPLE
文件重命名为.env
并在其中添加这些凭据。.env
包含在项目.gitignore
中,以避免意外提交这些值。
现在我们已经设置好了,我们可以创建我们的 API 路由,将我们的应用程序链接到无服务器 Redis 数据库。
API 路由
我们将尽量减少 JavaSript 的使用并严重依赖 HTML。为了实现这一点,在主要部分,我们将使用HTML 表单元素action
和method
属性来启动 create、update 和 delete 过程。要阅读可用笔记列表,我们使用 HTTP GET 请求。这些过程都从index.astro
或 Svelte 组件文件代码开始。
src/pages/api.ts
文件将处理所有这些,最终来自 Cloudflare Worker。从那里我们联系到 Upstash 无服务器数据库以完成 CRUD(创建、读取、更新和删除)操作。让我们仔细看看文件。所以,这个文件处理发送到http://localhost:3000/api
的 HTTP 请求。我们正在使用@upstash/redis
包来连接我们的远程数据库。我们导入它,然后在顶部配置 Redis 实例:
import { Redis } from "@upstash/redis";
/* TRUNCATED */
const HASHSET_KEY = "notes";
const url = import.meta.env.UPSTASH_REDIS_REST_URL;
const token = import.meta.env.UPSTASH_REDIS_REST_TOKEN;
const redis = new Redis({ url, token });
进入全屏模式 退出全屏模式
要访问 Astro 中的秘密环境变量,我们使用import.meta.env
。
HTTP 请求
如您所料,get
和put
方法(进一步向下)中的代码响应端点接收的 HTTP GET 和 PUT 请求。这些函数可以访问输入的 HTTP 请求并返回一个响应。
我们设置了我们的笔记创建过程,以便新笔记的标题自动设置为Untitled
和一个空的正文。然后用户可以从浏览器进行更新。我们将使用查询参数将浏览器重定向到注释编辑表单。那是我们创建了骨架注释之后。这是src/components/NotesList.svelte
中的代码,它从浏览器启动注释 create 工作流:
<form action="/api" method="post">
<input type="hidden" name="action" value="create" />
<button>[ new ]</button>
</form>
进入全屏模式 退出全屏模式
该表单包含上面端点代码中使用的隐藏action
字段。这里的action是create
,后面我们会用到update
和delete
(其他形式)。本质上,我们的put
函数使用标准 API 获取操作和请求 URL。如果动作是create
,我们在 Hashset 数据结构 中做一个新的注释。这是一个Redis 结构,非常适合我们的用例。我们使用当前时间戳(作为自 ECMAScript 纪元以来的毫秒数)作为元素id
。这适用于我们的简单应用程序。redis.hset
调用负责为我们将新便笺放入数据库:
const date = new Date();
const id = date.getTime();
/* TRUNCATED */
await redis.hset(HASHSET_KEY, {
[id]: JSON.stringify(note),
});
urlParams.append("edit", "true");
urlParams.append("note", id.toString(10));
/* TRUNCATED */
return Response.redirect(`${redirectURL}?${urlParams.toString()}`);
进入全屏模式 退出全屏模式
最后,请注意我们如何在重定向 URL 的末尾添加edit
和note
查询参数。我们将更新前端代码以使用这些。
完整API
目前我们只能创建一个骨架注释,甚至不能编辑它,所以这里是 API 的完整代码。使用完整功能更新src/pages/api.ts
:
import { Redis } from "@upstash/redis/cloudflare";
import type { APIRoute } from "astro";
import type { Note } from "../types/note";
import { getDomainUrl } from "../utilities/utilities";
const HASHSET_KEY = "notes";
const url = import.meta.env.UPSTASH_REDIS_REST_URL;
const token = import.meta.env.UPSTASH_REDIS_REST_TOKEN;
const redis = new Redis({ url, token });
export const get: APIRoute = async function get() {
try {
const notes: Record<string, Note> | null = await redis.hgetall(HASHSET_KEY);
/* notes (when not null) has structure:
{
'1660841122914': { // this is the note id
title: 'First One',
text: 'First text',
modified: '2022-08-18T16:45:22.914Z'
},
'1660843285978': {
title: 'Second one',
text: 'Hi',
modified: '2022-08-18T17:21:25.978Z'
}
}
*/
if (notes) {
const sortedNotes: Note[] = Object.entries(notes)
.map(([id, { title, text, modified }]) => ({
id,
title,
text,
modified,
}))
.sort((a, b) => Date.parse(b.modified) - Date.parse(a.modified));
return new Response(JSON.stringify({ notes: sortedNotes }), {
headers: { "content-type": "application/json" },
status: 200,
});
}
return new Response(JSON.stringify({ notes: [] }), {
headers: { "content-type": "application/json" },
status: 200,
});
} catch (error: unknown) {
console.error(`Error in /api GET method: ${error as string}`);
return new Response(JSON.stringify({ notes: [] }), {
headers: { "content-type": "application/json" },
status: 200,
});
}
};
export const post: APIRoute = async function post({ request }) {
try {
const form = await request.formData();
const action = form.get("action");
const redirectURL: string = getDomainUrl(request);
const urlParams = new URLSearchParams();
switch (action) {
case "create": {
const date = new Date();
const id = date.getTime();
const modified = date.toISOString();
const note = {
title: "Untitled",
text: "",
modified,
};
await redis.hset(HASHSET_KEY, {
[id]: JSON.stringify(note),
});
urlParams.append("edit", "true");
urlParams.append("note", id.toString(10));
break;
}
case "update": {
const id = form.get("id") as string;
const title = form.get("title");
const text = form.get("text");
const modified = new Date().toISOString();
await redis.hset(HASHSET_KEY, {
[id]: JSON.stringify({ title, text, modified }),
});
urlParams.append("note", id);
break;
}
case "delete": {
const id = form.get("id");
if (typeof id === "string") {
await redis.hdel(HASHSET_KEY, id);
}
break;
}
default:
}
return Response.redirect(`${redirectURL}?${urlParams.toString()}`);
} catch (error: unknown) {
console.error(`Error in /api PUT method: ${error as string}`);
return Response.redirect(getDomainUrl(request));
}
};
进入全屏模式 退出全屏模式
所以我们使用的主要 Upstash Redis 命令是:
-
创建:
await redis.hset(HASHSET_KEY, { [id]: JSON.stringify({ title, text, modified}) });
, -
阅读:
await redis.hgetall(HASHSET_KEY);
, -
更新:
await redis.hset(HASHSET_KEY, { [id]: JSON.stringify({ title, text, modified}) });
, -
删除:
await redis.hdel(HASHSET_KEY, id);
。
访问 HTTP 头
默认情况下,Astro 是一个静态站点生成器 (SSG),我们希望访问服务器端渲染 (SSR),因此我们始终显示数据库中的最新注释。稍后我们将添加一个 Cloudflare 适配器,这样做会自动将我们从 SSG 切换到 SSR。同时,我们希望访问请求标头,这是我们在下一节中使用的getDomainUrl
函数。要切换到 SSR,请更新astro.config.js
文件以包含output: 'server'
,然后重新启动您的开发服务器:
import { defineConfig } from "astro/config";
import svelte from "@astrojs/svelte";
// https://astro.build/config
export default defineConfig({
output: "server",
integrations: [svelte()],
});
进入全屏模式 退出全屏模式
前端:创建和更新
好的,我们现在正在取得一些进展。接下来我们要更新index.astro
文件以读取 URL 中的查询参数,然后将用户引导到正确的视图。一旦我们把它连接起来,我们将创建和编辑一些新的笔记。
Astro 的Astro.url
和Astro.request
API 让我们可以访问搜索参数,还可以帮助我们确保将 GET 和 PUT 请求发送到正确的 URL。更新index.astro
的脚本部分:
---
import EditNote from '~components/EditNote.svelte';
import HeadingBar from '~components/HeadingBar.svelte';
import NoteBody from '~components/NoteBody.svelte';
import NotesList from '~components/NotesList.svelte';
import Layout from '~layouts/Layout.astro';
import type { Note } from '~types/note';
import { getDomainUrl } from '~utilities/utilities';
const { request, url } = Astro;
const domainUrl = getDomainUrl(request);
const { searchParams } = url;
const selectedId = searchParams.get('note');
const editMode = searchParams.get('edit') === 'true';
const response = await fetch(`${domainUrl}/api`, { method: 'GET' });
const { notes }: { notes: Note[] } = await response.json();
const selectedNote = notes.find(({ id }) => id === selectedId) ?? notes[0];
const title = 'Upstash Astro Notes';
---
进入全屏模式 退出全屏模式
searchParams
是来自浏览器 API](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)的[URLSearchParams 对象。您可以看到我们还使用 fetch Browser API 将 get 请求发送到我们的端点并拉入笔记列表(当前为空)。 Astro 大量使用浏览器标准。
getDomainUrl
是一个实用函数。我们在src/utilities/utilities
中定义。这将暂时返回http://localhost:3000
,类似于http://localhost:8788
,当我们稍后使用 Cloudflare Wrangler 工具在本地运行站点时,当我们将站点部署到 Web 时,最后返回https://example.com
。
作为最后一步,更新index.astro
中的模板代码:
<Layout title={title}>
<main class="wrapper">
<h1>{title}</h1>
<div class="container">
<header class="heading">
{selectedNote ? <HeadingBar client:load note={selectedNote} {editMode} /> : null}
</header>
<aside class="list">
<NotesList client:load {notes} {selectedId} {editMode} />
</aside>
<section class="note">
{editMode && selectedNote ? <EditNote client:load note={selectedNote} /> : null}
{!editMode && selectedNote ? <NoteBody note={selectedNote} /> : null}
</section>
</div>
</main>
</Layout>
进入全屏模式 退出全屏模式
在这里,我们使用从 URL 中提取的editMode
参数。
开始使用 Astro 和 Redis:测试它
按[ new ]
按钮创建一个新笔记。检查浏览器地址栏中的 URL。我有http://localhost:3000/?edit=true¬e=1661428055833
。按下按钮,使用create
操作调用 APIput
函数。这在 Upstash Redis 数据库中创建了一个新注释,然后将浏览器重定向到该 URL。index.astro
中的逻辑提取了edit=true
查询参数,因此显示了EditNote
组件,而不是NoteBody
。我们主要依靠标准 API 完成所有这些工作,这难道不是很不可思议吗?
点击Save Changes
。我们在这里使用的表单与创建表单的工作方式类似,不过这次表单代码在src/components/EditNote.svelte
中。
保存第一个便笺后尝试创建另一个便笺。这次点击Cancel
,而不是保存。这里我们使用了一段 JavaScript,这就是为什么我们在index.astro
中的EditNote
中添加了client:load
指令。对于取消按钮,我们使用preventDefault
并运行这个 handleCancel 函数:
function handleCancel() {
const searchParams = new URLSearchParams({ note: id });
window.location.replace(`/?${searchParams}`);
}
进入全屏模式 退出全屏模式
这会将用户推回到主页,而不是提交对 Redis 的更改。这表明我们有一个 JavaScript “逃生舱”,可以在我们确实需要一些交互性的时候使用。选择您刚刚创建的未编辑的笔记,然后点击[ delete ]
按钮。其形式在HeadingBar
组件中。
构建和部署
Astro 为主要的云托管提供商构建了适配器。添加 Cloudflare Workers 适配器:
pnpm astro add cloudflare
进入全屏模式 退出全屏模式
接受 defaults 以自动配置它。现在我们可以构建站点:
pnpm run build
进入全屏模式 退出全屏模式
如果这是您第一次在您的机器上使用 Wrangler,您需要通过运行pnpm wrangler login
命令将本地实例链接到您的 Cloudflare 帐户。查看牧马人入门指南以获取完整的详细信息。wrangler
CLI 工具包含在package.json
项目中。
为了预览站点,由于 Cloudflare 工作人员与其他环境略有不同,我们更改了默认命令。我把这个作为preview:cloudlfare
脚本保存在package.json
中,所以运行pnpm preview:cloudflare
。要查看该站点,请在浏览器中访问http://127.0.0.1:8788
。
查看项目的dist
文件夹。这是 Astro 输出您的生产站点的地方。里面会有一个_worker.js
。这是 Astro 自动为您生成的 Cloudflare Worker。我们在 repo 中包含的wrangler.toml
文件中添加了路径。
部署到 Cloudflare
在 Cloudflare Worker 运行时支持读取秘密环境变量有一个小的变化。当 Astro 团队为开箱即用的支持找到永久解决方案时,这是一种解决方法。更新astro.config.js
:
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import svelte from "@astrojs/svelte";
import { defineConfig } from "astro/config";
import cloudflare from "@astrojs/cloudflare";
// https://astro.build/config
export default defineConfig({
output: "server",
integrations: [svelte()],
adapter: cloudflare(),
vite: {
define: {
"process.env.UPSTASH_REDIS_REST_URL": JSON.stringify(
process.env.UPSTASH_REDIS_REST_URL,
),
"process.env.UPSTASH_REDIS_REST_TOKEN": JSON.stringify(
process.env.UPSTASH_REDIS_REST_TOKEN,
),
},
},
});
进入全屏模式 退出全屏模式
这些行使我们的秘密环境变量在 Cloudflare 服务器将要查找它们的位置准确可用。最后,要设置远程构建环境使用 Node 16,在项目根文件夹中创建一个.nvmrc
文件,并将内容设置为16
。
我们现在准备部署。为了让网站上线,提交并将 repo 推送到 git 服务。接下来登录到您的 Cloudflare 帐户并选择 Pages 并 Create a Project / Connect to Git。链接到您的 git 服务。选择 Astro 作为框架预设。最后,记得在点击Save and Deploy之前添加两个环境变量。
Astro 和 Redis 入门:总结
这就是我现在想向您展示的全部内容。本教程仅演示了我们可以使用 Upstash 和 Astro 做的一小部分,但我希望它足以让您开始使用 Astro 和 Redis。按照上面的链接来探索我们提到的功能的详细信息。查看Upstash 博客以获取更多教程并且您可以从 Rodney Lab了解更多 Astro。告诉我您是如何找到本教程的以及您的下一个项目将是什么!
更多推荐
所有评论(0)