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 6 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 6, 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 7 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
Finally, to make sure the updates have worked as you expect, you run a quick query in the REPL:
>>> from pprint import pprint
>>> from tinydb import TinyDB, where
>>> with TinyDB("ten_countries.json") as countries_db:
... countries_table = countries_db.table(name="countries")
... pprint(countries_table.search(where("location") == "Mexico"))
[{'% of world': 1.6,
'date': '30 Jun 2025',
'location': 'Mexico',
'population': 130575786,
'source': 'National quarterly update'}]
As you can see, the updates were successful!
Earlier, you saw how .insert_multiple() allows you to perform multiple inserts. You can update multiple documents at the same time with .update_multiple(). The previous code could also have been written like this:
update_db_v2.py
from tinydb import TinyDB, where
with TinyDB("ten_countries.json") as countries_db:
countries_table = countries_db.table(name="countries")
countries_table.update_multiple(
[
(
{"population": 130_575_786},
where("location") == "Mexico",
),
(
{"source": "National quarterly update"},
where("location") == "Mexico",
),
]
)
This time, instead of passing in the dictionary and document information into .update() twice, you pass them in as a list of Python tuples to .update_multiple(). This code applies the same updates as before.
In the previous example, only the Mexico document was updated. This is because there was only one document with that location value. Had there been more, you’d have updated them as well.
Suppose you now want to update a single document, but one that can’t be uniquely identified by a field. The solution is to update specific documents based on their document ID values.
If you look at the ten_countries.json file, you’ll see that the source fields of documents 7 and 9 contain no meaningful information. Perhaps you want to update those fields to show that the populations are official estimates. The code below shows you how:
update_db_v3.py
from tinydb import TinyDB
with TinyDB("ten_countries.json") as countries_db:
countries_table = countries_db.table(name="countries")
countries_table.update({"source": "Official estimate"}, doc_ids=[7, 9])
To update documents by their unique document IDs, you again use .update(). As before, you pass in the update as a dictionary, but instead of identifying the document to be updated by a potentially duplicate field value, you use the doc_ids parameter and assign it a list of the document IDs you want to update.
As with the .get() method you used earlier, you can also use doc_id with .update() to update a single document. This is useful when documents can’t be uniquely identified by a field name.
Go ahead and run this code, then use the techniques you learned earlier to verify that the updates work as expected.
Note: You can also use upserting, which combines updating and inserting into a single operation. This allows you to insert a new document into your database. If that document already exists, the updates will be applied to it.
For example, table.upsert(dictionary, where("location") == "Japan") will take the contents of dictionary and either add its updates to an existing document whose location is Japan, or create a completely new document if one doesn’t already exist.
Finally, you’ll move on to the D in CRUD. It’s time to learn how to clear out unwanted crud by deleting documents and tables from your database.
Delete Documents From Your Database
There may be times when you no longer need some documents in your database. To keep file size small and search performance efficient, it’s a good idea to delete any obsolete documents. In this section, you’ll learn how to remove unwanted documents using the .remove() and .truncate() table methods. You’ll also learn how to remove entire tables using the database’s .drop_table() and .drop_tables() methods.
Suppose you decide that you no longer need the documents with ID values of 3, 5, and 7 stored in ten_countries.json.
To begin with, your database still contains all ten documents. You can verify this by passing a table reference to the Python len() function:
>>> from tinydb import TinyDB, where
>>> countries_db = TinyDB("ten_countries.json")
>>> countries_table = countries_db.table(name="countries")
>>> len(countries_table)
10
Since len(countries_table) returns 10, you’ve confirmed that ten documents are indeed present.
To remove documents, use the .remove() method of countries_table. You do this by passing a Python list of document IDs you want to remove to its doc_ids parameter:
>>> countries_table.remove(doc_ids=[3, 5, 7])
[3, 5, 7]
>>> len(countries_table)
7
You can see from the returned list [3, 5, 7] that these three documents have been removed, leaving seven remaining. Your second call to len() again confirms this.
Sometimes, you might want to remove a group of documents from your database based on their data. For example, suppose you want to remove documents with a population of fewer than 300 million. To do this, you once more use the .remove() method:
>>> countries_table.remove(where("population") < 300_000_000)
[4, 6, 8, 9, 10]
>>> len(countries_table)
2
To specify the documents to be removed, you decide to use the where() function to identify documents whose population field value meets a condition. You pass where("population") < 300_000_000 into the .remove() method of countries_table to remove them. Now only two documents remain.
Feel free to write a query to find out what they are, or take a look directly at the now poorly named ten_countries.json file to see what’s left.
Suppose you want to remove all documents from the countries table. You could do this by editing the ten_countries.json file directly, but it’s probably cleaner to do it using code:
>>> countries_table.truncate()
>>> len(countries_table)
0
To remove all documents from a table in your database, you call .truncate() on countries_table. This will remove all documents, but not the table itself. This means you can add more documents to it if you want. As you can see, the len() function tells you there are no documents left. Feel free to look inside your ten_countries.json file, and you’ll see what an empty table looks like.
To delete the table, whether it’s empty or not, you can use the .drop_table() method on your countries_db database and pass in the table name:
>>> countries_db.tables()
{'countries'}
>>> countries_db.drop_table(name="countries")
>>> countries_db.tables()
Before you drop a table, you decide to check to make sure you know its correct name. To do this, you call the .tables() method on your database. This returns a Python set containing the tables. In this case, there’s only one named countries.
To remove this table, you call .drop_table() on your database and pass it name="countries".
Finally, you check to see the updated list of tables. This time, an empty set object was returned, indicating the database is now empty. If you check the JSON file, and yes, it still exists, you’ll see a somewhat boring empty pair of curly braces. You can now delete it if you wish.
Note: If you need to drop several tables, you can do so by making multiple calls to .drop_table(). To drop all tables from your database at once, use .drop_tables().
Although TinyDB is an extremely useful tool, it’s important to be aware of its limitations to round off your learning experience.
Understand Limitations and Gotchas
Although TinyDB is lightweight and straightforward to use, its simplicity does come at a cost:
- Non-relational: It doesn’t provide full relational functionality or referential integrity, so if you want to link documents across multiple tables, you’ll need to write Python code to do so.
- Querying: TinyDB lacks projection capabilities. You’ve already seen a few of the techniques available to return entire documents. However, if you want to return partial documents—such as a list of values from a specific field—you need to write Python code to do this manually.
- Transactions: TinyDB doesn’t provide ACID guarantees because it’s not designed for use in distributed or multi-user environments. For example, it doesn’t support multithreading or concurrency, so it’s not suitable for multi-user web applications.
- Storage: Its default storage is JSON, so only JSON-serializable data types are supported. However, programmers can write extensions to support other storage formats such as YAML.
Also, make sure that the field names you use follow Python variable naming conventions to avoid limiting the options available to you when querying.
Conclusion
TinyDB provides a NoSQL, document-oriented database that supports the four CRUD operations found in larger database systems, but without their complex setup or configuration requirements. It’s written entirely in Python and requires no additional dependencies, which makes it easy to embed directly into your Python projects. With TinyDB, you can quickly persist documents in JSON format, query them, and update them through a clean, Pythonic API.
In this tutorial, you’ve learned how to:
- Create a database and tables for storing documents, and add documents to your database
- Write queries to view documents based on different criteria using multiple techniques
- Update single and multiple documents in your database
- Delete documents and tables from your database
- Understand the advantages and limitations of TinyDB
If TinyDB has grabbed your attention, then its official documentation will show you even more of its capabilities. Happy databasing!
Get Your Code: Click here to download the free sample code you’ll use in this tutorial to explore TinyDB.
Frequently Asked Questions
Now that you’ve gained some experience with TinyDB in Python, you can use the questions and answers below to check your understanding and recap what you’ve learned.
These FAQs are related to the most important concepts you’ve covered in this tutorial. Click the Show/Hide toggle beside each question to reveal the answer.
TinyDB works best for local, small-scale, single-process applications such as scripts, CLI tools, and prototypes that need simple data persistence without the overhead of setting up a full database server.
By default, TinyDB stores documents as dictionaries in a JSON file on disk. Each document is automatically assigned a unique document ID within its table.
No. TinyDB is schema-less, so documents in the same table can have different fields. Each document is simply a Python dictionary with its own set of key-value pairs.
The .insert() method adds a single document to a table, while .insert_multiple() accepts a list of documents and inserts them all at once.
Using a context manager ensures that the database is automatically closed when you’re done, which helps guarantee that all documents are safely saved. Without a context manager, you must call .close() manually to avoid potential data loss.
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.



