Cloudflare Workers + D1 向け Hono/Drizzle 薄ラッパー @nanokajs/core を作った
はじめに:なぜ "nanoka" という名前なのか
G.W.が明けた。現実が戻ってきた。
ライブラリの名前を決めるとき、たいてい「かっこいいエンジニアっぽい単語」か「アルファベットの略称」に落ち着く。だが今回は違うアプローチをとった。
名前の元ネタは、崩壊:スターレイルのピノコニー編に登場するキャラクター、サンデーの思想だ。
「誰も悲しまない楽園。週休7日制が理想の世の中」

ピノコニー編をプレイした人には、この言葉の受け取り方がいろいろあると思う。それはぜひ自分でたしかめてほしい。
ただ、Webアプリのルーティングを書くたびに「スキーマ定義 → 型生成 → バリデーション → クエリ → またマイグレーション」という儀式を繰り返すエンジニアには、週休7日という響きは刺さるものがあるのではないだろうか。
最初はそのまま sunday という名前にしようと思ったのだが……さすがにあからさますぎる。そこで「週休7日」から「七日(なのか)」をとって nanoka と命名した。
ちなみに、このライブラリが内包する Hono も「炎」という日本語をそのままローマ字にした名前だ。nanoka(七日)も同じく日本語のローマ字読み。意図してそう合わせた。先人へのリスペクトである。
なお、崩壊:スターレイルには「三月なのか」という超絶美少女キャラが存在するが、ネーミングがたまたま似ただけであり一切関係ない。
nanoka とは何か
@nanokajs/core は、Hono + Drizzle + Zod を毎回手で繋ぐ儀式を削るための薄いラッパーだ。
- GitHub: https://github.com/nanokajs/nanoka
- Doc Site: https://nanoka.dev
npm install @nanokajs/core
コアの思想は「モデル定義を DB スキーマと型の中核に置く」こと。ただしそれが API 入出力と完全に一致する、というナイーブな前提は捨てている。passwordHash は DB に存在するがレスポンスには絶対に出してはいけない、というような「DB と API が意図的に乖離する場面」は必ずある。だから 80% 自動、20% 明示 を設計方針とした。
import { nanoka, d1Adapter, t } from '@nanokajs/core'
const app = nanoka(d1Adapter(env.DB))
const User = app.model('users', {
id: t.uuid().primary(),
name: t.string(),
email: t.string().email(),
passwordHash: t.string(),
})
// omit でレスポンスから除外するのは「明示的に書く」
app.get('/users', async (c) => {
const users = await User.findMany({ limit: 20, offset: 0, orderBy: 'id' })
return c.json(users)
})
app.post('/users', User.validator('json', { omit: f => [f.passwordHash] }), async (c) => {
const data = c.req.valid('json')
const user = await User.create(data)
return c.json(user, 201)
})
export default app
Hono も Drizzle も捨てていない。生の Drizzle クエリが書きたければ app.db でエスケープハッチが常に開いている。
現状できること
- モデル定義 → DB スキーマ・TypeScript 型・Zod バリデーターが自動派生
-
CRUD メソッド:
findMany(limit 必須)、findOne、create、update、delete -
バリデーター:
User.validator('json', { pick/omit })で Hono validator として直接使える -
OpenAPI 自動生成:
app.openapi(metadata)+app.generateOpenAPISpec()、Swagger UI 同梱 -
アダプター: Cloudflare D1 ファーストクラス、Turso/libSQL も
@nanokajs/core/tursoで対応 -
CLI:
create-nanoka-appでプロジェクトの足場を一発生成 -
Relations:
t.hasMany()/t.belongsTo()を絶賛実装中
次のロードマップ:週休7日への道はまだ途中
1. @nanokajs/auth ――認証もオールインワンで
個人開発・スモールチームがいちばん「毎回書きたくない」と感じるのが、認証まわりだ。hono/jwt とパスワードハッシュと Drizzle をまた手で繋ぐのか、という徒労感。
そこで @nanokajs/core 本体を汚さず、必要なプロジェクトだけ追加できる独立パッケージとして @nanokajs/auth を計画している(Issue #74 参照)。
import { createAuth } from '@nanokajs/auth'
const auth = createAuth({
model: User,
secret: c.env.JWT_SECRET,
fields: { identifier: 'email', password: 'passwordHash' },
})
// ログインハンドラー、JWT ミドルウェア、リフレッシュトークンが即使える
app.post('/auth/login', auth.loginHandler())
app.post('/auth/refresh', auth.refreshHandler())
app.use('/api/*', auth.middleware())
デフォルト実装は Web Crypto API(PBKDF2)ベースで zero-dependency。Cloudflare Workers でも Node.js でも動く。argon2 のようなカスタム実装に差し替えるための hasher インターフェースも用意する予定だ。
パスワード + JWT というよくあるフローを、モデル定義と一行のセットアップで使えるようにしたい。
2. ElysiaJS 対応
崩壊:スターレイルに三月なのかというキャラクターがいる。では崩壊3rdには? そう、エリシアがいる。
それだけの理由で、筆者は ElysiaJS への対応を個人的にやりたいと思っている。ユーザーからのニーズは特にない。完全に筆者の趣味である。
まとめ
nanoka はまだ若いライブラリだ。G.W.は終わり、週休7日の楽園はまだ遠い。
でも、Hono + Drizzle + Zod を毎回手で繋ぐ という現状から、モデルを書けば型もバリデーションも CRUD も OpenAPI も出てくる という世界へ向けて、着実に歩みを進めている。
興味を持ってもらえたら、ぜひ試してみてほしい。Issues も PR も大歓迎だ。そしてできれば、週休7日の楽園を一緒に建設しよう。
- GitHub: https://github.com/nanokajs/nanoka
- Doc Site: https://nanoka.dev
npm create nanoka-app@latest <dir-name>
サンデーはいいぞ
Discussion