Привет, друзья!
В этом цикле из 2 статей я хочу рассказать вам о Supabase
— открытой (open source), т.е. бесплатной альтернативе Firebase
. Первая статья будет посвящена теории, во второй — мы вместе с вами разработаем полноценное social app с аутентификацией, базой данных, хранилищем файлов и обработкой изменения данных в режиме реального времени.
Что такое Supabase?
Supabase
, как и Firebase
— это SaaS
(software as a service — программное обеспечение как услуга) или BaaS
(backend as a service — бэкенд как услуга). Что это означает? Это означает, что при разработке fullstack app мы разрабатываем только клиентскую часть, а все остальное предоставляется Supabase
через пользовательские комплекты для разработки программного обеспечения (SDK) и интерфейсы прикладного программирования (API). Под "всем остальным" подразумевается сервис аутентификации (включая возможность использования сторонних провайдеров), база данных (PostgreSQL), файловое хранилище, realtime (реакцию на изменение данных в реальном времени), и сервер, который все это обслуживает.
Если вам это интересно, прошу под кат.
Лаконичность дальнейшего изложения обусловлена желанием уместить всю (ну почти) необходимую информацию в одной статье.
Поехали!
Установка
yarn add @supabase/supabase-js
Импорт
import supabase from '@supabase/supabase-js'
Клиент (Client)
Инициализация
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(url, key, options)
-
url
— путь, предоставляемый после создания проекта, доступный в настройках панели управления проектом; -
key
— ключ, предоставляемый после создания проекта, доступный в настройках панели управления проектом; -
options
— дополнительные настройки.
const options = {
// дефолтная схема
schema: 'public',
headers: { 'x-my-custom-header': 'my-app-name' },
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: true
}
const supabase = createClient('https://my-app.supabase.co', 'public-anon-key', options)
По умолчанию для выполнения HTTP-запросов supabase-js
использует библиотеку cross-fetch
. Это можно изменить следующим образом:
const supabase = createClient('https://my-app.supabase.co', 'public-anon-key', {
fetch: fetch.bind(globalThis)
})
Аутентификация (Auth)
Регистрация
Для регистрации нового пользователя используется метод signUp
:
async function registerUser({ email, password, first_name, last_name, age }) {
try {
const { user, session, error } = await supabase.auth.signUp({
// обязательно
email,
password
}, {
// опционально
data: {
// такой синтаксис является валидным в JS
// и более подходящим для Postgres
first_name,
last_name,
age
},
// дефолтная настройка
redirectTo: window.location.origin
})
if (error) throw error
return { user, session }
} catch (e) {
throw e
}
}
Такая сигнатура позволяет вызывать данный метод следующим образом (на примере React-приложения
):
// предположим, что `registerUser` возвращает только пользователя
const onSubmit = (e) => {
e.preventDefault()
if (submitDisabled) return
setLoading(true)
userApi
.registerUser(formData)
.then(setUser)
.catch(setError)
.finally(() => {
setLoading(false)
})
}
В TypeScript
можно использовать такую сигнатуру:
async function registerUser({ first_name, last_name, age, email, password }: UserData): ResponseData {
let data = { user: null, error: null }
try {
const { user, error } = await supabase.auth.signUp({
email,
password
}, {
data: {
first_name,
last_name,
age
}
})
if (error) {
data.error = error
} else {
data.user = user
}
} catch (e) {
data.error = e
} finally {
return data
}
}
Обратите внимание: по умолчанию после регистрации пользователь должен подтвердить свой email. Это можно отключить в разделе Authentication -> Settings
.
Авторизация
Для авторизации, в том числе с помощью сторонних провайдеров, используется метод signIn
:
async function loginUser({ email, password }) {
try {
const { user, session, error } = await supabase.auth.singIn({
// обязательно
email,
password
}, {
// опционально
returnTo: window.location.pathname
})
if (error) throw error
return { user, session }
} catch (e) {
throw e
}
}
Если авторизация выполняется только через email, пользователю отправляется так называемая магическая ссылка/magic link
.
Пример авторизации с помощью аккаунта GitHub
:
async function loginWithGitHub() {
try {
const { user, session, error } = await supabase.auth.signIn({
// провайдером может быть `google`, `github`, `gitlab` или `bitbucket`
provider: 'github'
}, {
scopes: 'repo gist'
})
if (error) throw error
// для доступа к `API` провайдера используется `session.provider_token`
return { user, session }
} catch (e) {
throw e
}
}
Выход из системы
Для выхода из системы используется метод signOut
:
async function logoutUser() {
try {
const { error } = await supabase.auth.signOut()
if (error) throw error
} catch (e) {
throw e
}
}
Для того, чтобы сообщить серверу о необходимости завершения сессии используется метод auth.api.signOut(jwt)
.
Сессия
Метод session
используется для получения данных активной сессии:
const session = supabase.auth.session()
Пользователь
Метод user
возвращает данные авторизованного пользователя:
const user = supabase.auth.user()
В данном случае возвращается объект из локального хранилища.
Для получения данных пользователя из БД используется метод auth.api.getUser(jwt)
.
На стороне сервера для этого используется метод auth.api.getUserByCookie
.
Обновление данных пользователя
Для обновления данных пользователя используется метод update
:
async function updateUser({ email, age }) {
try {
const { user, error } = await supabase.auth.update({
email
}, {
data: {
age
}
})
if (error) throw error
return user
} catch (e) {
throw e
}
}
Регистрация изменения состояния авторизации
supabase.auth.onAuthStateChange((event, session) => {
console.log(event, session)
})
Сброс пароля
const { data, error } = supabase.auth.api.resetPasswordForEmail(email, { redirectTo: window.location.origin })
После вызова этого метода на email пользователя отправляется ссылка для сброса пароля. Когда пользователь кликает по ссылке, он переходит по адресу: SITE_URL/#access_token=X&refresh_token=Y&expires_in=Z&token_type=bearer&type=recovery
. Мы регистрируем type=recovery
и отображаем форму для сброса пароля. Затем используем access_token
из URL
и новый пароль для обновления пользовательских данных:
async function resetPassword(access_token, new_password) {
try {
const { data, error } = await supabase.auth.api.updateUser(access_token, { password: new_password })
if (error) throw error
return data
} catch (e) {
throw e
}
}
База данных (Database)
Извлечение (выборка) данных
Сигнатура:
const { data, error } = await supabase
.from(table_name)
.select(column_names, options)
-
table_name
— название таблицы (обязательно); -
column_names
— список разделенных через запятую названий колонок (столбцов) или'*'
для выборки всех полей; -
options
— дополнительные настройки:-
head
— если имеет значениеtrue
, данные аннулируются (см. пример ниже); -
count
— алгоритм для подсчета количества строк в таблице:null | exact | planned | estimated
.
-
Пример:
async function getUsers() {
try {
const { data, error } = await supabase
.from('users')
.select('id, first_name, last_name, age, email')
if (error) throw error
return data
} catch (e) {
throw(e)
}
}
По умолчанию максимальное количество возвращаемых строк равняется 1000
. Это можно изменить в настройках.
select()
может использоваться совместно с модификаторами и фильтрами (будут рассмотрены позже).
Получение данных из связанных таблиц
async function getUsersAndPosts() {
try {
const { data, error } = await supabase
.from('users')
.select(`
id,
first_name,
last_name,
age,
email,
posts (
id,
title,
content
)
`)
if (error) throw error
return data
} catch (e) {
throw e
}
}
Фильтрация данных с помощью внутреннего соединения (inner join)
Предположим, что мы хотим извлечь сообщения (messages), принадлежащие пользователю с определенным именем (username):
async function getMessagesByUsername(username) {
try {
const { data, error } = await supabase
.from('messages')
// для такой фильтрации используется функция `!inner()`
.select('*, users!inner(*)')
.eq('users.username', username)
if (error) throw error
return data
} catch (e) {
throw e
}
}
Получение количества строк
async function getUsersAndUserCount() {
try {
const { data, error, count } = await supabase
.from('users')
// для получения только количества строк используется `{ count: 'exact', head: true }`
.select('id, first_name, last_name, email', { count: 'exact' })
if (error) throw error
return { data, count }
} catch(e) {
throw e
}
}
Получение данных из JSONB-столбцов
async function getUsersWithTheirCitiesByCountry(country) {
try {
const { data, error } = await supabase
.from('users')
.select(`
id, first_name, last_name, email,
address->city
`)
.eq('address->country', country)
if (error) throw error
return data
} catch(e) {
throw e
}
}
Получение данных в формате CSV
async function getUsersInCSV() {
try {
const { data, error } = await supabase
.from('users')
.select()
.csv()
if (error) throw error
return data
} catch(e) {
throw e
}
}
Запись (добавление, вставка) данных
Сигнатура:
const { data, error } = await supabase
.from(table_name)
.insert(data_array, options)
-
table_name
— название таблицы; -
data_array
— массив записываемых данных; -
options
— дополнительные настройки, например:-
returning: 'minimal'
— не возвращать данные после записи; -
upsert: true
— обновить существующую строку вместо создания новой.
-
Пример:
async function createPost({ title, body, author_id }) {
try {
const { data, error } = await supabase
.from('posts')
.insert([{
title, body, author_id
}])
if (error) throw error
return data
} catch (e) {
throw e
}
}
Разумеется, можно создавать несколько записей одновременно:
// предположим, что `messages` - массив объектов `{ title, body, user_id, room_id }`
async function saveMessagesForRoom(messages) {
try {
const { error } = await supabase
.from('messages')
.insert(messages, { returning: 'minimal' })
if (error) throw error
return data
} catch (e) {
throw e
}
}
Обратите внимание: для обновления строк с помощью upsert: true
данные должны содержать первичные или основные ключи/primary keys
соответствующей таблицы.
Модификация данных
Сигнатура:
const { data, error } = await supabase
.from(table_name)
.update({ column_name: 'column_value' }, options)
.match(condition)
-
table_name
— название таблицы; -
column_name: 'column_value'
— название обновляемой колонки и ее новое значение; -
options
— дополнительные настройки:-
returning
— определяет, возвращаются ли данные после обновления:minimal | presentation
; -
count
— алгоритм для подсчета количества обновленных строк;
-
-
condition
— условие для поиска обновляемой колонки.
Пример:
async function updateUser({ changes, user_id }) {
try {
const { data, error } = await supabase
.from('users')
.update({ ...changes })
.match({ id: user_id })
if (error) throw error
return data
} catch (e) {
throw e
}
}
Обратите внимание: метод update
должен всегда использоваться совместно с фильтрами.
Пример обновления JSON-колонки
async function updateUsersCity({ address, user_id }) {
try {
const { error } = await supabase
.from('users')
.update(`address: ${address}`, { returning: 'minimal' })
.match({ id: user_id })
if (error) throw error
} catch (e) {
throw e
}
}
В настоящее время поддерживается обновление только объекта целиком.
Модификация или запись данных
Сигнатура:
const { data, error } = await supabase
.from(table_name)
.upsert({ primary_key_name: 'primary_key_value', column_name: 'column_value' }, options)
-
options
— дополнительные настройки:-
returning
; -
count
; -
onConflict: string
— позволяет работать с колонками, имеющими ограничениеUNIQUE
; -
ignoreDuplicates: boolean
— определяет, должны ли игнорироваться дубликаты.
-
Пример:
async function updateOrCreatePost({ id, title, body, author_id }) {
try {
const { data, error } = await supabase
.from('posts')
.upsert({ id, title, body, author_id })
if (error) throw error
return data
} catch (e) {
throw e
}
}
Разумеется, можно обновлять или создавать несколько записей за раз.
Обратите внимание на 2 вещи:
- данные должны содержать первичные ключи;
- первичные ключи могут быть только натуральными (не суррогатными).
Удаление данных
Сигнатура:
const { data, error } = await supabase
.from(table_name)
.delete(options)
.match(condition)
-
options
:-
returning
; -
count
.
-
Пример:
async function removeUser(user_id) {
try {
const { error } = await supabase
.from('users')
.delete({ returning: 'minimal' })
.match({ id: user_id })
if (error) throw error
} catch (e) {
throw e
}
}
Вызов функций Postgres
Сигнатура:
const { data, error } = await supabase
.rpc(fn, params, options)
-
fn
— название функции; -
params
— параметры, передаваемые функции; -
options
:-
head
; -
count
.
-
Предположим, что мы определили такую функцию:
CREATE FUNCTION check_password(username_or_email TEXT, password_hash TEXT)
RETURNS BOOLEAN AS
$$
DECLARE is_password_correct BOOLEAN;
BEGIN
SELECT (hashed_password = $2) INTO is_password_correct
FROM users
WHERE username = $1 OR email = $1;
RETURN is_password_correct;
END;
$$
LANGUAGE plpgsql
Данную функцию можно вызвать следующим образом:
async function checkPassword(username_or_email, password) {
let password_hash
try {
password_hash = await bcrypt.hash(password, 10)
} catch (e) {
throw e
}
try {
const { data, error } = await supabase
.rpc('check_password', { username_or_email, password_hash })
if (error) throw error
return data
} catch(e) {
throw e
}
}
Метод rpc
может использоваться совместно с модификаторами и фильтрами, например:
const { data, error } = await supabase
.rpc('get_all_cities')
.select('city_name', 'population')
.eq('city_name', 'The Shire')
Модификаторы (Modifiers)
Модификаторы используются в запросах на выборку данных (select
). Они также могут использоваться совместно с методом rpc
, когда он возвращает таблицу.
limit()
Модификатор limit
ограничивает количество возвращаемых строк.
Сигнатура:
const { data, error } = await supabase
.from(table_name)
.select(column_names)
.limit(count, options)
-
count
: количество возвращаемых строк; -
options
:-
foreignTable
: внешняя таблица (для колонок с внешним ключом).
-
Пример:
async function getTop10Users() {
try {
const { data, error } = await supabase
.from('users')
.select('id, user_name, email')
.limit(10)
if (error) throw error
return data
} catch(e) {
throw e
}
}
Пример с внешней таблицей:
async function getTop10CitiesByCountry(country_name) {
try {
const { data, error } = await supabase
.from('countries')
.select('country_name, cities(city_name)')
.eq('country_name', 'Rus')
.limit(10, { foreignTable: 'cities' })
if (error) throw error
return data
} catch(e) {
throw e
}
}
order()
Модификатор order
сортирует строки в указанном порядке.
Сигнатура:
const { data, error } = await supabase
.from(table_name)
.select(column_names)
.order(column_name, options)
-
column_name
: название колонки, по которой выполняется сортировка; -
options
:-
ascending
: если имеет значениеtrue
, сортировка выполняется по возрастанию; -
nullsFirst
: еслиtrue
, колонки со значениемnull
идут впереди; -
foreignTable
: название внешней таблицы.
-
Пример:
async function getMostLikedPosts(limit) {
try {
const { data, error } = await supabase
.from('posts')
.select()
.order('like_count', { ascending: true })
.limit(limit)
if (error) throw error
return data
} catch (e) {
throw e
}
}
range()
Модификатор range
возвращает диапазон строк (включительно).
Сигнатура:
const { data, error } = await supabase
.from(table_name)
.select(column_names)
.range(start, end, options)
-
start
: начало диапазона; -
end
: конец диапазона; -
options
:-
foreignTable
.
-
Пример:
async function getPostSlice(from, to) {
try {
const { data, error } = await supabase
.from('posts')
.select()
.range(from, to)
if (error) throw error
return data
} catch(e) {
throw e
}
}
single()
Модификатор single
возвращает одну строку.
Пример:
async function getUserById(user_id) {
try {
// в данном случае мы получим объект вместо массива
const { data, error } = await supabase
.from('users')
.select('id, user_name, email')
.eq('id', user_id)
.single()
if (error) throw error
return data
} catch(e) {
throw e
}
}
Существует также модификатор maybeSingle
, возвращающий хотя бы одну строку.
Фильтры (Filters)
Фильтры могут использоваться в запросах select
, update
и delete
, а также в методе rpc
, когда функция возвращает таблицу.
Фильтры должны применяться в конце запроса. Их можно объединять в цепочки и применять условно.
Примеры:
const { data, error } = await supabase
.from('cities')
.select()
.eq('id', '1342')
.single()
const { data, error } = await supabase
.from('cities')
.select()
.gte('population', 1000)
.lt('population', 10000)
const filterByName = null
const filterPopLow = 1000
const filterPopHigh = 10000
let query = supabase
.from('cities')
.select()
if (filterByName) { query = query.eq('name', filterByName) }
if (filterPopLow) { query = query.gte('population', filterPopLow) }
if (filterPopHigh) { query = query.lt('population', filterPopHigh) }
const { data, error } = await query
Виды фильтров:
-
or('filter1, filter2, ...filterN', { foreignTable })
— значение колонки должно удовлетворять хотя бы одному условию, например:or('population.gt.1000, population.lte.100000')
; может использоваться в сочетании с фильтромand
; -
not(column_name, operator, value)
— значение колонки не должно удовлетворять условию, например:not('population', 'gte', 100000)
; -
match(query)
— строка должна точно соответствовать объекту, например:match({ user_name: 'Harry', life_style: 'webdev' })
; -
eq(column_name, column_value)
— строка должна соответствовать условию, например:eq('user_name', 'Harry')
-
neq(column_name, column_value)
— противоположностьeq
; -
gt(column_name, value)
— строка должна содержать колонку, которая имеет значение, большее чем указанное (greater than), например:gt('age', 17)
; -
gte(column_name, value)
,lt(column_name, value)
,lte(column_name, value)
— больше или равно, меньше и меньше или равно, соответственно; -
like(column_name, pattern)
иilike(column_name, pattern)
— возвращает все строки, значения указанной колонки которых совпадает с паттерном (шаблоном), например,like('user_name', '%oh%')
; -
is(column_name, value)
— значение колонки должно точно совпадать с указанным значением, например,is('married', false)
; -
in(column_name, column_value[])
— значение колонки должно совпадать хотя бы с одним из указанных в виде массива значений, например,in('city_name', ['Paris', 'Tokyo'])
; -
contains(column_name, [column_value])
—contains('main_lang', ['javascript'])
; -
overlaps(column_name, column_value[])
—overlaps('hobby', ['code', 'guitar'])
; -
textSearch(column_name, query, options)
— возвращает все строки, значения указанной колонки которой соответствуют запросуto_tsquery
, например:
const { data, error } = await supabase
.from('quotes')
.select('catchphrase')
.textSearch('catchphrase', `'fat' & 'cat'`, {
config: 'english'
})
Настройки также принимают второй параметр type
, возможными значениями которого являются plain
и phrase
, определяющие базовую и полную нормализацию, соответственно.
-
filter(column_name, operator, value)
— значение колонки должно удовлетворять фильтру.filter
должен использоваться в последнюю очередь, когда других фильтров оказалось недостаточно:
const { data, error } = await supabase
.from('cities')
.select('city_name, countries(city_name)')
.filter('countries.city_name', 'in', '("France", "Japan")')
Также имеется несколько других (более специфичных) фильтров.
Хранилище (Storage)
Для создания файлового хранилища используется метод createBucket
:
async function createAvatarBucket() {
try {
const { data, error } = await supabase
.storage
// createBucket(id, options)
.createBucket('avatars', { public: false })
if (error) throw error
return data
} catch (e) {
throw e
}
}
Для создания хранилища требуются следующие разрешения (permissions policy):
-
buckets
:insert
; -
objects
:none
.
В Supabase
безопасность работы с данными и файлами обеспечивается установкой и настройкой row level security (политик защиты строк). Мы рассмотрим их в следующей статье.
Для получения данных о хранилище используется метод getBucket
:
const { data, error } = await supabase
.storage
// getBucket(id)
.getBucket('avatars')
Требуются следующие разрешения:
-
buckets
:select
; -
objects
:none
.
Для получения списка хранилищ используется метод listBuckets
:
const { data, error } = await supabase
.storage
.listBuckets()
Требуются аналогичные разрешения.
Для обновления хранилища используется метод updateBucket(id, options)
(разрешение buckets
: update
), а для удаления — метод deleteBucket(id)
(buckets
: select
и delete
).
Обратите внимание: непустое хранилище не может быть удалено без его предварительной очистки с помощью метода emptyBucket(id)
(buckets
: select
, objects
: select
и delete
).
Загрузка файлов
Для загрузки файлов в хранилище используется метод upload
.
Сигнатура
await supabase
.storage
.from(bucket_name)
.upload(path, file, options)
-
bucket_name
— названия хранилища; -
path
— относительный путь к файлу. Должен иметь форматfolder/subfolder/fileName.fileExtension
. Разумеется, записывать файлы можно только в существующие хранилища; -
file
— загружаемый в хранилище файл: ArrayBuffer | ArrayBufferView | Blob | Buffer | File | FormData | ReadableStream | URLSearchParams | string (т.е. практически что угодно); -
options
— дополнительные настройки:-
cacheControl
: HTTP-заголовокCache-Control
; -
contentType
: тип содержимого, по умолчанию имеет значениеtext/plain;charset=utf-8
; -
upsert
: логический индикатор вставки (upsert) файла.
-
Разрешения:
-
buckets
:none
; -
objects
:insert
.
Пример
async function uploadAvatar({ userId, file }) {
// получаем расширение файла
const fileExt = file.name.split('.')[1]
try {
const { data, error } = await supabase
.storage
.from('users')
.upload(`avatars/${userId}.${fileExt}`, file, {
cacheControl: '3600',
upsert: false
})
if (error) throw error
return data
} catch(e) {
throw e
}
}
Скачивание файлов
Для скачивания файлов используется метод download
:
async function downloadFile({ bucketName, filePath }) {
try {
const { data, error } = await supabase
.storage
.from(bucketName)
.download(filePath)
if (error) throw error
return data
} catch(e) {
throw e
}
}
Разрешения:
-
buckets
:none
; -
objects
:select
.
Получение списка файлов
Для получения списка файлов используется метод list
.
Сигнатура
list(dir_name, options, parameters)
-
dir_name
: название директории; -
options
:-
limit
: количество возвращаемых файлов; -
offset
: количество пропускаемых файлов; -
sortBy
: -
column_name
: название колонки для сортировки; -
order
: порядок сортировки.
-
-
parameters
:-
signal
:AbortController.signal
.
-
Пример
async function getAvatars({ limit = 100, offset = 0, sortBy = { column: 'name', order: 'asc' } }) {
try {
const { data, error } = await supabase
.storage
.from('users')
.list('avatars', {
limit,
offset,
sortBy
})
if (error) throw error
return data
} catch(e) {
throw e
}
}
Разрешения:
-
buckets
:none
; -
objects
:select
.
Обновление файлов
Для обновления файлов используется метод update
.
Сигнатура
update(path, file, options)
-
options
: настройки, аналогичные передаваемымupload
.
Пример
const defaultUpdateAvatarOptions = { cacheControl: '3600', upsert: false }
async function updateAvatar({ filePath, file, options = defaultUpdateAvatarOptions }) {
try {
const { data, error } = await supabase
.storage
.from('users')
.update(`avatars/${filePath}`, file, options)
if (error) throw error
return data
} catch(e) {
throw e
}
}
Разрешения:
-
buckets
:none
; -
objects
:select
иupdate
.
Для перемещения файла с его опциональным переименованием используется метод move
:
const { data, error } = await supabase
.storage
.from('avatars')
.move('public/avatar1.png', 'private/moved_avatar.png')
Разрешения:
-
buckets
:none
; -
objects
:select
иupdate
.
Удаление файлов
Для удаления файлов используется метод remove
:
// список удаляемых файлов - массив `filePathArr`
async function removeFile({ bucketName, filePathArr }) {
try {
const { data, error } = await supabase
.storage
.from(bucketName)
.remove(filePathArr)
if (error) throw error
return data
} catch(e) {
throw e
}
}
Разрешения:
-
buckets
:none
; -
objects
:select
иdelete
.
Формирование пути к файлу
Для формирования пути к файлу без разрешения используется метод createSignedUrl
:
createSignedUrl(path, expiresIn)
-
expiresIn
— количество секунд, в течение которых ссылка считается валидной.
async function getSignedUrl({ bucketName, filePath, expiresIn = 60 }) {
try {
const { signedUrl, error } = await supabase
.storage
.from(bucketName)
.createSignedUrl(filePath, expiresIn)
if (error) throw error
return signedUrl
} catch(e) {
throw e
}
}
Разрешения:
-
buckets
:none
; -
objects
:select
.
Для формирования пути к файлу, находящемуся в публичной директории, используется метод getPublicUrl
:
async function getFileUrl({ bucketName, filePath }) {
try {
const { publicUrl, error } = supabase
.storage
.from(bucketName)
.getPublicUrl(filePath)
if (error) throw error
return publicUrl
} catch(e) {
throw e
}
}
Разрешения не требуются.
Регистрация изменений данных в режиме реального времени (Realtime)
Подписка на изменения
Для подписки на изменения, происходящие в БД, используется метод subscribe
в сочетании с методом on
.
Сигнатура
const subscription = supabase
.from(table_name)
.on(event, callback)
-
event
— регистрируемое событие. Для регистрации всех событий используется'*'
; -
callback
— функция обработки полезной нагрузки.
Обратите внимание:
-
realtime
отключена по умолчанию для новых проектов по причинам производительности и безопасности; - для того, чтобы получать "предыдущие" данные для обновлений и удалений, необходимо установить
REPLICA IDENTITY
в значениеFULL
, например:ALTER TABLE table_name REPLICA IDENTITY FULL;
.
Примеры
Регистрация всех изменений всех таблиц:
const subscription = supabase
.from('*')
.on('*', payload => {
console.log('***', payload)
})
.subscribe()
Регистрация изменений определенной таблицы:
const subscription = supabase
.from('posts')
.on('*', payload => {
console.log('***', payload)
})
.subscribe()
Регистрация записи данных в определенную таблицу:
const subscription = supabase
.from('posts')
// INSERT | UPDATE | DELETE
.on('INSERT', payload => {
console.log('***', payload)
})
.subscribe()
Обработчики можно объединять в цепочки:
const subscription = supabase
.from('posts')
.on('INSERT', insertHandler)
.on('DELETE', deleteHandler)
.subscribe()
Имеется возможность регистрации изменений определенных строк. Синтаксис:
table_name:column_name=eq.value
-
table_name
: название таблицы; -
column_name
: название колонки; -
value
: значение, которое должна содержать колонка.
const subscription = supabase
.from('countries.id=eq.123')
.on('UPDATE', onUpdate)
.subscribe()
Отписка от изменений
Метод removeSubscription
удаляет активную подписку и возвращает количество открытых соединений.
supabase.removeSubscription(subscription_name)
-
subscription_name
— название подписки.
Для удаления всех подписок и закрытия WebSocket-соединения
используется метод removeAllSubscriptions
.
Получение списка подписок
Для получения списка подписок используется метод getSubscriptions
.
const allSubscriptions = supabase.getSubscriptions()
Пожалуй, это все, чем я хотел поделиться с вами в данной статье.
Благодарю за внимание и happy coding!