Классический сценарий: есть база данных и приложение на бэкенде. Для подключения достаточно знать адрес, порт, имя пользователя, пароль — и прямой доступ перед вами. Но что делать, если необходимо подключить no-code базу данных, которой можно управлять только через REST API? Есть ли способ интегрировать такие подключения в логику «красиво», не поломав архитектуру?
Привет, Хабр! Меня зовут Влад, в свободное время я занимаюсь разработкой. В этой статье расскажу, как можно относительно нативно интегрировать работу с платформой NocoDB на бэкенде, какие паттерны подходят и зачем мне понадобилось разработать собственный Python-модуль. Подробности под катом!
Особенности работы с NocoDB
NocoDB — это платформа с открытым исходным кодом, которая превращает базы данных в удобные таблицы и интерфейсы.
Если у вас есть собственный проект, которым занимаются люди без знания SQL, то подобное решение будет закрывать базовые потребности. Сотрудники смогут самостоятельно создавать и просматривать таблицы, добавлять собственные строки, настраивать необходимые поля и не только, а вы — легко управлять политиками доступа. Но уже на этом этапе проявляются все нюансы решения.
Создание базы данных в NocoDB
Допустим, ваш сотрудник создает базу с клиентами: все удобно, за пределы графического интерфейса выходить не нужно. Таблицы созданной базы поддерживают полный функционал NocoDB. Но все просто ровно до тех пор, пока вы не решите подключиться к базе со своего бэкенда. Спойлер: напрямую, например, через ORM вроде Django ORM или SQLAlchemy у вас это сделать не получится. Потому что к созданной внутри NocoDB базе доступ будет только через REST API.

Подключение внешней базы данных в NocoDB
Если же вам нужно прямое подключение к базе данных, например PostgreSQL или MySQL, при этом важно сохранить графическую оболочку для других участников проекта, можно создать интеграцию.

Тогда NocoDB становится «прослойкой» c GUI и REST API, что в большинстве случаев правильно. Ведь вы получаете возможность напрямую подключиться к базе данных с бэкенда и не теря��те в производительности.
Однако вы теряете полную интеграцию с функционалом NocoDB. Встроенные базы создаются и управляются напрямую, что обеспечивает максимальную совместимость — например, с триггерами, связями между таблицами, интерфейсом по умолчанию и прочим. Внешняя база данных может не поддерживать все «фичи» NocoDB из коробки. Кроме того, прямое подключение тесно связано с логикой ORM, из-за чего, например, при смене фреймворка придется переписывать слой бизнес-логики.
Выводы
REST API NocoDB — в целом, универсальный вариант подключения, если ваш проект небольших или средних размеров. Однако даже в таком случае важно сделать оговорку: для моделей вашей бизнес-логики, к которым пользователи обращаются с высокой частотой, все равно лучше использовать внешнюю базу данных с подключением напрямую. Это будет и производительнее, и безопаснее.
В остальных случаях можно использовать встроенные в NocoDB базы данных и REST API подключение. Например, для таблиц со списком товаров, если ваш проект — интернет-магазин. Такие данные изменяются нечасто, а если добавить кэширование, чтобы по каждому запросу модель не обращалась в NocoDB, разница в производительности между прямым подключением и REST API будет минимальной.

Облачные базы данных
Создайте готовую базу данных в облаке за 5 минут. Поддерживаем PostgreSQL, MySQL, Redis и не только.
Интеграция NocoDB
Допустим, вы определили, что чувствительные и часто обновляемые данные будете хранить во внешней базе данных, с которой можно работать посредством ORM. Тем временем «наиболее статичные» данные, например список товаров, будут храниться в базе NocoDB. Тогда сотрудникам будет проще ими управлять: актуализировать стоимость, информацию о количестве и не только.
Если за ORM ответственны, очевидно, модели, то для интеграции NocoDB нам нужен особый тип моделей — некий Repository. Попробуем его описать с помощью нескольких сущностей, вдохновившись Django ORM.
Repository
Обычно я выношу всю бизнес-логику подальше от функций представления — так архитектура становится чище и в целом логичнее. Так, если пришел запрос на список товаров, мы можем обратиться к списку объектов через Product.objects.filter(name='Творог') и вернуть json. А если поступил запрос на покупку, то достаточно вызвать пользовательский метод buy — и вернуть результат, например номер продукта.
Нам нужно в том же models.py научиться локанично описывать свои модели, так называемые репозитории, которые будут инкапсулировать логику доступа «к другим данным» (в нашем случае — к NocoDB) и предоставлять чистый абстрактный интерфейс для выполнения запросов и операций. Такой подход называют Repository Pattern.
def method_allowed(func):
def wrapper(self, *args, **kwargs):
if hasattr(self, 'allowed_methods') and self.allowed_methods is not None:
if func.__name__ not in self.allowed_methods:
raise AttributeError(f"Method '{func.__name__}' is not allowed.")
return func(self, *args, **kwargs)
return wrapper
class Repository:
allowed_methods = ['get', 'values', 'filter', 'registration']
def __init__(self):
self.table = self.__class__.data_source(
table_name=self.__class__.__name__
)
self.objects = self.Objects(
parent=self,
allowed_methods=getattr(self, 'allowed_methods', None)
)
class Objects:
def __init__(self, parent, allowed_methods=None):
self.parent = parent
self.allowed_methods = allowed_methods
def recache(self):
self.parent.table._data_cache = None
self.parent.table.data
@method_allowed
def get(self, **kwargs):
if not kwargs:
return self.parent.table.data
for item in self.parent.table.data:
if all(getattr(item, k, None) == v for k, v in kwargs.items()):
return item
raise AttributeError(f'No item found matching {kwargs}')
@method_allowed
def values(self, *args):
if not args:
return self.parent.table.data
fields = [field for field in args]
return self.parent.table.values(fields=fields)
@method_allowed
def filter(self, *, gt='', **kwargs):
expression_mock = {}
for field in kwargs.keys():
value = kwargs[field]
if '__in' in field:
field = field.replace('__in', '')
expression_mock[field] = value
expression_list = []
for field in expression_mock.keys():
if isinstance(expression_mock[field], list):
exp_part = '~or'.join([f'({field},eq,{value})' for value in expression_mock[field]])
else:
exp_part = f'({field},eq,{expression_mock[field]})'
expression_list.append(exp_part)
expression_list.append(gt)
expression = '~and'.join(expression_list)
return self.parent.table.filter(where=expression)
@method_allowed
def registration(self, **kwargs):
if not kwargs:
return Exception(f'Append is broken: undefined target')
try:
target = kwargs
self.parent.table.append(target=target)
except Exception as e:
raise Exception(f'Append is broken: {e}')
Выше — простая реализация Repository, на базе которого можно будет создавать модели со своими пользовательскими методами. Здесь можно «творить» как хочется — например, я добавил динамический интерфейс allowed_methods, который блокирует нежелательный доступ к методам репозитория, если они явно не разрешены. Вот, как это выглядит на практике:
from common.repository import Repository
from transactions.services.nocodb_agent import TransactionsData
from transactions.exceptions import TransactionError
class Transactions(models.Model):
class Shop(Repository):
data_source = TransactionsData
allowed_methods = ['registration']
@staticmethod
def buy(username, pcode):
try:
transactionCode = randomCode()
Transactions.Shop.objects.registration(
transactionCode=transactionCode,
username=username,
pcode=pcode,
action=__name__
)
return Shop.Items.objects.get(pcode=pcode).item
except InsufficientFunds as e:
raise TransactionError(message=e, code=e.code)
except Exception as e:
print(e)
Как видим, мы создали модель Transactions.Shop, которая наследуется от Repository и явно указывает, какие методы разрешены: только регистрация новых транзакций, чтобы нельзя было считать чужие. Но что такое data_source?
RepositoryData
Repository должен брать откуда-то данные и возвращать коллекцию. Для этого у него есть слой данных приложения (data/repositorydata) — RepositoryData, который реализует прямой доступ к REST API NocoDB. В нашем примере за доступ отвечает TransactionsData:
from pycodb import DataBase, BaseTable
DataBase.DOMAIN = '127.0.0.1:80'
DataBase.NOCODB_API_KEY = '...'
class TransactionsData(DataBase):
class Shop(BaseTable):
view_id = '...'
table_id = '...'
Иначе говоря: есть база данных Transactions, внутри которой — таблица Shop. Ключ и параметры доступа (view_id и table_id) можно взять из самого NocoDB. При этом вся основная логика уже реализована в моем модуле PycoDB.
Я написал PycoDB, так как сам активно использую NocoDB для хранения данных приложения. Это довольно простой модуль — но этим он и отличается от аналогичных решений. Так что смело делайте форк, предлагайте улучшения и используйте в своих проектах.
Итого, используя Repository Pattern и слой данных, который я называю RepositoryData, вы можете легко работать с базами данных по REST API в ORM-based формате. Но это мы начали «со сложного». Если вам просто нужен удобный интерфейс для работы с NocoDB, можно использовать PycoDB в отрыве от всего остального.
Погружение в PycoDB
Установка и начало работы
1. Для начала создадим базу данных и таблицу. Пусть это будет некая база SpecificData с таблицей Topics, которая заполнена тестовыми значениями.

2. Далее установим модули pycodb и requests с помощью менеджера пакетов PyPi:
pip3 install requests pycodb
3. Отлично. Теперь можем импортировать модуль в код своего сервиса, прописать домен или IP-адрес, по которому доступен NocoDB, и ключ доступа:
from pycodb import DataBase, BaseTable
DataBase.DOMAIN = 'Your_NocoDB_Domain_or_IP'
DataBase.NOCODB_API_KEY = 'Your_NocoDB_API_KEY'
4. Следующим шагом нужно описать структуру. Для этого нам понадобятся два основных суперкласса, от которых будет наследоваться RepositoryData: класс базы данных и таблицы.
from pycodb import DataBase, BaseTable
DataBase.DOMAIN = 'Your_NocoDB_Domain_or_IP'
DataBase.NOCODB_API_KEY = 'Your_NocoDB_API_KEY'
class SpecificData(DataBase):
class Topics(BaseTable):
view_id = 'View_Id' # можно посмотреть в Swagger базы данных
table_id = 'Table_Id' # можно скопировать в списке таблиц
Получение данных
Можно приступать к работе с NocoDB. Сейчас REST API максимально скрыт за объектной мод��лью (но без лишних абстракций, так как это сервисный слой) и мы можем, например, вывести список всех записей:
all_topics = SpecificData(table_name='Topics').data
print(all_topics)
Вывод:
DataProxy([<DataItem Id=1, username=username1, Date time=2025-11-11 21:31:13+00:00, hash=hash1>, <DataItem Id=2, username=username2, Date time=2025-11-11 21:31:24+00:00, hash=hash2>, <DataItem Id=3, username=username3, Date time=2025-11-11 21:31:29+00:00, hash=hash3>, <DataItem Id=4, username=username4, Date time=2025-11-11 21:31:32+00:00, hash=hash4>])
Отлично! В качестве записей в таблице Topics мы получили коллекции DataProxy.
Фильтрация данных
Если нет цели получать все подряд данные, можем воспользоваться методом filter:
filtered_topics = Transactions(table_name='Topics').filter(where='(username,eq,username1)')
print(filtered_topics)
Вывод:
DataProxy([<DataItem Id=1, username=username1, Date time=2025-11-11 21:31:13+00:00, hash=hash1>])
В целом, с помощью метода filter можно задавать любые query-параметры, упрощающие получение данных из NocoDB. Полный список можно посмотреть в таблице.
Получение определенных полей
Допустим, что в таблице есть конфиденциальные данные, которые не стоит лишний раз получать на бэкенде во избежании утечек. В таком случае можно воспользоваться методом values:
limited_topics = Transactions(table_name='Topics').values(['Id', 'username'])
print(limited_topics)
Вывод:
DataProxy([<DataItem Id=1, username=username1>, <DataItem Id=2, username=username2>, <DataItem Id=3, username=username3>, <DataItem Id=4, username=username4>])
Добавление данных
Наконец, если нужно добавить новую запись в таблицу, достаточно использовать метод append и параметр target:
Transactions(table_name='Topics').append(target={
'username':'username9999', 'hash':'hash9999'
})
new_topic = Transactions(table_name='Topics').filter(where='(username,eq,username9999)')
print(new_topic)
Вывод:
DataProxy([<DataItem Id=5, username=username9999, Date time=2025-11-11 22:23:41+00:00, hash=hash9999>])
Разумеется, PycoDB еще нужно дорабатывать, так как его функционал я расширял по мере необходимости. Так, например, нет элементарных действий вроде удаления или обновления записей — потому что такие важные операции я предпочитаю выполнять вручную. Так что будут рад увидеть ваши форки на GitHub.
Заключение
Надеюсь, мой материал оказался для вас полезным. Repository Pattern — то, к чему я пришел не сразу, так как старался лишний раз не прибегать к no-code базам данных. На деле же это довольно удобное для абстрактного слоя решение, к которому можно адаптироваться, как мы выяснили в этой статье.
Что думаете на счет этого вы? Использовали ли подобные подходы в своих проектах? Делитесь опытом в комментариях!