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.



