TinyDB is a Python implementation of a NoSQL, document-oriented database. Unlike a traditional relational database, which stores records across multiple linked tables, a document-oriented database stores its information as separate documents in a key-value structure. The keys are similar to the field headings, or attributes, in a relational database table, while the values are similar to the table’s attribute values.
TinyDB uses the familiar Python dictionary for its document structure and stores its documents in a JSON file.
TinyDB is written in Python, making it easily extensible and customizable, with no external dependencies or server setup needed. Despite its small footprint, it still fully supports the familiar database CRUD features of creating, reading, updating, and deleting documents using an API that’s logical to use.
The table below will help you decide whether TinyDB is a good fit for your use case:
| Use Case | TinyDB | Possible Alternatives |
|---|---|---|
| Local, small dataset, single-process use (scripts, CLIs, prototypes) | ✅ | simpleJDB, Python’s json module, SQLite |
| Local use that requires SQL, constraints, joins, or stronger durability | — | SQLite, PostgreSQL |
| Multi-user, multi-process, distributed, or production-scale systems | — | PostgreSQL, MySQL, MongoDB |
Whether you’re looking to use a small NoSQL database in one of your projects or you’re just curious how a lightweight database like TinyDB works, this tutorial is for you. By the end, you’ll have a clear sense of when TinyDB shines, and when it’s better to reach for something else.
Get Your Code: Click here to download the free sample code you’ll use in this tutorial to explore TinyDB.
Take the Quiz: Test your knowledge with our interactive “TinyDB: A Lightweight JSON Database for Small Projects” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
TinyDB: A Lightweight JSON Database for Small ProjectsIf you're looking for a JSON document-oriented database that requires no configuration for your Python project, TinyDB could be what you need.
Get Ready to Explore TinyDB
TinyDB is a standalone library, meaning it doesn’t rely on any other libraries to work. You’ll need to install it, though.
You’ll also use the pprint module to format dictionary documents for easier reading, and Python’s csv module to work with CSV files. You don’t need to install either of these because they’re included in Python’s standard library.
So to follow along, you only need to install the TinyDB library in your environment. First, create and activate a virtual environment, then install the library using pip:
(venv) $ python -m pip install tinydb
Alternatively, you could set up a small pyproject.toml file and manage your dependencies using uv.
When you add documents to your database, you often do so manually by creating Python dictionaries. In this tutorial, you’ll do this, and also learn how to work with documents already stored in a JSON file. You’ll even learn how to add documents from data stored in a CSV file.
These files will be highlighted as needed and are available in this tutorial’s downloads. You might want to download them to your program folder before you start to keep them handy:
Get Your Code: Click here to download the free sample code you’ll use in this tutorial to explore TinyDB.
Regardless of the files you use or the documents you create manually, they all rely on the same world population data. Each document will contain up to six fields, which become the dictionary keys used when the associated values are added to your database:
| Field | Description |
|---|---|
continent |
The continent the country belongs to |
location |
Country |
date |
Date population count made |
% of world |
Percentage of the world’s population |
population |
Population |
source |
Source of population |
As mentioned earlier, the four primary database operations are Create, Read, Update, and Delete—collectively known as the CRUD operations. In the next section, you’ll learn how you can perform each of them.
To begin with, you’ll explore the C in CRUD. It’s time to get creative.
Create Your Database and Documents
The first thing you’ll do is create a new database and add some documents to it. To do this, you create a TinyDB() object that includes the name of a JSON file to store your data. Any documents you add to the database are then saved in that file.
Documents in TinyDB are stored in tables. Although it’s not necessary to create a table manually, doing so can help you organize your documents, especially when working with multiple tables.
To start, you create a script named create_db.py that initializes your first database and adds documents in several different ways. The first part of your script looks like this:
create_db.py
1from csv import DictReader
2
3from tinydb import TinyDB
4
5with TinyDB("countries.json", indent=4) as countries_db:
6 countries_table = countries_db.table(name="countries")
You import DictReader from Python’s built-in csv module. This allows you to read a CSV file and parse its header row and data rows into a Python dictionary. You also import the TinyDB class to enable you to create a database.
Then you create a database and a table. To create the database, you make a TinyDB instance in line 5 of your code. To do this, you provide the name of the file, countries.json, where the database will store its documents, and an optional indent parameter of 4. When you save your data, it’s stored as JSON. By passing indent=4, the JSON file will be indented within the file, making it more readable.
If you’re curious to see the difference the indent parameter makes to your JSON file, first look inside countries.json, then delete it to avoid adding the same documents a second time. Finally, run your code without indent. In the updated file, you’ll see that it’s less readable.
You’ll also notice that unique document identifiers have been added for each document. You’ll learn more about these later.
In line 5, you create the database and reference it using the countries_db variable. You do this within a context manager. This means your database remains open while the indented code below the with keyword runs and then automatically closes afterward. Had you not used the context manager, you’d need to call countries_db.close() to close the database manually.
You then create the table in line 6 of your code using the .table() method of your database. In this example, you’ve chosen countries as the table’s name and countries_table as its reference variable. At this point, you have your database with one empty table.
Note: In this example, you create your own table. This isn’t strictly necessary. If you don’t create a table, then one named _default will be created for you.
It’s considered good practice to give your table a sensible name for code readability. Also, if you’ve got multiple tables in your database, having one of them named _default will look out of place beside the others.
Next, you’ll investigate three ways to add documents. First up is how to add a single document:
create_db.py
8 countries_table.insert(
9 {"location": "Vatican City", "population": 501}
10 )
Starting at line 8, you use the .insert() method to insert a single document into countries_table. Documents are formed from Python dictionaries. In this case, you create a document containing two fields: location and population, along with their associated values. Now, suppose you want to add multiple documents at once:
create_db.py
12 countries_table.insert_multiple(
13 [
14 {"location": "India", "population": 1_417_492_000},
15 {"location": "China", "population": 1_408_280_000},
16 ]
17 )
You can add multiple documents by passing a Python list of them to your table’s .insert_multiple() method. In this case, your list contains two dictionaries, each representing a separate document. As before, each document contains both location and population information.
Sometimes, you may need to add a larger number of documents. For example, you might want to create documents from a file of data extracted from another system. While this feature isn’t supported natively by TinyDB, you can use your existing Python skills to read this file into your database.
In this example, you add the three documents stored in your countries_file.csv file:
countries_file.csv
location,population,continent
Argentina,45929925,South America
Switzerland,8990428,Europe
Mozambique,36147236,Africa
The countries_file.csv file’s first row defines the location, population, and continent fields, which will form the dictionary keys, while the three additional rows define the values. Notice that the continent field wasn’t included in the previous documents you added. This is fine because documents in a document-oriented database don’t all need to have the same fields.
To add these documents to your database, you convert them into Python dictionary format and insert them as before. You continue your script as follows:
create_db.py
19 with open("countries_file.csv", "r") as csv_source:
20 reader = DictReader(csv_source)
21
22 for row in reader:
23 row["population"] = int(row["population"])
24 countries_table.insert(row)
In line 19, you use another context manager, this time to open countries_file.csv for reading. You then pass the file, which you refer to as csv_source, to a new DictReader() object named reader. The DictReader() parses the information in each row of your countries_file.csv file into a series of Python dictionaries, with keys from the first row and values from the remaining rows.
Your DictReader(), in line 20, is an iterable, meaning that it can be used in a for loop. You use the for loop in line 22 to read each of the dictionaries from your DictReader(), and then pass them into your database table using the .insert() method as before. Just before you do so, you cast the population values to integers since DictReader reads them as strings.
With your database creation script now complete, all you need to do to create your database is run the script:
(venv) $ python create_db.py
If you want to quickly check that all of the added documents are present in your database, along with their ID values, feel free to open the countries.json file and take a look. There should be six documents stored in it.
Note: In this tutorial, the documents in your database are permanently stored in a JSON file, while the database itself exists only in memory. It’s also possible to create and work with documents entirely in-memory instead.
To do this, you again import TinyDB, but also add from tinydb.storages import MemoryStorage. This allows you to create an in-memory database using something like countries_db = TinyDB(storage=MemoryStorage).
Notice that you don’t specify a file, since nothing is saved to disk.
Now that you know how to create databases and add documents, it’s time to move on to the R in CRUD and learn how to read documents from your database.
Read Documents From Your Database
Once your documents are safely stored in the database, you’ll likely want to read some or all of them. To do this, you construct a query. As you’ll see in this section, you can query your database in several different ways.
You’ll use the ten_countries.json file available in your downloadables. It contains population details for the ten countries with the largest populations at the time of writing. Each document includes five of the fields explained in the table at the start of this tutorial. If you’d like to open the file in your favorite JSON or text editor, you’ll see its content.
To search for specific documents in your database, you need both a Query instance to define the information you need and a reference to the Table instance to search.
To allow you to write various ad hoc queries, you decide to use the Python REPL as follows:
>>> from pprint import pprint
>>> from tinydb import Query, TinyDB
>>> countries_db = TinyDB("ten_countries.json")
>>> countries_table = countries_db.table(name="countries")
Your first step is to set up a database with the existing documents from ten_countries.json. You import TinyDB, but this time also the Query class. You’ll use this to define what you’re looking for. You also import pprint to format query results.
You then create a database that contains the existing documents. To do this, you use the same syntax you used previously when you created a new database, but this time you reference an existing file. This opens the database and populates it with existing data.
This time, because you’ve opened the database outside of a context manager, you’ll need to remember to close it manually after you’ve finished with it. You again create a reference to an existing table using the same approach as before. In this example, you use countries_db.table() and specify the existing countries table name.
Next, you write a query to view some documents. Suppose you wanted to see the details of those documents with population field values between 220 million and 250 million:
>>> query = Query()
>>> query_def = (query.population > 220_000_000) & (
... query.population < 250_000_000
... )
You call the Query() constructor and assign the new object to the query variable. Then, you use it to construct a query definition that specifies the information you want to extract from your database. The object that query references contains a set of instance attributes that mirror the fields in the table you’re querying, or more precisely, match the keys of the dictionary it’ll query.
Since you want to see documents with population values between 220 million and 250 million, you define the query as (query.population > 220_000_000) & (query.population < 250_000_000). In this case, you’ve used the .population attribute to query against this field. To make sure both clauses are included, you use the bitwise & operator, as well as the > and < comparison operators.
With the query defined, run it to see the documents. To do this, you pass your query definition, query_def, into the table’s .search() method. This will run your query. To display the results neatly, you use the pprint() function:
>>> pprint(countries_table.search(query_def))
[{'% of world': 2.9,
'date': '1 Mar 2023',
'location': 'Pakistan',
'population': 241499431,
'source': '2023 Census result'},
{'% of world': 2.7,
'date': '1 Jul 2023',
'location': 'Nigeria',
'population': 223800000,
'source': 'Official projection'}]
As you can see, two documents match your criteria and are neatly displayed.
Note: In addition to the bitwise & operator used in the previous example, you can also use Python’s other bitwise operators such as OR (|) and NOT (~) in your queries. So, had you used countries_table.search(~query_def) in your previous example, you’d have seen the other eight documents that don’t match your original query. You can use Python’s comparison operators as well.
Now, suppose you want to see documents whose share of the global population is at least 17%. You might try to construct another query definition based on the % of world field. However, % of world can’t form an instance attribute of your query because it’s an invalid variable name. This would only produce a syntax error instead of the results you want.
Fortunately, there’s another way. You can use dictionary notation to reference a field instead:
>>> pprint(countries_table.search(query["% of world"] >= 17))
[{'% of world': 17.3,
'date': '1 Jul 2025',
'location': 'India',
'population': 1417492000,
'source': 'Official projection'},
{'% of world': 17.2,
'date': '31 Dec 2024',
'location': 'China',
'population': 1408280000,
'source': 'Official estimate'}]
This time, you use Python dictionary notation ([]) to reference a field in your query. The pprint() function prints the search results on screen.
Note: If you’re familiar with SQL, then you’ll know that its WHERE clause is used to filter values that meet certain criteria. Later, you’ll see how a where() function can be used to mimic SQL behavior. For example, the previous query could have been written as countries_table.search(where('% of world') >= 17).
You’ve already seen that each document in a TinyDB database gets assigned a unique identifier within its table. The first document added is assigned ID 1, the second 2, and so on. To read a document from a table by its identifier value, you can use the table’s .get() method.
Here, you decide to look at the documents whose IDs are 9 and 10, so you pass these values as a Python list to .get() using its doc_ids parameter. Had you wanted to see a single document, you’d have passed its ID using the singularly named doc_id parameter:
>>> pprint(countries_table.get(doc_ids=[9, 10]))
[{'% of world': 1.8,
'date': '1 Jan 2025',
'location': 'Russia',
'population': 146028325,
'source': '???'},
{'% of world': 1.6,
'date': '30 Jun 2025',
'location': 'Mexico',
'population': 0,
'source': '???'}]
As you can see, two documents have been returned. If you look carefully, you’ll notice something wrong with the source fields in each, and Mexico appears to be devoid of people. Don’t worry, you’ll fix these in the next section.
You’ve finished with this database, so to ensure all documents are safely saved, you must close it. You need to close the database manually because you didn’t use a context manager when you opened it:
>>> countries_db.close()
Now that you know how to find documents, next you’ll learn about the U in CRUD. It’s time to learn how to update documents.
Update Documents in Your Database
Sometimes, the documents in your database become outdated or contain errors. To fix this, you need to update them. In this section, you’ll use the .update() and .update_multiple() methods on your Table to make changes to some existing documents.
As you’ve seen, it seems no one lives in Mexico, and the source of this information is unclear. After conducting some research, you find that the most recent national quarterly estimate reports that Mexico has a population of 130,575,786. You decide to update your database to reflect this corrected information. Because you have multiple changes to make, you again decide to create a script.
As before, you create the references to the existing database table:
update_db.py
1from tinydb import TinyDB, where
2
3with TinyDB("ten_countries.json") as countries_db:
4 countries_table = countries_db.table(name="countries")
To begin, you import the libraries you’ll need, this time including the where() function. You’ll use where() to find the documents you want to update and, again, to verify that your updates have worked. Because you choose to use where(), there’s no need to import Query.
As before, you open the ten_countries.json file as a TinyDB instance and set the countries_table variable to reference the countries table.
Now that you can access the database, you can update it:
update_db.py
6 countries_table.update(
7 {"population": 130_575_786}, where("location") == "Mexico"
8 )
9
10 countries_table.update(
11 {"source": "National quarterly update"},
12 where("location") == "Mexico",
13 )
To update the Mexico document, you pass two arguments to the .update() method of countries_table. First, you pass a dictionary representing the part of the document you want to update, and second, you pass where("location") == "Mexico" to reference the document to update.
You then use the same technique to update the source field of the same document. To apply the updates, run the script:
(venv) $ python update_db.py