v0.8.0a stable API, one Store, four databases →

An API frameworkfor Node wherethe docscan't lie.

Write a route with its contract once. inkan checks what comes in, types your handler, checks what goes out, writes OpenAPI 3.1, serves the docs and runs every example as a test. On node:http, Bun, Deno and serverless, with zero dependencies.

Read the docs

or start from a working project: npx @vxnsin/inkan examples

PowerShell — inkan check
​
​
​
​
​
​
​
​
​
​
​
​
​

印this page asked its own API through direct(app), in the same process:asking…

印 one contract

Write it once. Everything else follows.

Params, query, body, responses and a few examples, next to the handler. inkan reads them and does the rest, so the docs, the types and the tests are never three things that drift apart.

src/app.ts
app.get("/teas/:id", {
  params: t.object({ id: t.int() }),
  response: { 200: Tea, 404: t.problem() },
  examples: [
    { name: "found", params: { id: 1 } },
    { name: "missing", params: { id: 99 }, status: 404 },
  ],
}, ({ params }) => findTea(params.id));
  • ✓ValidationGET /teas/abc → 400, naming the field
  • {}Typesparams.id is a number in the handler
  • ⊘Only the contractwhat is not in Tea never leaves the server
  • ≡OpenAPI 3.1written from the same schemas
  • ◫Docs page/docs, with the examples to try
  • ▶Testsinkan check runs every example

印 live

Talk to a real inkan app.

This site runs on inkan, through @inkanjs/next. Send it something, or break it on purpose. Its own docs are at /api/docs.

GET /api/teas

Every tea, trimmed to the Tea schema.

印 under the hood

Fast, and still honest.

Contracts as plain code

inkan seal compiles your schemas to a file you can read. A hash ties it to the live contract, so a change falls back instead of lying.

印 how fast

All of that, level with Fastify or a little ahead.

Requests per second, with plain node:http as 100. Every server runs at once and the measurements take turns, so a slow moment of the machine hits everyone alike.

inkan on uWS
134.4
node:http
100.0
inkan, sealed
93.2
inkan
91.0
Fastify
89.9
Hono
84.5
Express
46.0

and every one of those inkan requests was validated, typed and trimmed to its contract. how it is measured →

印 integrations

inkan where you already are.

One package each, all in inkanjs/integrations. Nuxt, SvelteKit, Astro and Remix need none: app.fetch is enough.

vite.config.ts
import inkan from "@inkanjs/vite";

export default {
  plugins: [inkan({ entry: "src/server/app.ts" })],
};
// npm run dev → the frontend, /api, /api/docs, one port
@inkanjs/vite docs →

印 hanko

Stamps for the rest 判子

Login, sessions, websockets, jobs on a schedule, a proxy, tracing, a store in Redis or Postgres. What does not belong in every app comes as a stamp you press onto it: one line to register, typed all the way to the client, and listed in your docs.

built-in
21
middleware
8
stores
4
integrations
4

no official middleware brings a dependency along; otel wants @opentelemetry/api beside it

印 inkan learn

Twelve quests, in your terminal.

A tutorial that watches your file as you save it. Each quest says what to build, checks it against a real app and tells you what is missing. Offline, no account.

$ npx @vxnsin/inkan learn
PowerShell — inkan learn
印 inkan learn · quest 3 of 12 · rules for a body​
​
A body schema is checked before your handler​
runs. A body that breaks it never reaches you.​
​
Add POST /teas: name at least 1 character,​
grams a whole number of at least 1.​
​
✗ POST /teas with { grams: -5 } answered 201:​
it got in. Give grams a .min(1)​
✓ quest 3 done! rules for a body​