npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kozojs/core

v0.3.36

Published

High-performance TypeScript framework with type-safe client generation

Readme

@kozojs/core

High-performance TypeScript framework with native Zod validation, type-safe client generation, and edge runtime compatibility.

3 runtime deps · 155 kB · MIT licensed

npm install @kozojs/core zod

Quick Start

import { createKozo, z } from '@kozojs/core';

const app = createKozo();

app.get('/users/:id', {
  params: z.object({ id: z.string().uuid() }),
  response: z.object({ id: z.string(), name: z.string() }),
}, (ctx) => ({
  id: ctx.params.id,
  name: 'John Doe',
}));

await app.listen(3000);

Table of Contents


Server Modes

Kozo offers three server transports. Same routes, same handlers — pick the transport that fits your deployment.

app.listen(port?) — Node.js HTTP

Standard node:http server via @hono/node-server. Works everywhere.

await app.listen(3000);

app.nativeListen(port?) — uWebSockets.js (C++ transport)

Routes are registered directly with uWS's C++ radix trie router — zero JS routing overhead per request. Requires uWebSockets.js as a peer dependency.

pnpm add uWebSockets.js
const { port, server } = await app.nativeListen(3000);
// or with CORS:
await app.nativeListen({ port: 3000, cors: { origin: '*' } });

app.listenSsr(port, config) — Unified API + SSR

Single server for both API routes and Vite-powered SSR pages. See SSR Integration.

await app.listenSsr(3000, {
  root: './web',
  entryServer: 'src/entry-server.tsx',
});

Route Registration

Register routes with .get(), .post(), .put(), .patch(), .delete(). Each accepts an optional schema object for validation.

// No schema — handler only
app.get('/health', () => ({ status: 'ok' }));

// With schema — body, query, params, response
app.post('/users', {
  body: z.object({ name: z.string(), email: z.string().email() }),
  response: z.object({ id: z.string().uuid(), name: z.string() }),
}, (ctx) => {
  return { id: uuid(), name: ctx.body.name };
});

The handler context ctx contains:

| Property | Type | Description | |----------|------|-------------| | ctx.body | Inferred from schema.body | Validated request body (POST/PUT/PATCH) | | ctx.query | Inferred from schema.query | Validated query parameters | | ctx.params | Inferred from schema.params | Validated path parameters | | ctx.services | TServices | Injected services | | ctx.json(data, status?) | Response | Return JSON response | | ctx.text(data, status?) | Response | Return text response | | ctx.html(data, status?) | Response | Return HTML response | | ctx.req | HonoRequest | Raw Hono request |

Handlers can return a plain object (auto-serialized as JSON) or a Response object for full control.


Route Groups

Group routes under a common prefix:

app.group('/api/v1', (r) => {
  r.get('/users', (ctx) => listUsers());
  r.get('/users/:id', { params: uuidParams }, (ctx) => getUser(ctx.params.id));
  r.post('/users', { body: CreateUserSchema }, (ctx) => createUser(ctx.body));
});
// Registers: GET /api/v1/users, GET /api/v1/users/:id, POST /api/v1/users

Schema Validation

Kozo uses Zod natively — no AJV, no JSON Schema intermediate step. Schemas are compiled once at route registration.

app.post('/users', {
  body: z.object({
    email: z.string().email(),
    name: z.string().min(2).max(50),
    age: z.number().min(18),
  }),
  query: z.object({
    dryRun: z.coerce.boolean().optional(),
  }),
  params: z.object({
    orgId: z.string().uuid(),
  }),
  response: UserSchema,         // bare schema -> normalized to { 200: UserSchema }
  // or: response: { 200: UserSchema, 201: CreatedSchema }
}, handler);

Invalid requests return RFC 7807 application/problem+json:

{
  "type": "https://kozo-docs.vercel.app/docs/core/errors#validation-failed",
  "title": "Validation Failed",
  "status": 400,
  "errors": [
    { "field": "email", "message": "Invalid email", "code": "invalid_string" }
  ]
}

Services (Dependency Injection)

Pass typed services at construction — every handler receives them via ctx.services:

interface AppServices {
  db: Database;
  cache: RedisClient;
  stripe: Stripe;
}

const app = createKozo<AppServices>({
  services: { db, cache, stripe },
});

app.get('/users', (ctx) => {
  // ctx.services.db is fully typed as Database
  return ctx.services.db.users.findMany();
});

Middleware

Register Hono middleware globally or per-path:

import { logger, cors, rateLimit, errorHandler } from '@kozojs/core/middleware';

// Built-in middleware
app.middleware(logger());                            // request logging
app.middleware(cors({ origin: 'https://app.com' })); // CORS
app.middleware('/api/*', rateLimit({ max: 100, window: 60 })); // rate limiting
app.middleware(errorHandler());                       // error handler (RFC 7807)

// Custom middleware
app.middleware('/admin/*', async (c, next) => {
  const user = await verifyJwt(c.req.header('authorization'));
  c.set('user', user);
  return next();
});

Built-in Middleware

| Middleware | Import | Options | |-----------|--------|---------| | logger(options?) | @kozojs/core/middleware | prefix?: string, colorize?: boolean | | cors(options?) | @kozojs/core/middleware | origin, allowMethods, allowHeaders, maxAge, credentials | | rateLimit(options) | @kozojs/core/middleware | max, window (seconds), keyGenerator?, store? (Redis etc.) | | errorHandler() | @kozojs/core/middleware | Catches KozoError -> RFC 7807 response |


Error Handling (RFC 7807)

All errors follow RFC 7807 Problem Details. Throw any KozoError subclass and it becomes a structured response.

import {
  KozoError,
  NotFoundError,
  BadRequestError,
  UnauthorizedError,
  ForbiddenError,
  ConflictError,
  GoneError,
  ValidationFailedError,
} from '@kozojs/core';

app.get('/users/:id', { params: uuidParams }, (ctx) => {
  const user = db.users.find(ctx.params.id);
  if (!user) throw new NotFoundError();        // -> 404
  if (!canAccess(user)) throw new ForbiddenError('Insufficient permissions'); // -> 403
  return user;
});

Error classes:

| Class | Status | Default Message | |-------|--------|-----------------| | KozoError | any | (custom) | | BadRequestError | 400 | "Bad Request" | | UnauthorizedError | 401 | "Unauthorized" | | ForbiddenError | 403 | "Forbidden" | | NotFoundError | 404 | "Resource Not Found" | | ConflictError | 409 | "Conflict" | | GoneError | 410 | "Gone" | | ValidationFailedError | 400 | (custom, includes .errors array) |

Pre-built response helpers (zero-allocation hot path):

import {
  notFoundResponse,
  unauthorizedResponse,
  forbiddenResponse,
  internalErrorResponse,
  validationErrorResponse,
} from '@kozojs/core';

Graceful Shutdown

Kozo drains in-flight requests before closing. No request is dropped mid-flight.

await app.listen(3000);

// Later (e.g. on SIGTERM):
await app.shutdown({
  timeoutMs: 30000,
  onShutdownStart: (inflight) => console.log('Draining ' + inflight + ' requests'),
  onShutdownComplete: () => console.log('Clean exit'),
});

Full lifecycle control via ShutdownManager:

const manager = app.getShutdownManager();

// Register database cleanup
manager.setDatabase(db, 'postgresql'); // also: 'mysql', 'sqlite'

// Custom cleanup hooks (run after draining, before DB close)
manager.addCleanupHook(async () => {
  await cache.quit();
  await queue.close();
});

// Wire to process signals
process.on('SIGTERM', () => app.shutdown());
process.on('SIGINT', () => app.shutdown());

During shutdown:

  1. New requests -> 503 Service Unavailable
  2. In-flight requests -> allowed to complete (up to timeoutMs)
  3. Cleanup hooks run
  4. Database connections closed
  5. Server closed

Type-Safe Client Generation

Generate a fully typed TypeScript client from your routes:

const code = app.generateClient({
  baseUrl: 'https://api.example.com',
  includeValidation: true,    // embed Zod schemas for client-side validation
  validateByDefault: false,   // opt-in per request
});

writeFileSync('./client/api.ts', code);

Generated client usage:

import { KozoClient } from './client/api';

const api = new KozoClient({ baseUrl: 'https://api.example.com' });

const users = await api.getUsers({ query: { page: 1 } });
//    ^? User[]

const user = await api.postUsers({ body: { name: 'Jane', email: '[email protected]' } });
//    ^? { id: string, name: string }

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | baseUrl | string | '' | API base URL | | includeValidation | boolean | true | Include Zod schemas in output | | validateByDefault | boolean | false | Enable validation in client constructor | | defaultHeaders | Record<string, string> | {} | Default request headers |


OpenAPI Generation

Generate an OpenAPI 3.1.0 spec from registered routes:

import { createOpenAPIGenerator, generateSwaggerHtml } from '@kozojs/core';

const generator = createOpenAPIGenerator({
  info: { title: 'My API', version: '1.0.0' },
  servers: [{ url: 'https://api.example.com' }],
});

// Get the spec as JSON
const spec = generator.generate(app.getRoutes());

// Serve Swagger UI
app.get('/docs', () => new Response(generateSwaggerHtml(spec), {
  headers: { 'Content-Type': 'text/html' },
}));

app.get('/openapi.json', () => spec);

Schemas are converted via Zod v4 native z.toJSONSchema(). Supports: path params, query params, request body (POST/PUT/PATCH), response schemas, tags, auth (Bearer), and summaries.


File-System Routing

Auto-register routes from the file system:

const app = createKozo({ routesDir: './src/routes' });
await app.loadRoutes();

Convention:

| File | Route | |------|-------| | routes/users.ts | GET/POST /users | | routes/users/[id].ts | GET/PUT/DELETE /users/:id | | routes/_middleware.ts | Skipped (prefixed with _) | | routes/users.test.ts | Skipped (test file) |

Each route file exports a default handler and optional schema/meta:

// routes/users/[id].ts
import { z } from 'zod';

export const schema = {
  params: z.object({ id: z.string().uuid() }),
  response: UserSchema,
};

export const meta = { auth: true, tags: ['users'] };

export default (ctx) => {
  return ctx.services.db.users.find(ctx.params.id);
};

Programmatic API:

import { createFileSystemRouting, applyFileSystemRouting } from '@kozojs/core/middleware';

WebSocket (uWS)

WebSocket support via uWebSockets.js native pub/sub. Requires nativeListen().

app.ws('/ws/chat', {
  open(ws) {
    ws.subscribe('chat');
  },
  message(ws, data) {
    ws.publish('chat', data);
  },
  close(ws) {
    console.log('disconnected');
  },
});

await app.nativeListen(3000);

With typed user data and auth upgrade:

app.ws<{ userId: string }>('/ws/secure', {
  upgrade(req) {
    const userId = verifyToken(req.headers['authorization']);
    if (!userId) return false; // reject upgrade
    return { userId };         // attached as ws.data
  },
  open(ws) {
    console.log(ws.data.userId + ' connected');
    ws.subscribe('user:' + ws.data.userId);
  },
  message(ws, data) {
    ws.publish('user:' + ws.data.userId, data);
  },
});

Note: app.listen() will warn if WebSocket routes are registered — use app.nativeListen() instead.


SSR Integration

Unified API + Vite SSR from a single server. No separate frontend server or proxy.

import path from 'node:path';
import { createKozo } from '@kozojs/core';

const app = createKozo({ routesDir: './src/routes' });
await app.loadRoutes();

await app.listenSsr(3000, {
  root: path.resolve('./web'),
  entryServer: 'src/entry-server.tsx',
  apiPrefix: '/api',
});

How it works: requests matching apiPrefix go to Hono, everything else -> Vite SSR pipeline.

  • Dev mode: Vite middleware for HMR + optional SSR rendering (auto-detected)
  • Prod mode: Static files from dist/client/ + pre-built SSR from dist/server/

Supports React 18 streaming (renderToPipeableStream) and string rendering.

SSR Config

| Option | Type | Default | Description | |--------|------|---------|-------------| | root | string | — | Web app root (where index.html lives) | | entryServer | string | — | Server entry relative to root | | apiPrefix | string \| string[] | '/api' | Routes that bypass SSR | | devSsr | boolean | auto-detected | Enable SSR in dev mode | | template | string | 'index.html' | HTML template path | | appPlaceholder | string | '<!--app-html-->' | Placeholder for rendered HTML | | headPlaceholder | string | '<!--ssr-head-->' | Placeholder for head tags | | distClient | string | 'dist/client' | Built client assets | | distServer | string | 'dist/server' | Server bundle directory |


Helper Schemas & Utilities

Common schemas to avoid repeating boilerplate:

import {
  paginationSchema,  // { page: z.coerce.number().default(1), limit: ... }
  uuidParams,        // { id: z.string().uuid() }
  idParams,          // { id: z.coerce.number().int().positive() }
  timestamps,        // { createdAt: z.date(), updatedAt: z.date() }
  sortSchema,        // { sortBy?: string, sortOrder: 'asc' | 'desc' }
  searchSchema,      // { q?: string }
  successSchema,     // { success: boolean, message?: string }
  deletedSchema,     // { success: boolean, deletedId: string }
  uuid,              // () => string (crypto.randomUUID)
  paginate,          // (items, page, limit) -> PaginatedResult
  defineEnv,         // validate process.env with Zod at startup
} from '@kozojs/core';

Environment validation:

const env = defineEnv({
  PORT: z.coerce.number().default(3000),
  DATABASE_URL: z.string().url(),
  JWT_SECRET: z.string().min(32),
});
// Throws at startup with clear message if any variable is missing/invalid

Pagination:

app.get('/users', { query: paginationSchema }, (ctx) => {
  return paginate(allUsers, ctx.query.page, ctx.query.limit);
  // -> { data: [...], total, page, limit, totalPages, hasNext, hasPrev }
});

Fast Response Utilities

Zero-allocation response helpers for custom native handlers:

import {
  fastWriteJson,       // 200 JSON
  fastWriteText,       // 200 text/plain
  fastWriteHtml,       // 200 text/html
  fastWriteJsonStatus, // JSON with custom status
  fastWrite404,        // pre-built 404
  fastWrite500,        // pre-built 500
  fastWrite400,        // validation error
  fastWriteError,      // KozoError -> problem+json
} from '@kozojs/core';

Runtime Compatibility

// Node.js
await app.listen(3000);

// Node.js + uWebSockets.js
await app.nativeListen(3000);

// Bun
await app.listen(3000);

// Cloudflare Workers / Deno
export default { fetch: app.fetch };

API Reference

createKozo<TServices>(config?)

Create a Kozo application.

| Option | Type | Default | Description | |--------|------|---------|-------------| | services | TServices | {} | Dependency injection container | | routesDir | string | — | Directory for file-system routing |

Instance Methods

| Method | Description | |--------|-------------| | .get(path, schema?, handler) | Register a GET route | | .post(path, schema?, handler) | Register a POST route | | .put(path, schema?, handler) | Register a PUT route | | .patch(path, schema?, handler) | Register a PATCH route | | .delete(path, schema?, handler) | Register a DELETE route | | .group(prefix, fn) | Group routes under a prefix | | .ws(path, handler) | Register a WebSocket route (requires nativeListen) | | .middleware(path?, handler) | Register Hono middleware | | .use(plugin) | Install a plugin | | .listen(port?) | Start Node.js HTTP server (default: 3000) | | .nativeListen(port?) | Start uWebSockets.js server | | .listenSsr(port, config) | Start unified API + SSR server | | .loadRoutes(dir?) | Load routes from file system | | .shutdown(options?) | Graceful shutdown | | .generateClient(options?) | Generate typed client SDK | | .getRoutes() | Inspect registered routes | | .getShutdownManager() | Access shutdown manager | | .getApp() | Access underlying Hono instance | | .fetch | Hono fetch handler (for Workers/Deno) |

Exports Map

import { ... } from '@kozojs/core';           // main exports
import { ... } from '@kozojs/core/middleware'; // middleware

License

MIT