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.
or start from a working project: npx @vxnsin/inkan examples
印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.
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.
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.
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.
import inkan from "@inkanjs/vite";
export default {
plugins: [inkan({ entry: "src/server/app.ts" })],
};
// npm run dev → the frontend, /api, /api/docs, one port印 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印 inkan learn · quest 3 of 12 · rules for a bodyA body schema is checked before your handlerruns. 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