Рекомендации по проекциям для ответов API
Вы спроектируете проекции в соответствии со структурой ответов REST API, уменьшив размер полезной нагрузки и защитив конфиденциальные поля.
«Рекомендации по проекциям для ответов API» — бесплатный урок MongoDB Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения MongoDB Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс MongoDB Academy содержит 4 уроков всего.
Согласование проекций со структурой ответа API
У каждой конечной точки REST или GraphQL, которую предоставляет Ваше приложение, есть определённая форма ответа. Идеальная проекция MongoDB возвращает ровно те поля, которые необходимы этой форме, — ни больше ни меньше. Когда проекция соответствует контракту ответа API, Вы избегаете двух распространённых антипаттернов: избыточной выборки (возврата полей, которые конечная точка никогда не отправляет) и недостаточной выборки (возврата полей, из-за которых требуется второй запрос).
Определяйте константы проекций
Жёсткое задание объектов проекций непосредственно в каждом запросе со временем приводит к дублированию и расхождениям. Определяйте константы проекций рядом с функциями доступа к данным или репозиториями. Если форма ответа API изменится, Вам потребуется обновить одну константу, а не искать каждый запрос в кодовой базе.
// projections.js — centralised projection definitions
export const USER_PUBLIC = { _id: 0, username: 1, avatarUrl: 1, createdAt: 1 };
export const USER_PROFILE = { _id: 0, username: 1, email: 1, bio: 1, avatarUrl: 1 };
export const USER_ADMIN = { _id: 0, username: 1, email: 1, role: 1, lastLoginAt: 1, isActive: 1 };
// Usage
const user = await db.collection('users').findOne({ username: 'alice' }, { projection: USER_PROFILE });Никогда не возвращайте клиентам конфиденциальные поля
Такие поля, как passwordHash, totpSecret, apiKey, ssn и paymentMethodToken, никогда не должны появляться в ответах API. Определите защищённую базовую проекцию, которая по умолчанию исключает эти поля, и запрашивайте их только во внутренних вызовах сервисов, которым они действительно нужны. Применяйте принцип минимальных привилегий на уровне данных.
// Always exclude sensitive fields from user queries
const SECURE_USER_BASE = {
passwordHash: 0,
totpSecret: 0,
resetToken: 0
};
// All user API responses go through this projection
const user = await db.collection('users').findOne(
{ _id: userId },
{ projection: SECURE_USER_BASE }
);
// Result never contains passwordHash or totpSecretКонечные точки списков: проецируйте только сводные поля
Конечные точки списков (например, GET /products) обычно возвращают сводку по каждому элементу, а не полный документ. В списке товаров могут отображаться name, price, thumbnailUrl и rating, но не полный массив description, specifications или reviews. Использование компактной проекции в запросах списков может уменьшить размер полезной нагрузки на 90%, если полные документы содержат большие тексты или массивы.
const PRODUCT_SUMMARY = {
_id: 0,
slug: 1,
name: 1,
price: 1,
thumbnailUrl: 1,
rating: 1,
reviewCount: 1
};
// GET /products — lightweight list query
const products = await db.collection('products')
.find({ category: 'electronics', isActive: true })
.projection(PRODUCT_SUMMARY)
.sort({ rating: -1 })
.limit(20)
.toArray();Конечные точки подробных данных: проецируйте полный объект
Конечные точки подробных данных (например, GET /products/:slug) возвращают более полное представление одного документа. Даже в этом случае подумайте об исключении полей, предназначенных только для внутреннего использования. Можно спроецировать все общедоступные поля, скрыв внутреннюю себестоимость, контактные данные поставщика или идентификаторы исходной системы учёта запасов, которые клиенты не должны видеть.
const PRODUCT_DETAIL = {
supplierCost: 0, // internal — never expose to clients
warehouseLocation: 0, // internal
syncedFromErpAt: 0 // internal audit field
};
// GET /products/:slug — rich detail query
const product = await db.collection('products').findOne(
{ slug: req.params.slug, isActive: true },
{ projection: PRODUCT_DETAIL }
);Используйте проекции и в конвейерах агрегации
Рекомендации по работе с проекциями распространяются и на конвейер агрегации. Размещайте этап $project после $match и перед затратными этапами, такими как $lookup или $unwind, чтобы уменьшить размер документов, проходящих через конвейер. Чем меньше документы в конвейере, тем меньше памяти и ресурсов CPU требуется на сервере.
db.orders.aggregate([
{ $match: { status: 'shipped', customerId: ObjectId('c1') } },
// Project early to reduce document size before $lookup
{ $project: { total: 1, createdAt: 1, customerId: 1, _id: 0 } },
{
$lookup: {
from: 'customers',
localField: 'customerId',
foreignField: '_id',
as: 'customer'
}
}
]);Проекции и версионирование API
Если Вы добавляете новые поля в документы MongoDB, старые клиенты API могут их не ожидать. Использование строгих проекций включения (с указанием ровно тех полей, которые нужно вернуть) означает, что новые поля документов невидимы существующим потребителям API, пока Вы явно не добавите их в проекцию. Это создаёт естественную границу версионирования: обновляйте проекцию при обновлении версии API.
// v1 projection — stable contract for existing clients
export const USER_V1 = { _id: 0, username: 1, email: 1 };
// v2 projection — includes new avatarUrl and bio fields
export const USER_V2 = { _id: 0, username: 1, email: 1, avatarUrl: 1, bio: 1 };Проверяйте соответствие проекций схемам ответов
Пишите модульные тесты, проверяющие соответствие объекта проекции MongoDB схеме ответа API (например, схеме Joi или типу TypeScript). Это предотвращает распространённую ошибку: разработчик добавляет поле в тип ответа API, но забывает включить его в проекцию. В рабочей среде поле возвращается как undefined, хотя проверки типов TypeScript проходят.
// Example test asserting projection covers all required response fields
const USER_RESPONSE_FIELDS = ['username', 'email', 'avatarUrl'];
const projection = { username: 1, email: 1, avatarUrl: 1, _id: 0 };
for (const field of USER_RESPONSE_FIELDS) {
if (projection[field] !== 1) {
throw new Error('Projection missing field: ' + field);
}
}
console.log('Projection covers all required response fields');Избегайте несоответствия проекций в Mongoose
Схемы Mongoose с параметром select: false для поля не позволяют этому полю появляться в результатах запросов, если его явно не включить повторно. Сочетайте это со схемными виртуальными полями для вычисления производных значений без их хранения. Вместе эти средства позволяют обеспечить безопасную проекцию по умолчанию на уровне модели и снизить вероятность случайной утечки данных из-за отсутствующей проекции запроса.
const userSchema = new mongoose.Schema({
username: String,
email: String,
// Excluded from all queries by default — must explicitly use +passwordHash
passwordHash: { type: String, select: false },
// Virtual — computed, not stored, never in DB
get displayName() { return this.username.toUpperCase(); }
});
userSchema.virtual('displayName').get(function() {
return this.username.toUpperCase();
});Отслеживайте эффективность проекций
Используйте explain('executionStats')
nReturned с keysExamined и docsExamined. Если docsExamined равно числу найденных документов (а не нулю), проекция не покрывается индексом, но всё равно работает корректно — Вы можете оценить, оправданы ли затраты на обслуживание покрывающего индекса.const result = await db.collection('users').find(
{ role: 'admin' },
{ projection: { username: 1, email: 1, _id: 0 } }
).explain('executionStats');
console.log('Docs examined:', result.executionStats.totalDocsExamined);
console.log('Keys examined:', result.executionStats.totalKeysExamined);
console.log('Docs returned:', result.executionStats.nReturned);Итоги: контрольный список рекомендаций по проекциям
Применяйте этот контрольный список к каждому запросу MongoDB в API:
- Определяйте именованные константы проекций — по одной на конечную точку или форму ответа
- Используйте режим включения для ответов API — указывайте ровно то, что Вам нужно
- Всегда исключайте конфиденциальные поля — passwordHash, токены, внутренние идентификаторы
- Используйте компактные проекции для конечных точек списков — только сводные данные, без больших текстов
- Размещайте $project в начале конвейеров агрегации — уменьшайте объём данных, передаваемых дальше
Быстрая проверка
Проверьте, насколько хорошо Вы усвоили концепции MongoDB и баз данных NoSQL из этого урока.
Итоги урока
В этом уроке Вы узнали, что константы проекций следует определять для каждой конечной точки API, чтобы предотвратить расхождения, конфиденциальные поля всегда нужно исключать из запросов, предназначенных для клиентов, а раннее размещение $project в конвейерах агрегации снижает нагрузку на память. Далее мы рассмотрим сортировку и разбиение на страницы, чтобы эффективно упорядочивать большие наборы результатов и просматривать их постранично.
Изучай JavaScript с ИИ-репетитором — бесплатно
Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.
- Курсы
- 30
- Уроки
- 120
Часто задаваемые вопросы
Урок «Рекомендации по проекциям для ответов API» бесплатный?
Да — полный текст урока «Рекомендации по проекциям для ответов API» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс MongoDB Academy, подпишись на CoddyKit PRO. Курс MongoDB Academy содержит 4 уроков всего.
Чему я научусь в уроке «Рекомендации по проекциям для ответов API»?
Вы спроектируете проекции в соответствии со структурой ответов REST API, уменьшив размер полезной нагрузки и защитив конфиденциальные поля. Ты практикуешь MongoDB Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать MongoDB Academy?
Предыдущий опыт не требуется. MongoDB Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Рекомендации по проекциям для ответов API»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке MongoDB Academy?
Да. Каждый урок MongoDB Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Проекции с включением и исключением
- Проецирование вложенных полей и полей массивов
- Проекции массивов с $ и $elemMatch
- Рекомендации по проекциям для ответов API