Tiny DB ASCII banner
import 'package:tiny_db/tiny_db.dart';

final db = TinyDb(JsonStorage('path/to/db.json'));
final table = db.table('users');

await table.insert({'name': 'John', 'age': 22});
final results = await table.search(where('name').equals('John'));

print(results); // [{name: John, age: 22}]

📦 Tiny DB ⚡

Ultra-lightweight, embeddable NoSQL database for Dart & Flutter

Pub Star on Github License: MIT Flutter Website Dart Version Flutter Version Platform Support Codecov Open Issues Pull Requests Contributors Last Commit


Tiny DB

Overview

Tiny DB is a modern, ultra-lightweight NoSQL database for Dart and Flutter, inspired by the beloved Python TinyDB. With over 80% feature parity and several unique enhancements, it brings the power and flexibility of document-oriented storage to your Dart and Flutter apps—no server required!

  • 🚀 Feature-rich: Supports advanced queries, update operations, and deep equality for robust data handling.
  • 🧠 In-memory & JSON file storage: Choose blazing-fast ephemeral memory mode or persistent, human-readable JSON file storage—switch at any time.
  • 🔄 Familiar, expressive API: Inspired by Python TinyDB, but fully Dart-idiomatic and enhanced for Dart and Flutter developers.
  • 🎯 Embeddable & portable: Works everywhere Dart or Flutter runs. In-memory mode is pure Dart (no native code). JSON file storage for Flutter apps uses the standard path_provider plugin for safe device storage.

Whether you need a simple embedded database for prototyping, testing, or production apps, Tiny DB offers a clean, intuitive, and powerful solution.

Wondering how Tiny DB compares to SharedPreferences, Isar, Hive, or SQLite? See our detailed comparison.

Table of Contents



Features

  • Document-oriented, schema-free data storage
  • Powerful query language (where, logical operators, deep matching)
  • Advanced update operations: push, pull, pop, addUnique (deep equality)
  • Multiple storage backends: In-memory (pure Dart), JSON file (pretty-print, dirs)
  • Batch insert, upsert, and multi-table support
  • Defensive copying for safe list/mutation operations
  • Portable: Dart & Flutter (mobile, desktop, server, CLI)
  • 200+ automated tests for reliability


Installation

Add to your project:

flutter pub add tiny_db

Or add to your pubspec.yaml:

dependencies:
  tiny_db: ^0.9

Then run:

flutter pub get


Quick Start

// Always remember to properly initialize and close your database
import 'package:tiny_db/tiny_db.dart';

Future<void> main() async {
  // Create the database
  final db = TinyDb(MemoryStorage());
  
  try {
    // Use the database
    await db.insert({'name': 'John', 'age': 30});
    final results = await db.search(where('name').equals('John'));
    print(results);
  } finally {
    // Always close the database when done
    await db.close();
  }
}

Important: Always call db.close() when you're done with the database to properly release resources, especially when using JsonStorage.



API Overview

Core Classes

  • TinyDb
    Main database entry point.

    • TinyDb(Storage backend)
    • table(String name) → Table
    • defaultTable
    • close() - Important: Always call this when done with the database
    • truncate(), tables(), all(), length, isEmpty, isNotEmpty, etc.
    • Note: truncate() only affects the default table. To clear other tables, call truncate() on the respective Table instance, or use dropTables() to remove all tables.
  • Table
    Represents a collection of documents.

    • insert(Map doc)
    • insertMultiple(List<Map> docs)
    • upsert(Map doc, QueryCondition condition)
    • update(UpdateOperations ops, QueryCondition condition)
    • search(QueryCondition condition)
    • get(QueryCondition condition)
    • getById(DocumentId id)
    • remove(QueryCondition condition)
    • all(), length, truncate(), containsId(id)
  • Query & QueryCondition
    Build expressive queries.

    • where('field').equals(value)
    • Logical operators: .and(), .or(), .not()
    • List/collection queries: .anyInList([...]), .allInList([...]), etc.
  • UpdateOperations
    Chainable update helpers for document mutation:

    • .push(field, value)
    • .pull(field, value)
    • .pop(field)
    • .addUnique(field, value)
    • .set(field, value)
    • .delete(field)
    • .increment(field, amount), .decrement(field, amount)
  • Storage Backends

    • MemoryStorage() (pure Dart, in-memory)
    • JsonStorage(path, {indentAmount, createDirs}) (persistent, file-based)

Example

final db = TinyDb(MemoryStorage());
final users = db.table('users');

// Insert
await users.insert({'name': 'Alice', 'age': 30});

// Query
final result = await users.search(where('name').equals('Alice'));

// Update
await users.update(
  UpdateOperations().increment('age', 1),
  where('name').equals('Alice'),
);

// Remove
await users.remove(where('age').equals(31));

Notes

  • All operations are async (Future-based).
  • Data is always deeply copied for safety.
  • Table names are strings; documents are Map<String, dynamic>.
  • Querying and update APIs are chainable and composable.
  • Resource Management: Always call db.close() when done with the database to properly release resources.

Advanced Usage

Below are a few advanced patterns and real-world use cases. For a comprehensive set of examples, see More Examples.

Multi-Table Usage

final db = TinyDb(MemoryStorage());
final users = db.table('users');
final products = db.table('products');

await users.insert({'username': 'alice', 'active': true});
await products.insert({'name': 'Widget', 'price': 9.99});

Batch Insert & Upsert

await users.insertMultiple([
  {'username': 'bob', 'active': false},
  {'username': 'charlie', 'active': true},
]);

await users.upsert(
  {'username': 'alice', 'active': false},
  where('username').equals('alice'),
);

Complex Queries

final results = await users.search(
  where('active').equals(true).and(where('username').anyInList(['alice', 'charlie']))
);

Advanced Update Operations

await users.update(
  UpdateOperations()
    .push('tags', 'newbie')
    .addUnique('roles', 'admin')
    .increment('loginCount', 1),
  where('username').equals('alice'),
);

Model Serialization

class Product {
  // ... fields ...

  factory Product.fromJson(Map<String, dynamic> json) => /* ... */;
  Map<String, dynamic> toJson() => /* ... */;
}

// Store
await products.insert(product.toJson());

// Retrieve
final docs = await products.search(where('price').greaterThan(5));
final productList = docs.map(Product.fromJson).toList();

See doc/examples.md for a full list of advanced and edge-case examples.



List & Update Operations

Tiny DB provides robust list mutation and update operations, all with deep equality and defensive copying for safe, predictable behavior.

  • addUnique(field, value): Add to a list only if the value (by deep equality) isn't already present.
  • push(field, value): Append to a list.
  • pull(field, value): Remove all occurrences of a value from a list (deep equality).
  • pop(field): Remove the last element from a list.

Deep equality means:

  • Lists: Equal if all elements are deeply equal.
  • Maps: Equal if all keys and values are deeply equal.
  • Primitives: Standard ==.

Defensive copying ensures all list operations create a deep copy before mutation, preventing accidental reference bugs.

Example

// Add unique value to a list
await table.update(UpdateOperations().addUnique('tags', 'flutter'), where('name').equals('Alice'));

// Remove all occurrences of a value
await table.update(UpdateOperations().pull('tags', 'old'), where('name').equals('Alice'));

For more details and advanced examples, see
doc/deep_equality_and_add_unique.md



Storage Backends

Tiny DB offers two storage backends to suit different needs:

MemoryStorage

final db = TinyDb(MemoryStorage());
  • Pure Dart: No native dependencies, works on all platforms
  • In-memory only: Data is lost when the app restarts
  • Fast: All operations happen in memory
  • Great for: Testing, prototyping, temporary caches, and ephemeral data

JsonStorage

final db = TinyDb(JsonStorage('path/to/db.json', 
  indentAmount: 2,  // Pretty-print with 2-space indentation
  createDirs: true, // Create parent directories if they don't exist
));
  • Persistent: Data is saved to a JSON file
  • Human-readable: JSON format is easy to inspect and edit
  • Flutter dependency: Uses path_provider plugin for safe file access on mobile
  • Configuration options:
    • indentAmount: Controls JSON formatting (null for compact output)
    • createDirs: Automatically creates parent directories

JSON File Structure

The JSON storage format organizes data by tables, with document IDs as keys:

{
  "_default": {                     // Default table
    "1": {                         // Document ID 1
      "type": "note",
      "title": "Shopping List",
      "items": ["Milk", "Eggs", "Bread"]
    },
    "2": { ... }                   // Document ID 2
  },
  "settings": {                    // Named table "settings"
    "1": {
      "theme": "dark",
      "notifications": true,
      "preferences": {
        "fontSize": 14,
        "language": "en",
        "autoSave": true
      }
    }
  },
  "profiles": {                    // Named table "profiles"
    "1": {
      "username": "alice",
      "email": "alice@example.com",
      "tags": ["admin", "verified", "active"]
    },
    "2": { ... }                   // Another document
  }
}

See json_storage_example.dart for a complete example.

Platform Considerations

  • Flutter apps: Use path_provider to get the correct app storage directory:

    import 'package:path_provider/path_provider.dart';
      
    Future<void> main() async {
      final appDir = await getApplicationDocumentsDirectory();
      final dbPath = '${appDir.path}/my_db.json';
      final db = TinyDb(JsonStorage(dbPath));
      // ...
    }
    
  • Pure Dart (CLI, server): Use direct file paths:

    final db = TinyDb(JsonStorage('data/my_db.json', createDirs: true));
    


Dependency Injection & App Integration

Here are some approaches to integrate Tiny DB into your application architecture:

Simple Global Instance

For smaller apps or prototypes, a global instance can be simple and effective:

// db_provider.dart
import 'package:tiny_db/tiny_db.dart';
import 'package:path_provider/path_provider.dart';

class DbProvider {
  static TinyDb? _instance;
  
  static Future<TinyDb> get instance async {
    if (_instance == null) {
      final appDir = await getApplicationDocumentsDirectory();
      final dbPath = '${appDir.path}/app_database.json';
      _instance = TinyDb(JsonStorage(dbPath, createDirs: true));
    }
    return _instance!;
  }
}

Then in your main.dart file, initialize it early:

// main.dart
import 'package:flutter/material.dart';
import 'db_provider.dart';

Future<void> main() async {
  // Ensure Flutter is initialized
  WidgetsFlutterBinding.ensureInitialized();
  
  // Initialize the database early
  final db = await DbProvider.instance;
  
  // Now you can run your app
  runApp(MyApp(db: db));
}

class MyApp extends StatelessWidget {
  final TinyDb db;
  
  const MyApp({super.key, required this.db});
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'TinyDB Demo',
      home: HomeScreen(db: db),
    );
  }
}

// Usage in any screen or service:
class HomeScreen extends StatelessWidget {
  final TinyDb db;
  
  const HomeScreen({super.key, required this.db});
  
  Future<void> _addUser() async {
    final users = db.table('users');
    await users.insert({'name': 'Alice', 'joined': DateTime.now().toIso8601String()});
  }
  
  // Rest of your widget...
}

Using get_it (Service Locator)

For more structured dependency injection, get_it is a popular choice:

// service_locator.dart
import 'package:tiny_db/tiny_db.dart';
import 'package:get_it/get_it.dart';
import 'package:path_provider/path_provider.dart';

final getIt = GetIt.instance;

Future<void> setupServices() async {
  // Register as a lazy singleton
  getIt.registerLazySingletonAsync<TinyDb>(() async {
    final appDir = await getApplicationDocumentsDirectory();
    final dbPath = '${appDir.path}/app_database.json';
    return TinyDb(JsonStorage(dbPath, createDirs: true));
  });
  
  // Initialize the database
  await getIt.isReady<TinyDb>();
}

// In main.dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await setupServices();
  runApp(MyApp());
}

// Usage anywhere in your app
final db = getIt<TinyDb>();
final products = db.table('products');

With Provider or Riverpod

For Flutter apps using Provider or Riverpod:

// With Provider
final dbProvider = Provider<TinyDb>((ref) {
  final db = TinyDb(MemoryStorage()); // Or JsonStorage
  ref.onDispose(() => db.close());
  return db;
});

// In a widget
final db = ref.watch(dbProvider);

Closing the Database

IMPORTANT: Always close the database when you're done with it, regardless of how it was created. This ensures proper resource cleanup and data integrity.

// For global instances
Future<void> closeDatabase() async {
  final db = await DbProvider.instance;
  await db.close();
}

// With get_it, in your app's dispose method
getIt<TinyDb>().close();

// In a stateful widget
@override
void dispose() {
  db.close();
  super.dispose();
}

// With direct usage, use try/finally
Future<void> someFunction() async {
  final db = TinyDb(MemoryStorage());
  try {
    // Use the database
  } finally {
    await db.close(); // Always called, even if an exception occurs
  }
}

Failure to close the database properly may result in resource leaks or data integrity issues, especially with JsonStorage.



Testing

Tiny DB includes comprehensive tests to help ensure reliability and correctness.

Running the Tests

To run the full test suite:

flutter test

The package includes over 200 automated tests covering core functionality, edge cases, and defensive copying behavior.

Note about test warnings: When running tests, you may see warnings like Cannot increment field "name" in document. Value is "Alice". Not a number.... These are expected and intentional - they're part of tests that verify the library correctly handles invalid operations (like trying to increment a string) by warning rather than crashing.

Common Testing Patterns

When writing tests for your app that uses Tiny DB:

// 1. Always use MemoryStorage for tests
setUp(() {
  db = TinyDb(MemoryStorage());
});

// 2. Always clean up after tests
tearDown(() async {
  await db.close();
});

// 3. Test document equality with deep comparisons
test('document equality', () async {
  final doc = {'nested': {'list': [1, 2, {'key': 'value'}]}};
  final id = await db.insert(doc);
  final retrieved = await db.getById(id);
  
  // Remove doc_id for comparison
  retrieved?.remove('doc_id');
  expect(retrieved, equals(doc)); // Deep equality works automatically
});


Contributing

Contributions to Tiny DB are welcome and appreciated! This project aims to maintain a high standard of code quality and test coverage.

Pull Request Guidelines

When submitting a PR, please ensure:

  1. Test Coverage: All new features or bug fixes include appropriate tests
  2. Documentation: Update relevant documentation for any changes
  3. Code Style: Follow the existing code style and Dart conventions
  4. Focused Changes: Keep PRs focused on a single issue/feature

Writing Tests

Tiny DB uses Dart's built-in testing framework. Here's a simple example of how to write a test:

import 'package:flutter_test/flutter_test.dart';
import 'package:tiny_db/tiny_db.dart';

void main() {
  late TinyDb db;
  
  setUp(() {
    // Use MemoryStorage for tests to avoid file system operations
    db = TinyDb(MemoryStorage());
  });
  
  tearDown(() async {
    // Always clean up after tests
    await db.close();
  });
  
  test('insert and retrieve document', () async {
    // Arrange
    final doc = {'name': 'Test', 'value': 42};
    
    // Act
    final id = await db.insert(doc);
    final result = await db.getById(id);
    
    // Assert
    expect(result?['name'], equals('Test'));
    expect(result?['value'], equals(42));
  });
  
  test('update operations work correctly', () async {
    // Arrange
    final id = await db.insert({'tags': ['a', 'b']});
    
    // Act
    await db.update(
      UpdateOperations().addUnique('tags', 'c'),
      where('doc_id').equals(id)
    );
    final result = await db.getById(id);
    
    // Assert
    expect(result?['tags'], containsAll(['a', 'b', 'c']));
  });
}

Edge Case Testing

When fixing bugs or adding features, consider these edge cases:

  • Empty collections/documents
  • Null values and optional fields
  • Deep nesting of objects and arrays
  • Concurrent operations (if applicable)
  • Resource cleanup (especially with JsonStorage)

Thank you for contributing to Tiny DB!


Roadmap

Features under consideration for future releases:

Plugin Support

  • Index plugins for faster queries on large datasets
  • Custom storage backends
  • Schema validation plugins

Performance Optimizations

  • Batch operations for JsonStorage
  • Streaming query results for large datasets

Other Considerations

Some features like query caching and middleware were considered but may not be implemented due to architectural decisions favoring simplicity and resource management. The current design emphasizes proper database closing and clean resource management over persistent middleware chains.



Credits

This package is heavily inspired by the outstanding TinyDB project for Python, created by Markus Unterwaditzer and contributors. Many thanks to the TinyDB community for their elegant design and documentation.

License

Tiny DB is available under the MIT License. See the LICENSE file for more information.



Comparisons vs SharedPreferences, Isar, Hive, SQLite

Wondering how Tiny DB compares to other storage solutions?

We've created a detailed comparison with SharedPreferences, Isar, Hive, and SQLite to help you choose the right tool for your needs.

Read the full comparison here

Tiny DB positions itself as the "just right" option between simple key-value stores and full-featured databases - powerful enough for real applications but simple enough to learn in minutes.

Libraries

tiny_db