В первой половине этой статьи мы настроили стек веб-разработки и создали простой пример приложения с использованием Bun, HTMX, Elysia и MongoDB. Здесь мы продолжим изучение нашего нового стека, очищая и абстрагируя уровень доступа к данным примера приложения и добавляя более сложные взаимодействия HTMX. Мы также добавим еще один компонент в набор технологий: Pug, популярный механизм шаблонов JavaScript, который хорошо работает с HTMX и помогает в настройке взаимодействия с DOM.
Пример приложения
Наш пример приложения на данный момент состоит из формы и таблицы. Форма позволяет пользователям вводить цитаты вместе с их авторами, которые затем можно искать и отображать с помощью пользовательского интерфейса приложения. Я добавил в интерфейс немного CSS, чтобы он выглядел более современно, чем то, на чем мы остановились в первой части:
Вот внешний код обновленного интерфейса:
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<form hx-post="/add-quote" hx-swap-oop="beforeend:#data-list" hx-trigger="every time">
<input type="text" name="quote" placeholder="Enter quote">
<input type="text" name="author" placeholder="Enter author">
<button type="submit">Add Quote</button>
</form>
<ul id="data-list"></ul>
<button hx-get="/quotes" hx-target="#data-list">Load Data</button>
Мы используем HTMX для управления процессом отправки формы и загрузки данных в таблицу. Я также очистил серверную часть приложения, поэтому подключение к базе данных теперь является общим. Вот эта часть src/index.ts
:
import { Elysia } from "elysia";
import { staticPlugin } from '@elysiajs/static';
const { MongoClient } = require('mongodb');
// Database connection details
const url = "mongodb://127.0.0.1:27017/quote?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+1.8.0";
const dbName = "quote";
const collectionName = "quotes";
let client = new MongoClient(url, { useUnifiedTopology: true });
// Connect to the database (called only once)
async function connectToDatabase() {
try {
await client.connect();
} catch (error) {
console.error(error);
throw error; // Re-throw the error to indicate connection failure
}
return { client, collection: client.db(dbName).collection(collectionName) };
}
// Close the database connection
async function closeDatabaseConnection(client) {
await client.close();
}
Здесь мы определяем URL-адрес базы данных как адрес локального хоста MongoDB по умолчанию, а также имя базы данных и коллекции. Затем мы используем async
функция, connectToDatabase()
, чтобы подключить клиента и вернуть его подключенным к коллекции. Наш код может затем вызывать этот метод всякий раз, когда ему потребуется доступ к базе данных, и когда это будет сделано, он может вызвать client.close()
.
Использование подключения к базе данных
Давайте посмотрим, как конечные точки нашего сервера будут использовать эту поддержку базы данных. Для краткости я просто показываю /quotes
конечная точка, которая управляет таблицей:
// Close the database connection
async function closeDatabaseConnection(client) {
await client.close();
}
async function getAllQuotes(collection) {
try {
const quotes = await collection.find().toArray();
// Build the HTML table structure
let html="<table border="1">";
html += '<tr><th>Quote</th><th>Author</th></tr>';
for (const quote of quotes) {
html += `<tr><td>${quote.quote}</td><td>${quote.author}</td></tr>`;
}
html += '</table>';
return html;
} catch (error) {
console.error("Error fetching quotes", error);
throw error; // Re-throw the error for proper handling
}
}
// Main application logic
const app = new Elysia()
.get("https://www.infoworld.com/", () => "Hello Elysia")
.get("/quotes", async () => {
try {
const { client, collection } = await connectToDatabase();
const quotes = await getAllQuotes(collection);
await closeDatabaseConnection(client);
return quotes;
} catch (error) {
console.error(error);
return "Error fetching quotes";
}
})
.use(staticPlugin())
.listen(3000);
console.log(
` Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);
Это дает нам серверную часть /quotes
Конечная точка GET, к которой мы можем обратиться, чтобы получить данные котировок. Конечная точка вызывает getAllQuotes()
метод, который использует коллекцию из connectToDatabase()
чтобы получить массив цитат и авторов. Затем он генерирует HTMX для строк.
Наконец, мы отправляем ответ, содержащий строки в формате HTMX, и строки вставляются в таблицу.
Добавьте шаблонизатор Pug
Создание строки HTMX вручную может вызвать разочарование и ошибки. Механизм шаблонов позволяет нам определить структуру HTMX в вечном файле с чистым синтаксисом.
Самый популярный движок шаблонов HTML для JavaScript — Pug. Его использование сделает создание представлений на сервере намного проще и масштабируемее, чем встраивание в код JavaScript. Основная идея состоит в том, чтобы взять наши объекты данных и передать их в шаблон, который применяет данные и выводит HTML. Разница здесь в том, что мы генерируем HTMX, а не HTML. Мы можем это сделать, потому что HTMX — это, по сути, HTML с расширениями.
Для начала добавьте в проект библиотеку Pug с помощью: $ bun add pug
.
Когда это завершится, создайте новый каталог в корне проекта с именем /views: ($ mkdir views)
затем добавьте новый файл с именем quotes.pug
:
doctype html
h1 Quotes
table
thead
tr
th Quote
th Author
th Actions
tbody
each quote in quotes
tr(id=`quote-${quote._id}`)
td #{quote.quote}
td #{quote.author}
td
button(hx-delete=`/quotes/${quote._id}` hx-trigger="click" hx-swap="closest tr" hx-confirm="Are you sure?") Delete
#{quote._id}
Pug использует отступы для обработки вложенных элементов. Атрибуты заключаются в круглые скобки. Обычный текст, например слово Удалить предоставляется как есть. Все это дает нам компактный способ описания HTML и/или HTMX. Посетите домашнюю страницу Pug, чтобы узнать больше о его синтаксисе.
Обратите внимание, что внутри строки нам нужно использовать ${}
. #{}
Синтаксис позволяет ссылаться на любые объекты данных, которые были внедрены в шаблон. Это похоже на интерполяцию токенов в такой среде, как React. Основная идея состоит в том, чтобы определить общую структуру HTML/HTMX, а затем предоставить шаблону переменные, на которые ссылается #{}
и ${}
.
Мы предоставляем переменные обратно на сервер /quotes
конечная точка, которая использует getAllQuotes()
:
import pug from 'pug';
//...
async function getAllQuotes(collection) {
try {
const quotes = await collection.find().toArray();
// Render the Pug template with the fetched quotes
const html = pug.compileFile('views/quotes.pug')({ quotes });
return html;
} catch (error) {
console.error("Error fetching quotes", error);
throw error; // Re-throw the error for proper handling
}
}
Итак, мы получаем цитаты из базы данных, затем компилируем шаблон Pug и передаем цитаты. Затем Pug выполняет работу по объединению HTML и данных. Общий поток составляет:
- Запрос поступает на
GET /quotes
. - Котировки извлекаются из MongoDB.
- Шаблон Pug получает котировки.
- Шаблон Pug отображает кавычки в формате HTML и/или HTMX.
- Заполненный HTML и/или HTMX отправляется в качестве ответа.
Результирующий экран выглядит примерно так:
Взаимодействия с DOM: удаление строки
Теперь нам нужно заставить нашу кнопку «Удалить» работать. Просто выпустив delete
Запросить и обработать его на сервере и в базе данных легко, как мы уже видели, но как насчет обновления таблицы, чтобы отразить изменения?
Есть несколько способов подойти к обновлению. Мы могли бы просто обновить всю таблицу или использовать JavaScript или HTMX для удаления строки из таблицы. В идеале мы хотели бы использовать последний вариант и сохранить все в формате HTMX.
В нашем views/quotes.pug
шаблон, мы можем использовать чистый HTMX для удаления строки:
tbody(hx-target="closest tr" hx-swap="outerHTML")
each quote in quotes
tr(id=`quote-${quote._id}`)
td #{quote.quote}
td #{quote.author}
td
button(hx-delete=`/quotes/${quote._id}` hx-trigger="click" hx-confirm="Are you sure?") Delete
Существенными частями здесь являются hx-target=”closest tr”
и hx-swap=”outerHTML”
на tbody
. ( hx-confirm
позволяет вам предоставить confirm
диалог.) hx-target
говорит заменить ближайший tr
к триггерному элементу (кнопке) с ответом. outHTML
в hx-swap
гарантирует удаление всего элемента строки таблицы, а не только его содержимого. На стороне сервера мы возвращаем успешный ответ (HTTP 200) с пустым телом, поэтому HTMX просто удалит строку:
async function deleteQuote(collection, quoteId) {
try {
const result = await collection.deleteOne({ _id: new ObjectId(quoteId) });
if (result.deletedCount === 1) {
return "";
} else {
throw new Error( "Quote not found");
}
} catch (error) {
console.error("Error deleting quote", error);
throw error; // Re-throw the error for proper handling
}
}
Здесь мы только начинаем углубляться в более сложные взаимодействия с DOM. HTMX также может добавлять простые эффекты перехода к обменам в сценарии удаления строк, подобном нашему. Вы можете увидеть пример на домашней странице HTMX.
Заключение
Хотя это руководство, состоящее из двух частей, включает в себя новые технологии, такие как Bun и Elysia, наиболее заметным компонентом является HTMX. Это действительно меняет способ работы приложения по сравнению с обычными API-интерфейсами JSON.
В сочетании с механизмом шаблонов, таким как Pug, и базой данных, такой как MongoDB, работа по созданию пользовательских интерфейсов и обработке запросов становится гладкой. По мере увеличения размера приложения такие функции Pug, как наследование шаблонов, также могут пригодиться.
Для взаимодействия с DOM HTMX предлагает гибкую функциональность «из коробки» через hx-swap
и hx-target
. Для более сложных случаев использования вы всегда можете использовать JavaScript.
В целом весь этот стек хорошо работает вместе. Вы также можете оценить скорость Bun всякий раз, когда вам нужно зайти в командную строку, чтобы сделать что-то вроде добавления зависимости.
Вы можете найти код этого руководства в моем репозитории GitHub.
Дальше читайте это:
- Почему компании уходят из облака
- 5 простых способов запустить LLM локально
- Программирование с помощью ИИ: советы и лучшие практики от разработчиков
- Знакомьтесь, Zig: современная альтернатива C
- Что такое генеративный ИИ? Искусственный интеллект, который создает
- Лучшее программное обеспечение с открытым исходным кодом 2023 года