Yet another simple ORM for Node.js.
Project status: Toshihiko v2 is under active development. The current packages use prerelease versions and require Node.js 22 or newer.
- Schema-derived TypeScript types. Model rows, query fields, primary keys, defaults, and custom field values are inferred directly from
define(). - Promise APIs. Queries, adapters, validators, writes, and transactions use native Promises.
- Model-centered API. Model, Query, Yukari, field types,
where(),find(), andfindById()form one consistent vocabulary. - Explicit adapter boundaries. Database-specific connections and result types stay inside adapter packages instead of leaking into the ORM core.
- Service-backed tests. GitHub Actions covers Node.js 22 and 24, with MySQL 5.7 and 8.4, Redis, and Memcached.
Install the core and the MySQL adapter:
npm install toshihiko @toshihiko/mysql-adapterDefine a model:
import { Toshihiko, Type } from 'toshihiko';
const database = new Toshihiko('mysql', {
database: 'app',
host: '127.0.0.1',
password: 'secret',
username: 'root',
});
const User = database.define('users', [
{
name: 'id',
column: 'user_id',
type: Type.Integer,
primaryKey: true,
autoIncrement: true,
},
{
name: 'name',
type: Type.String,
},
]);
const user = User.build({
name: 'Alice',
});
await user.insert();
const persistedUser = await User.findById(user.id);
if (persistedUser) {
persistedUser.name = 'Updated Alice';
await persistedUser.save();
}
const users = await User
.where({ id: { $gte: 1 }, name: { $like: 'A%' } })
.orderBy({ id: 'desc' })
.limit(10)
.find(true);
const userCount = await User.where({ name: { $like: 'A%' } }).count();
await persistedUser?.delete();build() returns a typed Yukari instance, so Toshihiko infers id as number, name as string, and the primary key as id directly from the schema without requiring a separately maintained row interface or type alias. insert() validates the Yukari, persists it through the configured Adapter, and hydrates database-generated values back into the same instance. An inserted Yukari remains a new row; query it before updating or deleting it. update() validates queried data and writes changed fields using the original primary key. save() inserts new rows and updates queried rows, while delete() removes a queried row using its original primary key.
Toshihiko is developed as a monorepo, but each package keeps an independent public API and release boundary.
| Package | Coverage | Directory | Purpose |
|---|---|---|---|
toshihiko |
packages/toshihiko |
ORM core, Model, Query, Yukari, and built-in field types | |
@toshihiko/base-adapter |
packages/base-adapter |
Typed, Promise-only foundation for adapter authors; installed transitively by concrete adapters | |
@toshihiko/mysql-adapter |
packages/mysql-adapter |
MySQL adapter built on the mysql2 Promise API |
|
@toshihiko/base-cache |
packages/base-cache |
Typed, Promise-only foundation for cache implementations | |
@toshihiko/redis-cache |
packages/redis |
Redis cache with stable key generation, batching, and positional misses | |
@toshihiko/memcached-cache |
packages/memcached |
Memcached cache with batched reads and customizable keys | |
@toshihiko/sql-utils |
packages/sql-utils |
SQL identifier mapping and escaping utilities |
The dependency direction is intentionally small:
flowchart LR
mysql["@toshihiko/mysql-adapter"] --> base["@toshihiko/base-adapter"]
mysql --> core["toshihiko"]
mysql --> sql["@toshihiko/sql-utils"]
redis["@toshihiko/redis-cache"] --> cache["@toshihiko/base-cache"]
memcached["@toshihiko/memcached-cache"] --> cache
cache --> core
base --> core
core --> sql
Built-in and custom field types flow through the complete model API:
const Article = database.define('articles', [
{ name: 'id', type: Type.Integer, primaryKey: true },
{ name: 'title', type: Type.String },
{ name: 'publishedAt', type: Type.Datetime, allowNull: true },
{ name: 'metadata', type: Type.Json },
]);
const article = Article.build({
id: 1,
title: 'Typed models without duplicate interfaces',
publishedAt: null,
metadata: { language: 'en' },
});
article.title = 'Types inferred directly from build()';
await article.validateAll();
await Article.findById(1);
// Query fields and primary-key values use the same inferred schema.
Article.where({ title: { $like: 'Typed%' } });Model.build() returns a Yukari instance whose known and optional properties reflect the supplied input and schema defaults. Asynchronous validators can then run through validateAll().
The complete v2 documentation is available in three languages:
The source documentation covers the current TypeScript and Promise APIs:
- Getting started
- Core concepts
- Model definition
- Model usage
- Querying
- Yukari instances
- Data types
- Transactions
- Raw SQL
- Caching
- Packages
- Writing extensions
- Migrating from v1
- API reference
- Testing
The core depends on a small Promise-based adapter contract. Concrete adapters own their connection, query, field, and mutation result types. A dialect name loads the corresponding installed Adapter package:
const database = new Toshihiko('mysql', {
database: 'app',
});Adapter constructors and instances can also be injected directly. Toshihiko calls a constructor with (toshihiko, options); @toshihiko/base-adapter and @toshihiko/mysql-adapter also support standalone new Adapter(options) construction.
import { MySQLAdapter } from '@toshihiko/mysql-adapter';
const injectedDatabase = new Toshihiko(MySQLAdapter, {
database: 'app',
});The MySQL Adapter uses mysql2 prepared execution for bound values and implements Toshihiko query operators.
Redis and Memcached cache implementations are developed and released from this monorepo. Cache instances can be configured globally and inherited by Models, overridden for one Model, or disabled with cache: false. The MySQL cache path is regression-tested with Memcached; Redis batch reads preserve key positions with null misses.
import { MemcachedCache } from '@toshihiko/memcached-cache';
const cache = new MemcachedCache('127.0.0.1:11211', {
prefix: 'app:',
});
const cachedDatabase = new Toshihiko('mysql', {
cache,
database: 'app',
});Queries read and fill the configured cache using the original Toshihiko key flow. Updates and deletes invalidate matching primary-key entries before the database mutation. Pass { noCache: true } to find() to bypass cache reads for one query.
- Node.js 22 or 24
- npm
- Rush 5.172.1
Install the development tools once:
npm install --global @microsoft/rush@5.172.1Install all workspace dependencies through Rush's npm integration:
rush updateRush downloads the pinned official npm version declared in rush.json, then manages installation, the project graph, build order, tests, version policies, and releases.
rush check
rush build
rush typecheck
rush testRun a command for a specific package and its dependencies:
rush build --to @toshihiko/mysql-adapter
rush test --to @toshihiko/mysql-adapterUnit and package-contract tests run locally without external services:
rush build
rush testService-backed tests run in GitHub Actions against MySQL 5.7 and MySQL 8.4 together with Redis and Memcached. Local development does not require Docker. If you already have compatible services, run the integration suite explicitly:
MYSQL_DATABASE=toshihiko_test \
MYSQL_HOST=127.0.0.1 \
MYSQL_PASSWORD=toshihiko \
MYSQL_PORT=3306 \
MYSQL_USER=root \
REDIS_HOST=127.0.0.1 \
REDIS_PORT=6379 \
MEMCACHED_HOST=127.0.0.1 \
MEMCACHED_PORT=11211 \
rush test:integrationRush tracks changes and publishes packages independently:
toshihiko,@toshihiko/base-adapter,@toshihiko/mysql-adapter,@toshihiko/base-cache,@toshihiko/redis-cache, and@toshihiko/memcached-cacheare locked to major version 2, but do not need to publish together.@toshihiko/sql-utilsremains on its independent 1.x version line.- A change to an adapter does not force an unrelated core release.
Contributors should add a Rush change file for user-visible package changes:
rush changeIssues and pull requests are welcome. Before opening a pull request:
- Install dependencies with
rush update. - Run
rush check,rush build,rush typecheck, andrush test. - Add or update tests for behavioral changes.
- Add a Rush change file when the change affects a published package.
Please keep public API changes intentional and update regression tests when behavior changes.
Toshihiko is a character from Touhou Warring States Nights, a collaborative Touhou fan work. In that character setting, Toshihiko is a manifested instance of Yakumo Yukari. This is why the API calls a record object a Yukari instance: the technical object-instance meaning echoes the relationship between the two characters. The names have been part of the project since its first release in 2014.
Toshihiko exists thanks to its maintainers, contributors, and users. Special thanks to the contributors highlighted by the original project:
