Начните работу с Astro и Redis


Начните работу с 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 происходит автоматически, если вы выбираете опции по умолчанию из подсказок. Далее, чтобы запустить сервер dev, выполните команду pnpm dev. CLI выдаст вам URL (что-то вроде http://localhost:3000). Откройте URL в браузере.

Астромаршрутизация

Давайте посмотрим на src/pages/index.astro. Это разметка Astro для главной страницы по адресу http://localhost:3000/. Astro использует маршрутизацию на основе файлов. Это означает, что файлы Astro, которые мы создаем в каталоге src/pages, выводятся на HTML-страницы с соответствующими путями. Мы также напишем конечную точку API на TypeScript в src/pages/api.ts. Файлы API также следуют шаблону маршрутизации на основе файлов, поэтому мы будем обращаться к конечной точке по адресу http://localhost:3000/api.

Вы можете заметить, что у нас есть favicon в браузере, но нет тега link в разметке index.astro. Это потому, что мы используем файл разметки по адресу src/layouts/Layout.astro. В index.astro мы обернули основное содержимое нашей страницы в компонент Layout. Этот шаблон позволяет нам избежать повторения кода макета в больших проектах.

В папке src/components находятся наши компоненты Svelte. Мы сможем импортировать их в index.astro так же, как мы это делали с компонентом Layout.

Приступайте к работе с Astro и Redis: Файлы Astro

Файлы Astro состоят из двух частей. В первой, скриптовой, части мы можем добавить любую логику 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, передавая ему реквизит. Это включает современное сокращение, позволяющее нам использовать сокращение {editMode} там, где мы могли бы написать editMode={editMode}.

Ранее мы упоминали, что Astro по умолчанию поставляется с нулевым JavaScript. На самом деле компонент HeadingBar, который мы только что добавили, следует этому умолчанию. Когда мы хотим, чтобы JavaScript выполнялся в компоненте, мы можем включить его, включив директиву client:load в его атрибуты (как в компоненте NotesList). Проверьте документацию Astro на наличие других директив, таких как hydrate once visible. Далее мы запустим нашу базу данных Redis, чтобы избавиться от статичных комментариев, созданных вручную.

Начните работу с Astro и Redis: бессерверный Redis

Мы будем использовать Upstash для предоставления базы данных Redis. Создайте учетную запись Upstash, если у вас ее еще нет, в противном случае просто войдите в систему. В консоли создайте новую базу данных. Из консоли нам понадобятся две переменные окружения для вашего проекта Astro. Прокрутите вниз до раздела REST API на странице Details базы данных. Нам нужны:

  • UPSTASH_REDIS_REST_URL
  • UPSTASH_REDIS_REST_TOKEN

Переименуйте файл .env.EXAMPLE в корневом каталоге проекта в .env и добавьте туда эти учетные данные. .env включен в проект .gitignore, чтобы избежать случайной фиксации этих значений.

Теперь, когда все готово, мы можем создать наш API-маршрут, связывающий наше приложение с бессерверной базой данных Redis.

API-маршрут

Мы будем минимизировать использование JavaSript и в значительной степени опираться на HTML. Для этого в основной части мы будем использовать атрибуты HTML-элемента формы action и method для инициирования процессов создания, обновления и удаления. Чтобы прочитать список доступных заметок, мы используем HTTP GET запрос. Все эти процессы начинаются в файле index.astro или файловом коде компонента Svelte.

Файл src/pages/api.ts будет обрабатывать их все, в конечном счете, с Cloudflare Worker. Оттуда мы обращаемся к бессерверной базе данных Upstash для завершения операции CRUD (создание, чтение, обновление и удаление). Давайте рассмотрим файл подробнее. Итак, этот файл обрабатывает HTTP-запросы, отправленные на http://localhost:3000/api. Мы используем пакет @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, который запускает рабочий процесс создания заметки из браузера:

<form action="/api" method="post">
  <input type="hidden" name="action" value="create" />
  <button>[ new ]</button>
</form>
Войти в полноэкранный режим Выход из полноэкранного режима

Форма содержит скрытое поле action, используемое в коде конечной точки выше. Здесь действие create, а update и delete мы будем использовать позже (в других формах). По сути, наша функция put получает действие и URL запроса, используя стандартные API. Если действие create, мы создаем новую заметку в структуре данных Hashset. Это структура Redis, которая хорошо подходит для нашего случая использования. В качестве элемента id мы используем текущую метку времени (как количество миллисекунд с момента эпохи ECMAScript). Это отлично работает для нашего простого приложения. Вызов 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()}`);
Вход в полноэкранный режим Выход из полноэкранного режима

Наконец, обратите внимание, как мы добавляем параметры запроса edit и note в конец URL перенаправления. Мы обновим код фронт-энда, чтобы использовать эти параметры.

Полный 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, которые мы используем, являются:

  • create: 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', затем перезапустите ваш dev-сервер:

import { defineConfig } from "astro/config";

import svelte from "@astrojs/svelte";

// https://astro.build/config
export default defineConfig({
    output: "server",
    integrations: [svelte()],
});
Войти в полноэкранный режим Выйти из полноэкранного режима

Фронтенд: Создание и обновление

Хорошо, теперь у нас есть некоторый прогресс. Далее мы хотим обновить файл index.astro для чтения параметров запроса в URL и направления пользователя к нужному представлению. Как только мы это подключим, мы создадим и отредактируем несколько новых заметок.

API Astro.url и Astro.request позволяют нам получить доступ к параметрам поиска, а также помогают нам убедиться, что мы отправляем наши 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 — это объект URLSearchParams из Browser API. Вы можете видеть, что мы также используем fetch Browser API для отправки запроса get на нашу конечную точку и получения списка заметок (в настоящее время пустого). Astro активно использует стандарты браузера.

getDomainUrl — это служебная функция. Мы определяем ее в src/utilities/utilities. Она будет возвращать http://localhost:3000 на данный момент, что-то вроде http://localhost:8788, когда мы позже запустим сайт локально с помощью инструмента Cloudflare Wrangler и, наконец, 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>
Вход в полноэкранный режим Выход из полноэкранного режима

Здесь мы используем параметр editMode, который мы извлекаем из URL.

Начните работу с Astro и Redis: Тестирование

Нажмите кнопку [ new ], чтобы создать новую заметку. Проверьте URL-адрес в адресной строке браузера. У меня http://localhost:3000/?edit=true&note=1661428055833. Нажатие на кнопку вызвало функцию API put с действием create. Это создавало новую заметку в базе данных Upstash Redis, а затем перенаправляло браузер на этот URL. Логика в index.astro уловила параметр запроса edit=true и отобразила компонент EditNote вместо NoteBody. Разве не невероятно, что мы сделали все это, опираясь в основном на стандартные API?

Нажмите Save Changes. Форма, которую мы здесь используем, работает аналогично форме создания, хотя на этот раз код формы находится в src/components/EditNote.svelte.

Попробуйте создать еще одну заметку после того, как первая будет сохранена. На этот раз вместо сохранения нажмите Cancel. Здесь мы используем немного JavaScript, поэтому мы добавили директиву client:load к EditNote в index.astro. Для кнопки отмены мы используем 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
Войдите в полноэкранный режим Выйдите из полноэкранного режима

Примите значения по умолчанию, чтобы настроить его автоматически. Теперь мы можем создать сайт:

pnpm run build
Войти в полноэкранный режим Выйти из полноэкранного режима

Если вы впервые используете Wrangler на своей машине, вам необходимо связать локальный экземпляр с учетной записью Cloudflare, выполнив команду pnpm wrangler login. Ознакомьтесь с руководством по началу работы с Wrangler для получения подробной информации. CLI-инструмент wrangler включен в проект package.json.

Для предварительного просмотра сайта, поскольку рабочие Cloudflare немного отличаются от других сред, мы изменим команду по умолчанию. Я сохранил ее как сценарий preview:cloudlfare в package.json, поэтому запустите pnpm preview:cloudflare. Чтобы увидеть сайт, перейдите по адресу http://127.0.0.1:8788 в вашем браузере.

Загляните в папку dist вашего проекта. Это место, куда Astro выводит ваш производственный сайт. Там будет _worker.js. Это Cloudflare Worker, который Astro генерирует автоматически для вас. Мы добавили путь в файл 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.

Теперь мы готовы к развертыванию. Чтобы запустить сайт, зафиксируйте и разместите репозиторий на git-сервисе. Затем войдите в свой аккаунт Cloudflare, выберите Pages и Create a Project / Connect to Git. Подключитесь к своему git-сервису. Выберите Astro в качестве предустановки фреймворка. Наконец, не забудьте добавить две переменные окружения перед тем, как нажать Save and Deploy.

Приступайте к работе с Astro и Redis: Подведение итогов

Это все, что я хотел показать вам на данный момент. Этот учебник демонстрирует лишь малую часть того, что мы можем сделать с помощью Upstash и Astro, но я надеюсь, что этого достаточно, чтобы вы начали работать с Astro и Redis. Перейдите по ссылкам выше, чтобы изучить детали упомянутых нами функций. Загляните в блог Upstash, чтобы найти еще больше уроков, а в Rodney Lab вы можете узнать больше об Astro. Дайте мне знать, как вы нашли этот учебник и каким будет ваш следующий проект!

Оцените статью
devanswers.ru
Добавить комментарий