Supabase GraphQL Direct против собственного сервера Apollo
Сравните pg_graphql непосредственно из Supabase с отдельным сервером Apollo: аутентификация, RLS, подписки, а также ситуации, когда преимущество имеет гибридный путь с использованием Edge Function.
GraphQL — это язык запросов, а не среда выполнения. Его можно использовать из более чем одного источника:
- Встроенный в Supabase инструмент
pg_graphql— схема генерируется из PostgreSQL, хранится на сервере Supabase, практически не требует настройок - Специализированный бэкенд — сервер на Node, Python или Go с Apollo Server, Hasura, Pothos или аналогичными решениями, который находится в вашем владении и развернут вами
В любом случае фронтенд может использовать Apollo Client. Изменяется лишь местоположение схемы GraphQL и тот, кто ею управляет.
Система Vision Expert System выбирает путь через Supabase:
// src/client/supabase-grphql-apollo.client.js
const httpLink = new HttpLink({
uri: `${SUPABASE_URL}/graphql/v1`, // ← Supabase's built-in endpoint
});
Нет процесса Express, нет Apollo Server, нет ручно написанных резолверов, нет дополнительного развертывания. Интерфейс API представляет собой схему базы данных.
Эскизы архитектуры
Подход А: прямое использование Supabase
Браузер взаимодействует непосредственно с GraphQL-сервером Supabase. Аутентификация осуществляется с помощью JWT от Supabase, а защита на уровне строк — основным механизмом контроля доступа.
Подход B: Отдельный бэкенд
Браузер взаимодействует с вашим сервером GraphQL. Резолверы обращаются к Supabase (или другим источникам) с помощью SDK для администрирования или SQL. Вы сами определяете типы и поля.
Интуитивное понимание
Прямой режим оптимизирован для реализации операций CRUD без необходимости хостинга API. Отдельный режим оптимизирован для использования пользовательских полей, объединения нескольких сервисов и подписок в стиле GraphQL. Те же шаблоны клиента Apollo; разная принадлежность схемы и правил.
Практическое применение Supabase-direct
Как это работает
При запуске pg_graphql анализирует PostgreSQL и генерирует полноценный GraphQL API. Таблицы превращаются в типы Collection, для которых операции CRUD адаптированы под соединения в стиле Relay.
Настройка клиента Apollo (этот проект)
// Two required headers — no custom backend token scheme
const authLink = setContext((_, { headers }) => ({
headers: {
...headers,
apikey: SUPABASE_ANON_KEY, // identifies the Supabase project
Authorization: `Bearer ${accessToken}`, // Supabase JWT for RLS
"Content-Type": "application/json",
},
}));
const httpLink = new HttpLink({
uri: `${SUPABASE_URL}/graphql/v1`, // Supabase endpoint — no server to host
});
Формат запроса
Коллекции следуют шаблону соединения:
query LoadStaff {
staffCollection { # ← always "Collection"
edges { # ← always "edges"
node { # ← always "node"
id
first_name
role { # ← relations via FK are auto-resolved
role_name
}
}
}
}
}
Формат мутации
mutation AddStaff($firstName: String!, $email: String!) {
insertIntostaffCollection( # ← prefix is always "insertInto"
objects: { first_name: $firstName, email: $email }
) {
records {
id
}
}
}
Безопасность базы данных
Политики RLS определяют, кто может видеть что. «Бэкенд» представлен PostgreSQL:
-- Only active staff can be read
CREATE POLICY "select active staff"
ON vision_expert.staff
FOR SELECT
USING (is_active = true AND auth_user_id = auth.uid());
Даже специально составленный запрос GraphQL не может обойти механизм RLS; фильтрация происходит непосредственно внутри Postgres.
Ограничения прямого подхода
- Для пользовательских полей, отсутствующих в базе данных, требуются функции или представления.
- Для вычисляемых/виртуальных полей необходимы вычисляемые столбцы или представления.
- Дополнение одного резолвера вызовом REST-сервиса сторонней компании затруднено без использования Edge Functions.
- Многоэтапные рабочие процессы с возможностью отката реализуются либо в функциях базы данных, либо на фронтенде.
- Нумерация соответствует конвенциям
pg_graphql— подчеркивания сохраняются, поэтому используются такие названия, какhead_officeCollection.
Практическое применение отдельного бэкенда
Как это работает
Вы запускаете Apollo Server (или аналогичный сервис), создаете схему и пишете резолверы, которые вызывают функции администрирования Supabase, Prisma или SQL.
Настройка клиента Apollo
// Separate backend — your own auth token, your own endpoint
const authLink = setContext((_, { headers }) => ({
headers: {
...headers,
Authorization: `Bearer ${yourCustomJwt}`,
"Content-Type": "application/json",
},
}));
const httpLink = new HttpLink({
uri: "https://api.yourapp.com/graphql", // your server
});
Персонализированная схема
# You define every type by hand
type Staff {
id: ID!
firstName: String!
lastName: String!
fullName: String! # computed field — not in DB
role: Role!
branch: Branch!
subordinates: [Staff!]! # custom logic
}
type Query {
staff(id: ID!): Staff
staffByBranch(branchId: ID!): [Staff!]!
}type Mutation {
addStaff(input: AddStaffInput!): StaffPayload!
deactivateStaff(id: ID!): Boolean!
}
Резолверы
// server/resolvers/staffResolvers.js
const Query = {
staffByBranch: async (_, { branchId }, { supabaseAdmin, currentUser }) => {
// Business logic: only managers can list their own branch
if (currentUser.role !== "manager" && currentUser.branchId !== branchId) {
throw new ForbiddenError("Access denied");
}
// Fetch from Supabase using admin SDK (bypasses RLS)
const { data, error } = await supabaseAdmin
.from("staff")
.select("*, branch(*), role(*)")
.eq("branch_id", branchId); return data;
},
};const Staff = {
// Computed field not in DB
fullName: (parent) => `${parent.first_name} ${parent.last_name}`,
};
Пути SDK администрирования обходят механизм RLS, поэтому авторизация должна реализовываться в коде резолверов с строгим соблюдением правил.
Подробный анализ: в чём именно разница
1. Процесс аутентификации
Режим Supabase-direct:
User logs in → supabase.auth.signInWithPassword()
→ Supabase returns JWT
→ JWT stored in session
→ Apollo authLink reads JWT from supabase.auth.getSession()
→ JWT sent in Authorization header with every GraphQL request
→ Supabase validates JWT and enforces RLS
// Token is read dynamically from the Supabase session
let accessToken = null;
supabase.auth.getSession().then(({ data: { session } }) => {
accessToken = session?.access_token ?? null;
});
supabase.auth.onAuthStateChange((_event, session) => {
accessToken = session?.access_token ?? null;
});
Отдельный бэкенд:
User logs in → POST /auth/login (your endpoint) or Supabase Auth
→ Your server returns a custom JWT
→ Frontend stores JWT
→ Apollo authLink reads JWT from localStorage / context
→ JWT sent to your server
→ Your server validates JWT in middleware
→ Resolver runs with the validated user context
В режиме прямого взаимодействия не требуется обмен токенами — JWT от Supabase служит одновременно доказательством входа и учетными данными для базы данных. Отдельный бэкенд добавляет дополнительный этап обработки, но позволяет настраивать параметры и сроки действия токенов.
2. Подключение через Apollo Link
Режим Supabase-direct:
// Two specific headers, one endpoint
const link = offlineMutationLink
.concat(authLink) // adds apikey + Bearer
.concat(httpLink); // → supabase.co/graphql/v1
Отдельный бэкенд:
// One standard auth header, your endpoint
const link = authLink // adds your token
.concat(httpLink); // → api.yourapp.com/graphql
Та же идея, что и у setContext + HttpLink. Различия заключаются в URL и в том, какие заголовки являются обязательными.
3. Запросы и изменения
Supabase-direct — генерируемые имена фиксированы:
# Table: staff → Query: staffCollection, Mutation: insertIntostaffCollection
query {
staffCollection(filter: { is_active: { eq: true } }) {
edges { node { id first_name } }
}
}
Отдельный бэкенд — вы сами называете всё:
# You chose the name "activeStaff"
query {
activeStaff {
id
fullName # computed in resolver
avatarUrl # fetched from a CDN API in resolver
}
}
Пользовательская схема позволяет упростить структуру данных, объединять сервисы и вычислять производные поля в одном ответе. Режим Direct возвращает данные, хранящиеся в таблицах.
4. Где находятся бизнес-правила
Supabase-direct:
Business logic → PostgreSQL functions, triggers, and RLS policies
OR handled in the React frontend (as in this project)
Пример — проверка на наличие дублирующихся NIC/электронных адресов на фронтенде перед вставкой:
// Frontend handles the business rule: check for dup NIC/email before inserting
const { data: dupData } = await checkDuplicate({ variables: { nic, email } });
const nicExists = dupData?.nicCheck?.edges?.length > 0;
if (nicExists) {
alert("A staff member with this NIC already exists.");
return;
}
await addStaff({ variables: { ... } });
Отдельный бэкенд:
// Backend resolver handles the business rule
const addStaff = async (_, { input }, { db }) => {
const existing = await db.staff.findFirst({
where: { OR: [{ nic: input.nic }, { email: input.email }] }
});
if (existing) throw new UserInputError("NIC or email already exists");
// Atomic transaction: create auth user + insert staff profile
return await db.$transaction([
supabaseAdmin.auth.admin.createUser({ email: input.email }),
db.staff.create({ data: input }),
]);
};
Правила бэкенда остаются централизованными для всех пользователей (веб, мобильные устройства, партнеры). Правила, находящиеся только на фронтенде, разбросаны и легко игнорируются.
5. Модель безопасности
В прямом режиме сложнее допустить неправильную настройку, приводящую к случайной утечке данных, поскольку Postgres контролирует доступ. Отдельный бэкенд обеспечивает большую гибкость, но требует тщательной настройки механизмов обработки запросов.
6. Подписки и реальное время
Supabase-direct использует каналы реального времени Supabase, а не подписки GraphQL:
// Supabase Realtime — separate from GraphQL
const channel = supabase
.channel("staff-changes")
.on("postgres_changes", { event: "*", schema: "vision_expert", table: "staff" },
(payload) => { refetch(); }
)
.subscribe();
Этот путь не связан с Apollo; клиент GraphQL сам по себе не получает данных в режиме реального времени.
Отдельный бэкенд может обеспечить настоящие подписки GraphQL через WebSocket:
subscription {
staffAdded {
id
first_name
role { role_name }
}
}
// Apollo Client with WebSocket link
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
const wsLink = new GraphQLWsLink(
createClient({ url: "wss://api.yourapp.com/graphql" })
);
7. Поддержка офлайн-режима
Обе стороны могут очередывать операции в офлайн-режиме через Apollo Link. Vision Expert System делает это в прямом режиме с использованием RxDB в качестве локальной очереди.
По сути принцип остается тем же:
Offline mutation → ApolloLink intercepts → stores in local DB → syncs on reconnect
Обновление токена при повторном подключении обычно проще, когда вы контролируете весь цикл жизни токена. В Supabase необходимо подтвердить, что JWT по-прежнему действителен, прежде чем очищать очередь.
Когда подходит режим Supabase-direct
Преферируйте его в следующих случаях:
- Работа в основном сводится к операциям CRUD и структурирована по таблицам
- Вы хотите запустить приложение без использования сервера API
- Команда небольшая, а глубина разработки бэкенда ограничена
- Логика умещается в функции PostgreSQL и механизмы RLS
- Практически есть только один фронтенд-клиент
- Вы хотите минимальные затраты на хостинг для слоя API
- Запросы остаются сфокусированными на таблицах без объединения данных из разных сервисов
Это соответствует Vision Expert System: внутреннему инструменту с несколькими ролями, всем данным хранится в Supabase, применяются механизмы RLS в зависимости от роли и ветки, используется один фронтенд.
Когда необходим отдельный бэкенд
Добавьте его в следующих случаях:
- Вам нужны вычисляемые/виртуальные поля, отсутствующие в базе данных
- Один ответ GraphQL должен объединять данные из Supabase, систем оплаты, CRM и других сервисов
- Многократные транзакции должны оставаться атомарными между таблицами и сервисами
- Внешние разработчики будут использовать публичный API
- Вам нужны подписки GraphQL в качестве средства реального времени
- Бизнес-правила слишком сложны для реализации с помощью SQL
- GraphQL выступает в роли шлюза федерации в архитектуре микросервисов
- Вам нужны ограничения по частоте запросов на уровне API, валидация данных или журналы аудита
- Генерируемые имена вида
pg_graphqlнеприемлемы и не подлежат исправлению
Гибридный подход
Многие продакшен-приложения сочетают оба подхода:
- Прямой использование GraphQL для обычных операций чтения и записи
- Supabase Edge Functions для решения сложных случаев в 10% сценариев: административный SDK (безопасное обходное решение для RLS), вызовы сторонних сервисов, многократные операции
Frontend Apollo Client
│
├── /graphql/v1 → Supabase pg_graphql (standard CRUD)
│
└── /functions/complex-operation → Supabase Edge Function (business logic)
→ calls Supabase admin SDK internally
Вы сохраняете высокую скорость и простоту обслуживания в режиме прямого подключения для большинства трафиков, а гибкость, характерную для бэкенда, оставляете для операций, которые её действительно требуют — при этом не создаётся полностью активный сервер для всего.