|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>MongoDB Associate Developer (Node.js) β Exam Cheat Sheet</title> |
|
<style> |
|
:root { |
|
--artifact-background: var(--theme-background, #ffffff); |
|
--artifact-background-light: var(--theme-background-light, #f6f6f6); |
|
--artifact-background-mid: var(--theme-background-mid, #efefef); |
|
--artifact-border-light: var(--theme-border-light, #e5e5e5); |
|
--artifact-border-dark: var(--theme-border-dark, #cccccc); |
|
--artifact-text-primary: var(--theme-text-primary, #1b1b1b); |
|
--artifact-text-secondary: var(--theme-text-secondary, #727272); |
|
--artifact-primary-highlight: var(--theme-primary-highlight, #1b1b1b); |
|
--artifact-primary-hover: var(--theme-primary-hover, #404040); |
|
--artifact-selected: var(--theme-selected, #e8e8e8); |
|
--artifact-text-highlight: var(--theme-text-highlight, #fff8e5); |
|
|
|
/* MongoDB brand green */ |
|
--mg-green: #00ed64; |
|
--mg-green-soft: #00684a; |
|
--mg-green-bg: #e6fff5; |
|
--mg-dark: #001e2b; |
|
|
|
/* Semantic colors */ |
|
--artifact-code-bg: #1e2a35; |
|
--artifact-code-text: #d4e6f1; |
|
--artifact-badge-red-bg: #ffe4e4; |
|
--artifact-badge-red-text: #b91c1c; |
|
--artifact-badge-orange-bg: #fff3e0; |
|
--artifact-badge-orange-text: #c2410c; |
|
--artifact-badge-yellow-bg: #fefce8; |
|
--artifact-badge-yellow-text: #854d0e; |
|
--artifact-badge-green-bg: #f0fdf4; |
|
--artifact-badge-green-text: #166534; |
|
--artifact-tip-bg: #e6fff5; |
|
--artifact-tip-text: #004d33; |
|
--artifact-warn-bg: #fff8e5; |
|
--artifact-warn-text: #7c5c00; |
|
--artifact-card-shadow: 0 1px 3px rgba(0,0,0,0.08); |
|
--sidebar-width: 230px; |
|
} |
|
html[data-theme='dark'] { |
|
--artifact-code-bg: #0d1b24; |
|
--artifact-code-text: #c9dfe8; |
|
--artifact-badge-red-bg: #4a1010; |
|
--artifact-badge-red-text: #fca5a5; |
|
--artifact-badge-orange-bg: #3d1f08; |
|
--artifact-badge-orange-text: #fdba74; |
|
--artifact-badge-yellow-bg: #3a2c00; |
|
--artifact-badge-yellow-text: #fde68a; |
|
--artifact-badge-green-bg: #0a2e18; |
|
--artifact-badge-green-text: #86efac; |
|
--artifact-tip-bg: #012e1e; |
|
--artifact-tip-text: #6ee7b7; |
|
--artifact-warn-bg: #2e2300; |
|
--artifact-warn-text: #fde68a; |
|
--artifact-card-shadow: 0 1px 3px rgba(0,0,0,0.3); |
|
--mg-green-bg: #012e1e; |
|
} |
|
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } |
|
|
|
body { |
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; |
|
font-size: 14px; |
|
line-height: 1.6; |
|
background: var(--artifact-background); |
|
color: var(--artifact-text-primary); |
|
display: flex; |
|
min-height: 100vh; |
|
} |
|
|
|
/* ββ SIDEBAR ββ */ |
|
#sidebar { |
|
width: var(--sidebar-width); |
|
min-width: var(--sidebar-width); |
|
background: var(--artifact-background-light); |
|
border-right: 1px solid var(--artifact-border-light); |
|
position: sticky; |
|
top: 0; |
|
height: 100vh; |
|
overflow-y: auto; |
|
display: flex; |
|
flex-direction: column; |
|
flex-shrink: 0; |
|
} |
|
.sidebar-header { |
|
padding: 16px 14px 10px; |
|
border-bottom: 1px solid var(--artifact-border-light); |
|
} |
|
.sidebar-logo { |
|
display: flex; |
|
align-items: center; |
|
gap: 8px; |
|
margin-bottom: 10px; |
|
} |
|
.sidebar-logo .leaf { |
|
width: 22px; height: 22px; |
|
background: var(--mg-green-soft); |
|
border-radius: 50% 50% 50% 0; |
|
transform: rotate(-45deg); |
|
flex-shrink: 0; |
|
} |
|
.sidebar-logo span { |
|
font-size: 12px; |
|
font-weight: 600; |
|
color: var(--artifact-text-primary); |
|
line-height: 1.2; |
|
} |
|
#search-box { |
|
width: 100%; |
|
padding: 6px 10px; |
|
font-size: 12px; |
|
border: 1px solid var(--artifact-border-dark); |
|
border-radius: 6px; |
|
background: var(--artifact-background); |
|
color: var(--artifact-text-primary); |
|
outline: none; |
|
} |
|
#search-box:focus { border-color: var(--mg-green-soft); } |
|
|
|
nav { padding: 8px 0; flex: 1; } |
|
.nav-section { font-size: 10px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--artifact-text-secondary); padding: 10px 14px 4px; } |
|
.nav-link { |
|
display: block; |
|
padding: 5px 14px; |
|
font-size: 12px; |
|
color: var(--artifact-text-secondary); |
|
text-decoration: none; |
|
border-left: 2px solid transparent; |
|
transition: all 0.15s; |
|
cursor: pointer; |
|
} |
|
.nav-link:hover { color: var(--artifact-text-primary); background: var(--artifact-selected); } |
|
.nav-link.active { color: var(--mg-green-soft); border-left-color: var(--mg-green-soft); background: var(--mg-green-bg); font-weight: 500; } |
|
|
|
/* ββ MAIN CONTENT ββ */ |
|
main { |
|
flex: 1; |
|
min-width: 0; |
|
padding: 0 32px 60px; |
|
max-width: 900px; |
|
} |
|
|
|
/* ββ PAGE HEADER ββ */ |
|
.page-header { |
|
padding: 28px 0 20px; |
|
border-bottom: 2px solid var(--mg-green-soft); |
|
margin-bottom: 24px; |
|
} |
|
.page-header h1 { |
|
font-size: 22px; |
|
font-weight: 700; |
|
color: var(--artifact-text-primary); |
|
margin-bottom: 4px; |
|
} |
|
.page-header .subtitle { |
|
font-size: 12px; |
|
color: var(--artifact-text-secondary); |
|
} |
|
.exam-pills { |
|
display: flex; |
|
flex-wrap: wrap; |
|
gap: 8px; |
|
margin-top: 12px; |
|
} |
|
.pill { |
|
display: inline-flex; |
|
align-items: center; |
|
gap: 5px; |
|
font-size: 11px; |
|
font-weight: 500; |
|
padding: 4px 10px; |
|
border-radius: 20px; |
|
background: var(--artifact-background-mid); |
|
color: var(--artifact-text-secondary); |
|
} |
|
.pill.green { background: var(--mg-green-bg); color: var(--mg-green-soft); } |
|
|
|
/* ββ SECTION ββ */ |
|
.section { |
|
margin-bottom: 8px; |
|
border: 1px solid var(--artifact-border-light); |
|
border-radius: 10px; |
|
overflow: hidden; |
|
box-shadow: var(--artifact-card-shadow); |
|
} |
|
.section-header { |
|
display: flex; |
|
align-items: center; |
|
gap: 10px; |
|
padding: 13px 16px; |
|
cursor: pointer; |
|
background: var(--artifact-background-light); |
|
user-select: none; |
|
transition: background 0.15s; |
|
} |
|
.section-header:hover { background: var(--artifact-selected); } |
|
.section-icon { font-size: 16px; } |
|
.section-title { font-size: 14px; font-weight: 600; flex: 1; color: var(--artifact-text-primary); } |
|
.section-badge { |
|
font-size: 10px; |
|
font-weight: 700; |
|
padding: 2px 8px; |
|
border-radius: 10px; |
|
} |
|
.badge-red { background: var(--artifact-badge-red-bg); color: var(--artifact-badge-red-text); } |
|
.badge-orange { background: var(--artifact-badge-orange-bg); color: var(--artifact-badge-orange-text); } |
|
.badge-yellow { background: var(--artifact-badge-yellow-bg); color: var(--artifact-badge-yellow-text); } |
|
.badge-green { background: var(--artifact-badge-green-bg); color: var(--artifact-badge-green-text); } |
|
.chevron { font-size: 11px; color: var(--artifact-text-secondary); transition: transform 0.2s; } |
|
.section.open .chevron { transform: rotate(90deg); } |
|
|
|
.section-body { display: none; padding: 16px 18px; border-top: 1px solid var(--artifact-border-light); } |
|
.section.open .section-body { display: block; } |
|
|
|
/* ββ SUBSECTION ββ */ |
|
.subsection { margin-bottom: 20px; } |
|
.subsection:last-child { margin-bottom: 0; } |
|
.subsection h3 { |
|
font-size: 13px; |
|
font-weight: 600; |
|
color: var(--artifact-text-primary); |
|
margin-bottom: 8px; |
|
padding-bottom: 4px; |
|
border-bottom: 1px solid var(--artifact-border-light); |
|
} |
|
.subsection h4 { |
|
font-size: 12px; |
|
font-weight: 600; |
|
color: var(--artifact-text-secondary); |
|
margin: 12px 0 5px; |
|
text-transform: uppercase; |
|
letter-spacing: 0.05em; |
|
} |
|
|
|
p { margin-bottom: 8px; font-size: 13px; color: var(--artifact-text-primary); } |
|
ul, ol { padding-left: 18px; margin-bottom: 8px; } |
|
li { font-size: 13px; margin-bottom: 3px; color: var(--artifact-text-primary); } |
|
li strong { font-weight: 600; } |
|
|
|
/* ββ CODE BLOCKS ββ */ |
|
.code-wrap { position: relative; margin: 10px 0; border-radius: 8px; overflow: hidden; } |
|
pre { |
|
background: var(--artifact-code-bg); |
|
color: var(--artifact-code-text); |
|
padding: 14px 16px; |
|
overflow-x: auto; |
|
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', Consolas, monospace; |
|
font-size: 12px; |
|
line-height: 1.65; |
|
border-radius: 8px; |
|
} |
|
code { font-family: inherit; } |
|
.copy-btn { |
|
position: absolute; |
|
top: 7px; right: 7px; |
|
background: rgba(255,255,255,0.12); |
|
color: #a0b8c8; |
|
border: none; |
|
border-radius: 5px; |
|
padding: 3px 8px; |
|
font-size: 10px; |
|
cursor: pointer; |
|
transition: background 0.15s, color 0.15s; |
|
} |
|
.copy-btn:hover { background: rgba(255,255,255,0.22); color: #fff; } |
|
.copy-btn.copied { background: var(--mg-green-soft); color: #fff; } |
|
|
|
/* inline code */ |
|
p code, li code, td code { |
|
font-family: 'SF Mono', 'Fira Code', Consolas, monospace; |
|
font-size: 11.5px; |
|
background: var(--artifact-background-mid); |
|
color: var(--mg-green-soft); |
|
padding: 1px 5px; |
|
border-radius: 4px; |
|
border: 1px solid var(--artifact-border-light); |
|
} |
|
|
|
/* ββ TABLES ββ */ |
|
.table-wrap { overflow-x: auto; margin: 10px 0; border-radius: 8px; border: 1px solid var(--artifact-border-light); } |
|
table { width: 100%; border-collapse: collapse; font-size: 12px; } |
|
th { |
|
background: var(--artifact-background-mid); |
|
color: var(--artifact-text-secondary); |
|
font-weight: 600; |
|
text-transform: uppercase; |
|
font-size: 10px; |
|
letter-spacing: 0.06em; |
|
padding: 8px 12px; |
|
text-align: left; |
|
border-bottom: 1px solid var(--artifact-border-light); |
|
} |
|
td { padding: 8px 12px; border-bottom: 1px solid var(--artifact-border-light); vertical-align: top; color: var(--artifact-text-primary); } |
|
tr:last-child td { border-bottom: none; } |
|
tr:hover td { background: var(--artifact-selected); } |
|
|
|
/* ββ CALLOUTS ββ */ |
|
.tip, .warn, .trap { |
|
border-radius: 7px; |
|
padding: 10px 14px; |
|
margin: 10px 0; |
|
font-size: 12.5px; |
|
line-height: 1.55; |
|
} |
|
.tip { background: var(--artifact-tip-bg); color: var(--artifact-tip-text); border-left: 3px solid var(--mg-green-soft); } |
|
.warn { background: var(--artifact-warn-bg); color: var(--artifact-warn-text); border-left: 3px solid #d97706; } |
|
.trap { background: var(--artifact-badge-red-bg); color: var(--artifact-badge-red-text); border-left: 3px solid #dc2626; } |
|
.tip strong, .warn strong, .trap strong { font-weight: 700; } |
|
|
|
/* ββ WEIGHTS TABLE (top) ββ */ |
|
.weights-grid { |
|
display: grid; |
|
grid-template-columns: 1fr 80px 120px; |
|
gap: 0; |
|
border: 1px solid var(--artifact-border-light); |
|
border-radius: 8px; |
|
overflow: hidden; |
|
margin-bottom: 16px; |
|
} |
|
.wg-header { background: var(--artifact-background-mid); font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--artifact-text-secondary); padding: 8px 12px; } |
|
.wg-row { display: contents; } |
|
.wg-cell { padding: 8px 12px; border-top: 1px solid var(--artifact-border-light); font-size: 12px; color: var(--artifact-text-primary); display: flex; align-items: center; gap: 6px; } |
|
.wg-row:hover .wg-cell { background: var(--artifact-selected); } |
|
|
|
/* ββ WEIGHT BAR ββ */ |
|
.w-bar { height: 5px; border-radius: 3px; background: var(--artifact-border-light); flex-shrink: 0; width: 60px; } |
|
.w-bar-fill { height: 100%; border-radius: 3px; } |
|
|
|
/* ββ QUICK NAV HEADER ββ */ |
|
.topic-weights { margin-bottom: 20px; } |
|
|
|
/* ββ SYNTAX COLOR HINTS ββ */ |
|
.kw { color: #f8a; } |
|
.fn { color: #7dd3fc; } |
|
.str { color: #86efac; } |
|
.cm { color: #64748b; font-style: italic; } |
|
.op { color: #fcd34d; } |
|
.nb { color: #c4b5fd; } |
|
|
|
/* ββ SEARCH HIGHLIGHT ββ */ |
|
mark { background: var(--artifact-text-highlight); color: var(--artifact-text-primary); border-radius: 2px; } |
|
|
|
/* ββ FOOTER ββ */ |
|
footer { |
|
margin-top: 40px; |
|
padding-top: 16px; |
|
border-top: 1px solid var(--artifact-border-light); |
|
font-size: 11px; |
|
color: var(--artifact-text-secondary); |
|
text-align: center; |
|
} |
|
footer a { color: var(--mg-green-soft); text-decoration: none; } |
|
|
|
@media (max-width: 700px) { |
|
#sidebar { display: none; } |
|
main { padding: 0 16px 40px; } |
|
} |
|
|
|
@media print { |
|
body { margin: 0; padding: 0.3in 0.5in; background: #fff; print-color-adjust: exact; -webkit-print-color-adjust: exact; } |
|
#sidebar { display: none; } |
|
main { max-width: 100% !important; padding: 0; } |
|
.section { break-inside: avoid; box-shadow: none; border: 1px solid #ddd; margin-bottom: 10px; } |
|
.section-body { display: block !important; } |
|
.copy-btn, .chevron { display: none; } |
|
pre { background: #1e2a35 !important; color: #d4e6f1 !important; } |
|
a { color: inherit; text-decoration: none; } |
|
button { display: none !important; } |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
|
|
<!-- SIDEBAR --> |
|
<div id="sidebar"> |
|
<div class="sidebar-header"> |
|
<div class="sidebar-logo"> |
|
<div class="leaf"></div> |
|
<span>MongoDB<br>Exam Cheat Sheet</span> |
|
</div> |
|
<input id="search-box" type="text" placeholder="Search topicsβ¦" oninput="filterSections(this.value)" /> |
|
</div> |
|
<nav> |
|
<div class="nav-section">Sections</div> |
|
<a class="nav-link active" onclick="navScrollTo('s0')">π Exam Weights</a> |
|
<a class="nav-link" onclick="navScrollTo('s1')">π Document Model & BSON</a> |
|
<a class="nav-link" onclick="navScrollTo('s2')">β‘ CRUD Operations</a> |
|
<a class="nav-link" onclick="navScrollTo('s3')">π Indexes</a> |
|
<a class="nav-link" onclick="navScrollTo('s4')">π Data Modeling</a> |
|
<a class="nav-link" onclick="navScrollTo('s5')">π© Node.js Driver</a> |
|
<a class="nav-link" onclick="navScrollTo('s6')">π§ Tools & Tooling</a> |
|
<a class="nav-link" onclick="navScrollTo('s7')">π Atlas Search</a> |
|
<a class="nav-link" onclick="navScrollTo('s8')">β οΈ Exam Scenarios</a> |
|
<a class="nav-link" onclick="navScrollTo('s9')">π― Exam Day Strategy</a> |
|
<a class="nav-link" onclick="navScrollTo('s10')">π‘ Real Exam Experience</a> |
|
</nav> |
|
</div> |
|
|
|
<!-- MAIN --> |
|
<main id="main-content"> |
|
|
|
<div class="page-header"> |
|
<h1>MongoDB Associate Developer (Node.js)</h1> |
|
<div class="subtitle">Exam Cheat Sheet Β· Built from official exam objectives + real exam experience</div> |
|
<div class="exam-pills"> |
|
<span class="pill">π 53 questions</span> |
|
<span class="pill">π― ~80% to pass</span> |
|
<span class="pill">β
No negative marking β always guess!</span> |
|
<span class="pill green">π’ Online Proctored</span> |
|
</div> |
|
</div> |
|
|
|
<!-- S0: WEIGHTS --> |
|
<div class="section open" id="s0"> |
|
<div class="section-header" onclick="toggleSection(this)"> |
|
<span class="section-icon">π</span> |
|
<span class="section-title">Exam Topic Weights</span> |
|
<span class="chevron">βΆ</span> |
|
</div> |
|
<div class="section-body"> |
|
<div class="weights-grid"> |
|
<div class="wg-header">Section</div><div class="wg-header">Weight</div><div class="wg-header">Priority</div> |
|
<div class="wg-row"> |
|
<div class="wg-cell">CRUD Operations</div> |
|
<div class="wg-cell">51%</div> |
|
<div class="wg-cell"><span class="section-badge badge-red">π΄ Must-nail</span></div> |
|
</div> |
|
<div class="wg-row"> |
|
<div class="wg-cell">Node.js Driver</div> |
|
<div class="wg-cell">18β19%</div> |
|
<div class="wg-cell"><span class="section-badge badge-red">π΄ Must-nail</span></div> |
|
</div> |
|
<div class="wg-row"> |
|
<div class="wg-cell">Indexes</div> |
|
<div class="wg-cell">17%</div> |
|
<div class="wg-cell"><span class="section-badge badge-orange">π High</span></div> |
|
</div> |
|
<div class="wg-row"> |
|
<div class="wg-cell">Document Model / BSON</div> |
|
<div class="wg-cell">8%</div> |
|
<div class="wg-cell"><span class="section-badge badge-yellow">π‘ Medium</span></div> |
|
</div> |
|
<div class="wg-row"> |
|
<div class="wg-cell">Data Modeling</div> |
|
<div class="wg-cell">4%</div> |
|
<div class="wg-cell"><span class="section-badge badge-green">π’ Quick review</span></div> |
|
</div> |
|
<div class="wg-row"> |
|
<div class="wg-cell">Tools & Tooling</div> |
|
<div class="wg-cell">2%</div> |
|
<div class="wg-cell"><span class="section-badge badge-green">π’ Quick review</span></div> |
|
</div> |
|
</div> |
|
<div class="tip"><strong>Tip:</strong> 70% of the exam is CRUD + Node.js driver. If you nail those two sections, you pass.</div> |
|
</div> |
|
</div> |
|
|
|
<!-- S1: DOCUMENT MODEL --> |
|
<div class="section open" id="s1"> |
|
<div class="section-header" onclick="toggleSection(this)"> |
|
<span class="section-icon">π</span> |
|
<span class="section-title">Section 1 β Document Model & BSON <span class="section-badge badge-yellow" style="margin-left:6px">8%</span></span> |
|
<span class="chevron">βΆ</span> |
|
</div> |
|
<div class="section-body"> |
|
<ul> |
|
<li>MongoDB stores data as <strong>BSON</strong> (Binary JSON) β a superset of JSON.</li> |
|
<li>BSON types JSON does <em>not</em> have: <code>ObjectId</code>, <code>Date</code>, <code>Binary</code>, <code>Decimal128</code>, <code>Int32</code>, <code>Int64</code>, <code>Timestamp</code>, <code>Null</code>, <code>Regex</code>, <code>Boolean</code>.</li> |
|
<li>Documents with <strong>different shapes</strong> can co-exist in the same collection β flexible schema.</li> |
|
<li>Every document gets a unique <code>_id</code> (auto-generated as <code>ObjectId</code> if not provided).</li> |
|
<li>Projections: you <strong>cannot mix</strong> inclusion (1) and exclusion (0) β except <code>_id</code>.</li> |
|
</ul> |
|
<div class="subsection"> |
|
<h3>Projections β valid vs invalid</h3> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// β
Inclusion β shows name, city, _id</span> |
|
{ <span class="str">projection</span>: { name: <span class="nb">1</span>, city: <span class="nb">1</span> } } |
|
|
|
<span class="cm">// β
Valid exception β _id is the ONLY field you can exclude in an inclusion projection</span> |
|
{ <span class="str">projection</span>: { name: <span class="nb">1</span>, _id: <span class="nb">0</span> } } |
|
|
|
<span class="cm">// β
Exclusion β hides password and token</span> |
|
{ <span class="str">projection</span>: { password: <span class="nb">0</span>, token: <span class="nb">0</span> } } |
|
|
|
<span class="cm">// β THROWS MongoServerError: Cannot do inclusion on field city in exclusion projection</span> |
|
{ <span class="str">projection</span>: { name: <span class="nb">1</span>, city: <span class="nb">0</span> } }</pre></div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<!-- S2: CRUD --> |
|
<div class="section open" id="s2"> |
|
<div class="section-header" onclick="toggleSection(this)"> |
|
<span class="section-icon">β‘</span> |
|
<span class="section-title">Section 2 β CRUD Operations <span class="section-badge badge-red" style="margin-left:6px">51% β THE BIG ONE</span></span> |
|
<span class="chevron">βΆ</span> |
|
</div> |
|
<div class="section-body"> |
|
|
|
<div class="subsection"> |
|
<h3>Insert</h3> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// insertOne β returns { acknowledged: true, insertedId: ObjectId(...) }</span> |
|
<span class="kw">await</span> collection.<span class="fn">insertOne</span>({ name: <span class="str">"Ayyub"</span>, city: <span class="str">"Mumbai"</span> }) |
|
|
|
<span class="cm">// insertMany β returns { acknowledged: true, insertedCount: 2, insertedIds: {...} }</span> |
|
<span class="kw">await</span> collection.<span class="fn">insertMany</span>([{ name: <span class="str">"A"</span> }, { name: <span class="str">"B"</span> }]) |
|
|
|
<span class="cm">// ordered: false β continue inserting even if one doc fails (duplicate _id etc)</span> |
|
<span class="kw">await</span> collection.<span class="fn">insertMany</span>(docs, { ordered: <span class="nb">false</span> })</pre></div> |
|
<div class="trap"><strong>Trap:</strong> If <code>_id</code> already exists β <code>insertOne</code> throws a duplicate key error.</div> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>Find / Read</h3> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// Find all</span> |
|
<span class="kw">await</span> collection.<span class="fn">find</span>({}).<span class="fn">toArray</span>() |
|
|
|
<span class="cm">// Find with filter</span> |
|
<span class="kw">await</span> collection.<span class="fn">find</span>({ status: <span class="str">"active"</span>, age: { <span class="op">$gt</span>: <span class="nb">25</span> } }).<span class="fn">toArray</span>() |
|
|
|
<span class="cm">// Find one β returns single doc or null</span> |
|
<span class="kw">await</span> collection.<span class="fn">findOne</span>({ _id: <span class="kw">new</span> <span class="fn">ObjectId</span>(<span class="str">"..."</span>) }) |
|
|
|
<span class="cm">// Cursor methods β chained</span> |
|
collection.<span class="fn">find</span>({}).<span class="fn">sort</span>({ age: <span class="op">-1</span> }).<span class="fn">limit</span>(<span class="nb">10</span>).<span class="fn">skip</span>(<span class="nb">20</span>).<span class="fn">toArray</span>() |
|
|
|
<span class="cm">// Count</span> |
|
<span class="kw">await</span> collection.<span class="fn">countDocuments</span>({ status: <span class="str">"active"</span> }) <span class="cm">// exact, uses filter</span> |
|
<span class="kw">await</span> collection.<span class="fn">estimatedDocumentCount</span>() <span class="cm">// fast, no filter, uses metadata</span></pre></div> |
|
|
|
<h4>Comparison Operators</h4> |
|
<div class="table-wrap"><table> |
|
<tr><th>Operator</th><th>Meaning</th><th>Example</th></tr> |
|
<tr><td><code>$eq</code></td><td>Equal</td><td><code>{ age: { $eq: 25 } }</code></td></tr> |
|
<tr><td><code>$ne</code></td><td>Not equal</td><td><code>{ status: { $ne: "inactive" } }</code></td></tr> |
|
<tr><td><code>$gt / $gte</code></td><td>Greater than / or equal</td><td><code>{ age: { $gte: 18 } }</code></td></tr> |
|
<tr><td><code>$lt / $lte</code></td><td>Less than / or equal</td><td><code>{ price: { $lt: 100 } }</code></td></tr> |
|
<tr><td><code>$in</code></td><td>Value in array</td><td><code>{ status: { $in: ["a","b"] } }</code></td></tr> |
|
<tr><td><code>$nin</code></td><td>Value NOT in array</td><td><code>{ role: { $nin: ["admin"] } }</code></td></tr> |
|
</table></div> |
|
|
|
<h4>Logical Operators</h4> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// $and (explicit)</span> |
|
{ <span class="op">$and</span>: [{ price: { <span class="op">$gt</span>: <span class="nb">10</span> } }, { price: { <span class="op">$lt</span>: <span class="nb">100</span> } }] } |
|
|
|
<span class="cm">// $or</span> |
|
{ <span class="op">$or</span>: [{ status: <span class="str">"active"</span> }, { role: <span class="str">"admin"</span> }] } |
|
|
|
<span class="cm">// $elemMatch β array element must satisfy ALL conditions</span> |
|
{ scores: { <span class="op">$elemMatch</span>: { <span class="op">$gt</span>: <span class="nb">80</span>, <span class="op">$lt</span>: <span class="nb">100</span> } } }</pre></div> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>Update</h3> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// updateOne β returns { matchedCount, modifiedCount, ... }</span> |
|
<span class="kw">await</span> collection.<span class="fn">updateOne</span>({ _id: id }, { <span class="op">$set</span>: { status: <span class="str">"inactive"</span> } }) |
|
|
|
<span class="cm">// updateMany</span> |
|
<span class="kw">await</span> collection.<span class="fn">updateMany</span>({ role: <span class="str">"user"</span> }, { <span class="op">$set</span>: { active: <span class="nb">true</span> }, <span class="op">$inc</span>: { version: <span class="nb">1</span> } }) |
|
|
|
<span class="cm">// upsert β insert if not found</span> |
|
<span class="kw">await</span> collection.<span class="fn">updateOne</span>( |
|
{ email: <span class="str">"user@example.com"</span> }, |
|
{ <span class="op">$set</span>: { name: <span class="str">"New User"</span> }, <span class="op">$setOnInsert</span>: { createdAt: <span class="kw">new</span> <span class="fn">Date</span>() } }, |
|
{ upsert: <span class="nb">true</span> } <span class="cm">// $setOnInsert only fires on INSERT, not on update</span> |
|
)</pre></div> |
|
|
|
<h4>Update Operators</h4> |
|
<div class="table-wrap"><table> |
|
<tr><th>Operator</th><th>Effect</th></tr> |
|
<tr><td><code>$set</code></td><td>Set a field value (other fields untouched)</td></tr> |
|
<tr><td><code>$unset</code></td><td>Remove a field from the document</td></tr> |
|
<tr><td><code>$inc</code></td><td>Increment or decrement a number</td></tr> |
|
<tr><td><code>$push</code></td><td>Append a value to an array</td></tr> |
|
<tr><td><code>$pull</code></td><td>Remove matching values from an array</td></tr> |
|
<tr><td><code>$addToSet</code></td><td>Add to array only if not already present (unique)</td></tr> |
|
<tr><td><code>$rename</code></td><td>Rename a field</td></tr> |
|
<tr><td><code>$setOnInsert</code></td><td>Only sets field when a new doc is inserted (upsert)</td></tr> |
|
</table></div> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>Replace β replaceOne & updateOne without $</h3> |
|
<div class="warn"><strong>β οΈ Warning:</strong> Passing a doc with NO <code>$</code> operators to <code>updateOne</code> (or using <code>replaceOne</code>) replaces the ENTIRE document β only <code>_id</code> is preserved. All other fields are gone.</div> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// BEFORE: { _id: 1, name: "Ayyub", city: "Mumbai", role: "SA" }</span> |
|
|
|
<span class="cm">// updateOne WITHOUT $ operator β same as full replace!</span> |
|
<span class="kw">await</span> collection.<span class="fn">updateOne</span>({ _id: <span class="nb">1</span> }, { name: <span class="str">"Ayyub"</span>, city: <span class="str">"Delhi"</span> }) |
|
<span class="cm">// AFTER: { _id: 1, name: "Ayyub", city: "Delhi" } β "role" is GONE β</span> |
|
|
|
<span class="cm">// replaceOne β explicit, same result, clearer intent. CANNOT contain $ operators.</span> |
|
<span class="kw">await</span> collection.<span class="fn">replaceOne</span>({ _id: <span class="nb">1</span> }, { name: <span class="str">"Ayyub"</span>, city: <span class="str">"Delhi"</span> })</pre></div> |
|
<div class="table-wrap"><table> |
|
<tr><th>Method</th><th>Behavior</th><th>$ operators allowed?</th></tr> |
|
<tr><td><code>updateOne</code> with <code>$set</code></td><td>Only specified fields updated β
</td><td>Yes</td></tr> |
|
<tr><td><code>updateOne</code> without <code>$</code></td><td>Full document replaced β οΈ</td><td>No (treat as replace)</td></tr> |
|
<tr><td><code>replaceOne</code></td><td>Full document replaced, explicit</td><td>β throws error if $ used</td></tr> |
|
</table></div> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>Delete</h3> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="kw">await</span> collection.<span class="fn">deleteOne</span>({ _id: id }) |
|
<span class="cm">// Returns: { acknowledged: true, deletedCount: 1 }</span> |
|
|
|
<span class="kw">await</span> collection.<span class="fn">deleteMany</span>({ status: <span class="str">"expired"</span> })</pre></div> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>findAndModify / findOneAndUpdate / findOneAndDelete</h3> |
|
<p><code>findAndModify</code> is the legacy shell command for atomic find-then-modify. In the modern Node.js driver it maps to three methods:</p> |
|
<div class="table-wrap"><table> |
|
<tr><th>Shell / Legacy</th><th>Node.js Driver Equivalent</th></tr> |
|
<tr><td><code>findAndModify</code> (update)</td><td><code>findOneAndUpdate()</code></td></tr> |
|
<tr><td><code>findAndModify</code> (delete)</td><td><code>findOneAndDelete()</code></td></tr> |
|
<tr><td><code>findAndModify</code> (replace)</td><td><code>findOneAndReplace()</code></td></tr> |
|
</table></div> |
|
<div class="tip"><strong>Key characteristic:</strong> The find + modify is a single <strong>atomic operation</strong> β no other write can interleave between the find and the update.</div> |
|
|
|
<div class="trap" style="border-left-color:#f4c430;"> |
|
β οΈ <strong>Deprecated but still on the exam!</strong> <code>findAndModify</code> is the original mongosh/shell command. It appeared as a real exam question. Know the syntax. |
|
</div> |
|
|
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// ββ findAndModify β SHELL / LEGACY SYNTAX (deprecated but exam-tested) ββββββ</span> |
|
<span class="cm">// db.collection.findAndModify({ query, update, options })</span> |
|
|
|
db.orders.<span class="fn">findAndModify</span>({ |
|
query: { status: <span class="str">"pending"</span> }, <span class="cm">// filter β which doc to find</span> |
|
update: { <span class="op">$set</span>: { status: <span class="str">"processing"</span> } }, <span class="cm">// update to apply</span> |
|
sort: { createdAt: <span class="nb">1</span> }, <span class="cm">// if multiple match, pick oldest</span> |
|
new: <span class="nb">true</span>, <span class="cm">// true = return doc AFTER update (default: false = BEFORE)</span> |
|
upsert: <span class="nb">false</span> <span class="cm">// insert if not found? (default: false)</span> |
|
}) |
|
|
|
<span class="cm">// findAndModify to REMOVE a document:</span> |
|
db.orders.<span class="fn">findAndModify</span>({ |
|
query: { status: <span class="str">"expired"</span> }, |
|
remove: <span class="nb">true</span> <span class="cm">// remove: true β deletes matched doc, returns it</span> |
|
}) |
|
|
|
<span class="cm">// ββ Key differences vs Node.js driver ββββββββββββββββββββββββββββββββββββββββ</span> |
|
<span class="cm">// Shell findAndModify β Node.js driver equivalent</span> |
|
<span class="cm">// new: false (default) β returnDocument: "before" (default)</span> |
|
<span class="cm">// new: true β returnDocument: "after"</span> |
|
<span class="cm">// remove: true β findOneAndDelete()</span> |
|
<span class="cm">// upsert: true β { upsert: true } option</span></pre></div> |
|
|
|
<div class="trap"> |
|
<strong>findAndModify shell vs Node.js driver β exam traps:</strong><br> |
|
β’ Shell uses <code>new: true/false</code> β driver uses <code>returnDocument: "after"/"before"</code>. The exam will mix these up.<br> |
|
β’ Shell default is <code>new: false</code> β returns doc <strong>before</strong> update. Same as driver default <code>returnDocument: "before"</code>.<br> |
|
β’ Shell uses <code>remove: true</code> to delete β driver has a separate <code>findOneAndDelete()</code> method.<br> |
|
β’ You <strong>cannot</strong> use both <code>update</code> and <code>remove</code> in the same <code>findAndModify</code> call β that's an error.<br> |
|
β’ <code>findAndModify</code> only ever affects <strong>one document</strong>, even if multiple match the query. |
|
</div> |
|
|
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// findOneAndUpdate β default returns doc BEFORE update</span> |
|
<span class="kw">const</span> result = <span class="kw">await</span> collection.<span class="fn">findOneAndUpdate</span>( |
|
{ status: <span class="str">"pending"</span> }, |
|
{ <span class="op">$set</span>: { status: <span class="str">"processing"</span> } }, |
|
{ |
|
returnDocument: <span class="str">"after"</span>, <span class="cm">// "before" (default) | "after"</span> |
|
sort: { createdAt: <span class="nb">1</span> }, <span class="cm">// pick oldest if multiple match</span> |
|
upsert: <span class="nb">false</span> |
|
} |
|
) |
|
|
|
<span class="cm">// findOneAndDelete β returns the deleted document</span> |
|
<span class="kw">const</span> deleted = <span class="kw">await</span> collection.<span class="fn">findOneAndDelete</span>( |
|
{ status: <span class="str">"expired"</span> }, |
|
{ sort: { expiredAt: <span class="nb">1</span> } } |
|
) |
|
|
|
<span class="cm">// findOneAndReplace β full replace, no $ operators</span> |
|
<span class="kw">const</span> replaced = <span class="kw">await</span> collection.<span class="fn">findOneAndReplace</span>( |
|
{ _id: id }, |
|
{ name: <span class="str">"New Name"</span>, status: <span class="str">"active"</span> }, |
|
{ returnDocument: <span class="str">"after"</span> } |
|
)</pre></div> |
|
<div class="trap"> |
|
<strong>Exam traps:</strong><br> |
|
β’ Default <code>returnDocument</code> is <strong>"before"</strong> β you get the doc as it was <em>before</em> the change.<br> |
|
β’ No match + <code>upsert: false</code> (default) β returns <code>null</code>.<br> |
|
β’ No match + <code>upsert: true</code> + <code>returnDocument: "before"</code> β also returns <code>null</code> (nothing existed before). |
|
</div> |
|
|
|
<h4 style="margin:18px 0 10px;color:#00ed64;">β‘ Concurrent findAndModify β Real Exam Question (appeared in the exam!)</h4> |
|
<p>The exam presents two <code>findAndModify</code> / <code>findOneAndUpdate</code> calls running <strong>simultaneously</strong> on the same document and asks you to pick the correct output for each operation. This is an atomicity + <code>returnDocument</code> / <code>new</code> reasoning question.</p> |
|
|
|
<div class="tip"> |
|
<strong>The golden rule:</strong> <code>findAndModify</code> and <code>findOneAndUpdate</code> are atomic β only ONE operation wins the race and modifies the document first. The second operation then sees the <em>already-modified</em> document. What each operation <em>returns</em> depends entirely on <code>new: true/false</code> (shell) or <code>returnDocument: "before"/"after"</code> (driver). |
|
</div> |
|
|
|
<p style="margin:10px 0 6px;font-weight:600;color:#c3cfd9;">π Exam-style scenario β Shell syntax (as it appeared):</p> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// Starting document in collection: { _id: 1, score: 10, status: "active" }</span> |
|
|
|
<span class="cm">// Two operations fire simultaneously. Op A wins the race.</span> |
|
|
|
<span class="cm">// ββ Operation A (executes FIRST) βββββββββββββββββββββββββββββββββββββββββββββ</span> |
|
db.scores.<span class="fn">findAndModify</span>({ |
|
query: { _id: <span class="nb">1</span> }, |
|
update: { <span class="op">$inc</span>: { score: <span class="nb">5</span> } }, <span class="cm">// score: 10 β 15</span> |
|
new: <span class="nb">false</span> <span class="cm">// return doc BEFORE update (default)</span> |
|
}) |
|
<span class="cm">// Returns: { _id: 1, score: 10, status: "active" } β OLD doc (before the $inc)</span> |
|
|
|
<span class="cm">// ββ Operation B (executes SECOND β sees updated doc) βββββββββββββββββββββββββ</span> |
|
db.scores.<span class="fn">findAndModify</span>({ |
|
query: { _id: <span class="nb">1</span> }, |
|
update: { <span class="op">$inc</span>: { score: <span class="nb">5</span> } }, <span class="cm">// score: 15 β 20</span> |
|
new: <span class="nb">true</span> <span class="cm">// return doc AFTER update</span> |
|
}) |
|
<span class="cm">// Returns: { _id: 1, score: 20, status: "active" } β NEW doc (after the $inc)</span> |
|
|
|
<span class="cm">// ββ Final state of document in DB ββββββββββββββββββββββββββββββββββββββββββββ</span> |
|
<span class="cm">// { _id: 1, score: 20, status: "active" }</span></pre></div> |
|
|
|
<p style="margin:14px 0 6px;font-weight:600;color:#c3cfd9;">π Same scenario β Node.js driver syntax:</p> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// Starting document: { _id: 1, score: 10, status: "active" }</span> |
|
|
|
<span class="cm">// Operation A β wins the race, runs first</span> |
|
<span class="kw">const</span> resultA = <span class="kw">await</span> col.<span class="fn">findOneAndUpdate</span>( |
|
{ _id: <span class="nb">1</span> }, |
|
{ <span class="op">$inc</span>: { score: <span class="nb">5</span> } }, |
|
{ returnDocument: <span class="str">"before"</span> } <span class="cm">// equivalent to new: false</span> |
|
) |
|
<span class="cm">// resultA β { _id: 1, score: 10, status: "active" } β score BEFORE $inc</span> |
|
|
|
<span class="cm">// Operation B β runs second, score is already 15</span> |
|
<span class="kw">const</span> resultB = <span class="kw">await</span> col.<span class="fn">findOneAndUpdate</span>( |
|
{ _id: <span class="nb">1</span> }, |
|
{ <span class="op">$inc</span>: { score: <span class="nb">5</span> } }, |
|
{ returnDocument: <span class="str">"after"</span> } <span class="cm">// equivalent to new: true</span> |
|
) |
|
<span class="cm">// resultB β { _id: 1, score: 20, status: "active" } β score AFTER $inc</span></pre></div> |
|
|
|
<div class="trap"> |
|
<strong>5-step method to crack any concurrent findAndModify question:</strong><br> |
|
β’ Step 1 β Find the starting document state (always given in the question).<br> |
|
β’ Step 2 β Identify which op runs first (question will state this, or the filter makes it clear).<br> |
|
β’ Step 3 β Apply Op A's update mentally. This is the new document state in the DB.<br> |
|
β’ Step 4 β Check Op A's <code>new</code> / <code>returnDocument</code>: <code>false/"before"</code> β original, <code>true/"after"</code> β updated.<br> |
|
β’ Step 5 β Op B now sees the post-A document. Apply Op B's update, then check its <code>new</code> / <code>returnDocument</code>.<br><br> |
|
<strong>Edge cases to watch for:</strong><br> |
|
β’ If Op A's update causes the doc to <strong>no longer match</strong> Op B's filter (e.g. Op A sets <code>status: "done"</code>, Op B filters <code>status: "active"</code>) β Op B returns <code>null</code>.<br> |
|
β’ <code>findAndModify</code> / <code>findOneAndUpdate</code> always modifies <strong>exactly one document</strong>, even if multiple match.<br> |
|
β’ Shell <code>new: false</code> (default) = driver <code>returnDocument: "before"</code> (default) β they are equivalent. The exam may use either syntax. |
|
</div> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>Aggregation Pipeline</h3> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="kw">await</span> collection.<span class="fn">aggregate</span>([ |
|
{ <span class="op">$match</span>: { status: <span class="str">"active"</span> } }, <span class="cm">// 1. filter early!</span> |
|
{ <span class="op">$group</span>: { _id: <span class="str">"$city"</span>, count: { <span class="op">$sum</span>: <span class="nb">1</span> } } }, <span class="cm">// 2. aggregate</span> |
|
{ <span class="op">$sort</span>: { count: <span class="op">-1</span> } }, <span class="cm">// 3. sort</span> |
|
{ <span class="op">$project</span>: { city: <span class="str">"$_id"</span>, count: <span class="nb">1</span>, _id: <span class="nb">0</span> } }, <span class="cm">// 4. reshape</span> |
|
{ <span class="op">$limit</span>: <span class="nb">5</span> } <span class="cm">// 5. paginate</span> |
|
]).<span class="fn">toArray</span>()</pre></div> |
|
|
|
<h4>Key Stages</h4> |
|
<div class="table-wrap"><table> |
|
<tr><th>Stage</th><th>Purpose</th></tr> |
|
<tr><td><code>$match</code></td><td>Filter documents (like <code>find()</code>) β put early to reduce data</td></tr> |
|
<tr><td><code>$group</code></td><td>Group + aggregate. <code>_id</code> is mandatory β use <code>null</code> for grand total</td></tr> |
|
<tr><td><code>$sort</code></td><td>Sort results (1 = asc, -1 = desc)</td></tr> |
|
<tr><td><code>$project</code></td><td>Reshape output β include/exclude/rename fields</td></tr> |
|
<tr><td><code>$lookup</code></td><td>LEFT OUTER JOIN with another collection</td></tr> |
|
<tr><td><code>$unwind</code></td><td>Deconstruct array β one doc per element</td></tr> |
|
<tr><td><code>$out</code></td><td>Write results to collection β <strong>wipes it first</strong>. Must be last stage.</td></tr> |
|
<tr><td><code>$merge</code></td><td>Write results to collection β <strong>upserts, keeps existing docs</strong>. Must be last stage.</td></tr> |
|
<tr><td><code>$limit / $skip</code></td><td>Paginate results</td></tr> |
|
</table></div> |
|
|
|
<h4>$lookup β LEFT OUTER JOIN</h4> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre>{ <span class="op">$lookup</span>: { |
|
from: <span class="str">"orders"</span>, <span class="cm">// foreign collection</span> |
|
localField: <span class="str">"_id"</span>, <span class="cm">// field in current collection</span> |
|
foreignField: <span class="str">"customerId"</span>, <span class="cm">// matching field in foreign collection</span> |
|
as: <span class="str">"orders"</span> <span class="cm">// output array field name</span> |
|
} }</pre></div> |
|
|
|
<h4>$out vs $merge β Exam Objective 2.22</h4> |
|
<div class="table-wrap"><table> |
|
<tr><th></th><th><code>$out</code></th><th><code>$merge</code></th></tr> |
|
<tr><td>What it does</td><td><strong>Replaces</strong> entire target collection</td><td><strong>Upserts/merges</strong> into target</td></tr> |
|
<tr><td>Target exists?</td><td>Drops it, rewrites from scratch</td><td>Keeps untouched docs</td></tr> |
|
<tr><td>Target doesn't exist?</td><td>Creates it</td><td>Creates it</td></tr> |
|
<tr><td>Must be last stage?</td><td>β
Yes</td><td>β
Yes</td></tr> |
|
<tr><td>Returns docs?</td><td>β No β empty cursor</td><td>β No β empty cursor</td></tr> |
|
</table></div> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// $out β destructive, replaces entire collection</span> |
|
{ <span class="op">$out</span>: <span class="str">"city_summary"</span> } |
|
<span class="cm">// cross-database: { $out: { db: "reporting", coll: "city_summary" } }</span> |
|
|
|
<span class="cm">// $merge β safe upsert, existing docs preserved</span> |
|
{ <span class="op">$merge</span>: { |
|
into: <span class="str">"city_summary"</span>, |
|
on: <span class="str">"_id"</span>, <span class="cm">// field to match on</span> |
|
whenMatched: <span class="str">"merge"</span>, <span class="cm">// merge | replace | keepExisting | fail</span> |
|
whenNotMatched: <span class="str">"insert"</span> <span class="cm">// insert | discard | fail</span> |
|
} }</pre></div> |
|
<div class="trap"><strong>Exam signal:</strong> "write results <em>without losing existing documents</em>" β <strong><code>$merge</code></strong>, not <code>$out</code>.</div> |
|
</div> |
|
|
|
</div> |
|
</div> |
|
|
|
<!-- S3: INDEXES --> |
|
<div class="section open" id="s3"> |
|
<div class="section-header" onclick="toggleSection(this)"> |
|
<span class="section-icon">π</span> |
|
<span class="section-title">Section 3 β Indexes <span class="section-badge badge-orange" style="margin-left:6px">17%</span></span> |
|
<span class="chevron">βΆ</span> |
|
</div> |
|
<div class="section-body"> |
|
<div class="table-wrap"><table> |
|
<tr><th>Index Type</th><th>How to create</th><th>Key fact</th></tr> |
|
<tr><td><strong>Default (_id)</strong></td><td>Automatic</td><td>Always exists, cannot be deleted</td></tr> |
|
<tr><td><strong>Single field</strong></td><td><code>createIndex({ field: 1 })</code></td><td>1=asc, -1=desc</td></tr> |
|
<tr><td><strong>Compound</strong></td><td><code>createIndex({ a: 1, b: -1 })</code></td><td>Follows <strong>ESR rule</strong>: Equality β Sort β Range</td></tr> |
|
<tr><td><strong>Multikey</strong></td><td>Auto when indexing array field</td><td>One index entry per array element</td></tr> |
|
<tr><td><strong>Unique</strong></td><td><code>createIndex({ email: 1 }, { unique: true })</code></td><td>Rejects duplicate values</td></tr> |
|
<tr><td><strong>Sparse</strong></td><td><code>{ sparse: true }</code></td><td>Only indexes docs where field exists</td></tr> |
|
<tr><td><strong>TTL</strong></td><td><code>{ expireAfterSeconds: N }</code></td><td>Auto-deletes docs after N seconds</td></tr> |
|
<tr><td><strong>Text</strong></td><td><code>createIndex({ field: "text" })</code></td><td>For basic full-text search (not Atlas Search)</td></tr> |
|
</table></div> |
|
|
|
<div class="subsection"> |
|
<h3>explain() β What to Look For</h3> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="kw">await</span> collection.<span class="fn">find</span>({ age: { <span class="op">$gt</span>: <span class="nb">25</span> } }).<span class="fn">explain</span>(<span class="str">"executionStats"</span>)</pre></div> |
|
<div class="table-wrap"><table> |
|
<tr><th>Stage in output</th><th>Meaning</th></tr> |
|
<tr><td><code>COLLSCAN</code></td><td>β Full collection scan β no index used</td></tr> |
|
<tr><td><code>IXSCAN</code></td><td>β
Index scan β efficient</td></tr> |
|
<tr><td><code>nReturned</code> β <code>totalDocsExamined</code></td><td>β
Good index selectivity</td></tr> |
|
<tr><td><code>nReturned</code> Β« <code>totalDocsExamined</code></td><td>β Index not selective enough</td></tr> |
|
</table></div> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>Covered Queries</h3> |
|
<p>A query is <strong>covered</strong> (served entirely from index, zero doc reads) when ALL of:</p> |
|
<ul> |
|
<li>All filter fields are in the index</li> |
|
<li>All projection fields are in the index</li> |
|
<li><code>_id</code> is excluded from projection (unless it's in the index)</li> |
|
</ul> |
|
</div> |
|
|
|
<div class="tip"><strong>Tradeoff:</strong> Indexes speed up reads but slow down writes β insert/update/delete must update every index too. Too many indexes = wasted disk + slower writes.</div> |
|
</div> |
|
</div> |
|
|
|
<!-- S4: DATA MODELING --> |
|
<div class="section" id="s4"> |
|
<div class="section-header" onclick="toggleSection(this)"> |
|
<span class="section-icon">π</span> |
|
<span class="section-title">Section 4 β Data Modeling <span class="section-badge badge-green" style="margin-left:6px">4%</span></span> |
|
<span class="chevron">βΆ</span> |
|
</div> |
|
<div class="section-body"> |
|
<div class="table-wrap"><table> |
|
<tr><th>Pattern</th><th>When to use</th></tr> |
|
<tr><td><strong>Embed</strong></td><td>Data accessed together, one-to-few, tightly coupled</td></tr> |
|
<tr><td><strong>Reference</strong></td><td>Data is large, many-to-many, accessed independently</td></tr> |
|
</table></div> |
|
|
|
<div class="subsection"> |
|
<h3>Anti-Patterns to Know</h3> |
|
<ul> |
|
<li><strong>Massive arrays</strong> β unbounded array growth in a document</li> |
|
<li><strong>Too many collections</strong> β splitting what should be one collection</li> |
|
<li><strong>Bloated documents</strong> β storing everything in one doc unnecessarily</li> |
|
<li><strong>Storing data in field names</strong> β using a dynamic value as a key</li> |
|
</ul> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// β BAD β date is the field NAME (anti-pattern). Can't query, can't index.</span> |
|
{ _id: <span class="nb">1</span>, <span class="str">"2024-01-01"</span>: <span class="nb">150</span>, <span class="str">"2024-01-02"</span>: <span class="nb">200</span>, <span class="str">"2024-01-03"</span>: <span class="nb">175</span> } |
|
|
|
<span class="cm">// β
GOOD β date is a VALUE inside an array. Queryable and indexable.</span> |
|
{ _id: <span class="nb">1</span>, sales: [ |
|
{ date: <span class="str">"2024-01-01"</span>, amount: <span class="nb">150</span> }, |
|
{ date: <span class="str">"2024-01-02"</span>, amount: <span class="nb">200</span> } |
|
]}</pre></div> |
|
<div class="tip"><strong>Exam tip:</strong> If field names look like they carry data (dates, usernames, categories as keys) β that's the anti-pattern.</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<!-- S5: NODE.JS DRIVER --> |
|
<div class="section open" id="s5"> |
|
<div class="section-header" onclick="toggleSection(this)"> |
|
<span class="section-icon">π©</span> |
|
<span class="section-title">Section 5 β Node.js Driver <span class="section-badge badge-red" style="margin-left:6px">18β19%</span></span> |
|
<span class="chevron">βΆ</span> |
|
</div> |
|
<div class="section-body"> |
|
|
|
<div class="subsection"> |
|
<h3>5.1 Install & Import</h3> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre>npm install mongodb |
|
|
|
<span class="kw">const</span> { MongoClient, ObjectId } = <span class="fn">require</span>(<span class="str">"mongodb"</span>)</pre></div> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>5.2 Connection Pattern</h3> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="kw">const</span> uri = <span class="str">"mongodb+srv://user:pass@cluster.mongodb.net/?retryWrites=true&w=majority"</span> |
|
<span class="kw">const</span> client = <span class="kw">new</span> <span class="fn">MongoClient</span>(uri) |
|
|
|
<span class="kw">async function</span> <span class="fn">run</span>() { |
|
<span class="kw">try</span> { |
|
<span class="kw">await</span> client.<span class="fn">connect</span>() |
|
<span class="kw">const</span> db = client.<span class="fn">db</span>(<span class="str">"myDatabase"</span>) |
|
<span class="kw">const</span> col = db.<span class="fn">collection</span>(<span class="str">"myCollection"</span>) |
|
<span class="cm">// ... operations</span> |
|
} <span class="kw">finally</span> { |
|
<span class="kw">await</span> client.<span class="fn">close</span>() |
|
} |
|
} |
|
<span class="fn">run</span>()</pre></div> |
|
|
|
<h4>URI Components</h4> |
|
<div class="table-wrap"><table> |
|
<tr><th>Part</th><th>Example</th><th>Purpose</th></tr> |
|
<tr><td>Protocol</td><td><code>mongodb+srv://</code></td><td>SRV connection (Atlas). Use <code>mongodb://</code> for local.</td></tr> |
|
<tr><td>Credentials</td><td><code>user:p%40ss</code></td><td>URL-encode special chars: <code>@</code> β <code>%40</code></td></tr> |
|
<tr><td>Host</td><td><code>cluster.mongodb.net</code></td><td>Atlas cluster hostname</td></tr> |
|
<tr><td>Database</td><td><code>/myDatabase</code></td><td>Optional default DB</td></tr> |
|
<tr><td>Options</td><td><code>?retryWrites=true&w=majority</code></td><td>Write concern & retry</td></tr> |
|
</table></div> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>5.3 Connection Pooling</h3> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// β
CORRECT β one client for the entire app lifetime</span> |
|
<span class="kw">const</span> client = <span class="kw">new</span> <span class="fn">MongoClient</span>(uri, { |
|
maxPoolSize: <span class="nb">10</span>, <span class="cm">// max simultaneous connections (default: 5)</span> |
|
minPoolSize: <span class="nb">2</span>, <span class="cm">// kept alive even when idle</span> |
|
maxIdleTimeMS: <span class="nb">30000</span> <span class="cm">// close idle connections after 30s</span> |
|
}) |
|
|
|
<span class="cm">// β WRONG β never create a new MongoClient per request</span> |
|
app.<span class="fn">get</span>(<span class="str">"/users"</span>, <span class="kw">async</span> (req, res) => { |
|
<span class="kw">const</span> client = <span class="kw">new</span> <span class="fn">MongoClient</span>(uri) <span class="cm">// β antipattern, wastes connections</span> |
|
})</pre></div> |
|
<ul> |
|
<li>Default pool size = <strong>5 connections</strong></li> |
|
<li><code>MongoClient</code> is <strong>thread-safe</strong> β share it across the app</li> |
|
<li>Pool eliminates connection overhead β requests reuse existing connections</li> |
|
</ul> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>5.4 Complete CRUD Quick Reference</h3> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// ββ INSERT ββββββββββββββββββββββββββββββββββββββββββ</span> |
|
<span class="kw">await</span> col.<span class="fn">insertOne</span>(doc) |
|
<span class="kw">await</span> col.<span class="fn">insertMany</span>([doc1, doc2], { ordered: <span class="nb">false</span> }) |
|
|
|
<span class="cm">// ββ READ βββββββββββββββββββββββββββββββββββββββββββββ</span> |
|
<span class="kw">await</span> col.<span class="fn">findOne</span>(filter) <span class="cm">// single doc or null</span> |
|
<span class="kw">await</span> col.<span class="fn">find</span>(filter).<span class="fn">toArray</span>() |
|
<span class="kw">await</span> col.<span class="fn">find</span>({}).<span class="fn">sort</span>({ age: <span class="op">-1</span> }).<span class="fn">limit</span>(<span class="nb">10</span>).<span class="fn">skip</span>(<span class="nb">20</span>).<span class="fn">toArray</span>() |
|
<span class="kw">await</span> col.<span class="fn">countDocuments</span>(filter) <span class="cm">// exact count</span> |
|
<span class="kw">await</span> col.<span class="fn">estimatedDocumentCount</span>() <span class="cm">// fast, no filter</span> |
|
|
|
<span class="cm">// ββ UPDATE βββββββββββββββββββββββββββββββββββββββββββ</span> |
|
<span class="kw">await</span> col.<span class="fn">updateOne</span>(filter, { <span class="op">$set</span>: { field: value } }) |
|
<span class="kw">await</span> col.<span class="fn">updateMany</span>(filter, { <span class="op">$set</span>: { field: value } }) |
|
<span class="kw">await</span> col.<span class="fn">updateOne</span>(filter, update, { upsert: <span class="nb">true</span> }) |
|
<span class="kw">await</span> col.<span class="fn">replaceOne</span>(filter, replacementDoc) <span class="cm">// no $ operators!</span> |
|
<span class="kw">await</span> col.<span class="fn">findOneAndUpdate</span>(filter, update, { returnDocument: <span class="str">"after"</span> }) |
|
<span class="kw">await</span> col.<span class="fn">findOneAndReplace</span>(filter, replacementDoc, { returnDocument: <span class="str">"after"</span> }) |
|
|
|
<span class="cm">// ββ DELETE βββββββββββββββββββββββββββββββββββββββββββ</span> |
|
<span class="kw">await</span> col.<span class="fn">deleteOne</span>(filter) |
|
<span class="kw">await</span> col.<span class="fn">deleteMany</span>(filter) |
|
<span class="kw">await</span> col.<span class="fn">findOneAndDelete</span>(filter, { sort: { createdAt: <span class="nb">1</span> } }) |
|
|
|
<span class="cm">// ββ AGGREGATION ββββββββββββββββββββββββββββββββββββββ</span> |
|
<span class="kw">await</span> col.<span class="fn">aggregate</span>([...stages]).<span class="fn">toArray</span>() |
|
|
|
<span class="cm">// ββ BULK βββββββββββββββββββββββββββββββββββββββββββββ</span> |
|
<span class="kw">await</span> col.<span class="fn">bulkWrite</span>([ |
|
{ insertOne: { document: { name: <span class="str">"A"</span> } } }, |
|
{ updateOne: { filter: { name: <span class="str">"B"</span> }, update: { <span class="op">$set</span>: { active: <span class="nb">true</span> } } } }, |
|
{ deleteOne: { filter: { name: <span class="str">"C"</span> } } } |
|
], { ordered: <span class="nb">false</span> }) <span class="cm">// ordered: true (default) stops on first error</span></pre></div> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>5.5 Write Concern & Read Preference</h3> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// Write concern β how many nodes must acknowledge</span> |
|
<span class="kw">const</span> client = <span class="kw">new</span> <span class="fn">MongoClient</span>(uri, { w: <span class="str">"majority"</span>, wtimeoutMS: <span class="nb">5000</span> }) |
|
<span class="cm">// w: 0 β fire and forget | w: 1 β primary only (default) | w: "majority" β safest</span> |
|
|
|
<span class="cm">// Read preference β which replica to read from</span> |
|
<span class="kw">const</span> db = client.<span class="fn">db</span>(<span class="str">"myDB"</span>, { readPreference: <span class="str">"secondaryPreferred"</span> }) |
|
<span class="cm">// "primary" (default) | "primaryPreferred" | "secondary"</span> |
|
<span class="cm">// "secondaryPreferred" | "nearest" (lowest latency)</span></pre></div> |
|
</div> |
|
|
|
</div> |
|
</div> |
|
|
|
<!-- S6: TOOLS --> |
|
<div class="section" id="s6"> |
|
<div class="section-header" onclick="toggleSection(this)"> |
|
<span class="section-icon">π§</span> |
|
<span class="section-title">Section 6 β Tools & Tooling <span class="section-badge badge-green" style="margin-left:6px">2%</span></span> |
|
<span class="chevron">βΆ</span> |
|
</div> |
|
<div class="section-body"> |
|
<div class="table-wrap"><table> |
|
<tr><th>Tool</th><th>What it is</th></tr> |
|
<tr><td><strong>Atlas Data Explorer</strong></td><td>GUI in Atlas UI to browse collections, run queries, create indexes</td></tr> |
|
<tr><td><strong>Atlas Sample Datasets</strong></td><td>Load via Atlas UI β "Load Sample Dataset"</td></tr> |
|
<tr><td><strong>mongosh</strong></td><td>Modern MongoDB shell for interactive queries</td></tr> |
|
<tr><td><strong>MongoDB Compass</strong></td><td>Desktop GUI for local MongoDB</td></tr> |
|
</table></div> |
|
</div> |
|
</div> |
|
|
|
<!-- S7: ATLAS SEARCH --> |
|
<div class="section open" id="s7"> |
|
<div class="section-header" onclick="toggleSection(this)"> |
|
<span class="section-icon">π</span> |
|
<span class="section-title">Atlas Search β Exam Objectives 2.18 & 2.19 <span class="section-badge badge-red" style="margin-left:6px">In CRUD section</span></span> |
|
<span class="chevron">βΆ</span> |
|
</div> |
|
<div class="section-body"> |
|
<div class="table-wrap"><table> |
|
<tr><th></th><th>Regular Index</th><th>Atlas Search Index</th></tr> |
|
<tr><td>Purpose</td><td>Query performance</td><td>Full-text / relevance search</td></tr> |
|
<tr><td>Used via</td><td><code>find()</code>, <code>$match</code></td><td><code>$search</code> aggregation stage</td></tr> |
|
<tr><td>Created via</td><td><code>createIndex()</code></td><td><code>createSearchIndex()</code></td></tr> |
|
<tr><td>Engine</td><td>MongoDB</td><td>Apache Lucene</td></tr> |
|
</table></div> |
|
|
|
<div class="subsection"> |
|
<h3>Creating a Search Index (Obj 2.18)</h3> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// Node.js driver</span> |
|
<span class="kw">await</span> collection.<span class="fn">createSearchIndex</span>({ |
|
name: <span class="str">"movieTitleIdx"</span>, |
|
type: <span class="str">"search"</span>, |
|
definition: { |
|
mappings: { |
|
dynamic: <span class="nb">false</span>, <span class="cm">// true = auto-index all fields | false = explicit only</span> |
|
fields: { |
|
title: { type: <span class="str">"string"</span> }, |
|
plot: { type: <span class="str">"string"</span> } |
|
} |
|
} |
|
} |
|
}) |
|
|
|
<span class="cm">// List search indexes</span> |
|
<span class="kw">await</span> collection.<span class="fn">listSearchIndexes</span>().<span class="fn">toArray</span>() |
|
|
|
<span class="cm">// Drop search index</span> |
|
db.movies.<span class="fn">dropSearchIndex</span>(<span class="str">"movieTitleIdx"</span>)</pre></div> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>Running a Search Query (Obj 2.19)</h3> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// $search MUST be the FIRST stage in the pipeline</span> |
|
<span class="kw">await</span> collection.<span class="fn">aggregate</span>([ |
|
{ |
|
<span class="op">$search</span>: { |
|
index: <span class="str">"movieTitleIdx"</span>, |
|
text: { query: <span class="str">"space adventure"</span>, path: [<span class="str">"title"</span>, <span class="str">"plot"</span>] } |
|
} |
|
}, |
|
{ <span class="op">$limit</span>: <span class="nb">5</span> }, |
|
{ <span class="op">$project</span>: { title: <span class="nb">1</span>, score: { <span class="op">$meta</span>: <span class="str">"searchScore"</span> } } } |
|
]).<span class="fn">toArray</span>() |
|
|
|
<span class="cm">// autocomplete β prefix/typeahead</span> |
|
{ <span class="op">$search</span>: { autocomplete: { query: <span class="str">"star"</span>, path: <span class="str">"title"</span> } } } |
|
|
|
<span class="cm">// compound β must / should / mustNot</span> |
|
{ <span class="op">$search</span>: { compound: { |
|
must: [{ text: { query: <span class="str">"action"</span>, path: <span class="str">"genres"</span> } }], |
|
mustNot: [{ text: { query: <span class="str">"horror"</span>, path: <span class="str">"genres"</span> } }] |
|
} } }</pre></div> |
|
|
|
<h4>$search Operators</h4> |
|
<div class="table-wrap"><table> |
|
<tr><th>Operator</th><th>Use case</th></tr> |
|
<tr><td><code>text</code></td><td>Full-text search β tokenizes and scores results</td></tr> |
|
<tr><td><code>autocomplete</code></td><td>Prefix/typeahead ("iph" β "iPhone")</td></tr> |
|
<tr><td><code>phrase</code></td><td>Exact phrase matching</td></tr> |
|
<tr><td><code>compound</code></td><td>Combine conditions: <code>must</code>, <code>should</code>, <code>mustNot</code></td></tr> |
|
<tr><td><code>range</code></td><td>Numeric or date range search</td></tr> |
|
</table></div> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>Tokenization & Analyzers</h3> |
|
<p>Analyzers control how Atlas Search breaks text into searchable tokens at index time.</p> |
|
<div class="table-wrap"><table> |
|
<tr><th>Analyzer</th><th>How it tokenizes</th><th>Best for</th></tr> |
|
<tr><td><code>lucene.standard</code></td><td>Splits on whitespace & punctuation, lowercases</td><td>General-purpose text (default)</td></tr> |
|
<tr><td><code>lucene.english</code></td><td>Standard + stemming ("running" β "run")</td><td>English prose, descriptions</td></tr> |
|
<tr><td><code>lucene.keyword</code></td><td><strong>No splitting</strong> β entire field = one token</td><td>Exact match: SKUs, emails, IDs</td></tr> |
|
<tr><td><code>lucene.whitespace</code></td><td>Splits on whitespace only, preserves case</td><td>When punctuation is meaningful</td></tr> |
|
<tr><td><code>lucene.simple</code></td><td>Splits on non-letter chars, lowercases</td><td>Simple case-insensitive search</td></tr> |
|
</table></div> |
|
<div class="trap"><strong>Exam trap:</strong> "Exact match on product codes / emails" β <strong><code>lucene.keyword</code></strong>. Any other analyzer splits the value into tokens and breaks exact matching.</div> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>Edge Grams, N-Grams & Token Filters</h3> |
|
<div class="table-wrap"><table> |
|
<tr><th>Filter</th><th>What it does</th><th>Use case</th></tr> |
|
<tr><td><strong>Edge gram</strong></td><td>Tokens from word start: "M","Mo","Mon","Mong"β¦</td><td>Autocomplete / prefix search</td></tr> |
|
<tr><td><strong>N-gram</strong></td><td>Tokens from any position: "tla","las","las"β¦</td><td>Mid-word / substring search</td></tr> |
|
<tr><td><strong>Stop words</strong></td><td>Removes noise: "the","a","is","on"</td><td>Cleaner relevance scoring</td></tr> |
|
<tr><td><strong>Stemming</strong></td><td>"running" β "run" (root form)</td><td>Broader English text matching</td></tr> |
|
<tr><td><strong>Lowercase</strong></td><td>Normalises case before indexing</td><td>Case-insensitive search</td></tr> |
|
</table></div> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// Edge gram index β powers autocomplete</span> |
|
db.movies.<span class="fn">createSearchIndex</span>(<span class="str">"autocomplete_idx"</span>, <span class="str">"search"</span>, { |
|
mappings: { |
|
dynamic: <span class="nb">false</span>, |
|
fields: { |
|
title: { |
|
type: <span class="str">"string"</span>, |
|
indexAnalyzer: { <span class="cm">// used at INDEX time</span> |
|
type: <span class="str">"custom"</span>, |
|
tokenizer: { type: <span class="str">"standard"</span> }, |
|
tokenFilters: [ |
|
{ type: <span class="str">"lowercase"</span> }, |
|
{ type: <span class="str">"edgeGram"</span>, minGram: <span class="nb">2</span>, maxGram: <span class="nb">10</span> } |
|
] |
|
}, |
|
searchAnalyzer: <span class="str">"lucene.standard"</span> <span class="cm">// used at QUERY time β NOT edge gram!</span> |
|
} |
|
} |
|
} |
|
})</pre></div> |
|
<div class="trap"><strong>Key trap:</strong> Edge gram is applied at <strong>index time only</strong>. Use the standard analyzer at query time β applying edge gram at both times breaks matching because the query term also gets chopped into grams.</div> |
|
</div> |
|
|
|
<div class="trap"> |
|
<strong>Atlas Search exam traps summary:</strong><br> |
|
β’ <code>$search</code> must be the <strong>first stage</strong> in the pipeline.<br> |
|
β’ <code>$text</code> β <code>$search</code> β the exam will offer both as options.<br> |
|
β’ <code>createIndex()</code> β <code>createSearchIndex()</code> β different engines.<br> |
|
β’ Use <code>{ $meta: "searchScore" }</code> in <code>$project</code> to return relevance scores.<br> |
|
β’ <code>dynamic: false</code> with no <code>fields</code> defined = nothing gets indexed. |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<!-- S8: FREQUENTLY TESTED SCENARIOS --> |
|
<div class="section open" id="s8"> |
|
<div class="section-header" onclick="toggleSection(this)"> |
|
<span class="section-icon">β οΈ</span> |
|
<span class="section-title">Frequently Tested Scenarios</span> |
|
<span class="chevron">βΆ</span> |
|
</div> |
|
<div class="section-body"> |
|
|
|
<div class="subsection"> |
|
<h3>1. Array Querying β Tricky Behavior</h3> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// Docs: { tags: ["mongodb", "nosql"] } AND { tags: ["mongodb"] }</span> |
|
|
|
{ tags: <span class="str">"mongodb"</span> } <span class="cm">// β
matches BOTH β checks if value EXISTS in array</span> |
|
{ tags: [<span class="str">"mongodb"</span>] } <span class="cm">// β οΈ matches ONLY { tags: ["mongodb"] } β exact array match!</span> |
|
|
|
{ tags: { <span class="op">$all</span>: [<span class="str">"mongodb"</span>, <span class="str">"nosql"</span>] } } <span class="cm">// array must contain ALL values (order irrelevant)</span> |
|
{ tags: { <span class="op">$size</span>: <span class="nb">2</span> } } <span class="cm">// array must have exactly 2 elements</span> |
|
{ <span class="str">"scores.0"</span>: { <span class="op">$gt</span>: <span class="nb">90</span> } } <span class="cm">// dot notation β first element of scores array</span></pre></div> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>2. Querying Nested / Embedded Documents</h3> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// Doc: { address: { city: "Mumbai", zip: "400001" } }</span> |
|
|
|
{ <span class="str">"address.city"</span>: <span class="str">"Mumbai"</span> } <span class="cm">// β
dot notation β correct</span> |
|
|
|
{ address: { city: <span class="str">"Mumbai"</span>, zip: <span class="str">"400001"</span> } } <span class="cm">// β
exact whole-object match</span> |
|
{ address: { zip: <span class="str">"400001"</span>, city: <span class="str">"Mumbai"</span> } } <span class="cm">// β different key order = no match!</span></pre></div> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>3. Element Operators</h3> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre>{ email: { <span class="op">$exists</span>: <span class="nb">true</span> } } <span class="cm">// docs that HAVE the email field</span> |
|
{ email: { <span class="op">$exists</span>: <span class="nb">false</span> } } <span class="cm">// docs that DON'T have the email field</span> |
|
|
|
{ age: { <span class="op">$type</span>: <span class="str">"int"</span> } } <span class="cm">// field must be BSON int type</span> |
|
{ age: { <span class="op">$type</span>: <span class="str">"string"</span> } } <span class="cm">// field must be string type</span></pre></div> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>4. $unwind β Aggregation Trap</h3> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="cm">// Doc: { name: "Ayyub", tags: ["a", "b", "c"] }</span> |
|
<span class="cm">// After $unwind on "tags" β produces THREE documents:</span> |
|
<span class="cm">// { name: "Ayyub", tags: "a" }</span> |
|
<span class="cm">// { name: "Ayyub", tags: "b" }</span> |
|
<span class="cm">// { name: "Ayyub", tags: "c" }</span></pre></div> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>5. $push with $each, $sort, $slice</h3> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre><span class="kw">await</span> collection.<span class="fn">updateOne</span>( |
|
{ _id: id }, |
|
{ <span class="op">$push</span>: { scores: { |
|
<span class="op">$each</span>: [<span class="nb">95</span>, <span class="nb">88</span>], <span class="cm">// push multiple values</span> |
|
<span class="op">$sort</span>: <span class="op">-1</span>, <span class="cm">// sort array descending after push</span> |
|
<span class="op">$slice</span>: <span class="nb">3</span> <span class="cm">// keep only top 3</span> |
|
} } } |
|
)</pre></div> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>6. $group β _id is Mandatory</h3> |
|
<div class="code-wrap"><button class="copy-btn" onclick="copyCode(this)">Copy</button><pre>{ <span class="op">$group</span>: { _id: <span class="nb">null</span>, total: { <span class="op">$sum</span>: <span class="str">"$amount"</span> } } } <span class="cm">// grand total (null = group all)</span> |
|
{ <span class="op">$group</span>: { _id: <span class="str">"$category"</span>, count: { <span class="op">$sum</span>: <span class="nb">1</span> } } } <span class="cm">// group by category</span></pre></div> |
|
</div> |
|
|
|
</div> |
|
</div> |
|
|
|
<!-- S9: EXAM STRATEGY --> |
|
<div class="section open" id="s9"> |
|
<div class="section-header" onclick="toggleSection(this)"> |
|
<span class="section-icon">π―</span> |
|
<span class="section-title">Exam Day Strategy</span> |
|
<span class="chevron">βΆ</span> |
|
</div> |
|
<div class="section-body"> |
|
<ul> |
|
<li><strong>No negative marking</strong> β always guess, never leave blank.</li> |
|
<li>51% is CRUD β if you nail CRUD, you pass.</li> |
|
<li>For update questions: <em>is there a <code>$</code> operator?</em> If not β full replacement.</li> |
|
<li>For aggregation: <code>$match</code> early, <code>$group</code> to aggregate, <code>$project</code> to reshape.</li> |
|
<li>"Write without losing existing docs" β <code>$merge</code>, not <code>$out</code>.</li> |
|
<li>"Autocomplete / prefix" β edge gram filter at index time.</li> |
|
<li>"Exact match on codes/emails" β <code>lucene.keyword</code> analyzer.</li> |
|
<li><code>$search</code> must be the <strong>first stage</strong> β and β <code>$text</code>.</li> |
|
<li><code>returnDocument</code> default is <strong>"before"</strong> in <code>findOneAndUpdate</code>.</li> |
|
<li>Watch for "which is <strong>NOT</strong> valid" or "which is <strong>incorrect</strong>" phrasing.</li> |
|
<li>Aim ~1 minute per question β 53 questions total.</li> |
|
</ul> |
|
|
|
<div class="tip" style="margin-top:14px"> |
|
<strong>You've got this.</strong> You work with MongoDB every day as a Solution Architect. Trust your hands-on experience β it will carry you through. π |
|
</div> |
|
|
|
<div style="margin-top:16px"> |
|
<a href="https://learn.mongodb.com/pages/mongodb-developer-practice-questions" target="_blank" style="display:inline-block; padding:9px 18px; background:var(--mg-green-soft); color:#fff; border-radius:7px; font-size:12px; font-weight:600; text-decoration:none;"> |
|
π Official Practice Questions β |
|
</a> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<!-- S10: REAL EXAM EXPERIENCE --> |
|
<div class="section open" id="s10"> |
|
<div class="section-header" onclick="toggleSection(this)"> |
|
<span class="section-icon">π‘</span> |
|
<span class="section-title">Real Exam Experience β Tips From Someone Who Just Took It</span> |
|
<span class="chevron">βΆ</span> |
|
</div> |
|
<div class="section-body"> |
|
|
|
<div class="tip" style="margin-bottom:16px"> |
|
<strong>First-hand account</strong> β these tips come from sitting the exam in June 2026. Not theory. What actually happened in the room. |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>π How to Read Questions</h3> |
|
<ul> |
|
<li><strong>Read the entire question once, then read all options</strong> before answering. Questions often have long sample documents embedded β resist the urge to skim. Read it fully first.</li> |
|
<li><strong>Eliminate options one by one</strong> β filter out incorrect syntax first, then narrow by limiting query conditions, then pick the best remaining option. Don't just pick the first thing that looks right.</li> |
|
<li>Some questions look complex but become obvious once you spot an incorrect syntax in 2β3 of the options. Start by ruling out syntactically wrong answers.</li> |
|
</ul> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>π© Flagging Questions β Use Sparingly</h3> |
|
<ul> |
|
<li><strong>Don't flag questions unnecessarily.</strong> When you revisit a flagged question, you have to re-read and re-understand it from scratch β and the clock is ticking, which makes it harder than it actually is.</li> |
|
<li>Flag <strong>only what is absolutely critical</strong> and genuinely needs a second look. If you're 70% confident, pick your answer and move on.</li> |
|
<li>The mental overhead of flagging too many is real β it creates anxiety mid-exam when you see a long flagged list.</li> |
|
</ul> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>β±οΈ Time Management</h3> |
|
<ul> |
|
<li><strong>Don't submit on the last question.</strong> It's easy to hit the final question and feel done β resist. Always go back and review before submitting.</li> |
|
<li>Some questions are genuinely quick and easy β answer them fast and bank that time for the harder ones later.</li> |
|
<li>Even with good prep, review time is tighter than expected. Budget for it consciously β don't assume you'll have lots of buffer. <em>(Only ~6 minutes were left at the end after being confident going in.)</em></li> |
|
<li>Aim for ~1 minute per question as a baseline. If you're over 90 seconds, make a call and move on.</li> |
|
</ul> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>π€« Proctored Exam Etiquette</h3> |
|
<ul> |
|
<li><strong>Do not read questions aloud or talk to yourself</strong> β even quietly. The proctor will interrupt you, which breaks your focus and costs you time. It's a surprisingly easy habit to slip into when thinking hard.</li> |
|
<li>Mouth movements are monitored. Think silently.</li> |
|
<li>Set up your environment beforehand β clear desk, stable internet, no phone nearby. Any interruption mid-exam is disruptive.</li> |
|
</ul> |
|
</div> |
|
|
|
<div class="subsection"> |
|
<h3>π Preparation That Actually Helped</h3> |
|
<ul> |
|
<li><strong>The official sample exam is non-negotiable</strong> β the scenario-based outcome questions are excellent. They make you genuinely think rather than just recall syntax, and they expose your real gaps before the actual exam. Treat them as a diagnostic, not a warmup.</li> |
|
<li><strong>This cheat sheet</strong> β built while prepping, used during revision. Having a single structured reference for CRUD, aggregation, Node.js driver syntax, and Atlas Search in one place made the difference. It's also useful post-exam in real customer conversations.</li> |
|
<li><strong>Hands-on practice is non-negotiable.</strong> Syntaxes only stick through doing. Reading about <code>findOneAndUpdate</code> vs <code>replaceOne</code> is very different from writing it yourself.</li> |
|
</ul> |
|
</div> |
|
|
|
<div class="warn"> |
|
<strong>The honest truth:</strong> Even working with MongoDB every day as a Solution Architect, the exam tests edges you don't always hit in real engagements. Prep deliberately. Use the sample exam. Know your syntaxes cold. |
|
</div> |
|
|
|
</div> |
|
</div> |
|
|
|
<footer> |
|
Built from official exam objectives & real exam experience Β· Share freely with the MongoDB community Β· |
|
<a href="https://learn.mongodb.com/pages/mongodb-associate-developer-exam" target="_blank">Official Exam Objectives β</a> |
|
</footer> |
|
|
|
</main> |
|
|
|
<script> |
|
// Toggle sections |
|
function toggleSection(header) { |
|
const section = header.parentElement; |
|
section.classList.toggle('open'); |
|
updateNavActive(); |
|
} |
|
|
|
// Scroll to section |
|
function navScrollTo(id) { |
|
const el = document.getElementById(id); |
|
if (el) { el.scrollIntoView({ behavior: 'smooth', block: 'start' }); el.classList.add('open'); } |
|
updateNavActive(); |
|
} |
|
|
|
// Copy code |
|
function copyCode(btn) { |
|
const pre = btn.nextElementSibling; |
|
const text = pre.innerText; |
|
navigator.clipboard.writeText(text).then(() => { |
|
btn.textContent = 'Copied!'; |
|
btn.classList.add('copied'); |
|
setTimeout(() => { btn.textContent = 'Copy'; btn.classList.remove('copied'); }, 1800); |
|
}); |
|
} |
|
|
|
// Search filter |
|
function filterSections(query) { |
|
const q = query.trim().toLowerCase(); |
|
const sections = document.querySelectorAll('.section'); |
|
sections.forEach(sec => { |
|
if (!q) { sec.style.display = ''; return; } |
|
const text = sec.innerText.toLowerCase(); |
|
sec.style.display = text.includes(q) ? '' : 'none'; |
|
}); |
|
} |
|
|
|
// Update active nav on scroll |
|
function updateNavActive() { |
|
const links = document.querySelectorAll('.nav-link'); |
|
const sections = document.querySelectorAll('.section'); |
|
let activeIdx = 0; |
|
sections.forEach((sec, i) => { |
|
const rect = sec.getBoundingClientRect(); |
|
if (rect.top < window.innerHeight * 0.4) activeIdx = i; |
|
}); |
|
links.forEach((l, i) => l.classList.toggle('active', i === activeIdx)); |
|
} |
|
window.addEventListener('scroll', updateNavActive, { passive: true }); |
|
</script> |
|
|
|
</body> |
|
</html> |