Создаем на JavaScript приложение записи заметок в реальном времени

Создавая приложение для ведения заметок при помощи Next.js вы бы следовали такой схеме:

  • Построение системы авторизации с помощью NextAuth.
  • Ее привязывание к MongoDB для сохранения пользователей в базе данных.
  • Если вам требуется синхронизация в реальном времени, то написание системы, которая будет реагировать на события в БД соответствующими действиями.

Может показаться, что сделать это легко, но программирование слушателя событий является не столь простой задачей. 

Здесь нам на помощь приходит Supabase. Это общедоступный конкурент Firebase, который предоставляет множество сервисов для улучшения приложения. Если при создании блокнота для заметок вы предпочтете задействовать Supabase, то выполнить нужно будет такие шаги:

  • Использовать сервис Auth, предоставляющий множество провайдеров авторизации в нескольких строках кода. 
  • Применить механизм Realtime, который за вас будет обрабатывать все процессы реального времени. Останется лишь сообщить фронтенду о необходимости отображения любых изменений при возникновении события.

В текущей статье мы создадим в Supabase приложение-блокнот с аутентификацией:

Что мы получим в итоге

В этом приложении также будет использоваться Realtime API, которое позволит Next.js отрисовывать новые заметки без обновления страницы:

Демонстрация работы в реальном времени

Приступим! 

Подготовка

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

Создание проекта Supabase

Для этого переходим на страницу Supabase Project и кликаем “New Project”. Здесь регистрируем учетные данные.

Регистрация учетных данных

После переходим в “Settings” и кликаем “API”:

Панель Api

Находясь в этой панели, скопируйте ваши ключи URL и anon public:

Извлечение ключей

Затем перейдите в проект Next.js и вставьте их в .env.local:

NEXT_PUBLIC_SUPABASE_URL = SupabaseURL
NEXT_PUBLIC_SUPABASE_ANON_KEY = AnonPublicKey

Теперь переходим к настройке экрана Google OAuth.

Google OAuth

Зайдите в Google Cloud Console и создайте новый Oauth Client ID. В разделе “Authorized redirect URIs” введите URL вашего проекта Supabase + /auth/v1/callback.

Страница “Authorized redirect URIs”

В завершении нужно ввести в Supabase ваши учетные данные Google. Для этого вернитесь к панели Supabase и вставьте ключи в раздел “Google Enabled” на странице “Auth Settings”.

Вставка ключей Google

А теперь напишем для нашего проекта код системы авторизации.

Аутентификация Google

Примечание: в этой статье мы реализуем только клиентскую аутентификацию. Если же вы захотите узнать об аутентификации в Supabase более подробнее, то почитайте эту статью (англ.).

Установка модулей и настройка проекта

Потребуются следующие пакеты:

  • @supabase/supabase-js: основная библиотека проекта.
  • @supabase/ui: предоставляет набор компонентов UI, необходимых для приложения.
  • formik: для захвата пользовательского ввода мы будем использовать формы. Библиотека Formik будет отвечать за обработку отправок этих форм.

Для установки перечисленных зависимостей выполните в терминале:

npm i @supabase/supabase-js @supabase/ui formik

В качестве второго шага удалите в файле pages/index.js код между тегами div, чтобы в итоге index.js выглядел так:

export default function Home() {
return <div></div>;
}

Структура файлов проекта

Создайте следующие каталоги в директории проекта Next.js:

  • utils: здесь будут находиться служебные функции, а также конфигурация клиента Supabase.
  • components: здесь будут находиться настраиваемые компоненты.

В итоге у проекта должна получиться такая структура:

Файловая структура проекта

Отлично! В следующем разделе мы настроим клиента Supabase.

Настройка Supabase

В каталоге utils создайте файл supabaseClient.js и пропишите в нем следующий код:

  • Строки 3–4: получаем ключи клиента из .env.local.
  • Строка 5: передаем эти значения в функцию createClient. Так мы инициализируем экземпляр Supabase.
  • Строка 7: экспортируем эту конфигурацию Supabase, чтобы использовать ее в проекте.

Далее мы напишем код, который позволит пользователю авторизовываться в приложении и выходить из него.

Служебные функциии авторизации

В каталоге utils создайте файл authentication.js. В нем наберите:

  • Строка 2: вызываем функцию auth.signIn. Она будет авторизовывать пользователя с помощью его Google-аккаунта. 
  • Строка 7: позволяет выходить из аккаунта.

Вот и все! Теперь настроим компонент аутентификации.

Компонент аутентификации

В каталоге components создайте файл Container.js. В этом файле запишите следующий код:

  • Строка 7: хук Auth.useUser будет извлекать пользовательские данные.
  • Строка 10: если пользователь авторизовался, отображает его имя.
  • Строка 13: при клике вызывает метод signOut.
  • Строка 14: позволяет пользователю проследовать по маршруту /note. Этот маршрут мы обработаем позже.
  • Строка 17: если пользователь не авторизован, отображает дочерние элементы этого компонента.

В качестве второго шага перейдите в pages/_app.js и измените его код так:

  • Строки 6–8: компонент Auth использует React’s Context API для отправки пользовательских данных, а также данных текущей сессии. Это позволит сохранять пользовательские сессии между разными маршрутами.

Отлично! Теперь займемся интерфейсом авторизации.

Создание UI авторизации

В /pages/index.js производим следующий импорт:

import { Button } from "@supabase/ui";
import supabaseClient from "../utils/supabaseClient";
import Container from "../components/Container";
import signIn from "../utils/authentication";

Далее находим блок return:

return <div></div>;

Изменяем его так:

  • Строка 3: передает экземпляр Supabase в Container в качестве пропса.
  • Строка 4: при клике вызывает метод signIn.

Выполняем код и получаем следующий результат:

Результат кода

Отлично! Код работает. Далее займемся построением вокруг него самого приложения.

В итоге /pages/index.js должен выглядеть так:

import { Button } from "@supabase/ui";
import supabaseClient from "../utils/supabaseClient";
import Container from "../components/Container";
import { signIn } from "../utils/authentication";

export default function Home() {
return (
<div>
<Container supabaseClient={supabaseClient}>
<Button onClick={() => signIn(supabaseClient)}>Log in</Button>
</Container>
</div>
);
}

Функциональность внесения заметок

Прежде, чем писать код, нужно настроить базу данных.

Настройка базы данных

Перейдите в проект Supabase и кликните по вкладке “SQL”, чтобы открыть редактор SQL:

Переход в SQL-редактор

Здесь пишем следующий код SQL:

  • Строка 3: в таблице notes у нас будет поле user_id, связывающее ее с auth.users, что позволит создать реляционную таблицу.
  • Строки 4–5: у нее также будут поля heading и body с типом text.
  • Строка 8: активируем row-level security , необходимую для создания политик.
  • Строки 10–17: создаем политики Supabase . Если кратко, то здесь мы сообщаем Supabase, что пользователь имеет доступ к выполнению операций в БД со своими записями.

После этого нужно настроить таблицу на использование Realtime API. Для этого выполняем следующие шаги:

Активация Realtime для таблицы

Это означает, что при каждом выборе пользователем записи Supabase будет уведомлять об этом приложение. Таким образом, приложение сможет показывать изменения без обновления веб-страницы.

Еще один этап завершен! Теперь перейдем к написанию кода вспомогательных функций, которые будут выполнять для базы данных операции CRUD.

Служебные функции

В каталоге utils создайте файл note.jsи введите в нем следующий код:

  • Строки 4–7: получаем пользовательские записи из таблицы notes и упорядочивает их по возрастанию id.
  • Строки 13–16: вставляем заметку в таблицу.
  • Строки 24–27: находим заметку с нужным id и удаляем.

После этого можно заняться интерфейсом, который позволит пользователям создавать заметки.

Компонент AddForm

Перейдите в каталог /components/ и создайте файл AddForm.js. В нем введите следующее:

  • Строки 8–9: значения-плейсхолдеры для текстовых полей body и heading будут пустыми.
  • Строки11–13: при отправке пользователем формы вызываем метод add, который вставляет запись в базу данных.
  • Строки 16–17: создаем поля heading и body.

Закончив с AddForm, пора переходить к созданию GUI, который даст пользователю возможность удалять заметки.

Страница Notes и активация подписок

В каталоге /pages создайте файл note.js. В нем введите следующий код:

  • Строки 8–10: создаем хуки для хранения пользовательских заметок, чтобы иметь возможность отображать их в DOM.
  • Строки 14–18: получаем данные из БД и сохраняем результаты в хуке data. Подписываемся на изменения, происходящие в таблице notes БД в реальном времени.
  • Строки 21–30: Подписываемся на изменения, происходящие в таблице notes БД в реальном времени. Если пользователь создал запись (событие INSERT), то сохраняем ее в хуке newData. Кроме того, если пользователь удаляет запись (событие DELETE), тогда устанавливаем значение deleteData на эту удаленную заметку.

Теперь нужно прописать логику, которая будет обрабатывать создание/удаление записей. Для этого добавляем в pages/note.js следующий код:

  • Строки 2–3: при первом монтировании компонента запускаем функцию getData и метод подписки.
  • Строка 6: отписываемся от Realtime, чтобы избежать утечек памяти.
  • Строки 12–14: если newData не null, добавляем значение newDataв массив data.
  • Строка 19: если у deletedData есть значение, удаляем связанный элемент из массива data.

Теперь нам остается лишь отрисовать интерфейс. Для этого пишем в /pages/note.js следующий код:

  • Строка 3: отрисовываем элемент AddForm и передаем его в пользовательские данные в качестве пропса.
  • Строки 5–10: вызываем метод map для массива data и отображаем поля heading, body и id каждого элемента.
  • Строка 11: при клике удаляем указанную заметку.

Выполните код и перейдите в каталог /note. Результат должен выглядеть так:

Вывод кода

Вот мы и закончили! Чтобы пронаблюдать real-time функциональность, откройте два экземпляра приложения:

Realtime работает!

Заметьте, что нам не пришлось обновлять приложение для просмотра изменений. Это подтверждает, что режим реального времени в Supabase работает, как положено. 

В итоге /pages/note.js должен выглядеть так:

import { useEffect, useState } from "react";
import { Auth, Button } from "@supabase/ui";
import supabaseClient from "../utils/supabaseClient";
import AddForm from "../components/AddForm";
import { fetchData, deleteNote } from "../utils/note";

export default function Note() {
const [data, setData] = useState([]);
const [newData, handleNewData] = useState(null);
const [deletedData, handleDeletedData] = useState(null);

const { user } = Auth.useUser();

const getData = async () => {
const data = await fetchData();
setData(data);
console.log(data);
};

const getChange = async () => {
const mySubscription = supabaseClient
.from("notes")
.on("INSERT", (payload) => {
handleNewData(payload.new);
})
.on("DELETE", (payload) => {
console.log(payload.old.id);
handleDeletedData(payload.old);
})
.subscribe();
return mySubscription;
};

useEffect(() => {
getData();
const mySubscription = getChange();

return () => {
supabaseClient.removeSubscription(mySubscription);
};
}, []);

useEffect(() => {
console.log("newData value", newData);
if (newData) {
setData([...data, newData]);
handleNewData(null);
}
}, [newData]);

useEffect(() => {
if (deletedData) {
setData(data.filter((data) => data.id !== deletedData.id));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [deletedData]);

return (
<div>
<AddForm user={user} />
<h1>Here's your data: </h1>
{data.map((item) => (
<div key={item.id}>
<h1>{item.heading}</h1>
<p>
{item.body}, {item.id}
</p>
<Button onClick={() => deleteNote(item.id)}> Remove </Button>
</div>
))}
</div>
);
}

Исходный код проекта.

Заключение

Supabase  —  это удобная в использовании библиотека, включающая разнообразные возможности, которые упрощают процесс разработки. А благодаря открытому доступу она становится отличным выбором для программистов, которые предпочитают использовать в приложениях open source-технологии. Если у вас есть проект, которому нужна аутентификация и прочие возможности, Supabase станет отличным дополнением для набора инструментов.

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод стати Hussain Arif: “Build a Real-Time Note-Taking App in JavaScript

Предыдущая статьяВекторы - прошлое ИИ, хэши - будущее
Следующая статьяНовые функции стандартной библиотеки Kotlin 1.5