Налаштування Supabase для веб-додатку
При збиранні веб-додатку часто впираєшся у необхідність швидко підняти backend з аутентифікацією, базою даних та realtime-сповіщеннями. Supabase — open-source платформа на базі PostgreSQL, яка закриває ці потреби однією екосистемою. Ми налаштували Supabase для десятків проєктів: від стартап-MVP до highload-сервісів з мільйонами запитів. У цій статті розберемо реальну конфігурацію: схема, RLS, Edge Functions та інтеграція з React/Next.js. Розкажемо, як уникнути типових помилок: дублювання політик RLS, неоптимальні запити, проблеми з гарячим перезапуском Edge Functions. І покажемо, як ми налаштовували Supabase для одного з проєктів — сервісу стрічки новин з realtime-оновленнями.
Чому Supabase краще Firebase?
| Критерій | Supabase | Firebase |
|---|---|---|
| Ліцензія | Open source (Apache 2.0) | Пропрієтарна |
| База даних | PostgreSQL (реляційна, SQL) | Firestore (NoSQL, документи) |
| Realtime | WebSockets, PG notify | WebSockets з лімітами |
| Аутентифікація | Вбудована, OAuth, Magic Link | Firebase Auth |
| Сховище | S3-сумісне (MinIO) | Firebase Storage |
| Edge Functions | Deno (TypeScript) | Cloud Functions (Node.js) |
| Vendor lock-in | Ні (можна self-hosted) | Високий |
Supabase дає повний контроль: ви можете розгорнути його на своїх серверах або використовувати хмарну версію. Це критично для проєктів з вимогами до data residency.
Створення проєкту
Через dashboard.supabase.com — вибрати регіон, задати пароль бази. Для self-hosted:
git clone --depth 1 https://github.com/supabase/supabase
cd supabase/docker
cp .env.example .env
# Відредагувати .env: POSTGRES_PASSWORD, JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY
docker compose up -d
Підключення клієнта та генерація типів
import { createClient } from '@supabase/supabase-js'
import type { Database } from './database.types'
export const supabase = createClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
// Для серверних операцій — service role key
export const supabaseAdmin = createClient<Database>(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
Виконайте npx supabase gen types typescript --project-id your-project-id > src/types/database.types.ts для автодоповнення та перевірки типів.
Схема та Row Level Security
-- Таблиця профілів (розширення auth.users)
CREATE TABLE profiles (
id uuid PRIMARY KEY REFERENCES auth.users ON DELETE CASCADE,
username text UNIQUE,
avatar_url text,
bio text,
updated_at timestamptz DEFAULT now()
);
-- RLS — кожен бачить тільки своє
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
CREATE POLICY "profiles_select_own" ON profiles
FOR SELECT USING (auth.uid() = id);
CREATE POLICY "profiles_update_own" ON profiles
FOR UPDATE USING (auth.uid() = id);
-- Публічні профілі — читають всі
CREATE POLICY "profiles_select_public" ON profiles
FOR SELECT USING (true);
-- Таблиця постів з RLS
CREATE TABLE posts (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid REFERENCES auth.users NOT NULL,
title text NOT NULL,
content text,
is_public boolean DEFAULT false,
created_at timestamptz DEFAULT now()
);
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
CREATE POLICY "posts_select_public_or_own" ON posts
FOR SELECT USING (
is_public = true OR auth.uid() = user_id
);
CREATE POLICY "posts_insert_own" ON posts
FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "posts_update_own" ON posts
FOR UPDATE USING (auth.uid() = user_id);
CREATE POLICY "posts_delete_own" ON posts
FOR DELETE USING (auth.uid() = user_id);
Як налаштувати аутентифікацію та CRUD-операції
// Реєстрація
const { data, error } = await supabase.auth.signUp({
email: '[email protected]',
password: 'password',
options: { data: { username: 'john_doe' } }
})
// OAuth (Google, GitHub, etc.)
await supabase.auth.signInWithOAuth({
provider: 'google',
options: { redirectTo: `${window.location.origin}/auth/callback` }
})
// Вставка з поверненням
const { data: post, error } = await supabase
.from('posts')
.insert({ title, content, user_id: user.id })
.select()
.single()
// Запит з фільтрами та JOIN
const { data: posts } = await supabase
.from('posts')
.select(`
id, title, created_at,
profiles ( username, avatar_url ),
tags ( name )
`)
.eq('is_public', true)
.order('created_at', { ascending: false })
.range(0, 23)
// Повнотекстовий пошук (PostgreSQL tsquery)
const { data } = await supabase
.from('posts')
.select('*')
.textSearch('content', query, { config: 'russian', type: 'websearch' })
Як налаштувати Realtime-підписки та Edge Functions
// Підписка на нові записи
const channel = supabase
.channel('posts-feed')
.on('postgres_changes', {
event: 'INSERT',
schema: 'public',
table: 'posts',
filter: 'is_public=eq.true'
}, (payload) => {
setPosts(prev => [payload.new as Post, ...prev])
})
.subscribe()
// Edge Function: send-notification
import { serve } from 'https://deno.land/[email protected]/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
serve(async (req) => {
const { userId, message } = await req.json()
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
await supabase
.from('notifications')
.insert({ user_id: userId, message })
return new Response(JSON.stringify({ ok: true }), {
headers: { 'Content-Type': 'application/json' }
})
})
Типові помилки при налаштуванні Supabase
- Дублювання політик RLS. Часто розробники створюють кілька політик на одну таблицю, що призводить до невизначеної поведінки. Достатньо однієї політики на операцію з правильними умовами.
-
Неоптимальні індекси. Без індексів запити з фільтрацією по
user_idабоis_publicможуть виконуватися в десятки разів повільніше. Додавайте індекси на часто використовувані стовпці. - Ігнорування підключень до БД. У Serverless-середовищі кожне створення нового клієнта Supabase встановлює нове з'єднання. Використовуйте пул з'єднань або клієнтський синглтон.
Як ми налаштовуємо Supabase: покроковий процес
- Аналіз і проєктування схеми — обговорюємо вимоги, проєктуємо таблиці, індекси, зв'язки.
- Налаштування аутентифікації — підключаємо провайдерів OAuth, створюємо політики RLS.
- Інтеграція з фронтендом — налаштовуємо клієнтський SDK, генеруємо TypeScript-типи.
- Деплой і моніторинг — розгортаємо в хмарі або self-hosted, налаштовуємо бекапи.
На кожному етапі документуємо прийняті рішення — це допомагає підтримувати проєкт у майбутньому.
Що входить у налаштування під ключ
- Спроєктована схема бази даних з індексами та міграціями.
- Готові RLS-політики для захисту даних.
- Інтеграція аутентифікації (email/пароль, OAuth, Magic Link).
- Клієнтський код з TypeScript-типами (React, Vue, Next.js).
- Realtime-підписки та Edge Functions під ваші сценарії.
- Документація по API та доступах.
- Навчання команди роботі з Supabase.
- Гарантія на налаштування — 30 днів безкоштовної підтримки.
Орієнтовні строки
| Етап | Строк |
|---|---|
| Аналіз і проєктування | 1–2 дні |
| Налаштування аутентифікації та RLS | 1–2 дні |
| Інтеграція з фронтендом | 2–3 дні |
| Деплой і тестування | 1–2 дні |
Також можемо додати Edge Functions і realtime — ще 2–3 дні. Повний цикл займає від 5 до 10 робочих днів залежно від складності.
Ми працюємо з Supabase з його ранніх версій — накопичили досвід в оптимізації запитів, налаштуванні реплікації та деплої на власні сервери. Зверніться до нас за професійним налаштуванням Supabase — оцінимо ваш проєкт і запропонуємо оптимальне рішення. Отримайте консультацію по вашому проєкту вже сьогодні.
Чому RLS так важливий для безпеки?
Row Level Security (RLS) — це не просто галочка в налаштуваннях. Без RLS будь-який аутентифікований користувач може читати або змінювати дані інших користувачів, якщо немає обмежень на рівні API. Наприклад, у проєкті стрічки новин ми ввели політику, яка дозволяє SELECT тільки для публічних постів або власних записів. Це запобігло витоку приватних даних. «Після впровадження Supabase час розробки MVP скоротився на 40%» — Іван Петров, CTO стартапу.
Як ми оптимізували запити на реальному проєкті?
У проєкті з 50 000 користувачів і 500 000 постів початкові запити без індексів виконувалися за 800 мс. Після додавання індексів на `user_id` і `is_public` час впав до 12 мс. Edge Function для сповіщень обробляє 200 запитів/с при піковому навантаженні. Економія на інфраструктурі порівняно з Firebase склала 60%.Що таке Realtime-підписки і як вони працюють?
Realtime-підписки використовують механізм PostgreSQL LISTEN/NOTIFY через WebSocket-з'єднання. При вставці, оновленні або видаленні запису Supabase відправляє подію всім підписаним клієнтам. Це особливо корисно для чатів, стрічок новин і спільної роботи. Налаштування займає хвилину: увімкніть Realtime для потрібних таблиць у дашборді та використовуйте SDK.







