Library / SDK
teamtnt/tntsearch avatar
teamtnt/tntsearch

TNTSearch: A PHP Full-Text Search Engine That Returns IDs, Not Rows

A fully featured full text search engine written in PHP

3,199 stars295 forksPHPMIT

At a glance

What is it?
TNTSearch is a pure-PHP full-text search engine that builds an inverted index from your own database and answers queries with ranked document IDs. It is a good fit when you want relevance ranking without running a separate search server, and a poor fit when you expect it to store or return your documents.
Who is it for?
Adopt TNTSearch if you already run PHP and want relevance-ranked search inside your application without operating a separate search daemon, especially if you can live with the index being a file in a writable storage folder. Do not adopt it if you need it to return the documents themselves, if you expect to change the stemmer or tokenizer after indexing, or if you need rollback of an index in progress, since the README does not document one.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 41 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem TNTSearch solves, and who it is for

Most PHP applications reach the point where a LIKE '%term%' query stops being acceptable. It cannot rank results, it cannot match on word stems, and it degrades as the table grows. The usual answer is to run Elasticsearch or Meilisearch next to the application, which means another service to deploy, monitor, and back up.

TNTSearch takes the other path. It is a full-text search engine written entirely in PHP, and the README describes it as storing its index in SQLite by default, with MySQL and Redis as alternatives. There is no daemon to run. The index is a file, or a set of rows in a database you already have.

The library is aimed at PHP developers who want ranked search inside their own application and are willing to own the indexing step. The README is explicit about the boundary: it returns matching document IDs and scores, not the documents themselves, and you fetch the rows from your own database using those IDs. That single design decision shapes everything else about how you use it.

How the index is built and what search actually returns

The mechanism is a conventional inverted index. You point TNTSearch at a source database, run a query, and the indexer streams the result set and writes an index in batched transactions. The README describes the flow as three steps: index your data once, select that index, then search it.

Two ordering rules are easy to get wrong. You must call createIndex() before indexing and selectIndex() before searching, and the README states that searching a not-yet-selected index throws. The first column of the indexer query is treated as the primary key, named id by default, and it can be changed with setPrimaryKey().

Search semantics are worth reading twice. The README states that search() uses relevance and OR semantics, so a document matching one term can still rank, while searchBoolean() gives AND, OR and NOT logic. The return value is an array containing ids, hits, docScores and execution_time. The README's own example shows the shape:

php
$res = $tnt->search('romeo and juliet', 20);
// $res = ['ids' => [7, 3, 10, ...], 'hits' => 42, 'docScores' => [...], 'execution_time' => '3.1 ms']

Because only IDs come back, you re-query your own database and restore the order yourself. The README suggests ORDER BY FIELD(id, ...) for MySQL. That is an extra round trip, and it is the price of not duplicating your document store inside the index.

Installing TNTSearch and running a first search

Installation is a single Composer command. The README lists the requirements as PHP 7.4 or newer, PDO, pdo_sqlite for the default engine, and mbstring.

bash
composer require teamtnt/tntsearch

If you see PDOException: could not find driver, the README points at the pdo_sqlite extension not being enabled for the PHP SAPI running your code, and notes that CLI and web often differ. That is an environment issue rather than a library one.

The minimal working example from the README configures a source SQLite database and a writable storage folder, builds an index, selects it, and searches. Note that the storage directory is where the index is written, and it is a different thing from the source database.

php
use TeamTNT\TNTSearch\TNTSearch;

$tnt = new TNTSearch;
$tnt->loadConfig([
    'driver'   => 'sqlite',
    'database' => __DIR__ . '/app.sqlite',
    'storage'  => __DIR__ . '/storage/',
    'stemmer'  => \TeamTNT\TNTSearch\Stemmer\PorterStemmer::class,
]);

$indexer = $tnt->createIndex('articles.index');
$indexer->query('SELECT id, title, article FROM articles;');
$indexer->run();

$tnt->selectIndex('articles.index');
$res = $tnt->search('romeo and juliet', 20);

After run() completes you should have an articles.index file in the storage folder. After search() you get back an array of document IDs and scores, which you then use to fetch rows from articles. The README notes that the storage folder is auto-created since v5.2.

Updates without a full reindex, and what that costs

The feature that separates TNTSearch from a batch-only indexer is dynamic updating. After selecting an index and calling getIndex(), you can insert, update and delete individual documents, and the README states that no full reindex is required. Each insert() is wrapped in a single transaction, which the README presents as fast even for large documents.

This matters for applications where content changes continuously, because rebuilding a large index on every edit is not viable. The trade-off is that the correctness of the index becomes your responsibility. Nothing in the README describes a reconciliation step that detects drift between your source table and the index, so a failed insert or a code path that writes to the database without touching the index leaves the two out of sync silently. If your application has more than one write path, that is a real risk to plan for.

The index backend is selectable. SqliteEngine is the default and needs no server, MysqlEngine keeps the index inside your MySQL database, and RedisEngine keeps it in Redis. The README states that all three expose the same API, so the choice is operational rather than structural.

Where the configuration choices become permanent

The most consequential detail in the README is easy to skim past: the stemmer and tokenizer are baked into the index at index time. You set them when you create the index, not when you search. Changing the stemmer later means rebuilding.

That is defensible design, since stemming changes the terms that get written, but it constrains how you can evolve a deployment. If you start with the default NoStemmer and later decide you want PorterStemmer, or switch to setLanguage('german'), the existing index cannot answer the new queries correctly. You reindex. For a small corpus that is trivial; for a large one it is a maintenance window.

Two smaller settings have similar weight. includePrimaryKey() is off by default, so the primary key column is not itself searchable unless you turn it on. And the wal option controls SQLite Write-Ahead Logging, defaulting to true, which affects how the index file behaves under concurrent access. The README documents these keys but does not discuss their failure modes, so testing under your own concurrency pattern is the only way to know.

Geo-search, text classification, result highlighting and custom tokenizers are listed as features in the README. The API cheat-sheet is referenced at the bottom of the document rather than reproduced in full here.

TNTSearch against a dedicated search server

The obvious alternative is a standalone search service such as Elasticsearch, and the difference is not subtle. Elasticsearch is a separate process with its own storage, memory requirements, clustering model and operational surface. It stores your documents, so a query returns the content directly, and it supports aggregations, complex analyzers and horizontal scaling.

TNTSearch inverts nearly all of that. It runs inside your PHP process, needs no server, and stores only an index of terms pointing at IDs. You keep the documents in your existing database and fetch them yourself. For a Laravel application, the related searches around a Laravel Scout driver point at the same decision: TNTSearch integrates where a hosted search backend would otherwise sit.

The practical dividing line is scale and query complexity. If you need faceting, multi-field boosting with per-field analyzers, or a corpus large enough that a single PHP process cannot hold the working set, a dedicated server is the right tool and TNTSearch is not. If your corpus fits comfortably in SQLite or MySQL and your queries are text relevance queries, running a second daemon to answer them is a lot of machinery for the job.

Licence and the cost of keeping the index current

TNTSearch is released under the MIT licence, which permits commercial and closed-source use with the usual requirement to preserve the copyright notice and licence text. That is permissive and imposes no copyleft obligation on your application. This is a description of the licence identifier, not legal advice; read LICENSE.md in the repository for the actual terms.

Upgrade cost is low on the surface. The package installs through Composer, and the API shown in the README has been stable enough that the examples are short and direct. The maintenance burden that does not go away is the index itself. Every deployment needs a writable storage directory, and the README notes the folder is auto-created since v5.2, so older versions may need it created manually. Multi-server deployments need a shared or replicated index location, or a per-server index that is built and kept current independently.

The last push to the repository was on 2026-08-20, and the most recent release listed is v5.3.0 from 2026-08-11. The index format is tied to the version that wrote it, so pinning a version and rebuilding on upgrade is the safer sequence than swapping the library under an existing index file.

Editorial conclusion

Adopt TNTSearch if you already run PHP and want relevance-ranked search inside your application without operating a separate search daemon, especially if you can live with the index being a file in a writable storage folder. Do not adopt it if you need it to return the documents themselves, if you expect to change the stemmer or tokenizer after indexing, or if you need rollback of an index in progress, since the README does not document one. Verify first that the PHP SAPI running your code has pdo_sqlite enabled, that the storage directory is writable, and that the first column of your indexer query is the primary key you intend to use.

Frequently asked questions

Does TNTSearch return the documents or just IDs?

It returns document IDs, scores and hit counts, not the documents. You fetch the matching rows from your own database using those IDs and restore the order yourself, for example with ORDER BY FIELD(id, ...) in MySQL.

Can I change the stemmer on an existing TNTSearch index?

No. The README states that the stemmer and tokenizer are baked into the index at index time, so you set them when you create the index rather than when you search. Changing either means rebuilding the index.

What are the requirements to install TNTSearch?

The README lists PHP 7.4 or newer, PDO, pdo_sqlite for the default engine, and mbstring. Installation is a single Composer command: composer require teamtnt/tntsearch.

What does the could not find driver error mean in TNTSearch?

The README attributes PDOException: could not find driver to the pdo_sqlite extension not being enabled for the PHP SAPI running your code, and notes that CLI and web often differ. It is an environment issue rather than a library one.

Can TNTSearch update its index without a full reindex?

Yes. After selecting an index and calling getIndex(), you can insert, update and delete individual documents, and the README states no full reindex is required. Each insert() is wrapped in a single transaction.

Which index backends does TNTSearch support?

The README lists SqliteEngine as the default, with MysqlEngine and RedisEngine as alternatives, and states that all three expose the same API. SQLite needs no server, while the other two keep the index in MySQL or Redis.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. teamtnt/tntsearch on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/teamtnt-tntsearch.svg)](https://hysenlabs.com/projects/teamtnt-tntsearch)