Что такое модель и зачем она нужна
В любой полной стек‑приложении на Next.js данные сохраняются в базе, обычно MongoDB. Модель — это описание структуры документов, которые будут храниться в коллекции. Она задаёт типы полей, обязательность, уникальность и любые дополнительные ограничения. Без модели приложение рискует сохранять «разбросанные» записи, что усложняет валидацию, поиск и поддержку кода. Модель выступает как «чертёж» данных: каждый документ, прошедший через неё, гарантирует соответствие заявленным правилам.
Выбор Mongoose для работы с MongoDB
Mongoose — популярный ODM (Object‑Document Mapper) для Node.js, который предоставляет декларативный API для описания схем и моделей. Он автоматически преобразует JavaScript‑объекты в BSON‑документы, поддерживает валидацию, хуки (middleware) и удобные методы запросов. В контексте Next.js Mongoose легко интегрируется как в API‑роуты, так и в серверные функции (getServerSideProps, getStaticProps).
Защита паролей: установка bcrypt
Хранить пароли в открытом виде недопустимо. Для их безопасного сохранения используется хеширование с солью. Наиболее распространённый пакет — bcryptjs, который полностью написан на JavaScript и не требует нативных зависимостей, что упрощает деплой в сервер‑лесных средах (Vercel, Netlify).
npm i bcryptjs
npm i -D @types/bcryptjs # типы для TypeScript
bcryptjs предоставляет функцию hash(password, saltRounds), где saltRounds определяет степень сложности вычислений (обычно 10‑12). При проверке вводимого пароля используется compare(plain, hash).
Описание модели пользователя
Ниже представлен типичный пример модели User в проекте на TypeScript. Она включает обязательные поля email и password, автоматически генерируемые метки времени и хук, который хеширует пароль перед сохранением.
import mongoose, { Schema, model, models } from 'mongoose';
import bcrypt from 'bcryptjs';
export interface IUser {
email: string;
password: string;
_id?: mongoose.Types.ObjectId;
createdAt?: Date;
updatedAt?: Date;
}
const userSchema = new Schema<IUser>(
{
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
},
{ timestamps: true }
);
// Хешируем пароль только если он изменён
userSchema.pre('save', async function () {
if (this.isModified('password')) {
this.password = await bcrypt.hash(this.password, 10);
}
});
const User = models.User || model<IUser>('User', userSchema);
export default User;
Ключевые моменты
- Типизация – интерфейс
IUserфиксирует типы полей, что повышает автодополнение и предотвращает ошибки на этапе компиляции. - Уникальность email – параметр
unique: trueзаставляет MongoDB создавать уникальный индекс, избавляя от дублирования учётных записей. - Тайм‑стампы – опция
timestamps: trueавтоматически добавитcreatedAtиupdatedAt. - Middleware
pre('save')– гарантирует, что пароль будет захеширован каждый раз при сохранении, но не будет повторно хеширован, если поле не менялось.
Пример модели видео
Для контента, например видеороликов, часто требуется хранить заголовок, описание, ссылку на файл и связь с пользователем‑автором. Ниже – простая схема:
import mongoose, { Schema, model, models } from 'mongoose';
export interface IVideo {
title: string;
description?: string;
url: string;
owner: mongoose.Types.ObjectId; // ссылка на User
_id?: mongoose.Types.ObjectId;
createdAt?: Date;
updatedAt?: Date;
}
const videoSchema = new Schema<IVideo>(
{
title: { type: String, required: true },
description: { type: String },
url: { type: String, required: true },
owner: { type: Schema.Types.ObjectId, ref: 'User', required: true },
},
{ timestamps: true }
);
const Video = models.Video || model<IVideo>('Video', videoSchema);
export default Video;
Что важно учесть
- Ссылка
ref: 'User'позволяет использоватьpopulateдля подгрузки данных автора без дополнительных запросов. - Опциональные поля (
description) объявляются безrequired, что даёт гибкость при создании записей. - Валидация URL может быть расширена кастомным валидатором, проверяющим, что строка действительно является корректным адресом.
Интеграция моделей в API‑роуты Next.js
Для работы с моделями в серверных функциях Next.js достаточно открыть соединение с MongoDB один раз и переиспользовать его. Частый паттерн — файл lib/mongoose.ts:
import mongoose from 'mongoose';
const MONGODB_URI = process.env.MONGODB_URI!;
if (!MONGODB_URI) {
throw new Error('Missing MONGODB_URI environment variable');
}
let cached = global.mongoose;
if (!cached) {
cached = global.mongoose = { conn: null, promise: null };
}
export async function connect() {
if (cached.conn) return cached.conn;
if (!cached.promise) {
const opts = {
bufferCommands: false,
};
cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => mongoose);
}
cached.conn = await cached.promise;
return cached.conn;
}
В API‑роуте (pages/api/auth/register.ts) используем модели так:
import type { NextApiRequest, NextApiResponse } from 'next';
import { connect } from '@/lib/mongoose';
import User from '@/models/User';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
await connect();
if (req.method !== 'POST') {
return res.status(405).end();
}
const { email, password } = req.body;
try {
const user = await User.create({ email, password });
res.status(201).json({ id: user._id });
} catch (err: any) {
res.status(400).json({ error: err.message });
}
}
Практические рекомендации
- Обрабатывайте ошибки уникального индекса (
E11000) и возвращайте клиенту понятные сообщения. - Не возвращайте хеш пароля в ответах API; используйте DTO (Data Transfer Object) без чувствительных полей.
- Проверяйте вводимые данные (email‑валидация, минимальная длина пароля) до передачи в модель, чтобы сократить количество исключений на уровне базы.
Заключительные мысли о моделях в Next.js
Моделирование данных через Mongoose упрощает работу с MongoDB, делая код более предсказуемым и безопасным. Правильное определение схем, использование middleware для хеширования паролей и аккуратная интеграция в API‑роуты позволяют построить надёжный бэкенд, полностью совместимый с сервер‑ленд Next.js. При разработке новых сущностей следует придерживаться единого стиля: типизированные интерфейсы, обязательные поля, индексы и, где необходимо, ссылки (ref) для построения отношений между документами. Такой подход минимизирует баги, ускоряет развитие проекта и облегчает масштабирование в будущем.