Episode 10 of 17
Selecting Fields (Projection)
Learn how to use MongoDB projections to select specific fields in query results. Master inclusion and exclusion projections, hide the _id field, project array elements with $slice and $elemMatch, and use computed fields.
By default, find() returns all fields in matching documents. In production, you rarely need every field. Projections let you specify exactly which fields to include or exclude, reducing network overhead and improving performance.
Basic Projection
Pass a second argument to find() to specify the projection:
// Include only name and email (plus _id by default)
db.users.find(
{}, // Filter (empty = all documents)
{ name: 1, email: 1 } // Projection
)
// Output:
// { _id: ObjectId(...), name: "Alice", email: "alice@example.com" }
// { _id: ObjectId(...), name: "Bob", email: "bob@example.com" }
Inclusion vs Exclusion
// INCLUSION — specify fields TO include (value: 1)
db.users.find({}, { name: 1, email: 1, age: 1 })
// Returns: _id, name, email, age
// EXCLUSION — specify fields TO exclude (value: 0)
db.users.find({}, { password: 0, internalNotes: 0 })
// Returns: everything EXCEPT password and internalNotes
// IMPORTANT: You cannot mix inclusion and exclusion
// This is INVALID:
db.users.find({}, { name: 1, password: 0 }) // Error!
// EXCEPTION: _id is the only field you can exclude with inclusions
db.users.find({}, { name: 1, email: 1, _id: 0 }) // Valid!
Hiding _id
// By default, _id is always included. Exclude it explicitly:
db.users.find(
{},
{ name: 1, email: 1, _id: 0 }
)
// Output (no _id):
// { name: "Alice", email: "alice@example.com" }
// { name: "Bob", email: "bob@example.com" }
Projecting Nested Fields
// Include nested fields using dot notation
db.orders.find(
{},
{
orderNumber: 1,
"customer.name": 1,
"customer.email": 1,
total: 1
}
)
// Output:
// {
// _id: ObjectId(...),
// orderNumber: "ORD-001",
// customer: { name: "John", email: "john@example.com" },
// total: 78000
// }
Array Projections
$slice — Limit Array Elements
// Return only the first 3 comments
db.posts.find(
{},
{ title: 1, comments: { $slice: 3 } }
)
// Return the last 2 comments
db.posts.find(
{},
{ title: 1, comments: { $slice: -2 } }
)
// Skip 5, return the next 3 (pagination within arrays)
db.posts.find(
{},
{ title: 1, comments: { $slice: [5, 3] } }
)
$elemMatch — Return First Matching Array Element
// Return only the first item in the order that costs > 50000
db.orders.find(
{},
{
orderNumber: 1,
items: {
$elemMatch: { price: { $gt: 50000 } }
}
}
)
$ Positional — Return Matched Array Element
// When filtering by array element, return only the matched element
db.orders.find(
{ "items.product": "Laptop" },
{ "items.$": 1 }
)
// Returns only the "Laptop" item from the items array
Projection with findOne()
// Works exactly the same way
db.users.findOne(
{ email: "alice@example.com" },
{ name: 1, role: 1, _id: 0 }
)
// Output: { name: "Alice", role: "developer" }
Using Projection with Aggregation
// $project stage in aggregation pipeline
db.users.aggregate([
{ $match: { role: "developer" } },
{
$project: {
_id: 0,
fullName: { $concat: ["$firstName", " ", "$lastName"] },
email: 1,
yearsActive: {
$dateDiff: {
startDate: "$joinedAt",
endDate: new Date(),
unit: "year"
}
}
}
}
])
Performance Impact
Projections improve performance in two ways:
- Reduced network transfer — Less data sent from server to client
- Covered queries — If all projected fields are in an index, MongoDB can return results directly from the index without reading the actual documents (extremely fast)
// Create a compound index
db.users.createIndex({ email: 1, name: 1 })
// This query is "covered" — served entirely from the index
db.users.find(
{ email: "alice@example.com" },
{ email: 1, name: 1, _id: 0 }
)
What's Next
You now know how to be precise about which fields to return. In the next episode, we'll cover cursor methods — limit(), skip(), and sort() — for pagination and data ordering.