Ответ
В Node.js-приложениях выбор структуры зависит от типа базы данных. Вот основные подходы:
1. Для реляционных БД (PostgreSQL, MySQL) — Связующая таблица (Many-to-Many): Это наиболее нормализованный и гибкий способ.
-- Таблицы
CREATE TABLE users (id SERIAL PRIMARY KEY, email VARCHAR(255));
CREATE TABLE roles (id SERIAL PRIMARY KEY, name VARCHAR(50));
-- Связующая таблица
CREATE TABLE user_roles (
user_id INT REFERENCES users(id) ON DELETE CASCADE,
role_id INT REFERENCES roles(id) ON DELETE CASCADE,
PRIMARY KEY (user_id, role_id)
);
Работа через Sequelize (ORM):
// Модели с ассоциацией Many-to-Many
User.belongsToMany(Role, { through: 'UserRole' });
Role.belongsToMany(User, { through: 'UserRole' });
// Назначение ролей пользователю
const user = await User.findByPk(1);
const adminRole = await Role.findOne({ where: { name: 'admin' } });
await user.addRole(adminRole);
// Получение пользователя с его ролями
const userWithRoles = await User.findByPk(1, {
include: Role
});
2. Для документ-ориентированных БД (MongoDB) — Массив в документе: Простой и быстрый способ для случаев, когда набор ролей ограничен и не требует сложных запросов.
// Схема Mongoose
const userSchema = new mongoose.Schema({
email: String,
roles: [{
type: String,
enum: ['user', 'editor', 'admin', 'moderator'] // Валидация
}]
});
// Добавление роли
await User.updateOne(
{ _id: userId },
{ $addToSet: { roles: 'editor' } } // $addToSet предотвращает дубликаты
);
3. Битовая маска (для простых, статичных наборов прав): Эффективно по памяти, но менее гибко и читаемо.
// Определение ролей как степеней двойки
const ROLES = {
USER: 1 << 0, // 1
EDITOR: 1 << 1, // 2
ADMIN: 1 << 2, // 4
MODERATOR: 1 << 3 // 8
};
// Пользователь - редактор и модератор
user.permissions = ROLES.EDITOR | ROLES.MODERATOR; // 10 (2 + 8)
// Проверка роли
const isEditor = (user.permissions & ROLES.EDITOR) === ROLES.EDITOR;
Выбор подхода:
- Связующая таблица — для сложных систем с динамическим управлением ролями, ACL (Access Control Lists).
- Массив в документе — для микросервисов или приложений, где данные пользователя часто читаются целиком.
- Битовая маска — для высоконагруженных систем с простой, неизменной логикой проверки прав.
Ответ 18+ 🔞
А, ну это же классика, ёпта! Сидишь ты такой, пишешь приложение на Node.js, и тут бац — нужно роли пользователям раздавать. И тут начинается: а как, блядь, это правильно организовать? Способов-то дохуя, и каждый норовит тебе мозг выебать по-своему.
Смотри, чувак, тут всё от твоей базы данных зависит, как от печки плясать. Если ты с PostgreSQL или MySQL дружишь, то нормальные пацаны делают через связующую таблицу. Это типа золотой стандарт, чтоб потом не охуеть от своей же архитектуры.
-- Таблицы
CREATE TABLE users (id SERIAL PRIMARY KEY, email VARCHAR(255));
CREATE TABLE roles (id SERIAL PRIMARY KEY, name VARCHAR(50));
-- Связующая таблица
CREATE TABLE user_roles (
user_id INT REFERENCES users(id) ON DELETE CASCADE,
role_id INT REFERENCES roles(id) ON DELETE CASCADE,
PRIMARY KEY (user_id, role_id)
);
В коде через Sequelize это выглядит как по маслу, если, конечно, ты не распиздяй.
// Модели с ассоциацией Many-to-Many
User.belongsToMany(Role, { through: 'UserRole' });
Role.belongsToMany(User, { through: 'UserRole' });
// Назначение ролей пользователю
const user = await User.findByPk(1);
const adminRole = await Role.findOne({ where: { name: 'admin' } });
await user.addRole(adminRole);
// Получение пользователя с его ролями
const userWithRoles = await User.findByPk(1, {
include: Role
});
А теперь, если ты на MongoDB сидишь, то там вообще манда с ушами простая. Зачем тебе лишние коллекции, когда можно роли прямо в документ пользователя запихнуть, как носки в чемодан?
// Схема Mongoose
const userSchema = new mongoose.Schema({
email: String,
roles: [{
type: String,
enum: ['user', 'editor', 'admin', 'moderator'] // Валидация
}]
});
// Добавление роли
await User.updateOne(
{ _id: userId },
{ $addToSet: { roles: 'editor' } } // $addToSet предотвращает дубликаты
);
Ну и для самых отчаянных перфекционистов, у которых терпения ноль ебать и нужно всё супер-быстро, есть старый добрый трюк — битовая маска. Выглядит, конечно, как шифровка от шпиона, но работает быстрее, чем я утром на работу собираюсь.
// Определение ролей как степеней двойки
const ROLES = {
USER: 1 << 0, // 1
EDITOR: 1 << 1, // 2
ADMIN: 1 << 2, // 4
MODERATOR: 1 << 3 // 8
};
// Пользователь - редактор и модератор
user.permissions = ROLES.EDITOR | ROLES.MODERATOR; // 10 (2 + 8)
// Проверка роли
const isEditor = (user.permissions & ROLES.EDITOR) === ROLES.EDITOR;
Так какого хуя выбрать-то?
- Связующая таблица — это когда у тебя система сложная, как жизнь, и роли могут плодиться, как кролики. Идеально для всяких ACL, где права — это отдельная песня.
- Массив в документе — твой выбор, если ты в микросервисах шаришь и тебе похуй на нормальные формы, лишь бы быстро достать всё про пользователя одним запросом.
- Битовая маска — это когда ты сам от себя охуел от требований к производительности, а логика прав проще, чем дважды два. Только не забудь потом, что там и как работает, а то через месяц будешь сидеть и думать, что за хуйню ты написал.
Вот и вся философия, блядь. Выбирай, что подходит, и не выебывайся с архитектурой просто так.