<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: VisuaLeaf</title>
    <description>The latest articles on DEV Community by VisuaLeaf (@visualeaf).</description>
    <link>https://dev.to/visualeaf</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3818324%2F6cb54fe1-a36c-4f1a-a7b5-fa360f5daf8a.png</url>
      <title>DEV Community: VisuaLeaf</title>
      <link>https://dev.to/visualeaf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/visualeaf"/>
    <language>en</language>
    <item>
      <title>Learn how to use DuckDB for local analysis: open a database, query CSV and Parquet files, inspect relationships, edit data, and create charts.</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Thu, 03 Sep 2026 07:21:04 +0000</pubDate>
      <link>https://dev.to/visualeaf/learn-how-to-use-duckdb-for-local-analysis-open-a-database-query-csv-and-parquet-files-inspect-1ceo</link>
      <guid>https://dev.to/visualeaf/learn-how-to-use-duckdb-for-local-analysis-open-a-database-query-csv-and-parquet-files-inspect-1ceo</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/roxana_haidiner/how-to-use-duckdb-for-local-data-analysis-1koe" class="crayons-story__hidden-navigation-link"&gt;How to Use DuckDB for Local Data Analysis&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/roxana_haidiner" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2224346%2Ff71cca7a-b282-4793-a3ae-b04d89957d47.png" alt="roxana_haidiner profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/roxana_haidiner" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Roxana-Maria Haidiner
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Roxana-Maria Haidiner
                
                
              
              &lt;div id="story-author-preview-content-4562903" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/roxana_haidiner" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2224346%2Ff71cca7a-b282-4793-a3ae-b04d89957d47.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Roxana-Maria Haidiner&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/roxana_haidiner/how-to-use-duckdb-for-local-data-analysis-1koe" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 3&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/roxana_haidiner/how-to-use-duckdb-for-local-data-analysis-1koe" id="article-link-4562903"&gt;
          How to Use DuckDB for Local Data Analysis
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/duckdb"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;duckdb&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/sql"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;sql&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/database"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;database&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/softwaredevelopment"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;softwaredevelopment&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/roxana_haidiner/how-to-use-duckdb-for-local-data-analysis-1koe" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;4&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/roxana_haidiner/how-to-use-duckdb-for-local-data-analysis-1koe#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            8 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>mongodump Tasks Automate and Schedule MongoDB Backups</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Mon, 31 Aug 2026 11:00:28 +0000</pubDate>
      <link>https://dev.to/visualeaf/mongodump-tasks-automate-and-schedule-mongodb-backups-1emf</link>
      <guid>https://dev.to/visualeaf/mongodump-tasks-automate-and-schedule-mongodb-backups-1emf</guid>
      <description>&lt;p&gt;Running a MongoDB backup once is not difficult. With &lt;a href="https://visualeaf.com/database/mongodb-gui-tool/" rel="noopener noreferrer"&gt;MongoDB Database Tools&lt;/a&gt; installed, the complete PowerShell command can be as short as this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Program Files\MongoDB\Tools\100\bin\mongodump.exe"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;--uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mongodb://127.0.0.1:27017"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;--db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gui_test_db"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;--gzip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;--out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"C:\MongoDBBackups\gui_test_db"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example uses an unauthenticated local MongoDB deployment. Authenticated or remote deployments also require the appropriate credentials, authentication database, TLS settings, and connection options.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mongodump&lt;/code&gt; is MongoDB's command-line utility for creating a binary export of a database or collection. The problem is rarely the first run. It is repeating the same backup every day or week, keeping the settings consistent, using a fresh destination, and checking whether the operation actually completed.&lt;/p&gt;

&lt;p&gt;That is where a saved task becomes useful. In VisuaLeaf, the same binary dump can be configured once, run manually when needed, scheduled, and checked later from its execution history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Save the Same mongodump as a VisuaLeaf Task
&lt;/h2&gt;

&lt;p&gt;Open &lt;a href="https://visualeaf.com/features/task-manager/" rel="noopener noreferrer"&gt;*&lt;strong&gt;&lt;em&gt;Task Manager&lt;/em&gt;&lt;/strong&gt;*&lt;/a&gt;, create a new job, and choose the binary dump/restore operation. Select *&lt;strong&gt;&lt;em&gt;Export to Dump&lt;/em&gt;&lt;/strong&gt;* for the operation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6jb72mohjnolhhspbz5x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6jb72mohjnolhhspbz5x.png" alt="VisuaLeaf Task Manager showing a completed mongodump task for the gui_test_db database and its four collections." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The full &lt;code&gt;gui_test_db&lt;/code&gt; database saved as a reusable mongodump task in VisuaLeaf.&lt;/p&gt;

&lt;p&gt;The task should have a name that still makes sense when you return to it later. I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Full Database mongodump – 4.6 GB (Gzip)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is more useful than a generic name such as &lt;code&gt;Backup&lt;/code&gt; or &lt;code&gt;New Job&lt;/code&gt;. It tells me the scope, approximate source size, and whether compression is enabled.&lt;/p&gt;

&lt;p&gt;For the source, I selected the complete &lt;code&gt;gui_test_db&lt;/code&gt; database running locally on port &lt;code&gt;27017&lt;/code&gt;. Its uncompressed data size was approximately 4.6 GB, spread across four collections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;dataset_30000&lt;/code&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;large_collection_4GB&lt;/code&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;large_docs_10mb&lt;/code&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;medium_docs_1mb&lt;/code&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The source panel displayed the database statistics before the job ran. This gave me a quick check that I had selected the full database before creating several gigabytes of output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back Up a Database or One Collection
&lt;/h2&gt;

&lt;p&gt;The correct scope depends on why you need the backup.&lt;/p&gt;

&lt;p&gt;Choose a *&lt;strong&gt;&lt;em&gt;collection dump&lt;/em&gt;&lt;/strong&gt;* when you only need one dataset, want to move a collection between environments, or need a smaller restore unit. Choose a *&lt;strong&gt;&lt;em&gt;database dump&lt;/em&gt;&lt;/strong&gt;* when the job should include all collections and their metadata.&lt;/p&gt;

&lt;p&gt;Be deliberate here. A successful task using the wrong source is still the wrong backup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the Output Folder and Enable Gzip
&lt;/h2&gt;

&lt;p&gt;I saved the VisuaLeaf output in its own folder and enabled *&lt;strong&gt;&lt;em&gt;Compress with Gzip&lt;/em&gt;&lt;/strong&gt;*. A separate folder for every run keeps completed backups distinct and prevents CLI files, task files, or partial attempts from being mixed.&lt;/p&gt;

&lt;p&gt;The job summary also showed *&lt;strong&gt;&lt;em&gt;auto threads&lt;/em&gt;&lt;/strong&gt;* and *&lt;strong&gt;&lt;em&gt;no timeout&lt;/em&gt;&lt;/strong&gt;*. Auto threads leaves the worker configuration to VisuaLeaf. The gear icon opens the advanced settings if the task needs a specific thread limit or timeout. I left both settings at their defaults for this workflow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwg4lh49hkffdzq9b5mxz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwg4lh49hkffdzq9b5mxz.png" alt="VisuaLeaf Binary Dump task with gui_test_db selected as the source MongoDB database." width="800" height="683"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A full &lt;code&gt;gui_test_db&lt;/code&gt; backup configured as a binary dump task in VisuaLeaf.&lt;/p&gt;

&lt;p&gt;After choosing the source and destination, save the job. The connection, database, folder, compression, and operation settings remain attached to the task, so the next backup does not require rebuilding the command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Schedule and Monitor the MongoDB Backup
&lt;/h2&gt;

&lt;p&gt;You can still start a saved job manually, but scheduling is the main reason to turn this workflow into a task. VisuaLeaf's &lt;a href="https://visualeaf.com/docs/task-manager" rel="noopener noreferrer"&gt;Task Manager documentation&lt;/a&gt; lists one-time, hourly, daily, weekly, monthly, and custom cron schedules.&lt;/p&gt;

&lt;p&gt;Choose the frequency based on how much recent data the application could afford to lose. A weekly backup is easy to manage, but it may be inadequate for a database that changes throughout the day.&lt;/p&gt;

&lt;p&gt;Use a dated destination for recurring runs and decide how many older backups to keep. Otherwise, the task may work perfectly while the backup drive quietly runs out of space.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6c4xqo0xovbbkjc0o4xs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6c4xqo0xovbbkjc0o4xs.png" alt="VisuaLeaf Schedule Job window with a recurring daily 6 AM MongoDB backup selected." width="800" height="694"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The full database dump is scheduled to run every day at 6:00 AM.&lt;/p&gt;

&lt;p&gt;After a run, *&lt;strong&gt;&lt;em&gt;Execution History&lt;/em&gt;&lt;/strong&gt;* provides a detailed dump summary. In my test, it recorded:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total duration, collections, documents, and database size.&lt;/li&gt;
&lt;li&gt;The thread used for each collection.&lt;/li&gt;
&lt;li&gt;Document count and size per collection.&lt;/li&gt;
&lt;li&gt;Individual collection duration and processing speed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frelq1v97ehhhcjntxjjt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frelq1v97ehhhcjntxjjt.png" alt="VisuaLeaf dump summary showing four MongoDB collections with document counts, sizes, threads, and export durations." width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The completed dump included four collections, 30,449 documents, and 4.72 GB of source data.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Performance note:&lt;/em&gt;&lt;/strong&gt;* On the same Windows machine, with Gzip enabled and the same 4.72 GB database, the VisuaLeaf task completed in 4 minutes 40 seconds. The equivalent direct &lt;code&gt;mongodump&lt;/code&gt; CLI run took 6 minutes 12 seconds, so VisuaLeaf finished about 25% faster in this test. Results may vary depending on the hardware, storage, data, and task settings.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn1y92olt2czobscn0e1u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn1y92olt2czobscn0e1u.png" alt="PowerShell result for a direct mongodump backup showing Completed, 372.31 seconds, and 3.1 GB of compressed output." width="800" height="738"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The equivalent direct &lt;code&gt;mongodump&lt;/code&gt; CLI run completed in 6 minutes and 12 seconds and produced 3.1 GB of compressed output.&lt;/p&gt;

&lt;p&gt;Before treating the exported files as a valid backup, check that the execution status is *&lt;strong&gt;&lt;em&gt;Completed&lt;/em&gt;&lt;/strong&gt;*. A cancelled or failed run may still leave partial files in the destination.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Files Does the Backup Create?
&lt;/h2&gt;

&lt;p&gt;A compressed directory dump creates one &lt;code&gt;.bson.gz&lt;/code&gt; data file and one &lt;code&gt;.metadata.json.gz&lt;/code&gt; file for each collection. The VisuaLeaf output for &lt;code&gt;gui_test_db&lt;/code&gt; contained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dataset_30000.bson.gz
dataset_30000.metadata.json.gz
large_collection_4GB.bson.gz
large_collection_4GB.metadata.json.gz
large_docs_10mb.bson.gz
large_docs_10mb.metadata.json.gz
medium_docs_1mb.bson.gz
medium_docs_1mb.metadata.json.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The BSON files contain the documents. The metadata files contain collection options and index definitions used during restoration. MongoDB describes this directory structure in its official &lt;code&gt;[mongodump](https://www.mongodb.com/docs/database-tools/mongodump/)&lt;/code&gt; &lt;a href="https://www.mongodb.com/docs/database-tools/mongodump/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The 4.6 GB test database produced approximately 3.18 GB of compressed output in VisuaLeaf. That result is specific to this data. A similarly sized database containing images, encrypted values, or already compressed content may shrink much less.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr2yr84z3a4vk90hei0ec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr2yr84z3a4vk90hei0ec.png" alt="Windows Explorer showing four bson.gz files and four metadata.json.gz files from gui_test_db, totaling 3.18 GB." width="800" height="907"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The compressed &lt;code&gt;gui_test_db&lt;/code&gt; backup contains eight BSON and metadata files totaling 3.18 GB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can You Run mongodump in MongoDB Compass?
&lt;/h2&gt;

&lt;p&gt;No. MongoDB Compass can export an individual collection as JSON or CSV, but MongoDB explicitly says Compass is not a backup tool. It does not create a full binary database dump containing compressed BSON files and collection metadata. For that, MongoDB directs users to its separate Database Tools. See the official &lt;a href="https://www.mongodb.com/docs/compass/import-export/" rel="noopener noreferrer"&gt;Compass import and export documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftg7czp28z6f1wmfwta1o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftg7czp28z6f1wmfwta1o.png" alt="MongoDB Compass Export Data window showing JSON and CSV as the available collection export formats." width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MongoDB Compass exports collection data as JSON or CSV; it does not create a binary database backup.&lt;/p&gt;

&lt;p&gt;This is an important difference between exporting data and creating a backup. JSON can preserve MongoDB types when the correct Extended JSON format is used, while CSV can lose type information. Neither Compass export is the same workflow as a BSON database dump intended for &lt;code&gt;mongorestore&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Restore the BSON Backup in VisuaLeaf
&lt;/h2&gt;

&lt;p&gt;Creating compressed BSON files is only half of a backup test. You also need to confirm that MongoDB can restore them.&lt;/p&gt;

&lt;p&gt;To avoid changing the original &lt;code&gt;gui_test_db&lt;/code&gt; database, I created a separate VisuaLeaf restore job. Under *&lt;strong&gt;&lt;em&gt;Source Configuration&lt;/em&gt;&lt;/strong&gt;&lt;em&gt;, I selected *&lt;/em&gt;&lt;strong&gt;Restore (BSON)&lt;/strong&gt;** and chose the folder containing the &lt;code&gt;.bson.gz&lt;/code&gt; and &lt;code&gt;.metadata.json.gz&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;For the target, I selected the same MongoDB connection and entered a new database name: &lt;code&gt;gui_test_db_restore&lt;/code&gt;. Because that database did not exist, &lt;a href="https://visualeaf.com" rel="noopener noreferrer"&gt;VisuaLeaf&lt;/a&gt; indicated that it would create it when the restore job ran.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjelmskcuwpwvddmpkyws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjelmskcuwpwvddmpkyws.png" alt="VisuaLeaf Restore BSON job using a compressed MongoDB dump folder and the new gui_test_db_restore database." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VisuaLeaf restore job configured to import a compressed BSON dump into a new MongoDB database.&lt;/p&gt;

&lt;p&gt;This operation uses the binary dump-and-restore workflow. It reads the BSON data and collection metadata without converting the documents to JSON or CSV.&lt;/p&gt;

&lt;p&gt;When restoring to a different deployment, check version compatibility first. MongoDB requires the source and destination to use the same major version or feature compatibility version. See the official &lt;a href="https://www.mongodb.com/docs/database-tools/mongodump/mongodump-behavior/" rel="noopener noreferrer"&gt;mongodump behavior documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After the job finishes, check the restored database rather than relying only on the Completed status. The restore took 7 minutes and 34 seconds and recreated all four collections with 30,449 documents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Four collections.&lt;/li&gt;
&lt;li&gt;30,449 documents in total.&lt;/li&gt;
&lt;li&gt;30,000 documents in &lt;code&gt;dataset_30000&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;292 documents in &lt;code&gt;large_collection_4GB&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;57 documents in &lt;code&gt;large_docs_10mb&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;100 documents in &lt;code&gt;medium_docs_1mb&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The expected collection indexes and options.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fix2a93c1lfxdl27dyjiq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fix2a93c1lfxdl27dyjiq.png" alt="VisuaLeaf BSON restore summary showing four restored MongoDB collections, 30,449 documents, and a total size of 3.19 GB." width="800" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VisuaLeaf restored all four collections and 30,449 documents from the compressed BSON dump.&lt;/p&gt;

&lt;p&gt;The document counts matched the source, confirming that the &lt;a href="https://visualeaf.com/blog/postgresql-jsonb-vs-mongodb-bson-architectural-tradeoffs/" rel="noopener noreferrer"&gt;BSON&lt;/a&gt; files could be restored successfully. A production restore test should also verify indexes, collection options, and several real application queries.&lt;/p&gt;

&lt;p&gt;Do not restore a test dump over the only copy of your original database. Use a new database name or a separate development environment.&lt;/p&gt;

&lt;p&gt;A backup is not verified until the restore finishes successfully and the restored data matches the source.&lt;/p&gt;

&lt;h2&gt;
  
  
  If mongodump Does Not Run on Windows
&lt;/h2&gt;

&lt;p&gt;Two small Windows problems appeared during this test.&lt;/p&gt;

&lt;p&gt;If PowerShell says that &lt;code&gt;mongodump&lt;/code&gt; is not recognized, MongoDB Database Tools may be installed without its &lt;code&gt;bin&lt;/code&gt; directory being added to PATH. Using the complete path to &lt;code&gt;mongodump.exe&lt;/code&gt;, as shown in the opening command, avoids that problem.&lt;/p&gt;

&lt;p&gt;A connection error can also mean that the command is using the wrong host or port. One attempt used &lt;code&gt;localhost:27019&lt;/code&gt;, while the current MongoDB instance was listening on &lt;code&gt;27017&lt;/code&gt;. Check the connection used by VisuaLeaf or MongoDB Compass before retrying.&lt;/p&gt;

&lt;p&gt;Cancelling a dump may leave partial files in the destination. Delete those incomplete files or use a new output folder before starting another backup.&lt;/p&gt;

&lt;h2&gt;
  
  
  MongoDB Backup Limitations to Plan For
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;mongodump&lt;/code&gt; backup still needs consistency planning, safe storage, and restore testing.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limitation&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Writes continue during the dump&lt;/td&gt;
&lt;td&gt;A database-scoped dump may not represent one exact point in time.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;--oplog&lt;/code&gt; cannot be combined with this &lt;code&gt;--db&lt;/code&gt; command; it requires a full replica-set member dump. Use another backup method when exact point-in-time consistency is required.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Collection names differ only by capitalization&lt;/td&gt;
&lt;td&gt;Directory dumps may conflict on case-insensitive Windows and macOS file systems.&lt;/td&gt;
&lt;td&gt;Avoid ambiguous collection names or use archive output with the CLI.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The backup stays on the MongoDB machine&lt;/td&gt;
&lt;td&gt;Disk failure, device loss, or ransomware could affect the database and backup together.&lt;/td&gt;
&lt;td&gt;Keep another copy on separate or off-machine storage.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The backup is never restored&lt;/td&gt;
&lt;td&gt;A Completed status and BSON files do not prove that the backup is usable.&lt;/td&gt;
&lt;td&gt;Restore into a separate database and verify document counts, indexes, and collection options.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Old backups are never removed&lt;/td&gt;
&lt;td&gt;Scheduled dumps can eventually fill the destination disk.&lt;/td&gt;
&lt;td&gt;Define a retention policy and monitor available storage space.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;MongoDB positions &lt;code&gt;mongodump&lt;/code&gt; and &lt;code&gt;mongorestore&lt;/code&gt; mainly for smaller deployments; larger or business-critical systems may require a different backup strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A reliable MongoDB backup workflow does not end when files appear in a folder. It ends when those files can be restored, and the resulting database has been verified.&lt;/p&gt;

&lt;p&gt;The command line remains useful for one-off dumps and scripts. In VisuaLeaf, you can &lt;a href="https://visualeaf.com/blog/automate-mongodb-exports-imports-sync-jobs/" rel="noopener noreferrer"&gt;save both export and restore jobs&lt;/a&gt;, schedule repeated backups, and review their status, duration, history, and logs. You still need off-machine copies, retention rules, consistency planning, and restore tests, but the recurring steps are easier to manage from one place.&lt;/p&gt;

</description>
      <category>mongodb</category>
      <category>nosql</category>
      <category>database</category>
      <category>sql</category>
    </item>
    <item>
      <title>PostgreSQL JSONB: Query, Update, and Index JSON Data</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Tue, 18 Aug 2026 11:20:25 +0000</pubDate>
      <link>https://dev.to/visualeaf/postgresql-jsonb-query-update-and-index-json-data-1h21</link>
      <guid>https://dev.to/visualeaf/postgresql-jsonb-query-update-and-index-json-data-1h21</guid>
      <description>&lt;p&gt;JSONB allows you to store JSON data within a PostgreSQL table row. It is suitable when certain columns remain unchanged, while the remaining columns may vary across rows.&lt;/p&gt;

&lt;p&gt;Let us take the support tickets table, where the status, priority, and the date a ticket was created cannot change. However, the client name, environment, tags, and even error details may be optional and have different formats. That is why the optional content may be stored in the &lt;a href="https://visualeaf.com/blog/postgresql-jsonb-vs-mongodb-bson-architectural-tradeoffs/" rel="noopener noreferrer"&gt;JSONB field&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This storage process is easy enough. However, you need answers to questions such as how to find a nested value, update a single field, or create an effective index for your database engine.&lt;/p&gt;

&lt;p&gt;This article explores how to achieve that goal using a support_tickets table and the corresponding details column.&lt;/p&gt;

&lt;h2&gt;
  
  
  PostgreSQL JSONB operations used in this guide
&lt;/h2&gt;

&lt;p&gt;We’ll use each of these against the same &lt;code&gt;support_tickets.details&lt;/code&gt; column, so you can see how querying, updating, and indexing work together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the support tickets table
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE support_tickets (
    ticket_id  BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    status     TEXT NOT NULL,
    priority   TEXT NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    details    JSONB NOT NULL
        CHECK (jsonb_typeof(details) = 'object')
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I kept &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;priority&lt;/code&gt;, and &lt;code&gt;created_at&lt;/code&gt; outside JSONB because they have stable types and are useful for filtering and sorting. The less predictable ticket context goes into &lt;code&gt;details&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is one record used in the test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT INTO support_tickets (status, priority, details)
VALUES (
    'open',
    'high',
    '{
      "customer": {
        "name": "Trevor Lisbon",
        "plan": "Professional"
      },
      "environment": {
        "browser": "Chrome",
        "os": "Windows 11",
        "appVersion": "4.8.2"
      },
      "tags": ["sync", "postgresql"],
      "error": {
        "code": "SYNC_TIMEOUT",
        "retryable": true
      }
    }'::jsonb
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CHECK&lt;/code&gt; constraint confirms that &lt;code&gt;details&lt;/code&gt; contains a JSON object. It does not guarantee that &lt;code&gt;customer.plan&lt;/code&gt; exists or that &lt;code&gt;tags&lt;/code&gt; is always an array. JSONB validates the JSON format, not your complete application schema.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5r6vnci4oj9i1yjtyrrj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5r6vnci4oj9i1yjtyrrj.png" alt="VisuaLeaf displaying a PostgreSQL support_tickets table with expanded customer and tags fields from a JSONB column." width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PostgreSQL table with regular columns and nested JSONB data displayed in VisuaLeaf.&lt;/p&gt;

&lt;h2&gt;
  
  
  Query nested JSONB values
&lt;/h2&gt;

&lt;p&gt;PostgreSQL provides two operators that look similar but return different data types:&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;-&amp;gt;&lt;/code&gt; when you want an object or array to remain JSONB.&lt;br&gt;&lt;br&gt;
Use &lt;code&gt;-&amp;gt;&amp;gt;&lt;/code&gt; when you need a scalar value for an ordinary SQL comparison.&lt;/p&gt;

&lt;p&gt;This query returns the complete customer object and extracts the plan as text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
    ticket_id,
    details -&amp;gt; 'customer' AS customer,
    details -&amp;gt; 'customer' -&amp;gt;&amp;gt; 'plan' AS plan
FROM support_tickets
WHERE ticket_id &amp;lt;= 4
ORDER BY ticket_id;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftkimn4ihws1k3zhnv7yw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftkimn4ihws1k3zhnv7yw.gif" alt="VisuaLeaf showing an expandable customer JSONB object and the customer plan returned as text in PostgreSQL." width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Expanding the &lt;code&gt;customer&lt;/code&gt; JSONB object while &lt;code&gt;plan&lt;/code&gt; is returned as text.&lt;/p&gt;

&lt;p&gt;A common mistake is to use &lt;code&gt;-&amp;gt;&lt;/code&gt; for the final value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WHERE details -&amp;gt; 'customer' -&amp;gt; 'plan' = 'Professional'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The left side is JSONB, while &lt;code&gt;Professional&lt;/code&gt; is being treated as a SQL string. PostgreSQL may return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;invalid input syntax for type json
Token "Professional" is invalid.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The clearer fix is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WHERE details -&amp;gt; 'customer' -&amp;gt;&amp;gt; 'plan' = 'Professional'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To filter by a nested value, use &lt;code&gt;-&amp;gt;&amp;gt;&lt;/code&gt; for the final part of the path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
    ticket_id,
    status,
    details -&amp;gt; 'customer' -&amp;gt;&amp;gt; 'name' AS customer_name
FROM support_tickets
WHERE details -&amp;gt; 'error' -&amp;gt;&amp;gt; 'code' = 'SYNC_TIMEOUT';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnmgp4il1nssvm2uadivi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnmgp4il1nssvm2uadivi.png" alt="VisuaLeaf SQL editor querying nested PostgreSQL JSONB fields with arrow operators and returning matching customer names." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Filtering tickets by a nested JSONB error code and returning the customer name as text.&lt;/p&gt;

&lt;p&gt;If a row has no &lt;code&gt;error&lt;/code&gt; object, PostgreSQL returns SQL &lt;code&gt;NULL&lt;/code&gt; for that expression. It does not fail.&lt;/p&gt;

&lt;p&gt;Search inside a JSONB array&lt;/p&gt;

&lt;p&gt;You can search the &lt;code&gt;tags&lt;/code&gt; array with the &lt;code&gt;?&lt;/code&gt; operator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT ticket_id, status, details -&amp;gt; 'tags' AS tags
FROM support_tickets
WHERE details -&amp;gt; 'tags' ? 'postgresql';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This assumes that &lt;code&gt;tags&lt;/code&gt; contains an array of strings. If some rows store &lt;code&gt;"tags": "postgresql"&lt;/code&gt; instead, the data is valid JSONB but has the wrong shape for this query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Match part of a document with &lt;code&gt;@&amp;gt;&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The containment operator checks whether one JSONB value contains another:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT ticket_id, status, details -&amp;gt; 'error' AS error
FROM support_tickets
WHERE details @&amp;gt; '{
  "error": {
    "code": "SYNC_TIMEOUT"
  }
}'::jsonb;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwd1e6puk0exx6iwo0jvf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwd1e6puk0exx6iwo0jvf.png" alt="VisuaLeaf running a PostgreSQL JSONB containment query and displaying two matching support tickets with expanded error details." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;@&amp;gt;&lt;/code&gt; to find tickets containing the &lt;code&gt;SYNC_TIMEOUT&lt;/code&gt; error object.&lt;/p&gt;

&lt;p&gt;The structure must match the document. This does not work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WHERE details @&amp;gt; '{"code": "SYNC_TIMEOUT"}'::jsonb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;code&lt;/code&gt; is nested under &lt;code&gt;error&lt;/code&gt;; it is not a top-level key. This matters when you add a GIN index, because containment queries can use that index directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update a nested value with &lt;code&gt;jsonb_set&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Ticket 1 was reproduced in version &lt;code&gt;4.8.4&lt;/code&gt;. Instead of replacing the entire &lt;code&gt;details&lt;/code&gt; document, I updated only &lt;code&gt;environment.appVersion&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;support_tickets&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonb_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'{environment,appVersion}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;to_jsonb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'4.8.4'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ticket_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;RETURNING&lt;/span&gt;
    &lt;span class="n"&gt;ticket_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'environment'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result shows &lt;code&gt;appVersion&lt;/code&gt; as &lt;code&gt;4.8.4&lt;/code&gt;, while &lt;code&gt;browser&lt;/code&gt; and &lt;code&gt;os&lt;/code&gt; remain unchanged.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhtaz3o7ae29s2aa7d2gq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhtaz3o7ae29s2aa7d2gq.png" alt="VisuaLeaf running jsonb_set to update a nested PostgreSQL JSONB value from version 4.8.2 to 4.8.4." width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Updating &lt;code&gt;environment.appVersion&lt;/code&gt; while preserving the other JSONB fields.&lt;/p&gt;

&lt;p&gt;The final &lt;code&gt;false&lt;/code&gt; means the key must already exist. The &lt;code&gt;::text&lt;/code&gt; cast is also important; without it, PostgreSQL may not know which type to convert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;could not determine polymorphic type because input has type unknown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  When the parent object is missing
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;jsonb_set&lt;/code&gt; can create the final key, but not a missing parent object. Ticket 3 has no &lt;code&gt;error&lt;/code&gt; object, so updating &lt;code&gt;error.firstSeenAt&lt;/code&gt; would run without an error but change nothing.&lt;/p&gt;

&lt;p&gt;This version creates the parent when needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;support_tickets&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonb_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'{error}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jsonb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;jsonb_build_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'firstSeenAt'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'2026-08-03T11:05:00Z'&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ticket_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;RETURNING&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'error'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;RETURNING&lt;/code&gt; lets you confirm that the field was actually added.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fudm1j9vsulslb1oudb1l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fudm1j9vsulslb1oudb1l.png" alt="JSONB error object with the new firstSeenAt field." width="799" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Creating the missing &lt;code&gt;error&lt;/code&gt; object.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add a GIN index for containment searches
&lt;/h2&gt;

&lt;p&gt;For the indexing test, I added 40,000 generated tickets. The table contained 40,004 rows in total, while the original four remained available for the earlier examples.&lt;/p&gt;

&lt;p&gt;Because the query uses &lt;code&gt;@&amp;gt;&lt;/code&gt; against &lt;code&gt;details&lt;/code&gt;, I created a containment-focused GIN index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_support_tickets_details_gin&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;support_tickets&lt;/span&gt;
&lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;GIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="n"&gt;jsonb_path_ops&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;ANALYZE&lt;/span&gt; &lt;span class="n"&gt;support_tickets&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;jsonb_path_ops&lt;/code&gt; works well for containment searches, but it does not support every JSONB operator. For example, it cannot support the &lt;code&gt;?&lt;/code&gt; operator used in the tags query.&lt;/p&gt;

&lt;p&gt;I then opened the same query in VisuaLeaf’s Explain view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;ticket_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;support_tickets&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'{
  "error": {
    "code": "SYNC_TIMEOUT"
  }
}'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jsonb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL found 402 matching tickets and used &lt;code&gt;idx_support_tickets_details_gin&lt;/code&gt; through a Bitmap Index Scan.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F93iz2oa3p9jho2gfgrvn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F93iz2oa3p9jho2gfgrvn.png" alt="VisuaLeaf Explain showing a PostgreSQL Bitmap Index Scan using a JSONB GIN index." width="800" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The JSONB query uses the GIN index instead of scanning the full table.&lt;/p&gt;

&lt;p&gt;The index worked for this query and dataset. PostgreSQL may still choose a sequential scan for a small table or when many rows match.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use JSONB
&lt;/h2&gt;

&lt;p&gt;JSONB is useful for flexible data, but stable fields usually belong in regular columns.&lt;/p&gt;

&lt;p&gt;In this example, &lt;code&gt;environment&lt;/code&gt; and &lt;code&gt;error&lt;/code&gt; fit inside JSONB. Fields such as &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;priority&lt;/code&gt;, and &lt;code&gt;created_at&lt;/code&gt; are better as regular columns.&lt;/p&gt;

&lt;p&gt;I ran these examples in &lt;a href="https://visualeaf.com/database/postgresql/" rel="noopener noreferrer"&gt;VisuaLeaf&lt;/a&gt;, using its SQL editor, nested JSONB table view, and visual query plan to inspect the results.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.postgresql.org/docs/current/datatype-json.html" rel="noopener noreferrer"&gt;PostgreSQL: JSON Types&lt;/a&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.postgresql.org/docs/current/functions-json.html" rel="noopener noreferrer"&gt;PostgreSQL: JSON Functions and Operators&lt;/a&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.postgresql.org/docs/current/using-explain.html" rel="noopener noreferrer"&gt;PostgreSQL: Using EXPLAIN&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>postgres</category>
      <category>postgressql</category>
      <category>database</category>
      <category>sql</category>
    </item>
    <item>
      <title>MongoDB Schema Design: Build or Reverse Engineer Visually</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Thu, 06 Aug 2026 11:45:16 +0000</pubDate>
      <link>https://dev.to/visualeaf/mongodb-schema-design-build-or-reverse-engineer-visually-5d28</link>
      <guid>https://dev.to/visualeaf/mongodb-schema-design-build-or-reverse-engineer-visually-5d28</guid>
      <description>&lt;p&gt;MongoDB collections can become difficult to understand once they contain nested objects, arrays, references, and documents with slightly different structures.&lt;/p&gt;

&lt;p&gt;Looking at one JSON document at a time shows you the data, but not the complete model. It is harder to see how collections connect, which fields use inconsistent BSON types, or whether a value is embedded or referenced elsewhere.&lt;/p&gt;

&lt;p&gt;The opposite problem happens when the database does not exist yet. You may know which collections and fields you need, but reviewing the design across separate JSON files or application models is not easy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://visualeaf.com/features/visual-schema/" rel="noopener noreferrer"&gt;VisuaLeaf’s Visual Schema&lt;/a&gt; supports both directions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate a diagram from an existing MongoDB database.&lt;/li&gt;
&lt;li&gt;Design a new schema from an empty diagram.&lt;/li&gt;
&lt;li&gt;Keep the schema virtual or materialize it into real collections.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I tested both workflows using a travel-planning database containing collections such as &lt;code&gt;travelers&lt;/code&gt;, &lt;code&gt;places&lt;/code&gt;, &lt;code&gt;flights&lt;/code&gt;, &lt;code&gt;accommodations&lt;/code&gt;, &lt;code&gt;restaurants&lt;/code&gt;, &lt;code&gt;activities&lt;/code&gt;, &lt;code&gt;carRentals&lt;/code&gt;, and &lt;code&gt;vacationPlans&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3df2kfd2ac64dpf9s0rn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3df2kfd2ac64dpf9s0rn.png" alt="MongoDB Visual Schema in VisuaLeaf showing the central vacationPlans collection connected to travelers, flights, accommodations, restaurants, car rentals, activities, and places." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VisuaLeaf generates a visual MongoDB schema showing collections, fields, BSON types, and relationships in the &lt;code&gt;TravelPlannerDemo&lt;/code&gt; database.&lt;/p&gt;

&lt;p&gt;Although this guide focuses on MongoDB, VisuaLeaf’s Visual Schema can also be used with &lt;a href="https://visualeaf.com/database/" rel="noopener noreferrer"&gt;SQL databases.&lt;/a&gt; If you’re deciding which database model fits your project or want to compare their structures, read &lt;a href="https://visualeaf.com/blog/mongodb-vs-postgresql-whats-the-difference/" rel="noopener noreferrer"&gt;MongoDB vs PostgreSQL: What’s the Difference?&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reverse engineer an existing MongoDB database
&lt;/h3&gt;

&lt;p&gt;If your database already contains data, you can generate a Visual Schema from its collections.&lt;/p&gt;

&lt;p&gt;Connect to MongoDB, open *&lt;strong&gt;&lt;em&gt;Visual Schema&lt;/em&gt;&lt;/strong&gt;*, and select the database you want to analyze. VisuaLeaf reads the existing documents and creates a diagram containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;collections and fields;&lt;/li&gt;
&lt;li&gt;detected BSON types;&lt;/li&gt;
&lt;li&gt;embedded objects;&lt;/li&gt;
&lt;li&gt;arrays and their nested fields;&lt;/li&gt;
&lt;li&gt;detected relationships between collections.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my &lt;code&gt;TravelPlannerDemo&lt;/code&gt; database, the generated diagram included the main travel collections and their nested structures.&lt;/p&gt;

&lt;p&gt;For example, &lt;code&gt;vacationPlans&lt;/code&gt; contains simple fields such as &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, and &lt;code&gt;createdAt&lt;/code&gt;, but it also includes more complex structures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;budget&lt;/code&gt; and &lt;code&gt;dates&lt;/code&gt; as embedded objects;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;itinerary&lt;/code&gt;, &lt;code&gt;checklist&lt;/code&gt;, and &lt;code&gt;documents&lt;/code&gt; as arrays;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;destinationIds&lt;/code&gt; and &lt;code&gt;travelerIds&lt;/code&gt; as arrays of references;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ownerTravelerId&lt;/code&gt; and &lt;code&gt;selectedAccommodationId&lt;/code&gt; as individual references.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of opening several documents to understand how these fields fit together, I could inspect the structure in one diagram.&lt;/p&gt;

&lt;p&gt;This is particularly useful when you are working with a database you did not design, or when the structure has changed, but its documentation has not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review the detected structure
&lt;/h2&gt;

&lt;p&gt;Here’s the trap most of us fall into: reverse-engineering a database shows what’s currently stored, not what was actually intended.&lt;/p&gt;

&lt;p&gt;MongoDB allows documents in the same collection to have different fields and BSON types. For example, most documents might store a date correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ISODate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-07-18T09:30:00Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while an older import stores it as a string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-07-18&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These values may look similar in a table, but one has the BSON type &lt;code&gt;date&lt;/code&gt; and the other has the type &lt;code&gt;string&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The same problem can occur with identifiers. A field such as &lt;code&gt;ownerTravelerId&lt;/code&gt; may contain an &lt;code&gt;ObjectId&lt;/code&gt; in most documents but a string in others. That difference matters when you try to connect it to &lt;code&gt;travelers._id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The generated schema represents the data VisuaLeaf analyzed. Rare fields may also be absent if they do not appear in the sampled documents. Review unusual records and any fields with multiple detected types before using the diagram as your final model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review and add relationships
&lt;/h2&gt;

&lt;p&gt;MongoDB does not store relationships as enforced foreign keys. It stores the reference value, but the meaning of that value often needs to be confirmed.&lt;/p&gt;

&lt;p&gt;In the travel database, relationships can connect fields such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vacationPlans.ownerTravelerId → travelers._id
vacationPlans.travelerIds[] → travelers._id
vacationPlans.destinationIds[] → places._id
vacationPlans.selectedAccommodationId → accommodations._id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other collections use similar connections:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;accommodations.placeId → places._id
restaurants.placeId → places._id
activities.placeId → places._id
flights.passengers.travelerId → travelers._id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check both ends before accepting or adding a relationship. An &lt;code&gt;ObjectId&lt;/code&gt; does not match a string containing the same characters.&lt;/p&gt;

&lt;p&gt;For example, these two values use different BSON types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nc"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;66af9138ad73c91224345c10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;66af9138ad73c91224345c10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The diagram can document a relationship, but it does not make MongoDB enforce it. Your application can still insert an &lt;code&gt;ownerTravelerId&lt;/code&gt; that does not exist in &lt;code&gt;travelers&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The generated diagram may also need some visual cleanup. In a database with many related collections, lines can overlap and detected connections may not represent the relationship you intended. Reposition the collections and review each relationship rather than accepting every connection without checking.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdlhmsx6fe1ms6dl1ndug.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdlhmsx6fe1ms6dl1ndug.png" alt="Close-up of a MongoDB schema diagram in VisuaLeaf showing nested fields, BSON types, arrays, and relationships between vacation plans, places, and accommodations." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A closer look at nested fields, BSON types, arrays, and relationships between&lt;/strong&gt; &lt;em&gt;`_vacationPlans&lt;/em&gt;&lt;code&gt;___,__ _&lt;/code&gt;&lt;em&gt;places&lt;/em&gt;&lt;code&gt;___, and__ _&lt;/code&gt;&lt;em&gt;accommodations&lt;/em&gt;`_ &lt;strong&gt;in VisuaLeaf.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Modify the generated schema
&lt;/h2&gt;

&lt;p&gt;A reverse-engineered schema does not have to remain a read-only picture of the current database.&lt;/p&gt;

&lt;p&gt;You can use it as a starting point and then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add or remove fields;&lt;/li&gt;
&lt;li&gt;correct BSON types;&lt;/li&gt;
&lt;li&gt;define embedded objects and arrays;&lt;/li&gt;
&lt;li&gt;add missing relationships;&lt;/li&gt;
&lt;li&gt;remove incorrect relationships;&lt;/li&gt;
&lt;li&gt;reorganize the diagram.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this example, I added a new optional &lt;code&gt;travelStyle&lt;/code&gt; field to &lt;code&gt;vacationPlans&lt;/code&gt;. I also manually connected &lt;code&gt;destinationIds&lt;/code&gt; to &lt;code&gt;places._id&lt;/code&gt; to represent the reference between the two collections.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhvmgwyzbmksmydxhiroc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhvmgwyzbmksmydxhiroc.png" alt="Editing the vacationPlans MongoDB schema in VisuaLeaf by adding the optional travelStyle field with the String BSON type." width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same collection contains embedded data as well. The &lt;code&gt;budget&lt;/code&gt; object belongs to one vacation plan, so storing it inside the document keeps the related values together. &lt;code&gt;destinationIds&lt;/code&gt;, however, contains references to documents stored in the &lt;code&gt;places&lt;/code&gt; collection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vacationPlans
├── budget
│   ├── currency
│   ├── estimatedTotal
│   └── paidSoFar
├── dates
├── itinerary[]
├── ownerTravelerId
├── travelerIds[]
└── destinationIds[]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows both modeling approaches in the same collection: embedded data for information owned by the vacation plan and references for information stored elsewhere and reused.&lt;/p&gt;

&lt;p&gt;Changing the virtual schema does not automatically migrate existing documents. If you rename a field, change its type, or move it into an embedded object, the records already stored in MongoDB remain unchanged.&lt;/p&gt;

&lt;p&gt;The diagram describes the proposed model. Data migration remains a separate operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design a new MongoDB schema from scratch
&lt;/h2&gt;

&lt;p&gt;You can also use Visual Schema before the database exists.&lt;/p&gt;

&lt;p&gt;Start with an empty diagram and add the collections you need. You can then define their fields, BSON types, embedded structures, arrays, and relationships.&lt;/p&gt;

&lt;p&gt;For this example, I created a small tour-booking schema with two collections:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Collection&lt;/th&gt;
&lt;th&gt;Example fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;departures&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;_id&lt;/code&gt;, &lt;code&gt;packageId&lt;/code&gt;, &lt;code&gt;startDate&lt;/code&gt;, &lt;code&gt;capacity&lt;/code&gt;, &lt;code&gt;meetingPoint&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reservations&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;_id&lt;/code&gt;, &lt;code&gt;departureId&lt;/code&gt;, &lt;code&gt;bookingReference&lt;/code&gt;, &lt;code&gt;leadGuest&lt;/code&gt;, &lt;code&gt;totalAmount&lt;/code&gt;, &lt;code&gt;paymentStatus&lt;/code&gt;, &lt;code&gt;createdAt&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;leadGuest&lt;/code&gt; field is an embedded object containing fields such as &lt;code&gt;fullName&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt;. The relationship connects:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;reservations.departureId → departures._id&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This represents a one-to-many relationship: one departure can have multiple reservations.&lt;/p&gt;

&lt;p&gt;Defining the schema visually makes several problems easier to notice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;references pointing to the wrong field;&lt;/li&gt;
&lt;li&gt;incorrect BSON types;&lt;/li&gt;
&lt;li&gt;fields that should be grouped inside an embedded object;&lt;/li&gt;
&lt;li&gt;missing or unnecessary relationships;&lt;/li&gt;
&lt;li&gt;inconsistent field names between collections.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhebkwgkol5au017x1dvf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhebkwgkol5au017x1dvf.png" alt="VisuaLeaf visual schema showing departures and reservations connected through departures._id and reservations.departureId, with BSON types and an embedded leadGuest object." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After completing the design, save the schema and bind it to the MongoDB connection where you want to create the collections. The diagram remains a virtual model until you materialize it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the schema virtual or materialize it
&lt;/h2&gt;

&lt;p&gt;The diagram can remain virtual if you only need it for documentation, planning, or discussing changes with your team.&lt;/p&gt;

&lt;p&gt;Nothing is created in MongoDB until you choose to materialize it.&lt;/p&gt;

&lt;p&gt;When the design is ready, save it and bind it to the MongoDB connection and target database. You can then click *&lt;strong&gt;&lt;em&gt;Materialize&lt;/em&gt;&lt;/strong&gt;* to review the collections and generated validators before applying the schema.&lt;/p&gt;

&lt;p&gt;This allows both workflows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Existing MongoDB database → Visual Schema
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Visual Schema → MongoDB database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can therefore reverse engineer an existing database such as &lt;code&gt;TravelPlannerDemo&lt;/code&gt;, modify its virtual schema, and materialize the result into a separate test database.  &lt;/p&gt;

&lt;p&gt;You can also begin with an empty diagram, as I did with &lt;code&gt;TourBookingSchema&lt;/code&gt;, and create a new database from that design.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fulny3425f5e17xilwyqe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fulny3425f5e17xilwyqe.png" alt="VisuaLeaf Materialize screen showing the reservations collection, its BSON fields, embedded leadGuest object, generated MongoDB validator, and Apply to Database button." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose whether to attach validators
&lt;/h2&gt;

&lt;p&gt;Materializing a schema does not require you to enforce every field and BSON type.&lt;/p&gt;

&lt;p&gt;The materialization screen lets you review the JSON Schema generated for each collection. You can then decide whether MongoDB should use it as a &lt;code&gt;$jsonSchema&lt;/code&gt; validator.&lt;/p&gt;

&lt;p&gt;If you want to create the collections without enforcing the schema, leave *&lt;strong&gt;&lt;em&gt;Attach validator&lt;/em&gt;&lt;/strong&gt;* disabled.&lt;/p&gt;

&lt;p&gt;These are separate decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The visual schema describes the intended structure.&lt;/li&gt;
&lt;li&gt;Materialization creates the collections in MongoDB.&lt;/li&gt;
&lt;li&gt;The generated JSON Schema describes the field and type rules.&lt;/li&gt;
&lt;li&gt;The attached validator makes MongoDB enforce those rules during writes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A field is mandatory only if it is included in the schema’s &lt;code&gt;required&lt;/code&gt; array. Otherwise, its BSON type is checked only when the field is present.&lt;/p&gt;

&lt;p&gt;This distinction matters when your application still writes inconsistent data.&lt;/p&gt;

&lt;p&gt;Suppose the generated schema expects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;bsonType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;date&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the application sends this instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-08-03&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MongoDB will reject the write when the validator is attached. A document validation failure commonly returns MongoDB error code &lt;code&gt;121&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The solution is not necessarily to remove validation. First check what your application actually sends. You may need to fix the value before inserting it or adjust the schema if both types are intentionally supported.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review the materialization plan
&lt;/h2&gt;

&lt;p&gt;Before clicking *&lt;strong&gt;&lt;em&gt;Apply to Database&lt;/em&gt;&lt;/strong&gt;*, check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the selected connection and database;&lt;/li&gt;
&lt;li&gt;which collections will be created;&lt;/li&gt;
&lt;li&gt;which collections already exist;&lt;/li&gt;
&lt;li&gt;collection and field names;&lt;/li&gt;
&lt;li&gt;BSON types;&lt;/li&gt;
&lt;li&gt;embedded objects and arrays;&lt;/li&gt;
&lt;li&gt;relationships in the visual diagram;&lt;/li&gt;
&lt;li&gt;generated JSON Schemas;&lt;/li&gt;
&lt;li&gt;which collections have the *&lt;strong&gt;&lt;em&gt;Attach validator&lt;/em&gt;&lt;/strong&gt;* enabled.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the first test, use a new database instead of applying the model directly to an existing one.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TourBookingDemo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the result easier to inspect and avoids mixing the materialized collections with the original &lt;code&gt;TravelPlannerDemo&lt;/code&gt; data.&lt;/p&gt;

&lt;p&gt;If you materialize changes against a database that already contains records, changing &lt;code&gt;reservations.departureId&lt;/code&gt; from &lt;code&gt;String&lt;/code&gt; to &lt;code&gt;ObjectId&lt;/code&gt; in the diagram will not convert the stored values. Data migration remains a separate operation.&lt;/p&gt;

&lt;p&gt;It also does not create enforced foreign keys. The relationship between &lt;code&gt;reservations.departureId&lt;/code&gt; and &lt;code&gt;departures._id&lt;/code&gt; remains part of the model, but your application must maintain valid references.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0sve8uozl6kk4og7t34z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0sve8uozl6kk4og7t34z.png" alt="VisuaLeaf showing the newly created departures and reservations collections in MongoDB." width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the result
&lt;/h2&gt;

&lt;p&gt;After applying the materialization plan, open the target database and confirm that the &lt;code&gt;departures&lt;/code&gt; and &lt;code&gt;reservations&lt;/code&gt; collections were created.&lt;/p&gt;

&lt;p&gt;If you enabled *&lt;strong&gt;&lt;em&gt;Attach validator&lt;/em&gt;&lt;/strong&gt;*, inspect the validation settings and test them with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one valid document that should be accepted;&lt;/li&gt;
&lt;li&gt;one invalid document that should be rejected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frhcezatzy0lhnfk0b1zh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frhcezatzy0lhnfk0b1zh.png" alt="Split-screen MongoDB shell in VisuaLeaf showing a valid reservation inserted successfully and an invalid reservation rejected by JSON Schema validation because several BSON types do not match." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you left the option disabled, the collections should still exist, but MongoDB will continue accepting documents with different structures.&lt;/p&gt;

&lt;p&gt;Also remember that a validator does not replace indexes. Fields used for lookups and filtering may still need indexes, such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reservations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createIndex&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;departureId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;departures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createIndex&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validators control document structure. Indexes support query performance and can enforce uniqueness when created with the &lt;code&gt;unique&lt;/code&gt; option.&lt;br&gt;&lt;br&gt;
Relationships explain how collections connect, but MongoDB does not enforce them as foreign keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  One schema, two directions
&lt;/h2&gt;

&lt;p&gt;Visual Schema is useful when you inherit an existing MongoDB database and need to understand its structure. It is also useful before you create the first collection.&lt;/p&gt;

&lt;p&gt;In my travel-planning example, I could generate a diagram containing &lt;code&gt;travelers&lt;/code&gt;, &lt;code&gt;places&lt;/code&gt;, &lt;code&gt;flights&lt;/code&gt;, &lt;code&gt;accommodations&lt;/code&gt;, &lt;code&gt;activities&lt;/code&gt;, &lt;code&gt;vacationPlans&lt;/code&gt;, and the other related collections. I could inspect nested structures such as &lt;code&gt;budget&lt;/code&gt; and &lt;code&gt;itinerary&lt;/code&gt;, review the references, and correct the model where necessary.&lt;/p&gt;

&lt;p&gt;The same workspace also supports the opposite workflow. You can start with an empty diagram, design the collections visually, and then decide whether to keep the model virtual or materialize it into MongoDB.&lt;/p&gt;

&lt;p&gt;When you materialize it, attaching the generated &lt;a href="https://visualeaf.com/blog/mongodb-json-schema-validation/" rel="noopener noreferrer"&gt;JSON Schema validators&lt;/a&gt; remains optional.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Want to turn your MongoDB database into a diagram—or your diagram into a database?&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;p&gt;&lt;a href="https://visualeaf.com/download" rel="noopener noreferrer"&gt;Download VisuaLeaf&lt;/a&gt; and generate a schema from your existing MongoDB database, or design a new one from scratch.&lt;/p&gt;

</description>
      <category>mongodb</category>
      <category>database</category>
      <category>javascript</category>
      <category>software</category>
    </item>
    <item>
      <title>Which MongoDB Client Handles Large Collections Best: VisuaLeaf vs Compass vs Studio 3T</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Tue, 04 Aug 2026 07:56:19 +0000</pubDate>
      <link>https://dev.to/visualeaf/which-mongodb-client-handles-large-collections-best-visualeaf-vs-compass-vs-studio-3t-1jam</link>
      <guid>https://dev.to/visualeaf/which-mongodb-client-handles-large-collections-best-visualeaf-vs-compass-vs-studio-3t-1jam</guid>
      <description>&lt;p&gt;A MongoDB client can feel fast with a few hundred documents and become frustrating as soon as the result grows.&lt;/p&gt;

&lt;p&gt;The database is not always the problem. MongoDB may finish the query while the desktop client is still fetching batches, converting BSON values, and building thousands of rows or JSON blocks.&lt;/p&gt;

&lt;p&gt;I tested VisuaLeaf, MongoDB Compass, and Studio 3T with the same collection of 50,844 documents. Then I tested three related workflows: comparing collections, automating repeated work, and investigating an unindexed query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test setup
&lt;/h2&gt;

&lt;p&gt;I ran all three applications on the same HP laptop with 16 GB of RAM and Windows 11 Pro. Each application was updated to the latest version available when I ran the test. All three connected to the same MongoDB database through the same internet connection and opened the same &lt;code&gt;largeCollection&lt;/code&gt;. The collection contained nested objects and arrays, not only flat fields.&lt;/p&gt;

&lt;p&gt;For the first test, I opened the collection without a filter, projection, or custom limit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.largeCollection.find({})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is what happened:&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 1: What the large-result test actually shows
&lt;/h3&gt;

&lt;p&gt;The three clients made different choices.&lt;/p&gt;

&lt;p&gt;VisuaLeaf allowed one continuous result. That is useful when you need to scroll through a large set, select many documents, inspect distant rows, or export the current result without moving through pages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuaranufuy6h5x7sazkjw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuaranufuy6h5x7sazkjw.png" alt="VisuaLeaf displaying 50,000 MongoDB documents with a load time of 13.481 seconds." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VisuaLeaf loaded 50,000 documents in one result in 13.481 seconds.&lt;/p&gt;

&lt;p&gt;Compass limited the amount of data visible at once. This protects the interface from an unexpectedly large query and works well when you only need to inspect a sample, edit one document, or confirm a filter. The tradeoff is that browsing the whole collection requires hundreds of pages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwm1gf7v40vgutjfgfojd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwm1gf7v40vgutjfgfojd.png" alt="MongoDB Compass showing documents 1–100 of 50,844 with page-size options up to 100." width="799" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MongoDB Compass displayed the collection in pages of up to 100 documents.&lt;/p&gt;

&lt;p&gt;Studio 3T did not finish this particular operation before I stopped it. That does not prove that every version, display mode, or computer will behave the same way. It does mean that an unrestricted result was not practical in this setup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzojedptegz8loztlb68f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzojedptegz8loztlb68f.png" alt="Studio 3T showing a not responding message while reading documents from a MongoDB collection." width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Studio 3T became unresponsive while attempting to load 50,000 documents.&lt;/p&gt;

&lt;p&gt;If a client struggles, reduce both the number of documents and the number of fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.largeCollection.find(
  {},
  {
    _id: 1,
    company: 1,
    registered: 1
  }
).limit(1000)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is usually the better daily workflow. A large result may be useful for inspection or export, but it still consumes database resources, network bandwidth, memory, and rendering time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 2: Comparing two MongoDB collections
&lt;/h3&gt;

&lt;p&gt;For this test, I used two controlled collections in the &lt;code&gt;QA_Edit&lt;/code&gt; database: &lt;code&gt;orders_compare_source&lt;/code&gt; and &lt;code&gt;orders_compare_target&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Both collections started with 1,000 identical documents. I then modified 100 documents, removed 50, and added 50 in the target collection. I matched the documents by &lt;code&gt;_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;VisuaLeaf and Studio 3T both found 850 identical documents, 100 modified documents, 50 missing from the source, and 50 missing from the target.&lt;/p&gt;

&lt;p&gt;A useful comparison must answer three questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Which documents exist on only one side?  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which fields changed inside matching documents?  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What will be inserted, updated, or deleted during synchronization?  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  VisuaLeaf
&lt;/h3&gt;

&lt;p&gt;VisuaLeaf has a dedicated &lt;a href="https://visualeaf.com/features/collection-compare/" rel="noopener noreferrer"&gt;Collection Compare&lt;/a&gt; workflow for schema and data differences. It keeps the comparison next to the document, query, and data-operation tools.&lt;/p&gt;

&lt;p&gt;The important setup choice is the matching field. &lt;code&gt;_id&lt;/code&gt; works only if both collections preserve the same values. If an import regenerated them, use a stable key such as &lt;code&gt;customerId&lt;/code&gt;, &lt;code&gt;orderId&lt;/code&gt;, or &lt;code&gt;externalId&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiq7a0zy8xurbsmqnxhcl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiq7a0zy8xurbsmqnxhcl.png" alt="VisuaLeaf comparison results for two MongoDB collections showing 850 identical documents, 100 modified, 50 missing in source, and 50 missing in target." width="800" height="683"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VisuaLeaf found 850 identical, 100 modified, and 50 documents missing from each collection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Studio 3T
&lt;/h3&gt;

&lt;p&gt;Studio 3T provides the most detailed comparison controls of the three. Its &lt;a href="https://studio3t.com/knowledge-base/articles/compare-mongodb-collections/" rel="noopener noreferrer"&gt;Data Compare &amp;amp; Sync&lt;/a&gt; supports collections on different databases or servers, document- and field-level differences, result export, and synchronization in either direction.&lt;/p&gt;

&lt;p&gt;Bi-directional synchronization is useful, but it increases the cost of a wrong choice. Decide which collection is the source of truth before applying changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsst1txnfatzh32wc7zwl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsst1txnfatzh32wc7zwl.png" alt="Studio 3T MongoDB collection comparison showing 850 identical documents, 100 different, 50 without a source match, and 50 without a target match." width="800" height="707"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Studio 3T returned the same differences using &lt;code&gt;_id&lt;/code&gt; as the matching field.&lt;/p&gt;

&lt;h3&gt;
  
  
  MongoDB Compass
&lt;/h3&gt;

&lt;p&gt;Compass does not provide the same dedicated collection-diff workflow. You can still build part of the comparison with an aggregation.&lt;/p&gt;

&lt;p&gt;For example, this pipeline finds source documents without a matching &lt;code&gt;_id&lt;/code&gt; in the target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.orders_compare_source.aggregate([
  {
    $lookup: {
      from: "orders_compare_target",
      localField: "_id",
      foreignField: "_id",
      as: "target"
    }
  },
  {
    $match: {
      target: {
        $eq: []
      }
    }
  },
  {
  $count: "missingInTarget"
  }
  }
])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This finds missing matches, not every field-level change. It also assumes both collections are in the same database. Comparing data on different servers requires a script, a temporary copy, or another tool.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffrss7hzgk6ctcpa765ul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffrss7hzgk6ctcpa765ul.png" alt="MongoDB Compass pipeline showing 50 documents missing from orders_compare_target." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Compass used an aggregation pipeline to find 50 source documents missing from the target.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test 3: Automating Daily Orders
&lt;/h2&gt;

&lt;p&gt;Large collections make small mistakes more expensive.&lt;/p&gt;

&lt;p&gt;For this test, I used a daily orders JSON file.&lt;/p&gt;

&lt;p&gt;In Studio 3T, I imported the file into a &lt;code&gt;daily_orders&lt;/code&gt; collection. Studio 3T can also save and schedule tasks, so it works well when you want to repeat imports, exports, or other database jobs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffpjf7d4nhpa03nrp24j5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffpjf7d4nhpa03nrp24j5.png" alt="Build a connected workflow: import to staging, then continue with the next task" width="800" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;JSON import task: import a file into a collection&lt;/p&gt;

&lt;p&gt;In VisuaLeaf, I used &lt;a href="https://visualeaf.com/features/task-manager/" rel="noopener noreferrer"&gt;Task Manager&lt;/a&gt; to connect more than one step:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Import Daily Orders JSON → Add Orders to Main Collection → Export Sales Report&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcte0fhqvveg4r2p5x5nl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcte0fhqvveg4r2p5x5nl.png" alt="VisuaLeaf can turn import, copy, and export steps into one connected workflow." width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VisuaLeaf lets you connect tasks into a workflow, not just run one import.&lt;/p&gt;

&lt;p&gt;The useful part is that tasks can have parent tasks. So the next task runs after the previous one is completed.&lt;/p&gt;

&lt;p&gt;This is helpful when the work is not just “import this file.” In a real workflow, you may need to import data, map fields, move it into the main collection, mask sensitive data, and export a report after that.&lt;/p&gt;

&lt;p&gt;For example, field mapping matters because &lt;code&gt;orderedAt&lt;/code&gt; should be mapped to &lt;code&gt;createdAt&lt;/code&gt; as a MongoDB Date, and values like email or phone numbers may need data masking before export.&lt;/p&gt;

&lt;p&gt;So for me, Studio 3T is strong for saving and scheduling database tasks. VisuaLeaf is easier when I want to see a connected workflow in one place and control the order of the steps.&lt;/p&gt;

&lt;p&gt;Compass is different. It is great for interactive work, but recurring workflows usually need something outside the desktop app, like a script, cron, Windows Task Scheduler, CI, or an Atlas service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test 4: Checking the Query Before Blaming the Client
&lt;/h2&gt;

&lt;p&gt;The first test measured how each tool handled a large result on screen.&lt;/p&gt;

&lt;p&gt;This test is different. Here, I wanted to check the work MongoDB was doing before the data even reached the client.&lt;/p&gt;

&lt;p&gt;I started with a query that had no supporting index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.largeCollection.find({
  company: "ZENSOR"
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part was the explain plan.&lt;/p&gt;

&lt;p&gt;I checked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scan stage in the winning plan: &lt;code&gt;COLLSCAN&lt;/code&gt; or &lt;code&gt;IXSCAN&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;totalDocsExamined&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;totalKeysExamined&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nReturned&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;execution time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the query uses &lt;code&gt;COLLSCAN&lt;/code&gt;, MongoDB has to scan the collection to find matching documents. That means the slow part is not always the GUI. Sometimes the database is simply doing too much work.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;COLLSCAN&lt;/code&gt; is not automatically a problem. But in this case, scanning 50,844 documents to return only 51 showed that the query could benefit from an index.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6tldf2vqff2bhxa8vown.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6tldf2vqff2bhxa8vown.png" alt="VisuaLeaf Query Profiler showing a collection scan with 50,844 documents examined and 51 returned." width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VisuaLeaf detected a &lt;code&gt;COLLSCAN&lt;/code&gt;, examining 50,844 documents to return 51.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiee01pq7102ob5althrg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiee01pq7102ob5althrg.png" alt="Studio 3T Visual Explain showing a collection scan with 50,844 documents examined and 51 returned." width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Studio 3T reported a collection scan across 50,844 documents for 51 results.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftsosejsdllswyi86leny.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftsosejsdllswyi86leny.png" alt="MongoDB Compass Explain Plan showing a collection scan with 50,844 documents examined and 51 returned." width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Compass used a &lt;code&gt;COLLSCAN&lt;/code&gt;, examining all 50,844 documents to find 51 matches.&lt;/p&gt;

&lt;p&gt;After that, I created an index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.largeCollection.createIndex({
  company: 1
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I ran the same explain operation again.&lt;/p&gt;

&lt;p&gt;A lower execution time is useful, but the stronger evidence is that the winning plan now contains an &lt;code&gt;IXSCAN&lt;/code&gt;. In this test, &lt;code&gt;totalDocsExamined&lt;/code&gt; dropped from 50,844 to 51, matching the 51 returned documents.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://visualeaf.com/features/query-profiler/" rel="noopener noreferrer"&gt;VisuaLeaf’s Query Profiler&lt;/a&gt; is useful here because it brings slow queries, execution plans, index usage, scanned documents, returned documents, and optimization hints into one place.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flnau13lp7bkxu91xst07.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flnau13lp7bkxu91xst07.png" alt="VisuaLeaf Query Profiler showing the company index with 51 documents and 51 index keys examined." width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After adding the &lt;code&gt;company_1&lt;/code&gt; index, VisuaLeaf examined 51 documents and 51 index keys.&lt;/p&gt;

&lt;p&gt;Studio 3T has the deepest performance tools in this comparison, with Query Profiler, Visual Explain, and Index Manager.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgog4gnfsvko3tdj4gdag.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgog4gnfsvko3tdj4gdag.png" alt="Studio 3T Visual Explain showing an index scan using company_1 and examining 51 documents." width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the &lt;code&gt;company_1&lt;/code&gt; index, Studio 3T used an index scan and examined 51 documents.&lt;/p&gt;

&lt;p&gt;Compass also has Explain Plan, which works well when you already know which query you want to inspect.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsxogwnyk27lb4qg0j8tm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsxogwnyk27lb4qg0j8tm.png" alt="MongoDB Compass Explain Plan showing an index scan with 51 documents and 51 index keys examined." width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After indexing &lt;code&gt;company&lt;/code&gt;, Compass changed to &lt;code&gt;IXSCAN&lt;/code&gt; and examined only 51 documents.&lt;/p&gt;

&lt;p&gt;For this test, the main point was simple: before blaming the client, check if MongoDB is scanning too much data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which client fits which workflow?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If you mainly need to…&lt;/th&gt;
&lt;th&gt;Best fit&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Open and work with a large continuous result&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;VisuaLeaf&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;It loaded 50,000 documents in 13.481 seconds and remained usable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compare two collections visually&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;VisuaLeaf&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;It clearly separated identical, modified, and missing documents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automate multi-step data workflows&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;VisuaLeaf&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Import, copy, export, and scheduling can be managed in one workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Find inefficient queries in the same workspace&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;VisuaLeaf&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Query Profiler combines plans, index usage, scanned documents, and optimization hints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browse documents in small pages with a free official client&lt;/td&gt;
&lt;td&gt;MongoDB Compass&lt;/td&gt;
&lt;td&gt;Pagination keeps browsing controlled and predictable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access advanced synchronization and administration controls&lt;/td&gt;
&lt;td&gt;Studio 3T&lt;/td&gt;
&lt;td&gt;It provides more granular diff, sync, and performance options&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There is no single client for every MongoDB job. Compass is a good option for controlled, paginated browsing, while Studio 3T offers advanced administration and synchronization controls. But for everyday work with large results, collection comparisons, automated tasks, and query analysis, &lt;a href="https://visualeaf.com" rel="noopener noreferrer"&gt;VisuaLeaf&lt;/a&gt; provides the most complete workflow in one place.&lt;/p&gt;

&lt;p&gt;Choose the client around the operation that costs you time, not the longest feature list.&lt;/p&gt;

</description>
      <category>mongodb</category>
      <category>javascript</category>
      <category>database</category>
      <category>software</category>
    </item>
    <item>
      <title>How to Copy and Sync a MongoDB Collection to PostgreSQL</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Tue, 28 Jul 2026 07:46:26 +0000</pubDate>
      <link>https://dev.to/visualeaf/how-to-copy-and-sync-a-mongodb-collection-to-postgresql-1of7</link>
      <guid>https://dev.to/visualeaf/how-to-copy-and-sync-a-mongodb-collection-to-postgresql-1of7</guid>
      <description>&lt;p&gt;I tested a MongoDB-to-PostgreSQL sync with a &lt;code&gt;visits&lt;/code&gt; collection.&lt;/p&gt;

&lt;p&gt;At first, I thought the hard part would be the sync itself. It was not. The harder part was making PostgreSQL understand the MongoDB document shape.&lt;/p&gt;

&lt;p&gt;MongoDB lets one document contain strings, dates, nested objects, arrays, and an &lt;code&gt;_id&lt;/code&gt;. PostgreSQL wants fixed columns, clear data types, and a primary key.&lt;/p&gt;

&lt;p&gt;So before running the sync, I had to decide what the PostgreSQL table should look like.&lt;/p&gt;

&lt;p&gt;For this test, I used &lt;a href="https://visualeaf.com/features/mongosync/" rel="noopener noreferrer"&gt;VisuaLeaf Mongo Sync.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I used it because I wanted to set up the workflow visually: source collection, target PostgreSQL table, field mapping, and sync status. I still had to create the PostgreSQL table and check the mapping carefully, but I did not have to write a custom sync script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source Collection
&lt;/h2&gt;

&lt;p&gt;The source was:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;database_compare_demo.visits&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Each MongoDB document had simple fields like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;visitId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;status&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;visitDate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;visitReason&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also had nested fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;patient&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;doctor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;symptoms&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prescriptions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;invoice&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;labResults&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;visitDetails&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vitals&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clinic&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl8hcpn2ccn6ad1z3w82i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl8hcpn2ccn6ad1z3w82i.png" alt="VisuaLeaf showing the MongoDB visits collection in a local replica set." width="800" height="621"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MongoDB &lt;code&gt;visits&lt;/code&gt; collection with nested patient, doctor, lab results, and vitals data.&lt;/p&gt;

&lt;p&gt;This is where the mapping matters. If you try to force every nested MongoDB field into separate SQL columns too early, the setup gets messy fast.&lt;/p&gt;

&lt;p&gt;For this test, I used a PostgreSQL landing table.&lt;/p&gt;

&lt;h2&gt;
  
  
  PostgreSQL Table
&lt;/h2&gt;

&lt;p&gt;I created one table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mongo_visits_sync&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;mongo_id&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;"visit_id"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;"visit_date"&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;"visit_reason"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;patient&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;doctor&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;symptoms&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;prescriptions&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;invoice&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;"lab_results"&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;"visit_details"&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;vitals&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;clinic&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I kept the simple fields as normal columns.&lt;/p&gt;

&lt;p&gt;I stored the nested MongoDB objects and arrays as &lt;code&gt;JSONB&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This was easier to test, and it kept the original document structure instead of flattening everything too early.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;mongo_id&lt;/code&gt; Is Important
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://visualeaf.com/database/postgresql/" rel="noopener noreferrer"&gt;PostgreSQL table&lt;/a&gt; needs a primary key.&lt;/p&gt;

&lt;p&gt;In this setup, MongoDB &lt;code&gt;_id&lt;/code&gt; maps to PostgreSQL &lt;code&gt;mongo_id&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_id -&amp;gt; mongo_id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this, the sync cannot safely update existing rows.&lt;/p&gt;

&lt;p&gt;One error I hit was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SQL target has no key columns — cannot upsert idempotently
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means PostgreSQL did not have a usable key for the sync job.&lt;/p&gt;

&lt;p&gt;The fix was to create &lt;code&gt;mongo_id&lt;/code&gt; as the primary key and map MongoDB &lt;code&gt;_id&lt;/code&gt; to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Field Mapping and Data Types
&lt;/h2&gt;

&lt;p&gt;In VisuaLeaf, the &lt;a href="https://visualeaf.com/docs/mongosync" rel="noopener noreferrer"&gt;mapping shows&lt;/a&gt; the MongoDB fields with simple source types like &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Object&lt;/code&gt;, and &lt;code&gt;Array&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That matters because not every MongoDB field should become a normal text column in PostgreSQL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnn7djc7733kh5pcy834r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnn7djc7733kh5pcy834r.png" alt="New transformation mapping screen in VisuaLeaf with Generate Field Mappings button." width="799" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Creating a new transformation mapping before generating the MongoDB to PostgreSQL fields.&lt;/p&gt;

&lt;p&gt;For this test, I used this logic:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm567sg8wo440fq8s0t1m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm567sg8wo440fq8s0t1m.png" alt="VisuaLeaf field mapping table for MongoDB to PostgreSQL sync." width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Field mapping from MongoDB fields to PostgreSQL columns, including objects and arrays.&lt;/p&gt;

&lt;p&gt;The simple string fields went into normal PostgreSQL text columns.&lt;/p&gt;

&lt;p&gt;The objects and arrays went into &lt;code&gt;JSONB&lt;/code&gt; columns.&lt;/p&gt;

&lt;p&gt;This was the important part. If I mapped an object or array into a normal text column, the sync would either fail or store the data in a way that is harder to query later.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;patient&lt;/code&gt; is an object, so I mapped it to a &lt;code&gt;JSONB&lt;/code&gt; column.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;symptoms&lt;/code&gt; is an array, so I also mapped it to &lt;code&gt;JSONB&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The main key was:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;_id -&amp;gt; mongo_id&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That gave PostgreSQL a stable primary key for updates and deletes during sync.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Error I Had With Column Names
&lt;/h2&gt;

&lt;p&gt;At first, my PostgreSQL table used names like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lab_results
visit_details
visit_reason
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the sync tried to insert into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;labResults
visitDetails
visitReason
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So PostgreSQL returned this error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR: column "labResults" of relation "mongo_visits_sync" does not exist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6donwbfxzdi6dt5v14k0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6donwbfxzdi6dt5v14k0.png" alt="VisuaLeaf Mongo Sync audit log showing a PostgreSQL column name mismatch error for labResults." width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sync failed because the MongoDB field &lt;code&gt;labResults&lt;/code&gt; did not match the PostgreSQL column &lt;code&gt;lab_results&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The problem was not PostgreSQL. The problem was the mismatch between the mapping and the real column names.&lt;/p&gt;

&lt;p&gt;You can fix this in two ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep snake_case in PostgreSQL and map each field manually.&lt;/li&gt;
&lt;li&gt;Use quoted camelCase column names in PostgreSQL.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Sync Setup
&lt;/h2&gt;

&lt;p&gt;The source MongoDB database was a replica set.&lt;/p&gt;

&lt;p&gt;That matters because continuous sync uses MongoDB change streams. Change streams work with replica sets and sharded clusters, not standalone MongoDB.&lt;/p&gt;

&lt;p&gt;The setup was:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvueygds3b4eyuotmzz5p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvueygds3b4eyuotmzz5p.png" alt="VisuaLeaf Mongo Sync rules screen mapping visits to a target table." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mapping the MongoDB &lt;code&gt;visits&lt;/code&gt; collection to the PostgreSQL target table.&lt;/p&gt;

&lt;p&gt;I used Full sync because I wanted the first copy and the later changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4cudvrsz4z1w8ojv1i9n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4cudvrsz4z1w8ojv1i9n.png" alt="VisuaLeaf Mongo Sync review screen before launching the sync job." width="800" height="575"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Final review before saving the MongoDB to PostgreSQL sync job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking the Initial Copy
&lt;/h2&gt;

&lt;p&gt;After running the sync, I checked the row count:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT COUNT(*) AS synced_rows
FROM public.mongo_visits_sync;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I checked a few fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
    "visit_id",
    status,
    "visit_date",
    patient -&amp;gt;&amp;gt; 'fullName' AS patient_name,
    doctor -&amp;gt;&amp;gt; 'fullName' AS doctor_name
FROM public.mongo_visits_sync;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9dmq1h98l2053vohpdoy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9dmq1h98l2053vohpdoy.png" alt="PostgreSQL table in VisuaLeaf showing synced MongoDB visit rows." width="800" height="621"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Synced MongoDB visit documents shown in the PostgreSQL target table.&lt;/p&gt;

&lt;p&gt;This confirmed that normal fields were copied into columns, and nested values were still queryable from &lt;code&gt;JSONB&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Insert, Update, and Delete
&lt;/h2&gt;

&lt;p&gt;The first copy is not enough to prove the sync works.&lt;/p&gt;

&lt;p&gt;So I tested changes from MongoDB too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw44x2yu0fkuglryshkzu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw44x2yu0fkuglryshkzu.png" alt="VisuaLeaf Mongo Sync monitor showing live insert update and delete events." width="800" height="575"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Live sync monitor showing inserts, updates, deletes, and connection status.&lt;/p&gt;

&lt;p&gt;For the update test, I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.visits.updateOne(
  { visitId: "VIS-SYNC-TEST" },
  {
    $set: {
      status: "completed",
      "visitDetails.notes": "Updated after initial sync"
    }
  }
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I checked PostgreSQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
    "visit_id",
    status,
    "visit_details" -&amp;gt;&amp;gt; 'notes' AS notes
FROM public.mongo_visits_sync
WHERE "visit_id" = 'VIS-SYNC-TEST';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The updated status and note appeared in PostgreSQL.&lt;/p&gt;

&lt;p&gt;I also tested insert and delete. The new document appeared in PostgreSQL, and the deleted document was removed from the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Would Watch For
&lt;/h2&gt;

&lt;p&gt;The sync works, but the setup is not automatic magic.&lt;/p&gt;

&lt;p&gt;You still need to be careful with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;primary keys&lt;/li&gt;
&lt;li&gt;field mapping&lt;/li&gt;
&lt;li&gt;camelCase vs snake_case column names&lt;/li&gt;
&lt;li&gt;nested objects&lt;/li&gt;
&lt;li&gt;arrays&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JSONB&lt;/code&gt; fields&lt;/li&gt;
&lt;li&gt;unmapped MongoDB fields&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a first version, one PostgreSQL landing table is usually easier than trying to normalize everything.&lt;/p&gt;

&lt;p&gt;After the data is synced, you can decide if some fields should move into separate SQL tables later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This kind of sync is useful when MongoDB is where the app data lives, but PostgreSQL is better for reporting, SQL queries, dashboards, or sharing data with teams that work mostly in relational databases.&lt;/p&gt;

&lt;p&gt;The goal is not only to copy the collection once. The useful part is keeping PostgreSQL updated when documents are inserted, updated, or deleted in MongoDB.&lt;/p&gt;

&lt;p&gt;For this test, the cleanest setup was to store simple fields as normal PostgreSQL columns and keep nested objects and arrays as &lt;code&gt;JSONB&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once the primary key, column names, data types, and field mapping were correct, &lt;a href="https://visualeaf.com/features/mongosync/" rel="noopener noreferrer"&gt;VisuaLeaf Mongo Sync&lt;/a&gt; could handle the copy and the later changes without writing a custom sync script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to Sync MongoDB to Atlas Instead?
&lt;/h2&gt;

&lt;p&gt;If you want to copy a local MongoDB collection to Atlas and keep it in sync, I covered the full process in this detailed guide: &lt;a href="https://visualeaf.com/blog/copy-sync-local-mongodb-collection-to-atlas/" rel="noopener noreferrer"&gt;How to Copy a Local MongoDB Collection to Atlas—and Keep It in Sync.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mongodb</category>
      <category>postgres</category>
      <category>database</category>
      <category>software</category>
    </item>
    <item>
      <title>How to Import JSON into MongoDB and Export to CSV with Data Masking</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Thu, 23 Jul 2026 09:20:23 +0000</pubDate>
      <link>https://dev.to/visualeaf/how-to-import-json-into-mongodb-and-export-to-csv-with-data-masking-58p4</link>
      <guid>https://dev.to/visualeaf/how-to-import-json-into-mongodb-and-export-to-csv-with-data-masking-58p4</guid>
      <description>&lt;p&gt;Every morning, an online store receives the previous day’s orders from a marketplace partner.&lt;/p&gt;

&lt;p&gt;The file comes in JSON format. The company needs to add those orders to its main MongoDB &lt;code&gt;orders&lt;/code&gt; collection. The sales manager also needs a CSV report that can be opened in Excel.&lt;/p&gt;

&lt;p&gt;That sounds like a small task. Import the file, copy the documents, export the report.&lt;/p&gt;

&lt;p&gt;But in practice, a few things can break the process.&lt;/p&gt;

&lt;p&gt;A date can be imported as a string. A field can have the wrong name. One batch may use &lt;code&gt;total&lt;/code&gt;, while the main collection uses &lt;code&gt;totalAmount&lt;/code&gt;. A temporary collection can keep old records and trigger duplicate key errors. A CSV export can create null values because the mapping points to fields that do not exist.&lt;/p&gt;

&lt;p&gt;And then there is customer data. The manager may need the sales numbers, but they probably do not need real customer names or internal customer IDs.&lt;/p&gt;

&lt;p&gt;This article walks through a real daily workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Import marketplace JSON
        ↓
Store the batch in a temporary MongoDB collection
        ↓
Copy the orders into the main orders collection
        ↓
Mask customer fields during export
        ↓
Create a CSV report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is not just to move data from JSON to CSV. The goal is to make the process repeatable, easier to check, and safer to share.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8g992dpxpflixeleq9x2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8g992dpxpflixeleq9x2.png" alt="VisuaLeaf Task Manager showing a MongoDB workflow that imports yesterday’s orders from JSON, adds them to the main orders collection, and exports a CSV report." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow
&lt;/h2&gt;

&lt;p&gt;The workflow has three jobs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Import Yesterday Orders
        ↓
Add Orders to Main
        ↓
Export Daily Sales Report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is the parent relationship between the jobs.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Add Orders to Main&lt;/code&gt; depends on &lt;code&gt;Import Yesterday Orders&lt;/code&gt;, so it only runs after the JSON file is imported successfully.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Export Daily Sales Report&lt;/code&gt; depends on &lt;code&gt;Add Orders to Main&lt;/code&gt;, so the CSV is created only after the main &lt;code&gt;orders&lt;/code&gt; collection has been updated.&lt;/p&gt;

&lt;p&gt;This prevents the report from being generated when data is missing or incomplete.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flvxggmrnum09wzra7ibg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flvxggmrnum09wzra7ibg.png" alt="VisuaLeaf Task Manager table view showing a daily MongoDB workflow where the import job has no parent, the copy job depends on the import, and the CSV export depends on the copy job." width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The incoming JSON file
&lt;/h2&gt;

&lt;p&gt;The partner sends a file with yesterday’s completed orders.&lt;/p&gt;

&lt;p&gt;A single order looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORD-2026-07-201"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customerId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CUST-1003"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customerName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sofia Rossi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"orderDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-21T08:20:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"completed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"paymentStatus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"paid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EL-002"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"productName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Wireless Mouse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Electronics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"unitPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;24.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"lineTotal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;24.99&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EL-003"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"productName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mechanical Keyboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Electronics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"unitPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;79.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"lineTotal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;79.99&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"itemsSummary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1x Wireless Mouse, 1x Mechanical Keyboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"itemCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subtotal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;104.98&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"discount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"shippingFee"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalAmount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;99.97&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"couponCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WELCOME10"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two fields worth pointing out here.&lt;/p&gt;

&lt;p&gt;The first is &lt;code&gt;items&lt;/code&gt;. This is the real order structure. It keeps each product as a nested object with its own SKU, quantity, price, and line total.&lt;/p&gt;

&lt;p&gt;The second is &lt;code&gt;itemsSummary&lt;/code&gt;. This is not as rich as the &lt;code&gt;items&lt;/code&gt; array, but it works better in a CSV report. Instead of putting a full JSON array into one spreadsheet cell, the manager sees a readable value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1x Wireless Mouse, 1x Mechanical Keyboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MongoDB handles nested arrays well. CSV does not. So for the database, keep the array. For the report, export the summary.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The first job imports the file into a temporary collection:&lt;br&gt;&lt;br&gt;
&lt;code&gt;online_store.daily_sales_import&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fns2vukq2ddqqaz5li67r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fns2vukq2ddqqaz5li67r.png" alt="VisuaLeaf Task Manager import job configured to load a JSON file into the online_store.daily_sales_import MongoDB collection." width="800" height="579"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why use a temporary collection?
&lt;/h2&gt;

&lt;p&gt;The first job imports the JSON file into &lt;code&gt;online_store.daily_sales_import&lt;/code&gt; instead of writing directly to &lt;code&gt;orders&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This extra step is worth it.&lt;/p&gt;

&lt;p&gt;The file comes from another system. Even if the partner usually sends the correct structure, a single change can break your report. A field can be renamed. A value can arrive as text instead of a number. A date can be formatted differently. Or the import job can use an old field mapping from another file.&lt;/p&gt;

&lt;p&gt;The temporary collection gives you a place to check the batch before it becomes part of the main order history.&lt;/p&gt;

&lt;p&gt;If it keeps old documents, the next run may copy the same orders again. That can cause duplicate key errors later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcqykslgrvrvu70gfnpxw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcqykslgrvrvu70gfnpxw.png" alt="VisuaLeaf showing the daily_sales_import MongoDB collection with one imported order document expanded, including fields such as customerId, customerName, items, itemsSummary, and orderDate." width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate field mappings every time the JSON structure changes
&lt;/h2&gt;

&lt;p&gt;This is the easiest step to skip, and it is also where a lot of bad imports start.&lt;/p&gt;

&lt;p&gt;When you select the JSON file, click:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generate Field Mappings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this order file, the mapping should include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_id           → _id
orderId       → orderId
customerId    → customerId
customerName  → customerName
orderDate     → orderDate
status        → status
paymentStatus → paymentStatus
currency      → currency
items         → items
itemsSummary  → itemsSummary
itemCount     → itemCount
subtotal      → subtotal
discount      → discount
shippingFee   → shippingFee
totalAmount   → totalAmount
couponCode    → couponCode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you do not regenerate the mappings, the job may reuse fields from an older import.&lt;/p&gt;

&lt;p&gt;For example, if the previous import was a customer file, the mapping may still expect fields like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;country
email
joinedAt
name
customerId
status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The import may still run. But the order fields will be missing, and the result will look wrong. You may see null values or documents that only contain a few shared fields like &lt;code&gt;customerId&lt;/code&gt; and &lt;code&gt;status&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is not a MongoDB issue. It is a mapping issue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frpztwq8l13bacqmnby5w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frpztwq8l13bacqmnby5w.png" alt="VisuaLeaf field mappings for a MongoDB copy job." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Copy the batch into the main orders collection
&lt;/h2&gt;

&lt;p&gt;The second job copies documents from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;daily_sales_import
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;orders
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This job should use &lt;code&gt;Import Yesterday Orders&lt;/code&gt; as its parent.&lt;/p&gt;

&lt;p&gt;A simple configuration is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source: online_store.daily_sales_import
Target: online_store.orders
Mode: Insert or Append
Parent: Import Yesterday Orders
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use insert or append because you are adding new orders to the order history.&lt;/p&gt;

&lt;p&gt;Do not use replace mode on &lt;code&gt;orders&lt;/code&gt; unless you really want to overwrite the full collection.&lt;/p&gt;

&lt;p&gt;The main collection should keep all orders. The temporary collection should only hold the latest imported file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjtg7s7u3lqcfy8s5phd8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjtg7s7u3lqcfy8s5phd8.png" alt="VisuaLeaf copy job from daily_sales_import to orders." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Duplicate key errors are useful, but they still need a fix
&lt;/h2&gt;

&lt;p&gt;If you run the same batch twice, MongoDB may return this error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;E11000 duplicate key error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means MongoDB blocked a duplicate value for a unique field, often &lt;code&gt;_id&lt;/code&gt; or &lt;code&gt;orderId&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is usually good. It stops the same order from being inserted twice.&lt;/p&gt;

&lt;p&gt;But in a daily workflow, the error also tells you something is wrong with the process.&lt;/p&gt;

&lt;p&gt;Common causes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The temporary collection was not cleared.
The same JSON file was imported twice.
The partner sent duplicate order IDs.
The copy job tried to insert records already present in orders.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In testing, the temporary collection is often the problem.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;daily_sales_import&lt;/code&gt; contains old records and new records, the copy job tries to insert all of them. MongoDB accepts the new ones and rejects the duplicates.&lt;/p&gt;

&lt;p&gt;The fix is simple: clear or replace the temporary collection before each new import.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvw2b97rdaaz1e01g8n0i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvw2b97rdaaz1e01g8n0i.png" alt="VisuaLeaf Task Manager showing an E11000 duplicate key error during a MongoDB copy job." width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Export the weekly report from the main collection
&lt;/h2&gt;

&lt;p&gt;The company imports marketplace orders every day, so the main MongoDB &lt;code&gt;orders&lt;/code&gt; collection stays up to date.&lt;/p&gt;

&lt;p&gt;But the manager does not need a CSV file every morning. In this case, the manager needs a weekly sales report.&lt;/p&gt;

&lt;p&gt;That changes the source of the export job.&lt;/p&gt;

&lt;p&gt;If you export from &lt;strong&gt;daily_sales_import,&lt;/strong&gt; you export only the latest imported batch.&lt;/p&gt;

&lt;p&gt;If you export from &lt;strong&gt;orders&lt;/strong&gt;, you export the main order history, including the orders that were imported during the week.&lt;/p&gt;

&lt;p&gt;For this workflow, the export job uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source: online_store.orders
Output: weekly-sales-report-{{yyyy-MM-dd}}.csv
Parent: Add Orders to Main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parent relationship still matters. The weekly CSV should be created only after the latest imported orders have been added to the main &lt;code&gt;orders&lt;/code&gt; collection.&lt;/p&gt;

&lt;p&gt;This gives the manager one report with the updated sales data, instead of sending a separate file every day.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffcef2dva65u1pwqndjhj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffcef2dva65u1pwqndjhj.png" alt="VisuaLeaf export job from MongoDB orders collection to a weekly CSV report." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Mask customer data during export
&lt;/h2&gt;

&lt;p&gt;The manager needs sales data, but they do not need the real customer name or internal customer ID.&lt;/p&gt;

&lt;p&gt;Instead of changing the original MongoDB documents, the export job applies masking only in the CSV output.&lt;/p&gt;

&lt;p&gt;In this workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customerId   → hash(value)
customerName → maskName(value)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the original data intact in MongoDB, while the exported report hides sensitive customer details.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnp5mjkvulbf1o4qx0p53.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnp5mjkvulbf1o4qx0p53.png" alt="VisuaLeaf export mappings with hash(value) and maskName(value) applied." width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What maskName(value) does
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://visualeaf.com/docs/data-masking" rel="noopener noreferrer"&gt;&lt;code&gt;maskName(value)&lt;/code&gt; transformation&lt;/a&gt; keeps the first character and replaces the rest with asterisks.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Alex Ionescu → A***********
Mia Thompson → M***********
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It does not create a fake name.&lt;/p&gt;

&lt;p&gt;It masks the original value. The report still has a customer-name column, but the real name is hidden.&lt;/p&gt;

&lt;p&gt;This is useful when the report needs to show that a customer exists, but not who the customer is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What hash(value) does
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;hash(value)&lt;/code&gt; transformation changes the customer ID into a hashed value.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CUST-1002 → 043a5f9b...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same input should produce the same hashed output.&lt;/p&gt;

&lt;p&gt;That means the report can still show that two orders belong to the same customer without exposing the original customer ID.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Original customerId: CUST-1002
Masked customerId:   043a5f9b...

Original customerId: CUST-1002
Masked customerId:   043a5f9b...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful for analysis.&lt;/p&gt;

&lt;p&gt;The manager can group orders by the masked ID, but they cannot see the real internal ID.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv68luj2i7s4jcit6rjxp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv68luj2i7s4jcit6rjxp.png" alt="VisuaLeaf export mappings with hash(value) and maskName(value) applied." width="799" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Schedule the workflow
&lt;/h2&gt;

&lt;p&gt;Once each job works on its own, schedule the full workflow.&lt;/p&gt;

&lt;p&gt;The final process is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Receive marketplace JSON
        ↓
Import the daily batch
        ↓
Add orders to MongoDB
        ↓
Mask customer details
        ↓
Export the CSV report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the schedule to run every morning at 09:00.&lt;/p&gt;

&lt;p&gt;The parent relationships keep the jobs in order.&lt;/p&gt;

&lt;p&gt;The report does not run before the copy job. The copy job does not run before the import job.&lt;/p&gt;

&lt;p&gt;That is the main reason to use a workflow instead of three separate jobs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvt34ftngxxddmfe4bsw0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvt34ftngxxddmfe4bsw0.png" alt="VisuaLeaf schedule dialog showing a daily 9 AM recurring job." width="799" height="685"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What this workflow actually solves
&lt;/h2&gt;

&lt;p&gt;This workflow is not only about converting JSON to CSV.&lt;/p&gt;

&lt;p&gt;It solves a real process that many teams deal with: marketplace data comes in one format, MongoDB stores the operational data, and the manager needs a spreadsheet they can review without seeing unnecessary customer details.&lt;/p&gt;

&lt;p&gt;In this setup, the daily JSON file is imported into &lt;code&gt;daily_sales_import&lt;/code&gt;. Then the new orders are copied into the main &lt;code&gt;orders&lt;/code&gt; collection. At the end, the weekly CSV report is created from the updated &lt;code&gt;orders&lt;/code&gt; collection.&lt;/p&gt;

&lt;p&gt;Before the CSV is exported, sensitive customer fields are masked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customerName → A***********
customerId   → 043a5f9b...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original MongoDB documents stay complete. Only the exported report hides customer details.&lt;/p&gt;

&lt;p&gt;This makes the process repeatable, easier to check, and safer to share.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final result
&lt;/h2&gt;

&lt;p&gt;At the end, you have one scheduled workflow that handles the daily marketplace file and keeps MongoDB up to date.&lt;/p&gt;

&lt;p&gt;You avoid repeating the same import and export steps by hand. You reduce the chance of copying old batches again. You keep the report readable with fields like &lt;code&gt;itemsSummary&lt;/code&gt;. And you share the sales data without exposing the original customer identities.&lt;/p&gt;

&lt;p&gt;The small details still matter: generate the mappings when the JSON structure changes, keep field names consistent, store dates as &lt;code&gt;DATE_TIME&lt;/code&gt;, and preview the export before trusting the CSV.&lt;/p&gt;

&lt;p&gt;Once those pieces are set, the workflow is easier to run, easier to check, and safer to share.&lt;/p&gt;

&lt;p&gt;I built this example in &lt;a href="https://visualeaf.com" rel="noopener noreferrer"&gt;VisuaLeaf&lt;/a&gt; using &lt;a href="https://visualeaf.com/features/task-manager/" rel="noopener noreferrer"&gt;Task Manager&lt;/a&gt; and export transformations.&lt;/p&gt;

&lt;p&gt;If you work with MongoDB imports, scheduled exports, or masked reports, you can try it here: &lt;a href="https://visualeaf.com/download" rel="noopener noreferrer"&gt;https://visualeaf.com/download&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mongodb</category>
      <category>database</category>
      <category>software</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>How to Copy and Sync a Local MongoDB Collection to Atlas</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Mon, 20 Jul 2026 14:04:46 +0000</pubDate>
      <link>https://dev.to/visualeaf/how-to-copy-and-sync-a-local-mongodb-collection-to-atlas-9me</link>
      <guid>https://dev.to/visualeaf/how-to-copy-and-sync-a-local-mongodb-collection-to-atlas-9me</guid>
      <description>&lt;p&gt;Moving data from a local MongoDB database to Atlas usually means exporting files, importing them again, and repeating the same process when something changes.&lt;/p&gt;

&lt;p&gt;I wanted to test a simpler way in VisuaLeaf: copy a local collection to MongoDB Atlas and keep the Atlas collection updated automatically.&lt;/p&gt;

&lt;p&gt;For this example, I used a local &lt;code&gt;visits&lt;/code&gt; collection containing seven documents. The documents included ordinary fields, nested objects, arrays, dates, and ObjectIds, so I could also check whether the original MongoDB structure remained intact in Atlas.&lt;/p&gt;

&lt;p&gt;Here is the complete process, followed by an insert, update, and delete test.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I used
&lt;/h2&gt;

&lt;p&gt;My source and target were:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Connection&lt;/th&gt;
&lt;th&gt;Database&lt;/th&gt;
&lt;th&gt;Collection&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Local Replica Set&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;database_compare_demo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;visits&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MongoDB Atlas&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;database_compare_demo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;visits_atlas_sync&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I used a different target collection name so I could verify the copied data without mixing it with anything already stored in Atlas.&lt;/p&gt;

&lt;p&gt;The local source was a replica set because continuous synchronization relies on MongoDB change streams. Change streams are available for replica sets and sharded clusters, not standalone MongoDB deployments. (&lt;a href="https://www.mongodb.com/docs/manual/changestreams/" rel="noopener noreferrer"&gt;MongoDB change streams&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;If the source is standalone, the job can still perform an initial copy, but it cannot continue watching for later changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fel4d5b2jahtrnq4ifit7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fel4d5b2jahtrnq4ifit7.png" alt="The local  raw `visits` endraw  collection before it was copied to Atlas." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The local &lt;code&gt;visits&lt;/code&gt; collection before it was copied to Atlas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing the Atlas connection
&lt;/h2&gt;

&lt;p&gt;Before creating the sync job, I made sure VisuaLeaf could write to my Atlas cluster.&lt;/p&gt;

&lt;p&gt;Before using a standard Atlas connection, I configured two access items:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A database user with permission to access the target database.&lt;/li&gt;
&lt;li&gt;My computer’s public IP address added to the project’s IP access list.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Atlas database users are separate from the account used to sign in to the Atlas website. For this test, I used a database user with read and write access to &lt;code&gt;database_compare_demo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I added only my current IP address to the Atlas access list instead of allowing connections from every IP. Atlas only accepts standard client connections from addresses included in that list. (&lt;a href="https://www.mongodb.com/docs/atlas/mongo-shell-connection/" rel="noopener noreferrer"&gt;MongoDB Atlas connection prerequisites&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;From the Atlas cluster’s *&lt;strong&gt;&lt;em&gt;Connect&lt;/em&gt;&lt;/strong&gt;* window, I copied the driver connection string. Its general format was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mongodb+srv://&amp;lt;username&amp;gt;:&amp;lt;password&amp;gt;@&amp;lt;cluster-host&amp;gt;/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used that URI to create a connection in VisuaLeaf and named it &lt;code&gt;MongoDB Atlas&lt;/code&gt;. I tested the connection before continuing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the sync job
&lt;/h2&gt;

&lt;p&gt;I opened *&lt;strong&gt;&lt;em&gt;Mongo Sync&lt;/em&gt;&lt;/strong&gt;* and created a new job with these details:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Job name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Local Visits to Atlas&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Description&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Copy local clinic visits to Atlas and keep later changes synchronized.&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source connection&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Local Replica Set&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source database&lt;/td&gt;
&lt;td&gt;&lt;code&gt;database_compare_demo&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The source connection showed full change-stream support, which meant I could select Full sync later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Futet1ywge0qu0vowdlph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Futet1ywge0qu0vowdlph.png" alt="The local replica set and  raw `database_compare_demo` endraw  database selected as the source." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The local replica set and &lt;code&gt;database_compare_demo&lt;/code&gt; database selected as the source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mapping the local collection to Atlas
&lt;/h2&gt;

&lt;p&gt;In the Rules step, I added the Atlas connection as the target and configured one rule:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Target connection&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MongoDB Atlas&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target database&lt;/td&gt;
&lt;td&gt;&lt;code&gt;database_compare_demo&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source collection&lt;/td&gt;
&lt;td&gt;&lt;code&gt;visits&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target collection&lt;/td&gt;
&lt;td&gt;&lt;code&gt;visits_atlas_sync&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter&lt;/td&gt;
&lt;td&gt;Empty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transformation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None (pass-through)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I left the filter empty because I wanted all seven documents.&lt;/p&gt;

&lt;p&gt;I also used *&lt;strong&gt;&lt;em&gt;None (pass-through)&lt;/em&gt;&lt;/strong&gt;* instead of generating field mappings. Both sides were MongoDB, so I wanted to preserve each document exactly as it was, including its &lt;code&gt;_id&lt;/code&gt;, nested objects, arrays, dates, and optional fields.&lt;/p&gt;

&lt;p&gt;Field mappings are useful when renaming fields, changing types, masking values, or sending data into a different schema. They were unnecessary for this direct MongoDB-to-MongoDB copy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5sik23zrxfj0d3idawy6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5sik23zrxfj0d3idawy6.png" alt="The  raw `visits` endraw  collection mapped to  raw `visits_atlas_sync` endraw  with pass-through documents." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;visits&lt;/code&gt; collection mapped to &lt;code&gt;visits_atlas_sync&lt;/code&gt; with pass-through documents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing Full sync
&lt;/h2&gt;

&lt;p&gt;The Options step offered three modes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;When to use it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Initial Only&lt;/td&gt;
&lt;td&gt;Copies the documents that currently exist, then stops&lt;/td&gt;
&lt;td&gt;One-time collection copy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Incremental Only&lt;/td&gt;
&lt;td&gt;Watches only for changes made after the job starts&lt;/td&gt;
&lt;td&gt;The target is already populated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full sync&lt;/td&gt;
&lt;td&gt;Copies existing documents and then watches for later changes&lt;/td&gt;
&lt;td&gt;Initial copy plus continuous synchronization&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I selected *&lt;strong&gt;&lt;em&gt;Full sync&lt;/em&gt;&lt;/strong&gt;* and enabled Start sync immediately after creation.&lt;/p&gt;

&lt;p&gt;This mode first copies the seven documents already stored locally. After the initial phase finishes, it continues listening for new inserts, updates, and deletes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffkkb4ugkbnseowrd2gl6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffkkb4ugkbnseowrd2gl6.png" alt="Full sync copies existing documents and continues watching the local collection." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Full sync copies existing documents and continues watching the local collection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reviewing and launching the job
&lt;/h2&gt;

&lt;p&gt;The Review screen showed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Local Replica Set / database_compare_demo&lt;/code&gt; as the source;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MongoDB Atlas / database_compare_demo&lt;/code&gt; as the target;&lt;/li&gt;
&lt;li&gt;one rule from &lt;code&gt;visits&lt;/code&gt; to &lt;code&gt;visits_atlas_sync&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;no filter or transformation;&lt;/li&gt;
&lt;li&gt;Full sync with automatic start.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also estimated an initial copy of approximately seven documents.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzsxs3cu9wn3x2lsofapc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzsxs3cu9wn3x2lsofapc.png" alt="The complete local-to-Atlas configuration before launching the job." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The complete local-to-Atlas configuration before launching the job.&lt;/p&gt;

&lt;p&gt;I clicked *&lt;strong&gt;&lt;em&gt;Create &amp;amp; launch&lt;/em&gt;&lt;/strong&gt;* and waited for the initial phase to finish. Once the monitor showed that the job was running and waiting for changes, I checked the Atlas collection directly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4nmdoyzxh5vol2kh0z8a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4nmdoyzxh5vol2kh0z8a.png" alt="The initial copy is complete and the job is monitoring later changes." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The initial copy is complete and the job is monitoring later changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying the initial copy in Atlas
&lt;/h2&gt;

&lt;p&gt;I opened &lt;code&gt;database_compare_demo.visits_atlas_sync&lt;/code&gt; on the Atlas connection and counted its documents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visits_atlas_sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;countDocuments&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result was &lt;code&gt;7&lt;/code&gt;, matching the local source collection.&lt;/p&gt;

&lt;p&gt;I also opened several documents and checked their BSON types. The ObjectIds, dates, nested patient and doctor objects, and arrays were preserved because the job used pass-through documents rather than converting them into a new schema.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqahb7ju8f9ooh4czebxp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqahb7ju8f9ooh4czebxp.png" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The initial count proved that the collection was copied. It did not yet prove that the Full sync job would handle later changes, so I tested each main operation separately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 1: Inserting a new local document
&lt;/h3&gt;

&lt;p&gt;I inserted one clearly labeled test visit into the local source:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy3f1wjvd9t2bozeroukt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy3f1wjvd9t2bozeroukt.png" alt="Insert a new document into the local connection." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Insert a new document into the local connection.&lt;/p&gt;

&lt;p&gt;Then I searched for the same &lt;code&gt;visitId&lt;/code&gt; in Atlas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visits_atlas_sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;visitId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;VIS-ATLAS-TEST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3091869bg990uofkm4ru.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3091869bg990uofkm4ru.gif" alt="A new local visit appears automatically in the Atlas collection." width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A new local visit appears automatically in the Atlas collection.&lt;/p&gt;

&lt;p&gt;The new document appeared in the target collection without restarting or recreating the job. The target count increased from seven to eight.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 2: Updating the local document
&lt;/h3&gt;

&lt;p&gt;Next, I updated both a top-level field and a nested field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;visitId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;VIS-ATLAS-TEST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;$set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;doctor.roomNumber&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;204&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;updatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I checked the Atlas document again:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fic7sucvfx38dsx3gud77.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fic7sucvfx38dsx3gud77.png" alt="Top-level and nested changes from the local document are visible in Atlas." width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Top-level and nested changes from the local document are visible in Atlas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 3: Deleting the local document
&lt;/h3&gt;

&lt;p&gt;Finally, I deleted the test document from the local collection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deleteOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;visitId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;VIS-ATLAS-TEST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I verified the target one more time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visits_atlas_sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;countDocuments&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;visitId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;VIS-ATLAS-TEST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdiimdu68xq4ruwp6xysc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdiimdu68xq4ruwp6xysc.png" alt="Deleting the local test document removes its synchronized Atlas copy." width="799" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Deleting the local test document removes its synchronized Atlas copy.&lt;/p&gt;

&lt;p&gt;The result returned to &lt;code&gt;0&lt;/code&gt;, and the full Atlas collection returned to seven documents.&lt;/p&gt;

&lt;h2&gt;
  
  
  When this workflow is useful
&lt;/h2&gt;

&lt;p&gt;This type of Mongo Sync job makes sense when I want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;copy selected local collections to an Atlas development or staging environment;&lt;/li&gt;
&lt;li&gt;keep a cloud copy updated while continuing to write locally;&lt;/li&gt;
&lt;li&gt;move a collection without repeatedly exporting and importing JSON files;&lt;/li&gt;
&lt;li&gt;test an application against Atlas using recent local data;&lt;/li&gt;
&lt;li&gt;synchronize only the collections needed for a specific workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important word is *&lt;strong&gt;&lt;em&gt;selected&lt;/em&gt;&lt;/strong&gt;*. This was a collection-level sync, not a complete production migration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgs5hoieozvptjgpsd2sy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgs5hoieozvptjgpsd2sy.png" alt="VisuaLeaf Mongo Sync monitor showing live inserts, updates, and deletes." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Live MongoDB sync from local replica set to Atlas.&lt;/p&gt;

&lt;h2&gt;
  
  
  How this differs from other Atlas migration methods
&lt;/h2&gt;

&lt;p&gt;MongoDB provides other tools for different jobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mongodump&lt;/code&gt; and &lt;code&gt;mongorestore&lt;/code&gt; are useful for a self-managed, one-time transfer using a BSON dump.&lt;/li&gt;
&lt;li&gt;Atlas Live Migration is designed for moving a deployment into Atlas and managing a production cutover with minimal downtime.&lt;/li&gt;
&lt;li&gt;This Mongo Sync workflow focuses on copying selected collections and continuing to apply their changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Atlas recommends its Live Migration workflow when performing a supported production migration. It uses MongoDB’s &lt;code&gt;mongosync&lt;/code&gt; technology underneath and includes a cutover process for redirecting applications to the Atlas destination. (&lt;a href="https://www.mongodb.com/docs/atlas/live-migration/" rel="noopener noreferrer"&gt;Atlas Live Migration&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;I would not present a collection-level sync as a replacement for that process.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would still verify before using it in production
&lt;/h2&gt;

&lt;p&gt;This small test verified document copying and three change types. It did not test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;high write volume or very large collections;&lt;/li&gt;
&lt;li&gt;long network interruptions and recovery;&lt;/li&gt;
&lt;li&gt;schema changes while the job was running;&lt;/li&gt;
&lt;li&gt;independent writes made to both source and target;&lt;/li&gt;
&lt;li&gt;users, roles, indexes, validation rules, or other database metadata;&lt;/li&gt;
&lt;li&gt;a full application cutover from local MongoDB to Atlas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I treated the local collection as the source of truth and used an empty target collection. That kept the test easy to understand and avoided conflicts with existing Atlas documents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final takeaway
&lt;/h2&gt;

&lt;p&gt;The first copy was easy to check: 7 local documents appeared in Atlas.&lt;/p&gt;

&lt;p&gt;But the more important part was what happened after that. When I inserted, updated, and deleted a local document, the same changes appeared in Atlas without exporting another file or recreating the job.&lt;/p&gt;

&lt;p&gt;Because both sides were MongoDB, I could use pass-through mode and keep the original document structure.&lt;/p&gt;

&lt;p&gt;For development, staging, testing, or selected collection sync, this is a practical way to move local MongoDB data to Atlas and keep it updated. For a full production migration, I would still follow MongoDB’s official migration process and plan the cutover separately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Want to try it?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://visualeaf.com/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs7aaysdkhl0hzivzdm6k.png" alt="CTA Image" width="799" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://visualeaf.com/" rel="noopener noreferrer"&gt;Download for Free and test Mongo Sync with your own local MongoDB database and Atlas cluster.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.mongodb.com/docs/manual/changestreams/" rel="noopener noreferrer"&gt;MongoDB change streams&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mongodb.com/docs/atlas/mongo-shell-connection/" rel="noopener noreferrer"&gt;Connect to a MongoDB Atlas cluster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mongodb.com/docs/atlas/security/add-ip-address-to-list/" rel="noopener noreferrer"&gt;Manage the Atlas IP access list&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mongodb.com/docs/atlas/live-migration/" rel="noopener noreferrer"&gt;Atlas Live Migration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mongodb.com/docs/database-tools/mongorestore/" rel="noopener noreferrer"&gt;&lt;code&gt;mongorestore&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mongodb</category>
      <category>database</category>
      <category>software</category>
      <category>javascript</category>
    </item>
    <item>
      <title>7 MongoDB Query Mistakes That Return the Wrong Results</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Tue, 14 Jul 2026 09:39:05 +0000</pubDate>
      <link>https://dev.to/visualeaf/7-mongodb-query-mistakes-that-return-the-wrong-results-5faj</link>
      <guid>https://dev.to/visualeaf/7-mongodb-query-mistakes-that-return-the-wrong-results-5faj</guid>
      <description>&lt;p&gt;MongoDB queries look simple. You type a field, give it a value, hit run, and you get your data back.&lt;/p&gt;

&lt;p&gt;But just because a query runs without throwing an error doesn't mean it worked right. Sometimes you get a blank screen. Sometimes you get way too many records. Other times, the data looks fine at first glance, but it doesn't actually match what you asked for.&lt;/p&gt;

&lt;p&gt;Most of these slip-ups happen for one basic reason: the query structure doesn't match the way the data actually sits in the database.&lt;/p&gt;

&lt;p&gt;To show you what we mean, we’ll use a clinic database with a collection called &lt;code&gt;visits&lt;/code&gt;. Here is what a typical document looks like:&lt;/p&gt;

&lt;p&gt;JSON&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "_id": "6871b6f9c3f1d1a4c2a10001",
  "status": "completed",
  "visitDate": "2026-07-01T09:30:00.000Z",
  "patient": { "name": "Anna Keller", "age": 34 },
  "doctor": { "name": "Dr. James Carter", "specialty": "Cardiology" },
  "symptoms": ["cough", "fever"],
  "prescriptions": [
    { "name": "Ibuprofen", "active": false },
    { "name": "Paracetamol", "active": true }
  ],
  "invoice": { "paid": true, "method": "card", "total": 250 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can run these examples right in the &lt;a href="https://visualeaf.com/blog/mongodb-shell-visual-output/" rel="noopener noreferrer"&gt;VisuaLeaf MongoDB Shell&lt;/a&gt;. Using visual tools makes a big difference because you can see exactly what MongoDB is returning in real time.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Forgetting the Curly Braces
&lt;/h2&gt;

&lt;p&gt;This is just a quick typo, but it breaks things right away.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Mistake:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Correct Query&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiu7e84hx4awafe8ue9eq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiu7e84hx4awafe8ue9eq.png" alt="MongoDB query examples showing common mistakes with $or, $in, arrays, dates, and text search in VisuaLeaf." width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;find()&lt;/code&gt; tool always expects an object. Even if you are only looking for one specific thing, you still need to wrap that condition in curly braces &lt;code&gt;{}&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Treating &lt;code&gt;$or&lt;/code&gt; Like a Regular Object
&lt;/h2&gt;

&lt;p&gt;This one trips a lot of people up because the broken version looks like it should work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Mistake:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.visits.find({
  $or: {
    status: "completed",
    "invoice.paid": false
  }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What is wrong:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$or expects an array of conditions, but this query gives it one object.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The error will usually be something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MongoServerError: $or must be an array
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Correct Query&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F42cfbhf55y928ge2j9yg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F42cfbhf55y928ge2j9yg.png" alt="MongoDB Shell query using $or with separate conditions, showing matching visit records in VisuaLeaf Table View." width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first query is wrong because &lt;code&gt;$or&lt;/code&gt; needs an array, not one regular object.&lt;/p&gt;

&lt;p&gt;Each condition has to be written as its own object inside &lt;code&gt;[]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The corrected query returns visits where the status is &lt;code&gt;pending&lt;/code&gt; or &lt;code&gt;completed&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Querying Nested Fields Like They Are Flat
&lt;/h2&gt;

&lt;p&gt;MongoDB documents often have fields inside other fields.&lt;/p&gt;

&lt;p&gt;In our &lt;code&gt;visits&lt;/code&gt; collection, &lt;code&gt;doctor&lt;/code&gt; is an object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;doctor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dr. Michael Brown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;specialization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Neurology&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Query to avoid:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;doctor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Neurology&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This asks MongoDB to find a &lt;code&gt;doctor&lt;/code&gt; field that is exactly &lt;code&gt;"Neurology"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But &lt;code&gt;doctor&lt;/code&gt; is not a string. It is an object.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better query:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;doctor.specialization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Neurology&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhs6po958xjoqt0qxj5dy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhs6po958xjoqt0qxj5dy.png" alt="MongoDB query for a nested doctor.specialization field shown in VisuaLeaf Tree View." width="799" height="615"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The dot tells MongoDB to go inside the &lt;code&gt;doctor&lt;/code&gt; object and check the &lt;code&gt;specialization&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;So instead of asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is the whole &lt;code&gt;doctor&lt;/code&gt; field equal to Neurology?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;you are asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is the doctor’s specialization Neurology?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  4. Typing the Same Field Twice
&lt;/h2&gt;

&lt;p&gt;Say you want to find visits with doctors from Cardiology or Dermatology.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Query to avoid:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;doctor.specialization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cardiology&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;doctor.specialization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dermatology&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Better query:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvo1fjvgc4mtvbocfj91y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvo1fjvgc4mtvbocfj91y.png" alt="MongoDB Shell query using $in to search visits by multiple values in the same field, with results shown in VisuaLeaf Table View." width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In JavaScript and MongoDB, you should not reuse the same key within a single object.&lt;/p&gt;

&lt;p&gt;If you type &lt;code&gt;"doctor.specialization"&lt;/code&gt; twice, the second value can overwrite the first one. So MongoDB may only search for &lt;code&gt;Dermatology&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If one field can match more than one value, use &lt;code&gt;$in&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Treating Dates Like Plain Text
&lt;/h2&gt;

&lt;p&gt;Dates can look like regular text, but in MongoDB they are often stored as real Date values.&lt;/p&gt;

&lt;p&gt;In our &lt;code&gt;visits&lt;/code&gt; collection, the invoice date is stored inside the &lt;code&gt;invoice&lt;/code&gt; object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;issuedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ISODate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-05-18T10:15:00Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;So this query will not work as expected:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.visits.find({
  "invoice.issuedAt": "2026-05-18"
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This searches for a string, not a Date, so it will usually return nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better query:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faeb7guvs2u7ncb45nrsn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faeb7guvs2u7ncb45nrsn.png" alt="MongoDB Shell query in VisuaLeaf using $gte and $lt with new Date() to find invoices issued on May 18, 2026, with the matching result shown in Table View." width="799" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This returns invoices created on May 18, 2026.&lt;/p&gt;

&lt;p&gt;The range matters because dates usually include time. A visit at &lt;code&gt;1:15 PM&lt;/code&gt; is still on May 18, but it is not equal to midnight.&lt;/p&gt;

&lt;p&gt;So instead of asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is the date exactly &lt;code&gt;"2026-05-18"&lt;/code&gt;?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;you are asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is the date on or after May 18 and before May 19?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  6. Expecting Text Search to Work Automatically
&lt;/h2&gt;

&lt;p&gt;MongoDB does not search text like Google by default.&lt;/p&gt;

&lt;p&gt;In our &lt;code&gt;visits&lt;/code&gt; collection, the doctor name is stored like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;doctor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dr. Michael Brown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;So this query will not work:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.visits.find({
  "doctor.fullName": "Michael"
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This searches for an exact value. MongoDB checks if &lt;code&gt;doctor.fullName&lt;/code&gt; is exactly &lt;code&gt;"Michael"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But the real value is &lt;code&gt;"Dr. Michael Brown"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better setup:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For text search, you need to create a text index first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createIndex&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;doctor.fullName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can search inside the text field:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwptblq34hq57llfos5ra.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwptblq34hq57llfos5ra.png" alt="MongoDB Shell query in VisuaLeaf using $text search for “Michael,” with the matching doctor name shown in Table View." width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now MongoDB can search for the word &lt;code&gt;Michael&lt;/code&gt; inside the indexed field.&lt;/p&gt;

&lt;p&gt;The important part is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;$text&lt;/code&gt; search does not work unless the collection has a text index.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  7. Reading &lt;code&gt;$or&lt;/code&gt; Logic Backwards
&lt;/h2&gt;

&lt;p&gt;This query runs fine, but it is easy to read it *&lt;strong&gt;&lt;em&gt;the wrong way&lt;/em&gt;&lt;/strong&gt;*:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;$or&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;doctor.specialization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Neurology&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invoice.paymentStatus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unpaid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does &lt;strong&gt;not&lt;/strong&gt; mean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;completed OR Neurology OR unpaid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It actually means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;completed AND (Neurology OR unpaid)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;status&lt;/code&gt; is outside the &lt;code&gt;$or&lt;/code&gt; block, MongoDB treats it as required.&lt;/p&gt;

&lt;p&gt;So MongoDB first looks for visits where &lt;code&gt;status&lt;/code&gt; is &lt;code&gt;completed&lt;/code&gt;. Then, from those visits, it checks if the doctor is specialized in &lt;code&gt;Neurology&lt;/code&gt; or the invoice is &lt;code&gt;unpaid&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you want all three conditions to be part of the OR logic, move &lt;code&gt;status&lt;/code&gt; inside the &lt;code&gt;$or&lt;/code&gt; array.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better query:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;$or&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;doctor.specialization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Neurology&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invoice.paymentStatus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unpaid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the query means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;completed OR Neurology OR unpaid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same fields. Different structure. Different result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzqjs8i0zq2a1mw1zswj8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzqjs8i0zq2a1mw1zswj8.png" alt="Side-by-side VisuaLeaf MongoDB Shell screenshot comparing two $or queries, showing how moving status inside the $or array returns more matching documents." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One More Thing to Do before Using Your Query
&lt;/h2&gt;

&lt;p&gt;A MongoDB query is only as good as your understanding of the underlying document structure.&lt;/p&gt;

&lt;p&gt;Before you spend hours rewriting a broken query, do one simple thing: &lt;a href="https://visualeaf.com/blog/how-to-explore-and-work-with-mongodb-data-visually/" rel="noopener noreferrer"&gt;&lt;strong&gt;Open a single raw document from your collection.&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Look closely at the exact spelling of field names.&lt;/li&gt;
&lt;li&gt;Check the data types (Are numbers stored as strings? Are dates stored as objects?).&lt;/li&gt;
&lt;li&gt;Map out your nested objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is exactly where a visual tool like &lt;a href="https://visualeaf.com" rel="noopener noreferrer"&gt;&lt;strong&gt;VisuaLeaf&lt;/strong&gt;&lt;/a&gt; saves the day. Instead of guessing from your code editor, you can test your queries inside the built-in shell and instantly toggle over to the &lt;a href="https://visualeaf.com/features/browse-collections/" rel="noopener noreferrer"&gt;&lt;strong&gt;Table View&lt;/strong&gt; or &lt;strong&gt;Tree View&lt;/strong&gt;&lt;/a&gt; to see exactly how your documents are laid out in real-time. No more guessing.&lt;/p&gt;

</description>
      <category>mongodb</category>
      <category>coding</category>
      <category>software</category>
      <category>database</category>
    </item>
    <item>
      <title>DBeaver Alternative for MongoDB Queries, Aggregations, and Visual Workflows</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Wed, 08 Jul 2026 13:29:52 +0000</pubDate>
      <link>https://dev.to/visualeaf/dbeaver-alternative-for-mongodb-queries-aggregations-and-visual-workflows-18gn</link>
      <guid>https://dev.to/visualeaf/dbeaver-alternative-for-mongodb-queries-aggregations-and-visual-workflows-18gn</guid>
      <description>&lt;p&gt;DBeaver is a strong tool if you work with many databases.&lt;/p&gt;

&lt;p&gt;It makes sense for teams using PostgreSQL, MySQL, SQL Server, Oracle, and MongoDB in the same place. But if most of your work is in MongoDB, the workflow matters more.&lt;/p&gt;

&lt;p&gt;DBeaver does support MongoDB, but according to its documentation, the MongoDB driver is available in Lite, Enterprise, and Ultimate editions only. So this is not about saying DBeaver cannot work with MongoDB. It can, if you pay for those versions.&lt;/p&gt;

&lt;p&gt;Many developers stick with DBeaver because they are comfortable writing SQL. But VisuaLeaf bridges that gap natively with its own &lt;a href="https://visualeaf.com/features/sql-mode/" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;em&gt;SQL Mode&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;, allowing you to write standard SQL queries that automatically translate into optimized MongoDB syntax.&lt;/p&gt;

&lt;p&gt;The question is whether you want a broad database tool, or a workspace built more directly around MongoDB documents, queries, aggregations, schemas, and charts.&lt;/p&gt;

&lt;p&gt;That is where &lt;a href="https://visualeaf.com" rel="noopener noreferrer"&gt;VisuaLeaf&lt;/a&gt; fits better.&lt;/p&gt;

&lt;p&gt;It is focused on &lt;a href="https://www.mongodb.com/" rel="noopener noreferrer"&gt;MongoDB&lt;/a&gt; workflows, not every database type at once.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvisualeaf.com%2Fblog%2Fcontent%2Fimages%2F2026%2F07%2Fvisualeaf-interface.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvisualeaf.com%2Fblog%2Fcontent%2Fimages%2F2026%2F07%2Fvisualeaf-interface.png" alt="VisuaLeaf brings MongoDB queries, schema diagrams, charts, dashboards, and team workflows into one visual workspace.&lt;br&gt;
" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VisuaLeaf brings MongoDB queries, schema diagrams, charts, dashboards, and team workflows into one visual workspace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why MongoDB Users May Want a Different Tool
&lt;/h2&gt;

&lt;p&gt;MongoDB work is usually more visual than people expect.&lt;/p&gt;

&lt;p&gt;You are not only reading rows, but you are also checking what is inside each document.&lt;/p&gt;

&lt;p&gt;You are opening nested objects, or you are filtering by fields that may or may not exist in every record.&lt;/p&gt;

&lt;p&gt;You are building aggregation pipelines and trying to understand what changes after each stage.&lt;/p&gt;

&lt;p&gt;A general database tool can help, but it may not always feel built for this kind of work.&lt;/p&gt;

&lt;p&gt;VisuaLeaf is designed for the daily MongoDB tasks that users actually repeat:&lt;/p&gt;

&lt;p&gt;-&amp;gt; querying data, reading documents, building aggregations, checking schemas, creating charts, and working with collections more visually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build MongoDB Queries Visually
&lt;/h2&gt;

&lt;p&gt;Writing MongoDB queries by hand is useful.&lt;/p&gt;

&lt;p&gt;But not every filter needs to start with code.&lt;/p&gt;

&lt;p&gt;Sometimes you just want to find documents where &lt;code&gt;status&lt;/code&gt; is &lt;code&gt;paid&lt;/code&gt;, sort them by date, limit the results, and check the output.&lt;/p&gt;

&lt;p&gt;In VisuaLeaf, you can &lt;a href="https://visualeaf.com/features/visual-query-builder/" rel="noopener noreferrer"&gt;build that query visually.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You add filters, combine conditions, sort results, and see the generated MongoDB query behind it.&lt;/p&gt;

&lt;p&gt;That last part matters.&lt;/p&gt;

&lt;p&gt;The tool does not hide the query from you. It helps you understand it.&lt;/p&gt;

&lt;p&gt;So if you are learning MongoDB, you can see how the query is created.&lt;/p&gt;

&lt;p&gt;And if you already know MongoDB, you can move faster without typing every condition from scratch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvisualeaf.com%2Fblog%2Fcontent%2Fimages%2F2026%2F07%2Fquery-builder.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvisualeaf.com%2Fblog%2Fcontent%2Fimages%2F2026%2F07%2Fquery-builder.png" alt="Build MongoDB queries visually and keep the generated code visible.&lt;br&gt;
" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Build MongoDB queries visually and keep the generated code visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build Aggregation Pipelines Step by Step
&lt;/h2&gt;

&lt;p&gt;Aggregation pipelines are powerful, but they can get hard to read fast.&lt;/p&gt;

&lt;p&gt;A pipeline with one &lt;code&gt;$match&lt;/code&gt; stage is simple.&lt;/p&gt;

&lt;p&gt;A pipeline with &lt;code&gt;$match&lt;/code&gt;, &lt;code&gt;$group&lt;/code&gt;, &lt;code&gt;$sort&lt;/code&gt;, &lt;code&gt;$project&lt;/code&gt;, &lt;code&gt;$lookup&lt;/code&gt;, and &lt;code&gt;$unwind&lt;/code&gt; is different.&lt;/p&gt;

&lt;p&gt;At that point, the problem is not only writing the syntax.&lt;/p&gt;

&lt;p&gt;The problem is knowing what happened to the data after each stage.&lt;/p&gt;

&lt;p&gt;VisuaLeaf lets you &lt;a href="https://visualeaf.com/features/aggregation-pipeline/" rel="noopener noreferrer"&gt;build aggregation pipelines visually&lt;/a&gt; and check the output as you go.&lt;/p&gt;

&lt;p&gt;You can add a stage, see the result, then continue.&lt;/p&gt;

&lt;p&gt;That makes it easier to find mistakes, test ideas, and explain the pipeline to someone else.&lt;/p&gt;

&lt;p&gt;You are not guessing what the pipeline does. You can see it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvisualeaf.com%2Fblog%2Fcontent%2Fimages%2F2026%2F07%2Faggregation-builder.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvisualeaf.com%2Fblog%2Fcontent%2Fimages%2F2026%2F07%2Faggregation-builder.png" alt="Build MongoDB aggregation pipelines step by step with live preview&lt;br&gt;
" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Build MongoDB aggregation pipelines step by step with live preview&lt;/p&gt;

&lt;h2&gt;
  
  
  Read MongoDB Documents More Clearly
&lt;/h2&gt;

&lt;p&gt;MongoDB documents can be clean, but real data is often messy.&lt;/p&gt;

&lt;p&gt;A document may have nested fields, arrays, dates, ObjectIds, embedded data, and references to other collections.&lt;/p&gt;

&lt;p&gt;If everything is forced into a flat table, the structure can be hard to follow.&lt;/p&gt;

&lt;p&gt;VisuaLeaf gives you different ways to &lt;a href="https://visualeaf.com/features/browse-collections/" rel="noopener noreferrer"&gt;browse MongoDB data&lt;/a&gt;, so you can inspect documents without losing the shape of the data.&lt;/p&gt;

&lt;p&gt;You can look at the document structure, check nested values, and switch views depending on what you need.&lt;/p&gt;

&lt;p&gt;This is useful when you open a collection you did not create yourself.&lt;/p&gt;

&lt;p&gt;You should not have to guess what is inside the data. You should be able to see it clearly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvisualeaf.com%2Fblog%2Fcontent%2Fimages%2F2026%2F07%2Ftree-view.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvisualeaf.com%2Fblog%2Fcontent%2Fimages%2F2026%2F07%2Ftree-view.png" alt="Browse MongoDB collections in the 3 different view modes.&lt;br&gt;
" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Browse MongoDB collections in the 3 different view modes.&lt;/p&gt;

&lt;h2&gt;
  
  
  See the MongoDB Schema
&lt;/h2&gt;

&lt;p&gt;MongoDB is flexible, but that flexibility can make the structure harder to understand.&lt;/p&gt;

&lt;p&gt;In SQL, tables and foreign keys usually show the structure.&lt;/p&gt;

&lt;p&gt;In MongoDB, relationships may be embedded inside documents, stored as references, or only suggested by field names.&lt;/p&gt;

&lt;p&gt;VisuaLeaf helps by showing collections, fields, nested structures, and relationships in a &lt;a href="https://visualeaf.com/features/visual-schema/" rel="noopener noreferrer"&gt;visual schema diagram.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This helps when you are learning a project, documenting a database, or explaining the structure to someone else.&lt;/p&gt;

&lt;p&gt;Instead of opening collection after collection and trying to remember everything, you can see the schema in one place.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8ubuu6uxot6r6hbhh51z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8ubuu6uxot6r6hbhh51z.png" alt="DBeaver MongoDB alternative for Schema Diagram" width="799" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Visual schema diagrams make flexible NoSQL data easier to understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn Query Results Into Charts
&lt;/h2&gt;

&lt;p&gt;Sometimes a result table is enough. Sometimes it is not.&lt;/p&gt;

&lt;p&gt;If you want to see payments by method, orders by status, revenue by month, or expenses by category, a chart is easier to understand.&lt;/p&gt;

&lt;p&gt;VisuaLeaf lets you turn &lt;a href="https://visualeaf.com/features/chart-builder/" rel="noopener noreferrer"&gt;MongoDB data into charts&lt;/a&gt; and dashboards without exporting the results into another tool.&lt;/p&gt;

&lt;p&gt;You can query the data, check the result, and build a chart from the same workflow.&lt;/p&gt;

&lt;p&gt;That makes it easier to understand patterns in the data, not just read raw documents.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvisualeaf.com%2Fblog%2Fcontent%2Fimages%2F2026%2F07%2Fcharts-and-dashboards.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvisualeaf.com%2Fblog%2Fcontent%2Fimages%2F2026%2F07%2Fcharts-and-dashboards.png" alt="Create charts and dashboards from real MongoDB data.&lt;br&gt;
" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create charts and dashboards from real MongoDB data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mongo Shell for Developers
&lt;/h2&gt;

&lt;p&gt;Visual tools are helpful, but developers still need direct access to the database.&lt;/p&gt;

&lt;p&gt;VisuaLeaf includes a &lt;a href="https://visualeaf.com/features/mongo-shell/" rel="noopener noreferrer"&gt;MongoDB shell&lt;/a&gt;, so you can run commands without leaving the workspace.&lt;/p&gt;

&lt;p&gt;You can test a &lt;code&gt;find()&lt;/code&gt; query, run an update, check an aggregation, or execute MongoDB commands directly with autocomplete and syntax highlighting.&lt;/p&gt;

&lt;p&gt;The difference is that the shell stays connected to the rest of the MongoDB workflow.&lt;/p&gt;

&lt;p&gt;You can write commands when you need full control, then switch back to visual results, query building, schema views, or charts when they help.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvisualeaf.com%2Fblog%2Fcontent%2Fimages%2F2026%2F07%2Fmongo-shell.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvisualeaf.com%2Fblog%2Fcontent%2Fimages%2F2026%2F07%2Fmongo-shell.png" alt="Run MongoDB commands with autocomplete, syntax highlighting, and visual results.&lt;br&gt;
" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run MongoDB commands with autocomplete, syntax highlighting, and visual results.&lt;/p&gt;

&lt;h2&gt;
  
  
  DBeaver vs VisuaLeaf for MongoDB Users
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;DBeaver&lt;/th&gt;
&lt;th&gt;VisuaLeaf&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MongoDB access&lt;/td&gt;
&lt;td&gt;Available in Lite, Enterprise, and Ultimate&lt;/td&gt;
&lt;td&gt;Free Community Edition + 14-day Pro trial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query building&lt;/td&gt;
&lt;td&gt;SQL and JavaScript workflow&lt;/td&gt;
&lt;td&gt;Visual MongoDB query builder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggregations&lt;/td&gt;
&lt;td&gt;Manual SQL or JavaScript workflow&lt;/td&gt;
&lt;td&gt;Visual aggregation builder with stage preview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Charts&lt;/td&gt;
&lt;td&gt;Available in paid editions&lt;/td&gt;
&lt;td&gt;Charts and dashboards in Professional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best fit&lt;/td&gt;
&lt;td&gt;Teams using many database types&lt;/td&gt;
&lt;td&gt;Developers focused on MongoDB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;DBeaver is stronger when you need one tool for many database systems.&lt;/p&gt;

&lt;p&gt;VisuaLeaf is a better fit when MongoDB is your main focus, and you want visual tools for queries, aggregations, schema, documents, and charts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;DBeaver is a practical choice if you need one tool for many databases.&lt;/p&gt;

&lt;p&gt;But if you came here looking for a DBeaver alternative for MongoDB, you probably care more about the MongoDB workflow than the number of databases a tool supports.&lt;/p&gt;

&lt;p&gt;That is where VisuaLeaf is different.&lt;/p&gt;

&lt;p&gt;It gives you a &lt;a href="https://visualeaf.com/features/" rel="noopener noreferrer"&gt;MongoDB-focused workspace&lt;/a&gt; for browsing collections, building visual queries, creating aggregation pipelines, using the shell, viewing schema structure, and turning results into charts. And these are only some of the features. VisuaLeaf also includes tools for indexes, validation rules, collection comparison, query profiling, dashboards, saved queries, and more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://visualeaf.com/download" rel="noopener noreferrer"&gt;Try VisuaLeaf&lt;/a&gt; if you want a MongoDB workspace that feels more focused, visual, and easier to follow.&lt;/p&gt;

</description>
      <category>mongodb</category>
      <category>nosql</category>
      <category>database</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>MongoDB vs PostgreSQL: The Arguments Everyone Gets Wrong</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Wed, 08 Jul 2026 13:08:13 +0000</pubDate>
      <link>https://dev.to/visualeaf/mongodb-vs-postgresql-the-arguments-everyone-gets-wrong-36f9</link>
      <guid>https://dev.to/visualeaf/mongodb-vs-postgresql-the-arguments-everyone-gets-wrong-36f9</guid>
      <description>&lt;p&gt;Most of the Postgres vs Mongo arguments I see online are based on assumptions that are either super outdated or just kind of technically wrong. The same topics keep coming up over and over on reddit, youtube, or whatever, and I wanted to write some of them down.&lt;/p&gt;

&lt;h2&gt;
  
  
  "PostgreSQL JSONB is basically MongoDB"
&lt;/h2&gt;

&lt;p&gt;JSONB lets Postgres store documents, but it doesn't turn Postgres into a document database. Mongo's storage engine, replication, sharding, aggregation framework, indexing, update semantics, and query engine were all designed around documents from day one. JSONB is bolted onto a relational engine.&lt;/p&gt;

&lt;p&gt;It's still really useful. But "just use JSONB" is kind of like saying "just throw a truck bed on a sedan and now it's a pickup." Like, technically there's overlap, but they're still doing different things.&lt;/p&gt;

&lt;h2&gt;
  
  
  "MongoDB is schemaless"
&lt;/h2&gt;

&lt;p&gt;Actually, that's one of the most common misconceptions.&lt;/p&gt;

&lt;p&gt;Mongo lets you have flexible documents, but modern Mongo has schema validation, required fields, type enforcement, unique indexes, and most of the guardrails you'd want from a regular database.&lt;/p&gt;

&lt;p&gt;Most Mongo deployments that actually work in production aren't truly schema-free. They just enforce schema differently (usually at the app layer or with validators).&lt;/p&gt;

&lt;h2&gt;
  
  
  "MongoDB loses data"
&lt;/h2&gt;

&lt;p&gt;This one is basically a meme from like 2013.&lt;/p&gt;

&lt;p&gt;Back then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Default durability settings were weaker&lt;/li&gt;
&lt;li&gt;A lot of deployments ran without replica sets&lt;/li&gt;
&lt;li&gt;Some people disabled journaling (which... yeah)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Modern Mongo defaults are way safer. You get journaling, replica sets, majority-acknowledged writes, multi-document transactions, point-in-time recovery, and distributed replication. The "MongoDB loses data" thing is mostly people repeating something they heard a decade ago.&lt;/p&gt;

&lt;h2&gt;
  
  
  "MongoDB is only for lazy devs"
&lt;/h2&gt;

&lt;p&gt;There are a lot of really smart database engineers and architects working at companies that run Mongo at scale. Choosing the "correct" database  usually has to do with the workload, not with anyone being lazy, and some workloads just map to documents better than they map to normalized tables.&lt;/p&gt;

&lt;p&gt;The misconception usually comes from MongoDB's flexibility, plus developers designing their databases recklessly without thinking about the future. In Postgres, it's much harder to do that because of all its guardrails. But that doesn't mean MongoDB is for lazy developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Postgres is always better"
&lt;/h2&gt;

&lt;p&gt;That's... debatable. Postgres is one of the best databases ever made, and it's usually a safe default. I'm not arguing against that. But like everything in software engineering, databases are tools, not teams to root for.&lt;/p&gt;

&lt;p&gt;The question isn't "which one is better." It's "which one matches the workload better."&lt;/p&gt;

&lt;p&gt;If your data is super relational, Postgres usually wins. If your data is document-shaped, Mongo usually wins. A lot of real systems end up using both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why JSONB vs BSON Benchmarks Are Kinda Misleading
&lt;/h2&gt;

&lt;p&gt;You see a lot of benchmarks where someone compares Mongo documents against Postgres JSONB documents and concludes Mongo is faster. From this video: &lt;a href="https://www.youtube.com/watch?v=ZZ2tx8iL3P4" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=ZZ2tx8iL3P4&lt;/a&gt; (I am not the author of the video).&lt;/p&gt;

&lt;p&gt;And for that test, the numbers are probably right. But the conclusion people pull from them often isn't.&lt;/p&gt;

&lt;p&gt;There's two things going on with benchmarks like this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The data models aren't really comparable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;JSONB isn't really Postgres's main data model. So when you benchmark Mongo documents against Postgres JSONB documents, what you're actually comparing is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A document database&lt;/li&gt;
&lt;li&gt;A relational database being used as a document database&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A fairer Postgres benchmark would model the same data using normalized tables, foreign keys, real indexes, joins, the whole thing. A lot of workloads that suck in JSONB are actually really fast once you model them properly. The reverse is true too. Workloads that feel natural in Mongo get weird when you force them into rows and joins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The durability defaults aren't comparable either.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In that video specifically, Mongo was running with its default writeConcern, which only flushes the journal every 100ms. Postgres was running with its default WAL fsync on every commit. So Mongo gets to acknowledge writes faster, because it's literally promising less durability per write. That's not "Mongo is faster than SQL." That's "Mongo is faster when you let it relax its durability." Crank Mongo up to &lt;code&gt;j: true&lt;/code&gt; or &lt;code&gt;w: "majority"&lt;/code&gt; with proper sync and the gap closes a lot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Durability Isn't a Mongo Weakness Anymore
&lt;/h2&gt;

&lt;p&gt;Like I mentioned above, the "Mongo loses data" thing is mostly an artifact of how people ran Mongo in the early 2010s.&lt;/p&gt;

&lt;p&gt;Modern Mongo gives you journaling, replica sets, majority-acknowledged writes, multi-doc transactions, point-in-time recovery, and distributed replication.&lt;/p&gt;

&lt;p&gt;A write with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;writeConcern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;w&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;majority"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is durable across multiple replica set members before it even gets acknowledged.&lt;/p&gt;

&lt;p&gt;The actual durability gap between a properly configured Mongo cluster and a properly configured Postgres cluster is way smaller than people assume. Most "is it durable" arguments these days are really arguments about operational choices and consistency models, not about the storage engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Mongo Actually Wins
&lt;/h2&gt;

&lt;p&gt;There are real workloads where Mongo is just the better tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documents that get mutated a lot
&lt;/h3&gt;

&lt;p&gt;Mongo is great when you're updating documents constantly and each update only touches a small piece of the doc. Stuff like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Counters&lt;/li&gt;
&lt;li&gt;User state&lt;/li&gt;
&lt;li&gt;Device state&lt;/li&gt;
&lt;li&gt;Telemetry that gets enriched over time&lt;/li&gt;
&lt;li&gt;Session data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Postgres JSONB has to rewrite the whole JSONB value every time. Mongo often doesn't. This gap is huge in practice. Mongo can churn through partial updates at a rate that JSONB just can't match unless you pull the hot fields out into real columns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deeply nested data
&lt;/h3&gt;

&lt;p&gt;Mongo handles nested objects, arrays, and embedded docs naturally. You don't have to join your way through five tables to assemble a real object.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query patterns you can't predict yet
&lt;/h3&gt;

&lt;p&gt;If your users can add arbitrary fields and then query them later, Mongo's flexibility is hard to beat. You don't have to know the schema ahead of time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Horizontal write scaling
&lt;/h3&gt;

&lt;p&gt;Mongo's sharding was built into the database from the start. Postgres can scale out too, but you usually end up with partitioning, extensions, distributed variants, or app-level sharding. None of those feel as native as Mongo's sharding does. That's one of MongoDB's biggest strengths.&lt;/p&gt;

&lt;h3&gt;
  
  
  Document-centric workloads
&lt;/h3&gt;

&lt;p&gt;Mongo really shines when each request reads one doc, updates one doc, writes one doc. No joins, no fan-out, just the doc as the unit of work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Big documents that change a lot
&lt;/h3&gt;

&lt;p&gt;If your documents are large, mutated frequently, and accessed as a single unit, Mongo is basically designed for that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Postgres Still Wins
&lt;/h2&gt;

&lt;p&gt;Mongo's good at a lot, but Postgres still owns a few areas pretty cleanly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Complex joins
&lt;/h3&gt;

&lt;p&gt;Postgres's query planner is incredible. It's hard to overstate how good it is at joins. It actually outperforms MongoDB by a lot in a couple of public benchmarks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Referential integrity
&lt;/h3&gt;

&lt;p&gt;Foreign keys actually enforce relationships. Mongo can't really do this natively.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reporting / analytics
&lt;/h3&gt;

&lt;p&gt;SQL is still the language for analytics and BI. Pretty much every tool on the planet speaks it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Heavily relational data
&lt;/h3&gt;

&lt;p&gt;When your data is naturally graphy/relational, Postgres usually produces a cleaner design.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-row transactions
&lt;/h3&gt;

&lt;p&gt;Postgres's transaction model is one of its biggest strengths. Mongo has transactions too, but they're more limited.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Java vs Python Way of Thinking About It
&lt;/h2&gt;

&lt;p&gt;Here's an analogy that kind of clicks for me. You can almost think of SQL like Java and Mongo like Python.&lt;/p&gt;

&lt;p&gt;Java has built-in types, strict structure, classes everywhere. It kind of forces you to organize your code and keep things modular. You can't really get away with being sloppy, because the language won't let you. SQL is similar. Schemas, types, constraints, foreign keys. The structure is enforced for you whether you want it or not.&lt;/p&gt;

&lt;p&gt;Python is more like Mongo. Super flexible, super widely used, still really popular and not going anywhere. But if you don't have the self-discipline to make actual classes, structure your modules properly, and keep things organized, your codebase falls apart pretty fast. It's the same with Mongo. The flexibility is great until you stop enforcing schema at the app layer and your collections turn into a mess of inconsistent docs.&lt;/p&gt;

&lt;p&gt;Neither is bad. Python is huge, Mongo is huge. They give you a lot of room to move fast. But the room to move fast is also the room to shoot yourself in the foot. Java and SQL take that choice away from you, which sucks when you want flexibility and is great when you need guardrails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Different Tools, Different Assumptions
&lt;/h2&gt;

&lt;p&gt;The way I think about it is that Postgres and Mongo aren't really competing for the same spot. They're built on different assumptions.&lt;/p&gt;

&lt;p&gt;Postgres assumes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Relationships matter&lt;/li&gt;
&lt;li&gt;Consistency matters&lt;/li&gt;
&lt;li&gt;Joins matter&lt;/li&gt;
&lt;li&gt;Transactions matter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mongo assumes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Documents are the unit of work&lt;/li&gt;
&lt;li&gt;Schema evolves&lt;/li&gt;
&lt;li&gt;Updates are localized&lt;/li&gt;
&lt;li&gt;Horizontal scaling matters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither one is universally right. The mistake is forcing your workload into the wrong set of assumptions, and then being mad when the database fights you. The best database is usually just the one whose assumptions most closely match what your app actually does.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Quick Note on Where This Is Coming From
&lt;/h2&gt;

&lt;p&gt;For context: I've been building a tool called &lt;a href="https://visualeaf.com/download" rel="noopener noreferrer"&gt;VisuaLeaf &lt;/a&gt;for the last year and a half. It's a visual GUI for both MongoDB and PostgreSQL. Same workspace, same workflow, whichever database your data happens to live in. You can even migrate from sql databases to a mongodb database using VisuaLeafs Tasks/&lt;/p&gt;

&lt;p&gt;Building it means I spend a kind of unreasonable amount of time inside both engines. A lot of these misconceptions come up in support tickets, Reddit threads, and questions from people picking between the two, so I figured I'd dump them in one spot instead of typing the same reply over and over.&lt;/p&gt;

</description>
      <category>mongodb</category>
      <category>postgres</category>
      <category>database</category>
      <category>software</category>
    </item>
    <item>
      <title>PostgreSQL JSONB vs MongoDB BSON: The Real Architectural Tradeoffs</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Wed, 08 Jul 2026 13:04:12 +0000</pubDate>
      <link>https://dev.to/visualeaf/postgresql-jsonb-vs-mongodb-bson-the-real-architectural-tradeoffs-4n8k</link>
      <guid>https://dev.to/visualeaf/postgresql-jsonb-vs-mongodb-bson-the-real-architectural-tradeoffs-4n8k</guid>
      <description>&lt;p&gt;Most teams pick between Postgres and Mongo by arguing about SQL vs documents, transactions, joins, or what everyone's using at the moment. The format on disk barely makes it into the discussion. That’s the wrong place to stop, because each database’s byte-level design carries the philosophy of the engine. BSON is a binary echo of MongoDB's runtime: a self-describing wire format that the server can scan, mutate, and ship easily. JSONB is a parse tree frozen into a Postgres tuple: optimized for read-time access, indifferent to write-time mutation, and beholden to MVCC.&lt;/p&gt;

&lt;p&gt;Once you know what each format actually is, the rest of the comparison stops being a religious argument and starts being a set of engineering tradeoffs you can reason about. This piece walks through what BSON and JSONB are at the byte level, how they behave once they hit storage, what each one costs to index, update, and read back, and which workloads each one quietly punishes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu00kqt61872hhcwdsj46.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu00kqt61872hhcwdsj46.png" alt="mongodb bson vs postgres jsonb.png" width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the format matters more than the API
&lt;/h2&gt;

&lt;p&gt;Application developers see JSON. Both engines accept JSON on the way in and serve JSON on the way out, so it is tempting to assume the storage format is a cosmetic detail. It is not. The storage format dictates:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How much byte juggling the server does on every read.&lt;/li&gt;
&lt;li&gt;Whether a partial update can mutate a value in place or has to rewrite the entire document.&lt;/li&gt;
&lt;li&gt;What kinds of indexes can be built and how big they get.&lt;/li&gt;
&lt;li&gt;How much write amplification you pay when a single field changes.&lt;/li&gt;
&lt;li&gt;Whether the disk representation survives a torn page or a partial flush.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A query that runs in 1ms on a hot row can run in 30ms if the format forces the server to materialize and reparse the row. A field update that flips a boolean can rewrite 8KB of heap, generate WAL, trigger toast churn, and invalidate three indexes, or it can patch four bytes. The format decides which world you live in.&lt;/p&gt;

&lt;h2&gt;
  
  
  BSON: a binary echo of MongoDB's runtime
&lt;/h2&gt;

&lt;p&gt;BSON stands for Binary JSON. BSON is a length-prefixed, type-tagged, ordered key-value format that was designed with three properties in mind: cheap to parse linearly, cheap to mutate in place when the new value is the same size, and rich enough to carry types that JSON cannot express.&lt;/p&gt;

&lt;p&gt;The wire layout of a BSON document looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int32  total_document_length_in_bytes
{ element }*
byte   0x00  // terminator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each element is itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;byte    type_tag    (0x01 double, 0x02 string, 0x03 embedded doc, 0x07 ObjectId, ...)
cstring field_name
&amp;lt;payload depending on type&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things follow from that layout. First, every document and every embedded subdocument carries its own length prefix, which means a parser can skip an entire subtree in one pointer arithmetic step. Second, fields are ordered. The same logical document can be encoded in different byte sequences depending on insertion order, and the server preserves that order.&lt;/p&gt;

&lt;p&gt;The type tag set is wider than JSON's. BSON has dedicated tags for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;32 bit and 64 bit integers (JSON has only a generic number).&lt;/li&gt;
&lt;li&gt;IEEE 754 decimal128 for financial workloads.&lt;/li&gt;
&lt;li&gt;ObjectId (12 bytes: timestamp, machine id, counter).&lt;/li&gt;
&lt;li&gt;UTC datetime (int64 milliseconds since epoch).&lt;/li&gt;
&lt;li&gt;Binary blobs with a subtype byte.&lt;/li&gt;
&lt;li&gt;UUID (a binary subtype).&lt;/li&gt;
&lt;li&gt;Regular expressions.&lt;/li&gt;
&lt;li&gt;A JavaScript code type, mostly historical.&lt;/li&gt;
&lt;li&gt;MinKey and MaxKey sentinels used for index bounds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two consequences for engineers. One: BSON round trips numeric types faithfully. A column that stores 64 bit account ids does not silently become a double the way it would in pure JSON. Two: BSON carries metadata that JSON cannot, which is part of the reason a MongoDB driver feels chatty when you push it through a strict JSON pipeline.&lt;/p&gt;

&lt;p&gt;The clever bit, and the one that distinguishes BSON from a hundred other binary JSON formats: every field's payload is preceded by enough information that a server can walk the document linearly without recursive parsing, and many field updates can be performed by patching the payload in place. If you change an int32 field from 7 to 8, the document length does not change, the field offset does not move, and the engine writes four bytes. That property is what lets WiredTiger keep update latency flat across a wide range of document sizes.&lt;/p&gt;

&lt;p&gt;It also explains the cost of BSON. Field names are stored as raw cstrings in every document. A collection of a billion documents with a &lt;code&gt;created_at&lt;/code&gt; field carries the literal bytes &lt;code&gt;created_at\0&lt;/code&gt; a billion times. There is no schema, no dictionary, no shared symbol table. Wide documents with long field names waste a lot of disk and a lot of memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSONB: a parse tree frozen on disk
&lt;/h2&gt;

&lt;p&gt;JSONB is the spiritual opposite of BSON. Where BSON is a wire format that happens to live on disk, JSONB is a storage format that happens to be transferable. Postgres parses incoming JSON, normalizes it (keys sorted, whitespace stripped, duplicate keys deduplicated with last write wins), and serializes the result into a binary structure that maps directly onto the engine's value tree.&lt;/p&gt;

&lt;p&gt;There is no public byte spec the way there is for BSON. Internally, a JSONB value is a header plus a sequence of entries that describe each key value pair, plus the data area. The headers contain length and type bits so the engine can binary search keys inside an object, and offsets are stored every N entries to bound the cost of finding a specific key. That structure has two big implications.&lt;/p&gt;

&lt;p&gt;First, JSONB is a read optimized format. Once the document is stored, looking up a key by name is logarithmic in the number of keys, not linear. For wide objects with hundreds of fields, this matters. BSON, by contrast, is linear: it walks the document until it finds the key, leveraging the length prefixes to skip subtrees.&lt;/p&gt;

&lt;p&gt;Second, JSONB cannot be patched in place. The header layout, the offset cache, and the dedup pass all assume the value is being constructed from scratch. Updating a single boolean inside a 4KB JSONB document materializes a brand new 4KB JSONB document, writes a new heap tuple, marks the old one dead, and updates every index that references it. This is not a JSONB design flaw, it is the consequence of binding the format to Postgres's MVCC and TOAST machinery. JSONB does what makes Postgres fast at reads. Postgres pays for the rest at write time.&lt;/p&gt;

&lt;p&gt;JSONB throws away two things compared to JSON. Key order is lost (objects are stored with keys sorted), and duplicate keys are collapsed. Most applications never notice, but if you are relying on either property, you want &lt;code&gt;json&lt;/code&gt; (the text type) not &lt;code&gt;jsonb&lt;/code&gt;. Almost nobody should be relying on either property.&lt;/p&gt;

&lt;p&gt;JSONB also does not preserve numeric type fidelity in the same way BSON does. Postgres has its own &lt;code&gt;numeric&lt;/code&gt; type which is arbitrary precision, and JSONB encodes numbers using that representation. You will not lose precision the way you would with a 64 bit float, but you also do not get a distinct int32 vs int64 tag the way BSON gives you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Binary blobs and where the type systems leak
&lt;/h2&gt;

&lt;p&gt;The gap between BSON and JSONB shows up the fastest when data falls outside JSON's native type system.&lt;/p&gt;

&lt;p&gt;Binary blobs are the usual offender. BSON has a native binary type with a subtype tag, JSONB does not. In Postgres your options are base64 encoding the bytes into JSONB (ugly), or pulling them out into a separate &lt;code&gt;bytea&lt;/code&gt; column and referencing them by id (cleaner). Splitting the document works fine when you own the schema, it becomes awkward when you are ingesting third party documents that already embed binary fields, like S3 events with attachments or message payloads with thumbnails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storage: WiredTiger pages vs heap tuples plus TOAST
&lt;/h2&gt;

&lt;p&gt;The byte format is half the story. The other half is what the storage engine does with the bytes.&lt;/p&gt;

&lt;p&gt;MongoDB stores BSON documents inside WiredTiger, a B+ tree storage engine that also backs a number of other databases. WiredTiger gives MongoDB two important properties:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Block compression by default.&lt;/strong&gt; Snappy compresses each block; zstd is available and can roughly halve the disk footprint on text heavy collections. The compression happens at the storage layer, so the in memory representation is uncompressed but the on disk and on wire (replication) footprint is much smaller. For a workload that stores web event payloads or product catalogs, the ratio of on disk size to logical size is routinely 3:1 or better.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In place updates when possible.&lt;/strong&gt; If a field changes and the new BSON encoding fits in the existing slot, WiredTiger patches the page. If the document grows beyond its allocated space, WiredTiger rewrites the document, which is the expensive path. Schema decisions matter here: documents with arrays that grow over time will rewrite often, and the standard advice is to size arrays carefully or break them out into separate collections.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;WiredTiger maintains a journal (write ahead log) that commits to disk every 100ms by default, and snapshots dirty pages to disk at 60 second checkpoints. The two intervals together keep recovery time bounded without grinding the write path on every commit.&lt;/p&gt;

&lt;p&gt;PostgreSQL stores JSONB inside a heap tuple, the same structure that holds every other Postgres row. The heap is a page based store (8KB pages by default) with row level MVCC. Every update writes a new tuple, links it to the old one, and lets autovacuum reclaim the dead copy later.&lt;/p&gt;

&lt;p&gt;JSONB also interacts with TOAST (The Oversized Attribute Storage Technique). Any column value that exceeds approximately 2KB after compression gets pushed into a separate TOAST table and replaced in the main heap with a pointer. JSONB documents larger than that threshold therefore live in two places: the heap tuple holds a pointer, and the actual JSONB lives in toast chunks. Reading the document means following the pointer and reassembling the chunks. The default TOAST strategy for &lt;code&gt;jsonb&lt;/code&gt; is &lt;code&gt;EXTENDED&lt;/code&gt;, which means values are first compressed (using the cluster's default compression, pglz or lz4 since PG 14), and then chunked if still over threshold.&lt;/p&gt;

&lt;p&gt;The two consequences are easy to miss:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;code&gt;SELECT *&lt;/code&gt; on a table with TOASTed JSONB columns will fetch the entire chain even if you only wanted three scalar fields from the row. Project the columns you need or extract the JSONB fields explicitly.&lt;/li&gt;
&lt;li&gt;Updating one field in a JSONB document materializes a new JSONB, which gets TOASTed again. The old TOAST chunks become dead and wait for autovacuum. On a heavy update workload against large JSONB documents, the TOAST table can balloon faster than the main heap.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Postgres 14 added LZ4 compression for TOAST, which is meaningfully faster than the legacy pglz both for compression and decompression. If your JSONB columns are large and updated often, switching to LZ4 is the single highest leverage TOAST change you can make.&lt;/p&gt;

&lt;h2&gt;
  
  
  Updates: in place vs MVCC tombstones
&lt;/h2&gt;

&lt;p&gt;Spend ten minutes profiling a heavy update workload on each engine and the contrast jumps out.&lt;/p&gt;

&lt;p&gt;In MongoDB, a field level update with &lt;code&gt;$set&lt;/code&gt;, &lt;code&gt;$inc&lt;/code&gt;, or &lt;code&gt;$push&lt;/code&gt; is interpreted by the server and translated into a targeted mutation of the BSON document. If the new value fits, WiredTiger updates the page in place. If it does not fit, the document is rewritten. Index entries are only touched for the fields that actually changed and that are indexed. The journal records the delta, not the whole document.&lt;/p&gt;

&lt;p&gt;In PostgreSQL, &lt;code&gt;UPDATE&lt;/code&gt; on a JSONB column always rewrites the entire JSONB value. The &lt;code&gt;jsonb_set&lt;/code&gt; function looks surgical at the SQL layer (it lets you set a specific path) but underneath, it builds a new JSONB and the row update replaces the old tuple. MVCC then leaves a dead tuple behind, autovacuum reclaims it later, and every index on the table that does not satisfy the HOT (Heap Only Tuple) conditions has to insert a new entry pointing at the new tuple. If your indexes reference fields inside the JSONB column, HOT is off the table and every update is amplified.&lt;/p&gt;

&lt;p&gt;In practice this means two patterns dominate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MongoDB rewards documents with mutable internals.&lt;/strong&gt; A counter inside a document, a status field that flips often, an array that gets pushed to on every event: these are cheap operations. The flip side is that document growth is the silent killer. A document that starts at 1KB and grows to 50KB over a year is rewritten by WiredTiger every time it crosses its current allocation, which means the late life updates are dramatically more expensive than the early life updates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PostgreSQL rewards documents that are read more than they are updated.&lt;/strong&gt; Catalog data, feature flags, event payloads that are written once and read many times, configuration that changes weekly rather than per second: JSONB is excellent for these. The moment you start treating a JSONB document as a per row mutable object that changes on every request, you are fighting the engine.&lt;/p&gt;

&lt;p&gt;The benchmark that exposes this gap is straightforward. Take a million row table or collection, each row holding a 4KB document with a &lt;code&gt;view_count&lt;/code&gt; integer somewhere inside it. Increment the counter once per second per row across all rows. MongoDB may only need to modify the bytes associated with the changed field, avoiding the full document rewrite that JSONB incurs. Postgres rewrites 4KB of JSONB per document, generates 4KB of WAL per update, and starts producing dead tuples faster than autovacuum can clean them. The Postgres answer here is simple: do not store &lt;code&gt;view_count&lt;/code&gt; in JSONB. Pull it into a regular &lt;code&gt;bigint&lt;/code&gt; column. That fix is real and lasting, and it is also a tax on schemas that try to be JSON first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Indexes: GIN, multikey, and what each can answer
&lt;/h2&gt;

&lt;p&gt;Both engines let you index inside documents. The mechanisms are different, and the resulting indexes have different shapes.&lt;/p&gt;

&lt;p&gt;PostgreSQL offers two index strategies for JSONB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GIN indexes on the whole document.&lt;/strong&gt; The default operator class supports containment (&lt;code&gt;@&amp;gt;&lt;/code&gt;), key existence, and path queries. A more aggressive variant, &lt;code&gt;jsonb_path_ops&lt;/code&gt;, supports only &lt;code&gt;@&amp;gt;&lt;/code&gt; but produces a smaller, faster index. Both are inverted indexes that emit one entry per path through the document.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_events_data&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;GIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="n"&gt;jsonb_path_ops&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'{"type": "signup"}'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;B tree indexes on specific expressions.&lt;/strong&gt; When you know the exact path you want to query, an expression B tree is dramatically smaller and faster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_events_user_id&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'12345'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MongoDB indexes work on field paths directly. The standard &lt;code&gt;createIndex({user_id: 1, created_at: -1})&lt;/code&gt; builds a B tree on a compound path. The syntax is different from Postgres, but the behavior is familiar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multikey indexes.&lt;/strong&gt; When the indexed field is an array, MongoDB automatically creates one index entry per array element. The same B tree, traversed differently. A document with &lt;code&gt;tags: ["postgres", "mongodb", "bson"]&lt;/code&gt; generates three index entries pointing at the same document, and any element match query can use the index. This is the killer feature for tags, references, and other array shaped data.&lt;/p&gt;

&lt;p&gt;For fixed, known query shapes, Postgres ties or beats Mongo with expression B trees. For varied or array heavy data, Mongo's first class multikey support gives it more leverage with less ceremony.&lt;/p&gt;

&lt;p&gt;One more thing Postgres has on Mongo here: partial indexes with arbitrary predicates. You can build an index that only covers the rows where some JSONB field has a specific value, or a date range, or whatever. Mongo has partial indexes too but the filter expressions you can use are way more limited, so anything more than a simple equality check usually has to live in the query side instead of the index.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read paths and the cost of materialization
&lt;/h2&gt;

&lt;p&gt;The end to end read path is where the two formats earn or lose their reputation.&lt;/p&gt;

&lt;p&gt;A MongoDB find that hits an index and returns a single document does roughly the following: traverse the B tree to find the record id, fetch the page from WiredTiger, decompress the block, locate the BSON document inside the page, copy it into the response buffer, ship it over the wire. The document is already in BSON. If the driver supports it (most do), the bytes can travel from the storage page to the client with no parse on the server side. Field projection (&lt;code&gt;$project&lt;/code&gt; in aggregation, or projection on &lt;code&gt;find&lt;/code&gt;) walks the BSON in place and emits a trimmed version.&lt;/p&gt;

&lt;p&gt;A PostgreSQL select that hits an index and returns a single row does roughly the following: traverse the B tree to find the heap tuple location, check the visibility map and the tuple's xmin/xmax to confirm the row is visible to this transaction, fetch the heap page, locate the tuple, follow any TOAST pointers to reassemble JSONB columns, materialize the row into the executor's tuple format, apply any projections, and emit. The visibility check is the MVCC tax. The TOAST chase is the wide column tax. The materialization is the format tax.&lt;/p&gt;

&lt;p&gt;For point reads of small documents, both engines are fast and the differences are noise. For wide documents (tens of KB) or selective projections on a large document, BSON has a structural edge because the on disk format and the on wire format are the same, and projection is a walk. JSONB has to be unpacked and the projection produces new JSONB. The win is not huge in absolute terms, but it shows up on workloads that fan out reads.&lt;/p&gt;

&lt;p&gt;Aggregations flip it though. Postgres has been working on its query planner for like 30 years. Parallel scans, hash joins, merge joins, a cost based optimizer that has seen basically every shape of analytical query a person can write. Mongo's aggregation pipeline has gotten way better in recent versions (SBE is faster, &lt;code&gt;$lookup&lt;/code&gt; is actually usable now), but once you're doing real analytics with multiple joins and big group by aggregates, Postgres just wins. Not by a small margin either, you'll often see big multiples on the same workload.&lt;/p&gt;

&lt;h2&gt;
  
  
  When PostgreSQL JSONB is the right answer
&lt;/h2&gt;

&lt;p&gt;Reach for JSONB when:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The bulk of your schema is relational and a few columns happen to be semi structured. Customer records with a metadata field. Products with a variable attributes blob. Orders with line items that have inconsistent shape across categories.&lt;/li&gt;
&lt;li&gt;Reads dominate writes against the JSONB columns. Catalog data, configuration, audit payloads, settings.&lt;/li&gt;
&lt;li&gt;You need joins. Postgres can join JSONB columns against regular columns against full text indexes against PostGIS geometries against time series partitions in one query. Mongo can do this through &lt;code&gt;$lookup&lt;/code&gt; but it is not the same.&lt;/li&gt;
&lt;li&gt;You need transactions across many rows. JSONB inherits Postgres's transaction model for free.&lt;/li&gt;
&lt;li&gt;The team is already running Postgres and the operational cost of adding a second database is not justified.&lt;/li&gt;
&lt;li&gt;You want strong typing in some columns and document flexibility in others, in the same table.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Workloads that fit this shape: SaaS application data, e commerce catalogs, CMS storage, configuration stores, audit logs that need to be queried by structured fields, anything where the analytical query layer is going to use SQL anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  When MongoDB BSON is the right answer
&lt;/h2&gt;

&lt;p&gt;Reach for MongoDB when:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The unit of work is the document. Each request reads or writes one document, occasionally a few. Most operations do not span documents.&lt;/li&gt;
&lt;li&gt;Documents are large, mutate frequently, and the mutations are field local. Telemetry events that get enriched over time. Game state. IoT device shadows. Anything where the same document gets touched many times and each touch is small.&lt;/li&gt;
&lt;li&gt;The query patterns are varied and the schema is genuinely fluid. You do not know which fields users will query next month.&lt;/li&gt;
&lt;li&gt;You need horizontal write scaling out of the box. Mongo's native sharding has gotten very good. Postgres has sharding solutions (Citus, partitioning) but they are bolted on rather than first class.&lt;/li&gt;
&lt;li&gt;You need a document oriented secondary index pattern that Postgres expression indexes cannot model cleanly.&lt;/li&gt;
&lt;li&gt;The team has Mongo expertise and the workload does not need SQL analytics.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Workloads that fit this shape: real time event streams with rich payloads, mobile and game backends, IoT device fleets, content management with deeply nested structures, anything where the data model is naturally a graph of nested objects and the queries are mostly key value style with occasional secondary lookups.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical takeaway
&lt;/h2&gt;

&lt;p&gt;If you remember three things from this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The format is the engine.&lt;/strong&gt; BSON is what makes Mongo fast at field updates and fast at projection. JSONB is what makes Postgres flexible without giving up relational performance. The choice between them is not about JSON vs binary, it is about update behavior, index shape, and read path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSONB is a read optimized format.&lt;/strong&gt; It pays for itself when you write once and read many. It punishes you when you treat a JSONB column as a mutable object that changes on every request. Pull hot mutable fields out into regular columns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BSON is an update optimized format.&lt;/strong&gt; It pays for itself when documents are touched often and the touches are local. It punishes you when documents grow unboundedly, when field names are long, and when the query pattern wants joins and aggregations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pick the engine whose architectural assumptions match your workload's access pattern. Everything else is a tax you will pay every day until you fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with MongoDB documents?
&lt;/h2&gt;

&lt;p&gt;Understanding BSON is useful, but seeing the structure of your collections makes the work much easier.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://visualeaf.com/download" rel="noopener noreferrer"&gt;VisuaLeaf&lt;/a&gt;, you can browse MongoDB documents, inspect schemas visually, build queries, create aggregation pipelines, and understand how your data is actually organized.&lt;/p&gt;

</description>
      <category>mongodb</category>
      <category>postgres</category>
      <category>database</category>
      <category>software</category>
    </item>
  </channel>
</rss>
