shell และ Compass
มีสามวิธีที่คุณจะคุยกับ MongoDB: shell mongosh สำหรับคำสั่งเร็ว ๆ ที่ทำเป็นสคริปต์ได้ MongoDB Compass ซึ่งเป็น graphical client ทางการ สำหรับคลิกสำรวจ และ driver ในภาษาของแอปพลิเคชันสำหรับงานจริง บทเรียนนี้ครอบคลุมทั้งสามอย่าง และปิดท้ายด้วยวิธีเปิดการเชื่อมต่อจากทั้งห้าสภาพแวดล้อมที่คอร์สนี้ใช้
ทุกตัวเชื่อมต่อด้วยที่อยู่ชนิดเดียวกัน — connection string — เมื่อเข้าใจสิ่งนี้แล้ว การสลับระหว่างเครื่องมือจึงง่ายมาก ทุกอย่างด้านล่างรันบน mongo 8.3.11 กับ mongosh 2.11.1 และ Node driver 7.7.0 บน Mac เครื่องเดียว transcript แบบ interactive มาจาก container mongo:8 ชั่วคราวที่มี database library ซึ่งมีสมาชิก 25 คน
พื้นฐาน mongosh
หัวข้อที่มีชื่อว่า “พื้นฐาน mongosh”mongosh คือ JavaScript shell แบบ interactive คุณพิมพ์คำสั่ง มันรันกับ server แล้วพิมพ์ผลลัพธ์ นี่คือ session จริง (prompt test> / library> เป็นของ shell เอง บรรทัดที่คุณพิมพ์ตามหลัง):
test> show dbsadmin 100.00 KiBconfig 12.00 KiBlibrary 8.00 KiBlocal 40.00 KiBtest> use libraryswitched to db librarylibrary> db.members.find({}, { _id: 0, name: 1 })[ { name: 'Member 1' }, { name: 'Member 2' }, ... { name: 'Member 19' }, { name: 'Member 20' }]Type "it" for morelibrary> it[ { name: 'Member 21' }, { name: 'Member 22' }, { name: 'Member 23' }, { name: 'Member 24' }, { name: 'Member 25' }]library> itno cursorshow dbsแสดงรายการ database;use libraryเลือกอันหนึ่ง (ไม่จำเป็นต้องมีอยู่แล้ว — MongoDB สร้างให้ตอนเขียนครั้งแรก);db.members.find()อ่านข้อมูล- shell พิมพ์ 20 document ต่อ batch (
config.get("displayBatchSize")คืน20) แล้วพิมพ์Type "it" for moreitย่อมาจาก iterate และเมื่อ cursor หมดจะตอบno cursor
show, use และ it เป็น คำสั่งเฉพาะ shell ไม่ใช่ JavaScript: ใช้ได้ที่ prompt แต่ไม่ใช่ในสคริปต์ สตริง --eval หรือ driver ทุกตัวมีคำเทียบเป็น JavaScript ธรรมดาที่ใช้ได้ทุกที่ และ fence นี้พิสูจน์ให้เห็น (มัน seed สมาชิก library ของตัวเองก่อน):
db.members.insertMany(Array.from({ length: 25 }, (_, i) => ({ name: `Member ${i + 1}`, joined: 2020 + (i % 6), fines: i % 4 })));
const lib = db.getSiblingDB("library_demo"); // like `use library_demo`lib.members.insertOne({ name: "Grace" });const names = db.getMongo().getDBNames(); // like `show dbs`console.log(names.includes("library_demo")); // true: created by the first write
const cursor = db.members.find({}, { _id: 0, name: 1 });console.log(cursor.next(), cursor.hasNext()); // like `it`, one document at a timeconsole.log(db.members.find().toArray().length); // or drain the whole cursorconsole.log(config.get("displayBatchSize")); // the interactive page size
assert.strictEqual(db.members.find().toArray().length, 25);assert.strictEqual(config.get("displayBatchSize"), 20);db.getSiblingDB("library_demo").dropDatabase();true{ name: 'Member 1' } true2520รัน mongosh โดยไม่ต้องพิมพ์: --eval และ --file
หัวข้อที่มีชื่อว่า “รัน mongosh โดยไม่ต้องพิมพ์: --eval และ --file”สำหรับสคริปต์และ cron job คุณไม่ได้นั่งอยู่หน้า prompt --eval รันหนึ่ง expression, --file รันสคริปต์ และ --quiet ปิด banner ทั้งหมดนี้รันกับ container ชั่วคราว (พร้อม credentials ดูหัวข้อถัดไป):
mongosh --quiet "mongodb://root:secret@localhost:27017/library?authSource=admin" \ --eval 'db.members.countDocuments({ fines: { $gt: 1 } })'
mongosh --quiet "mongodb://root:secret@localhost:27017" --file report.js121 of 2 members owe finesโดย report.js เป็นสคริปต์ธรรมดา — สังเกต console.log ไม่ใช่ show:
const library = db.getSiblingDB("report_demo");library.members.insertMany([{ name: "Ada", fines: 0 }, { name: "Alan", fines: 3 }]);const total = library.members.countDocuments();const owing = library.members.countDocuments({ fines: { $gt: 0 } });console.log(`${owing} of ${total} members owe fines`);assert.strictEqual(owing, 1);library.dropDatabase();ทัวร์ Compass
หัวข้อที่มีชื่อว่า “ทัวร์ Compass”MongoDB Compass คือ desktop GUI ทางการ และเป็นวิธีที่เป็นมิตรที่สุดในการสำรวจข้อมูลที่คุณยังไม่เข้าใจ คุณเชื่อมต่อโดยวาง connection string ลงในช่องบนหน้าจอเริ่มต้นแล้วกด Connect Compass ยังฝัง MongoDB shell ไว้ด้วย
เมื่อเชื่อมต่อแล้ว sidebar จะแสดง database และ collection ของคุณ เอกสารของ Compass (mongodb.com/docs/compass, เรียกอ่านเมื่อ 2026-09-30) ระบุ tab เหล่านี้สำหรับ collection:
- Documents — insert, เรียกดู และ query document; แก้ field ในที่ได้
- Aggregations — pipeline builder แบบภาพ: เพิ่ม stage ทีละอัน แล้วดู document เปลี่ยนรูป (โมดูล aggregation พึ่งพามัน)
- Schema — สุ่มตัวอย่าง collection แล้วแสดงว่ามี field ใดและเก็บ type อะไร
- Explain Plan — MongoDB รัน query อย่างไร ใช้ index หรือสแกนทั้งหมด (หัวใจของโมดูล indexes)
- Indexes — แสดง สร้าง และลบ index
- Validation — กฎ schema validation
Compass เป็นแอปเดสก์ท็อป เราจึงรันมันแบบอัตโนมัติเหมือนส่วนอื่นของบทนี้ไม่ได้ และหน้าตาเปลี่ยนไปตามรุ่น จึงให้เชื่อหน้าเอกสารมากกว่าภาพหน้าจอใด ๆ Compass และ shell เสริมกัน: Compass สำหรับมองและสำรวจ shell สำหรับทำซ้ำและเขียนสคริปต์ ไม่มีตัวใดแทน driver ในโค้ดแอปพลิเคชันของคุณได้
เชื่อมต่อจากแต่ละสภาพแวดล้อม
หัวข้อที่มีชื่อว่า “เชื่อมต่อจากแต่ละสภาพแวดล้อม”เมื่อถึงเวลาเขียนแอปพลิเคชัน คุณเชื่อมต่อด้วย driver connection string เหมือนกัน; ต่างกันแค่ API รอบ ๆ นี่คือวิธีเปิดการเชื่อมต่อในแต่ละห้าสภาพแวดล้อม Python, Go, Rust และ Node พิมพ์ connected เมื่อเรารัน snippet ด้านล่างตรง ๆ กับ container mongo:8 ธรรมดาที่พอร์ต 27017
mongosh "mongodb://localhost:27017"// app/connect.mjs @skip-verify ran verbatim against a throwaway mongo:8 on host port 27017import { MongoClient } from "mongodb";
const client = new MongoClient("mongodb://localhost:27017");await client.connect();await client.db("library").command({ ping: 1 });console.log("connected");from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017")client.admin.command("ping")print("connected")client, err := mongo.Connect(options.Client().ApplyURI("mongodb://localhost:27017"))if err != nil { return err}if err := client.Ping(ctx, nil); err != nil { return err}fmt.Println("connected")let client = Client::with_uri_str("mongodb://localhost:27017").await?;client .database("library") .run_command(doc! { "ping": 1 }) .await?;println!("connected");(เวอร์ชันที่ใช้: PyMongo 4.18.2, Go driver v2.9.1, Rust driver 3.9.1, Node driver 7.7.0 แท็บ Python, Go และ Rust รันในโปรแกรม scratch ที่จัดหา client/ctx ให้รอบ snippet)
option ใน connection string ที่ทำให้สะดุด
หัวข้อที่มีชื่อว่า “option ใน connection string ที่ทำให้สะดุด”รูปแบบคือ mongodb://[user:password@]host[:port][/database][?options] มีสาม option ที่ตัดสิน “เมื่อวานยังใช้ได้” ส่วนใหญ่ เรารันแต่ละอันกับ server จริงด้วย serverSelectionTimeoutMS 2 วินาที:
// app/connection-options.mjs @skip-verify needs a second, auth-enabled container on host port 27019import { MongoClient } from "mongodb";
async function tryUri(label, uri) { const client = new MongoClient(uri, { serverSelectionTimeoutMS: 2000 }); try { await client.connect(); await client.db("library").collection("members").findOne(); console.log(label, "-> ok"); } catch (e) { console.log(label, "->", e.name + ":", e.message.split("\n")[0]); } finally { await client.close(); }}await tryUri("no authSource ", "mongodb://root:secret@localhost:27019/library");await tryUri("authSource=admin", "mongodb://root:secret@localhost:27019/library?authSource=admin");await tryUri("replicaSet=rs0 ", "mongodb://localhost:27117/?replicaSet=rs0");await tryUri("replicaSet=nope ", "mongodb://localhost:27117/?replicaSet=nope");await tryUri("directConnection", "mongodb://localhost:27117/?directConnection=true");no authSource -> MongoServerError: Authentication failed.authSource=admin -> okreplicaSet=rs0 -> okreplicaSet=nope -> MongoServerSelectionError: Server selection timed out after 2000 msdirectConnection -> okauthSource— root user ถูกสร้างใน databaseadminเมื่อ string ระบุ database (/library) และไม่มีauthSourcedriver จะยืนยันตัวตน กับ database นั้น ซึ่งไม่มี user นั้นอยู่ credentials เดิม ได้Authentication failed.จนกว่าจะเพิ่ม?authSource=adminถ้าไม่มี database ใน path ค่าเริ่มต้นคือadminreplicaSet— ต้องตรงกับชื่อจริงของ set; ชื่อผิดไม่มีวันหาสมาชิกที่ตรงกัน driver จึง timeout ด้วย server-selection error แทนที่จะบอกว่า “ชื่อผิด”directConnection=true— ข้ามการ discovery แล้วคุยเฉพาะ address ที่คุณให้ (มีประโยชน์เมื่อ set ประกาศ hostname ที่เครื่องคุณเข้าไม่ถึง ดูบท Docker)
ใช้ client ร่วมกันตัวเดียว
หัวข้อที่มีชื่อว่า “ใช้ client ร่วมกันตัวเดียว”เมื่อ client เชื่อมต่อแล้ว คุณใช้มันซ้ำตลอดอายุของโปรแกรม client เป็นเจ้าของ connection pool (พร้อม connection สำหรับ monitor ไปยัง server) และถูกออกแบบมาให้ใช้ร่วมกัน เราวัดความต่างด้วย “request” ต่อเนื่อง 50 ครั้งบน server ในเครื่อง:
50 requests, new client each, never closed: 203 ms, connections +10050 requests, one shared client: 33 ms, connections +2(รันครั้งที่สอง: 162 ms เทียบ 36 ms; +100 เทียบ +2 ทั้งสองครั้ง) รั่ว connection ฝั่ง server สองตัวต่อ client และ latency มากขึ้นราวหกเท่า บน local socket ข้ามเครือข่ายที่มี TLS ช่องว่างยิ่งกว้างขึ้น
เลือกเครื่องมือ
หัวข้อที่มีชื่อว่า “เลือกเครื่องมือ”flowchart TD Q["What do you want to do"] --> S["Quick one-off command or a script"] Q --> C["Explore and understand unfamiliar data"] Q --> D["Build a feature in your app"] S --> SH["mongosh shell"] C --> CO["Compass GUI"] D --> DR["A driver in your language"]
เคล็ดลับและข้อควรระวัง
หัวข้อที่มีชื่อว่า “เคล็ดลับและข้อควรระวัง”- รูปแบบ connection string คือ
mongodb://[user:password@]host:port[/database][?options]มีเพียง host เท่านั้นที่จำเป็น - เลือก shell กับ Compass กับ driver ตามเจตนา: shell สำหรับคำสั่งที่เร็วและทำซ้ำได้ Compass สำหรับการสำรวจด้วยภาพและค้นหา schema และ driver สำหรับทุกอย่างที่แอปพลิเคชันของคุณทำใน production
- Authentication เปลี่ยน string ไม่ใช่ workflow ถ้าคุณตั้ง root user ในบท Docker ให้ใส่
root:secret@ก่อน host ในทุกเครื่องมือ และauthSource=adminเมื่อ path ระบุ database อื่น show,use,itไม่ใช่ JavaScript ในสคริปต์ให้ใช้db.getSiblingDB(...),db.getMongo().getDBNames()และเมธอดของ cursor
อ่านโค้ดที่ AI สร้าง
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI สร้าง”ผู้ช่วย AI ที่เขียน endpoint ของ Express บน MongoDB มักจะ “ทำ handler ให้ครบในตัวเอง”:
🐛 หา bug ในโค้ดนี้
// AI comment: "self-contained handler - connects, queries, done"app.get("/members/:name", async (req, res) => {const client = new MongoClient(process.env.MONGO_URL);const member = await client.db("library").collection("members").findOne({ name: req.params.name });res.json(member);});เฉลย
ทุก request สร้าง client ใหม่ เชื่อมต่อ และไม่เคยปิด เรารัน loop นี้ 50 ครั้งกับ server ในเครื่อง: ใช้เวลา 203 ms (162 ms ในการรันครั้งที่สอง) เทียบกับ 33 ms สำหรับ client ร่วมตัวเดียว และจำนวน connection ของ server เพิ่มขึ้น 100 สองตัวต่อ client ที่ถูกทิ้ง (หนึ่งสำหรับ pool และหนึ่งสำหรับ monitor) ภายใต้ traffic จริง จำนวน connection จะไต่ขึ้นจนถึงขีดจำกัดของ server หรือ OS แล้ว request เริ่มล้มเหลว วิธีแก้คือ client ตัวเดียวที่สร้างครั้งเดียวตอนเริ่มระบบและใช้ซ้ำ (และปิดตอน shutdown):
// One client for the whole process: this is what the prelude's `client` is.const members = client.db("library").collection("members");await members.insertOne({ name: "Grace" });const member = await members.findOne({ name: "Grace" }); // called per request, same poolif (member?.name !== "Grace") throw new Error("expected Grace");console.log(member.name);GraceMental Model
หัวข้อที่มีชื่อว่า “Mental Model”connection string คือ ที่อยู่พร้อมคำสั่ง: ไปเคาะที่ไหน (host:port) คุณคือใคร (user:password) บัญชีของคุณอยู่ที่ไหน (authSource) และคาดหวัง topology แบบไหน (replicaSet, directConnection) shell, Compass และ driver คือสาม front end ของประตูเดียวกัน object เดียวที่มี state คือ client — pool ของ connection อายุยาว — และนั่นคือเหตุผลที่สร้างมันครั้งเดียว
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| mongosh | เขียนสคริปต์และทำซ้ำได้เร็ว รันผ่าน SSH บน server ที่ไม่มี GUI ได้ --eval / --file เหมาะกับ cron และ CI | ต้องจำไวยากรณ์คำสั่งและชื่อ field เอง; show/use/it ใช้นอก prompt ไม่ได้ |
| Compass (GUI) | เห็น schema และ document จริงทันที สร้าง aggregation pipeline ทีละ stage เข้าใจข้อมูลใหม่เร็วขึ้น | ต้องมีหน้าจอ — ใช้ผ่าน SSH หรือทำอัตโนมัติไม่ได้ — และหนักกว่าคำสั่ง terminal เดียว |
| driver client ร่วมตัวเดียว | 33 ms เทียบ 203 ms สำหรับ 50 request, +2 connection เทียบ +100 (วัดด้านบน) | ต้องจัดการ lifecycle ของมัน (สร้างตอนเริ่ม ปิดตอน shutdown) |
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “Compass เป็นเครื่องมือที่ดีสำหรับทำงานประจำแบบอัตโนมัติ” — มันสร้างมาเพื่อคลิกสำรวจ งานที่รันทุกวันควรเป็นสคริปต์ mongosh
--fileหรือโค้ด driver - “client ใหม่ต่อ request ก็ได้ เพราะ driver มี pool อยู่แล้ว” — pool อยู่ใน client; client ใหม่คือ pool ใหม่ วัดได้:
connections +100สำหรับ 50 request - “mongosh กับ driver ใช้ไวยากรณ์เหมือนกันทุกประการ” —
use,show dbs,itมีเฉพาะที่ prompt ของ shell driver ใช้ API ของตัวเอง - “credentials เดียวกัน connection string แบบไหนก็ใช้ได้” — ถ้าไม่มี
authSource=adminstring ด้านบนล้มเหลวด้วยAuthentication failed. - “ชื่อ
replicaSetผิดจะได้ error ที่ชัดเจน” — ได้เป็น timeout ของ server-selection 2 วินาที
ตัวอย่างจากของจริง
ตัว driver เองฝังกฎนี้ไว้:
MongoClientที่สร้างโดยไม่มี option รายงานmaxPoolSize100 และminPoolSize0 (Node driver 7.7.0,client.options) เป็น pool ขนาดต่อ process — ไม่ใช่ต่อ request