Skip to main content

MongoDB - A simple yet Powerful Non-SQL Database

MongoDB is a simple but still powerful non-sql database. You heard right, no sql on a database?
MongoDB uses a concept of multi dimensional JSON (Java Script Object Notation) Objects. A cool thing about MongoDB is we have no schema like we would with a sql database. So how does it work then? We just define our schema while we insert data to the database. The special thing about this is, we always have a unique ID that MongoDB generates for us. Let's jump into how the construct of a mongo db works:

MongoDB Structure

After we now looked at MongoDB let's start with the Installation: To install MongoDB the easiest way is to just spin up a container like so docker run --name mongo -d mongodb/mongodb-community-server:latest. Like that we already have our MongoDB Server. But for sure there are more options to install MongoDB then just Docker but i guess that's the simplest and fastest way.

How to use MongoDB

To use MongoDB we need the command line tool "mongosh" if we have installed our MongoDB Server via Docker we can just use the following command to enter the mongo shell docker exec -it <container name> mongosh the output should be similar to this one:

Current Mongosh Log ID: 654dedeab5363a5f56dac0a2
Connecting to: mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.0.2
Using MongoDB: 7.0.3
Using Mongosh: 2.0.2

For mongosh info see: https://docs.mongodb.com/mongodb-shell/


To help improve our products, anonymous usage data is collected and sent to MongoDB periodically (https://www.mongodb.com/legal/privacy-policy).
You can opt-out by running the disableTelemetry() command.

------
The server generated these startup warnings when booting
2023-11-10T08:43:52.721+00:00: Access control is not enabled for the database. Read and write access to data and configuration is unrestricted
2023-11-10T08:43:52.721+00:00: vm.max_map_count is too low
------

test>

Show Databases and Collections

The most basic command to use is show dbswith this command we can look at all databases that are available on the Server.

test> show dbs
admin 40.00 KiB
config 72.00 KiB
local 40.00 KiB

in order to use one of the Databases we just need to use the Command use <DB Name> if we now want to create a new Database just use the same command and type a Database name that does not yet exist. After we choose a Database we view all Collections inside the Database with show collections.

Database command and Data entry

We have a general command that give us our functionality in the selected database called db, the command has multiple sub commands based on the collection we choose to use. So every upcoming command we gonna use soon will start with db.<collection> and then the subcommands after a second dot. To create our first collection and to add data we use the following command db.<collection>.inserOne(<our Data as JSON Array>) that can then look like this.

exampleDB> db.users.insertOne({name: "John"})
{
acknowledged: true,
insertedId: ObjectId("6551e911ded8ac9988ef867c")
}

If we now want to know if our object is really inserted we want to take a short look at the response we got from the MongoDB acknowledged: true , that should tell us already, that the data got inserted, but we also want to see our data in the collection. To do that we use the "find" command in the db collection part like so db.<collection>.find() or with our example above like smongodb

exampleDB> db.users.find()
[ { _id: ObjectId("6551e911ded8ac9988ef867c"), name: 'John' } ]

One of the Keypoints of MongoDB is, that you don't have a structure for your documents inside the collections, that means even though we created the database with John we can just continue with inserting data with a different structure like this.

exampleDB> db.users.insertOne({ name: "Sally", age: 19, email: [ "sally@example.de", "info@example.de" ], username: "SallyExampleCorp" })
{
acknowledged: true,
insertedId: ObjectId("6551f821ded8ac9988ef867d")
}

if we now execute the find command again, we can see that we have multiple documents with different structures.

exampleDB> db.users.find()
[
{ _id: ObjectId("6551e911ded8ac9988ef867c"), name: 'John' },
{
_id: ObjectId("6551f821ded8ac9988ef867d"),
name: 'Sally',
age: 19,
email: [ 'sally@example.de', 'info@example.de' ],
username: 'SallyExampleCorp'
}
]

just adding a single Data entry is kind of boring and not user friendly when you have multiple data sets you want to insert, but MongoDB has also a solution for that.

exampleDB> db.users.insertMany([{ name: "Johnny", age: 25}, {name: "someone Mysterious", age: 45}])
{
acknowledged: true,
insertedIds: {
'0': ObjectId("6551fbedded8ac9988ef867e"),
'1': ObjectId("6551fbedded8ac9988ef867f")
}
}

like this we added two more persons to our collection.

How to find Data in the collections

As shown before, to get all the data we just use db.<collection>.find() that is the simplest variant to get data from the database and the collections. but we have the ability to limit our output to a specific number of entries by just typing .limit(<number>)at the end of the find command so in the end we will get the following db.<collection>.find().limit(<number>) .

Sorting find output

To sort the output from the .find()command we can just use the .sort(<sorting object>)command after the find command. The sort command takes an object with a key were we can define 1or -1as you may can guess, with 1we get the "default" ascending order and with -1we get the descending order. For example with names or strings that would mean 1=> Alphabetical Order and -1=> Reverse Alphabetical Order. But a useful feature in MongoDB is that we can combine the commands with each other. So from a simple db.<colletion>.find()we can fast get a db.<collection>.find().sort(<sorting object>).limit(<number>). That is already way longer then the other ones. But let me now show you an example of how to use the sorting command.

exampleDB> db.users.find().sort({ name: 1 })
[
{ _id: ObjectId("6551e911ded8ac9988ef867c"), name: 'John' },
{
_id: ObjectId("6551fbedded8ac9988ef867e"),
name: 'Johnny',
age: 25
},
{
_id: ObjectId("6551f821ded8ac9988ef867d"),
name: 'Sally',
age: 19,
email: [ 'sally@example.de', 'info@example.de' ],
username: 'SallyExampleCorp'
},
{
_id: ObjectId("6551fbedded8ac9988ef867f"),
name: 'someone Mysterious',
age: 45
}
]
exampleDB> db.users.find().sort({ name: -1 })
[
{
_id: ObjectId("6551fbedded8ac9988ef867f"),
name: 'someone Mysterious',
age: 45
},
{
_id: ObjectId("6551f821ded8ac9988ef867d"),
name: 'Sally',
age: 19,
email: [ 'sally@example.de', 'info@example.de' ],
username: 'SallyExampleCorp'
},
{
_id: ObjectId("6551fbedded8ac9988ef867e"),
name: 'Johnny',
age: 25
},
{ _id: ObjectId("6551e911ded8ac9988ef867c"), name: 'John' }
]

And like i said before, we can easily put a limit on the output so we se all but a little portion.

exampleDB> db.users.find().sort({ name: 1 }).limit(2)
[
{ _id: ObjectId("6551e911ded8ac9988ef867c"), name: 'John' },
{
_id: ObjectId("6551fbedded8ac9988ef867e"),
name: 'Johnny',
age: 25
}
]

if you happen to put multiple sorting objects into the method then it gets first sorted by the one most in the front (first Object) and if there a multiple ones the same then the second sorting object is used. So if we have this build:

db.<collection>.find().sort({ age: 1 , name: 1 })
^ ^
first Object |
Second Parameter

Skipping data entries while querying data

While we are querying through the data of our collections we have the option to skip data entries with the .skip(<number>)command. Our command could then look like this db.<collection>.find().skip(<number>). How that could look when using the command you can see below.

exampleDB> db.users.find().skip(2)
[
{
_id: ObjectId("6551fbedded8ac9988ef867e"),
name: 'Johnny',
age: 25
},
{
_id: ObjectId("6551fbedded8ac9988ef867f"),
name: 'someone Mysterious',
age: 45
}
]

This command can of course again be combined with the others as shown below.

exampleDB> db.users.find().sort({ name: 1 }).skip(2).limit(2)
[
{
_id: ObjectId("6551f821ded8ac9988ef867d"),
name: 'Sally',
age: 19,
email: [ 'sally@example.de', 'info@example.de' ],
username: 'SallyExampleCorp'
},
{
_id: ObjectId("6551fbedded8ac9988ef867f"),
name: 'someone Mysterious',
age: 45
}
]

Searching for specific Data

With the .find() command we cannot only get all the data in the collection, we can also specify parameters we want to search for. That means if we want in our exampleDB only the user Johnny we can use this command db.users.find({ name: "Johnny" }) here you can see how this looks in reality.

exampleDB> db.users.find({ name: "Johnny" })
[
{
_id: ObjectId("6551fbedded8ac9988ef867e"),
name: 'Johnny',
age: 25
}
]

But sometimes when you search for entries then you want only specific data in an entry to be shown and not all. You can do that with passing multiple objects like so db.users.find({ name: "Johnny" }, { name: 1 }) or here a generic version db.<collection>.find(<Search Object>,<Field Object> that is how it looks when we use that second object.

exampleDB> db.users.find({ name: "Johnny" }, { name: 1 })
[ { _id: ObjectId("6551fbedded8ac9988ef867e"), name: 'Johnny' } ]

If you are familiar with Objects then you know, that you can nest objects inside other objects but you should also be able to access these nested objects like this db.users.find({ "address.street": "Main Str" })like this we can access the parameter "address" and there the nested object parameter "street" for this you need to have it in the quotation marks, otherwise its not going to work

Extended Search Querying

We also have the options to use Extended Search Parameters like $eq to find specific phrases of a text but equals means equals so if there is just one letter or number or similar more then it will not find the Entry you are searching for. But there are more parameters:

ParameterWhat it doesValues
$eqOnly matches to the specified TextNot Specified
$neOnly matches to everything that is not the specified TextNot Specified
$gtMatches every value that is greater then the specified oneNot Specified
$gteMatches every value that is greater then or equal to the specified oneNot Specified
$ltMatches every value that is less then the specified oneNot Specified
$lteMatches every value that is less then or equal to the specified oneNot Specified
$inmatches every value that is in the specified arrayNot Specified
$ninMatches every value except the ones in the arrayNot Specified
$existsMatches every entry where the specific field exists (the field can also be null)True / False

Even with these extended search Parameters you have the option to combine them. So for example you could query for an entry where the age ist $gte and at the same time $lt another Value. By Standard MongoDB combines these search parameters as "and" means, if we search for a name that $eq"Johnny" and we ask for an age that is $gte MongoDB is going to combine it, so only when both parameters are true then we get a result if one is false then we don't get one. But we also have the option to use $orfor "or" queries, there it is needed only one parameter has to be true that we may get a result. Here is an example how such a query could look like

exampleDB> db.users.find({ $or: [{name: 'Johnny'}, {age: {$lte: 19 }}]})
[
{
_id: ObjectId("6551f821ded8ac9988ef867d"),
name: 'Sally',
age: 19,
email: [ 'sally@example.de', 'info@example.de' ],
username: 'SallyExampleCorp'
},
{
_id: ObjectId("6551fbedded8ac9988ef867e"),
name: 'Johnny',
age: 25
}
]

Pay attention to the Brackets, MongoDB is giving you good advices if there is something wrong in your query. But you can prevent errors in the first place if you watch out if all your brackets are correct and / or closed. We have more to search queries like $notwith this parameter that just takes another object. If we use the not command we get every result except what is defined in out $notobject.

Expressions in search queries

When you are searching for data and want to compare it to each other like you would do for example in an "if" statement in a programming language. Here you can use $exprthis option take an object where you then have your usual statement like this (thats an example):

exampleDB> db.users.find({ $expr: { $gt: ["$debt", "$balance"]}})
[
{
_id: ObjectId("655347dfded8ac9988ef8680"),
name: 'Justin',
age: 25,
balance: 100,
debt: 200
}
]

in this example we compare if the "debt" field is greater then the "balance" field. As you may noticed we used the dollar in the strings of the greater then expression. We do that because without the dollar signs in front of the name in the string, MongoDB does not know that we mean the field in the entries and not the word "debt" or "balance".

Get only one entry

If you only need one entry then you also can just use db.<collection>.findOne(<search parameters>)this command always gives you only one entry back.

exampleDB> db.users.findOne({ age: 19})
{
_id: ObjectId("6551f821ded8ac9988ef867d"),
name: 'Sally',
age: 19,
email: [ 'sally@example.de', 'info@example.de' ],
username: 'SallyExampleCorp'
}

Count Data Entries

Instead of reading entries out we also have the option to just count them like this db.<collection>.CountDocuments(<search parameters>)here is a practical example:

exampleDB> db.users.countDocuments({ age: 45 })
2

you can also use the command without any parameters, then you get the count of all documents like so

exampleDB> db.users.countDocuments()
6

Update Data entries

When we update we need so called "atomic operators" here is a table of the atomic operators

OperatorUsage
$setWith this operator we can set the value of a field to another one or can add a new parameter to an entry
$incWith this operator, we can increment a Number by the given amount
$renameWith this operator we can rename a "coulmn" or parameter
$unsetWitch this we can delete a parameter in a data entry

You can update data entries similar to how you find data with this command db.<collection>.updateOne(<search parameters>, <object with update and atomic operator>) here is an example how we could use that

exampleDB> db.users.updateOne({ _id: ObjectId("6551fbedded8ac9988ef867e")}, {$set: { name: "Trinity" }})
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}

and if we now search for the object again we can see that the name got changed

exampleDB> db.users.findOne({_id: ObjectId("6551fbedded8ac9988ef867e")})
{ _id: ObjectId("6551fbedded8ac9988ef867e"), name: 'Trinity', age: 25 }

Push Data to an existing entry array

We also have the option to "push" a new entry into an existing entry array. We can do that by simply using $push atomic operator our query would then look like this db.<collection>.updateOne(<search parameters>, {$push: <object to push>}) or in an example

exampleDB> db.users.updateOne({_id: ObjectId("6551f821ded8ac9988ef867d")}, {$push: {email: 'admin@example.de'}})
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}

if we now search for the same entry we can see that the email got pushed into the array

exampleDB> db.users.findOne({_id: ObjectId("6551f821ded8ac9988ef867d")})
{
_id: ObjectId("6551f821ded8ac9988ef867d"),
name: 'Sally',
age: 19,
email: [ 'sally@example.de', 'info@example.de', 'admin@example.de' ],
username: 'SallyExampleCorp'
}

Pull Data from an existing entry array

The same we can push data to an array we can also remove it or "pull" it again by using the $pullatomic operator. Let's try to remove the same email address we added before.

exampleDB> db.users.updateOne({_id: ObjectId("6551f821ded8ac9988ef867d")}, {$pull: {email: 'admin@example.de'}})
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}

and now we can search for the same entry again and we will see that there will no longer be the email "admin@example.de"

exampleDB> db.users.findOne({ _id: ObjectId("6551f821ded8ac9988ef867d") })
{
_id: ObjectId("6551f821ded8ac9988ef867d"),
name: 'Sally',
age: 19,
email: [ 'sally@example.de', 'info@example.de' ],
username: 'SallyExampleCorp'
}

now there is something were you can mix very much around with because all the atomic operators and options from the .find()command work perfectly with update one.

Updating multiple Users

With updating we are not bound to updating only one user at a time, we can also update many users at the same time with the .updateMany(<search parameters>, <update Parameters>) command. Here in the example we are going to give all users an address parameter.

exampleDB> db.users.updateMany({ name: { $exists: true }}, { $set: { address: { street: "main str", postal: 1234  }}})
{
acknowledged: true,
insertedId: null,
matchedCount: 6,
modifiedCount: 6,
upsertedCount: 0
}
exampleDB> db.users.find()
[
{
_id: ObjectId("6551e911ded8ac9988ef867c"),
name: 'John',
address: { street: 'main str', postal: 1234 }
},
{
_id: ObjectId("6551f821ded8ac9988ef867d"),
name: 'Sally',
age: 19,
email: [ 'sally@example.de', 'info@example.de' ],
username: 'SallyExampleCorp',
address: { street: 'main str', postal: 1234 }
},
{
_id: ObjectId("6551fbedded8ac9988ef867e"),
name: 'Trinity',
age: 25,
balance: [ 100 ],
address: { street: 'main str', postal: 1234 }
},
{
_id: ObjectId("6551fbedded8ac9988ef867f"),
name: 'someone Mysterious',
age: 45,
address: { street: 'main str', postal: 1234 }
},
{
_id: ObjectId("655347dfded8ac9988ef8680"),
name: 'Justin',
age: 25,
balance: 100,
debt: 200,
address: { street: 'main str', postal: 1234 }
},
{
_id: ObjectId("655347dfded8ac9988ef8681"),
name: 'Benny',
age: 45,
balance: 100,
debt: 0,
address: { street: 'main str', postal: 1234 }
}
]

as you can see, every of our entries now has an address parameter with the according object with a street and postal parameter.

Replacing complete entries with new data

The upcoming command is not directly an update but more likely an replacement of existing data. With the command .replaceOne(<search parameters>, <replace parameter>) with a simple query we can replace the complete structure of one entry. This is the entry we are going to change:

{
_id: ObjectId("655347dfded8ac9988ef8680"),
name: 'Justin',
age: 25,
balance: 100,
debt: 200,
address: { street: 'main str', postal: 1234 }
}

now we are going to use the replace command to create a new structure for the ObjectId above.

exampleDB> db.users.replaceOne({_id: ObjectId("655347dfded8ac9988ef8680")}, { firstName: "Stranger", lastName: "Gum"})
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}

now when we search for the ObjectId again we will find our changed structure with the "firstName" and the "lastName".

exampleDB> db.users.find({_id: ObjectId("655347dfded8ac9988ef8680")})
[
{
_id: ObjectId("655347dfded8ac9988ef8680"),
firstName: 'Stranger',
lastName: 'Gum'
}
]

as we can see using the replace command is very simple but can also be Dangerous!

Deleting Data entries

To delete Data entires we can simply use the .deleteOne(<search parameters>) command. The delete command takes the same parameters as the search command and then deletes the first entry that comes back.

exampleDB> db.users.deleteOne({ _id: ObjectId("6551f821ded8ac9988ef867d")})
{ acknowledged: true, deletedCount: 1 }