Как заставить ваш браузер напрямую общаться с базой данных

Нет, вы невнимательно прочитали. Это PostgREST, а не Postgres. Несмотря на то, что название может быть неверно истолковано и недружелюбно для поисковых систем, оно прекрасно отражает то, чем занимается проект — добавление слоя RESTful API в PostgreSQL. Эта история рассказывает, что это такое, как это работает и для каких сценариев подходит лучше всего.

Эта статья является частью серии статей о библиотеках и сервисах, которые упрощают создание серверной части веб-приложений. Другие статьи вы можете найти здесь:



Современная веб-архитектура без серверной части — использование «Supabase
Давайте поговорим об облачном сервисе, который может значительно сократить время выхода на рынокbetterprogramming.pub»





Почему уровень RESTful?

Большинство веб-приложений — это просто диалоги между браузером и базой данных. Однако это редко происходит напрямую; есть посредники: балансировщик нагрузки, API-шлюз, серверная служба и т. д. Поэтому возникает естественный вопрос: могу ли я отказаться от этих посредников и позволить моему браузеру напрямую общаться с базой данных?

Да, ты можешь! Это именно то, что предлагает PostgREST. Но очевидно, что он не может наивно выставлять операции базы данных на волю, не решив сначала две незначительные проблемы:

  1. Пользователи должны пройти аутентификацию и авторизацию. Раньше это была работа бэкенда.
  2. Соединения от большого количества клиентов могут быстро исчерпать пул соединений Postgres. Требуется некоторый дополнительный пул.

Как это работает?

Чтобы использовать PostgREST, вы настраиваете файл конфигурации, указываете строку подключения к фактической базе данных Postgres, а затем запускаете ее серверный процесс. Он анализирует схему вашей базы данных и сразу же начинает отвечать на HTTP-запросы.

Строго говоря, PostgREST не следует соглашениям RESTful, поскольку не использует вложенных маршрутов. Скажем, если у вас есть ресурс User в REST, по соглашению доступ к одному пользователю должен осуществляться через:

GET /user/1

в то время как в PostgREST вы получаете это так:

GET /user?id=1

Его API выглядит как смесь RESTful и GraphQL. Он использует HTTP-глаголы, как и REST, и позволяет выполнять очень гибкие клиентоориентированные запросы (например, извлечение связанных сущностей за один вызов), аналогично GraphQL.

Вот несколько быстрых примеров:

  • Выбор и фильтрация
// Get all paid users who're at least 18-year-old
GET /user?age=gte.18&paid=is.true
  • Пейджинг и ограничение
// Get 15 users starting at offset 30
GET /user?limit=15&offset=30
  • Встраивание (извлечение связанных сущностей)
// Get users, selecting their last names 
// together with titles of their blog posts
GET /user?select=last_name,post(title)
  • Вставка
POST /user
{ "name": "J Doe", "age": 23 }

Его синтаксис запроса довольно универсален. Если вы ограничены выразительностью синтаксиса, всегда есть выход — создайте представление базы данных (где вы можете выполнять произвольное объединение/фильтрацию/…), и PostgREST также автоматически отображает его как ресурс RESTful.

Что насчет безопасности?

Без надлежащих механизмов контроля доступа эта штука едва ли полезна. К счастью, PostgREST предназначен для того, чтобы база данных находилась в центре безопасности API.

Давайте разберем его и разберем по частям. Здесь мы используем простую систему ведения блога в качестве примера.

1. Процесс регистрации

Когда пользователь регистрируется, система аутентификации создает новую запись пользователя. В то же время он создает «роль» на стороне базы данных. «Роль» в Postgres аналогична концепции пользователя или группы пользователей. Он используется для предоставления доступа к ресурсам базы данных и назначения разрешений на уровне строк. Мы вернемся к этому чуть позже.

Служба аутентификации может быть серверной службой, сторонней (например, Auth0) или даже самим PostgREST. Его единственная задача — создавать и аутентифицировать пользователей.

2. Разрешения на уровне строк

Наше требование безопасности заключается в том, что владелец сообщения может делать с сообщением все, а пользователь может читать все сообщения, которые были опубликованы (т. е. не в статусе черновика).

Мы можем выразить правила с безопасностью на уровне строк Postgres (RLS) следующим образом:

CREATE POLICY post_owner_policy ON post
    USING (owner = current_user);

CREATE POLICY post_read_policy ON post FOR SELECT
    USING (published = true);

Вы можете думать о RLS как об автоматически вставленных предложениях WHERE во время запроса и изменения. current_user — это встроенная переменная, представляющая текущую роль базы данных. Не волнуйся; вы скоро увидите, откуда она исходит.

3. Токен JWT

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

PostgREST позволяет вам использовать токен JWT в HTTP-заголовке авторизации при выполнении запросов. Токен обычно создается и подписывается службой аутентификации, упомянутой на шаге 1. Сервер PostgREST делится секретом, используемым для подписи, чтобы убедиться, что токен не подделан.

Когда пользователь входит в систему, служба аутентификации выдает токен JWT:

Внутри полезной нагрузки токена должно быть как минимум поле «роль», указывающее соответствующую роль пользователя в базе данных:

{
    ...
    "role": "user123"
}

4. Смена ролей

Наконец, когда пользователь выполняет вызов RESTful, он отправляет токен JWT, полученный при входе в систему, вместе с запросом CRUD в PostgREST.

PostgREST проверяет токен, извлекает информацию о роли из полезной нагрузки, а затем переключается на роль в текущем сеансе с базой данных (как при выполнении su в Linux). Наконец, операция CRUD отправляется в базу данных.

Благодаря «переключению ролей» операция базы данных выполняется с правильным current_use, поэтому безопасность на уровне строк, которую мы установили на шаге № 2, может сработать и отклонить недействительные запросы.

Уф, у нас наконец-то есть безопасная система! У него есть некоторые особенности, но я надеюсь, что вы получили общее представление.

Больше, чем CRUD

Что интересно в PostgREST, так это то, что таблицы, представления и хранимые процедуры отображаются на API. Если хранимые процедуры вам чужды, подумайте о них как о пользовательской функции внутри базы данных.

Например, хранимая процедура, например:

  ; A stored procedure to add two integers

  CREATE FUNCTION add_them(a integer, b integer)
  RETURNS integer AS $$
    SELECT a + b;
  $$ LANGUAGE SQL IMMUTABLE;

сопоставляется с:

// mapped to an endpoint under /rpc
POST /rpc/add_them
{ "a": 1, "b": 2 }

=> 3

Это открывает много возможностей, потому что процедуры хранилища могут делать и другие вещи, кроме CRUD, с гарантией транзакций. Фактически это означает, что вы можете использовать его для реализации внутренних API-интерфейсов, которые не являются просто манипуляциями с БД. Например, расширение pgsql-http позволяет делать HTTP-запросы. Должны ли мы тогда создать API-шлюз с PostgREST?

Мне все еще нужен бэкенд?

Хороший вопрос. Да и нет.

Если ваше приложение в основном CRUD, вероятно, вы можете использовать исключительно PostgREST в качестве своего бэкэнда. Настройте кучу узлов PostgREST с Nginx перед ними, и вы получите аккуратную архитектуру.

Но многие приложения выходят за рамки простого CRUD. Да, с хранимыми процедурами можно творить чудеса, но иногда это слишком — например, перекодирование видео с помощью хранимой процедуры.

Вызов PostgREST из серверных служб также является допустимым сценарием, если ваш сервер работает на границе. Многие пограничные среды, такие как пограничная среда выполнения Vercel, поддерживают только протокол HTTP. Это означает, что вы не можете напрямую подключиться к своей базе данных Postgres. По другой причине вам все равно не следует этого делать: граничные узлы часто создаются и уничтожаются, что будет продолжать взбалтывать пул соединений вашей базы данных. После этого PostgREST можно использовать в качестве прокси-сервера данных и пула соединений.

Это хороший выбор для меня?

В основном это зависит от двух существенных факторов:

1. Вы согласны с тем, что вас запирают в базе данных Postgres?

Бэкэнд-сервис, использующий ORM, позволяет вам в будущем переключаться на другие базы данных без многих (даже каких-либо) изменений кода. С PostgREST вы используете Postgres.

2. Вам удобно работать с SQL?

Правда, с RESTful API вам не нужно писать SQL в коде; однако настройка разрешений, создание представлений, добавление безопасности на уровне строк и написание хранимых процедур — это нетривиальная работа SQL.

Заворачивать

PostgREST — отличный инструмент, который выполняет одну единственную миссию и делает это хорошо. Это позволяет иметь более простую архитектуру и избежать бремени кодирования скучных API-интерфейсов CURD.

Что-то, что стоит рассмотреть в вашем следующем проекте.

Want to Connect?

I'm the creator of ZenStack, a toolkit that supercharges
Prisma ORM with a powerful access control layer and
unleashes its full potential for full-stack development.
Our goal is to let you save time writing boilerplate code
and focus on building what matters - the user experience.