Классический сценарий: есть база данных и приложение на бэкенде. Для подключения достаточно знать адрес, порт, имя пользователя, пароль — и прямой доступ перед вами. Но что делать, если необходимо подключить 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 базам данных. На деле же это довольно удобное для абстрактного слоя решение, к которому можно адаптироваться, как мы выяснили в этой статье.
Что думаете на счет этого вы? Использовали ли подобные подходы в своих проектах? Делитесь опытом в комментариях!
