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

miqro

v7.3.4

Published

**experimental** Node.js web framework built from scratch with minimal runtime dependencies.

Readme

miqro

experimental Node.js web framework built from scratch with minimal runtime dependencies.

fully typed TypeScript · REST · WebSocket · SQL · JWT · HTTP

runtime dependencies: jose, esbuild, cookie, showdown

  • Web Components using JSX — server-rendered and client-side
  • static site generation.html.tsx handlers run against a live DB at build time
  • REST API endpoints with request/response validation
  • databasesqlite3, node:sqlite, postgres with migrations
  • cluster — multi-worker with shared cache and hot reload
  • NODE:SEA — compile to single binary, no Node.js required on target machine
  • built-in editor, test runner, API doc generation

packages

miqro
├── @miqro/core       router, middleware, session, CORS, logger
├── @miqro/jsx        vDOM, hooks, SSR runtime
├── @miqro/jsx-dom    Web Component define, browser runtime
├── @miqro/jsx-node   node SSR runtime
├── @miqro/query      query builder, ORM, migrations
├── @miqro/parser     schema validation
├── @miqro/request    http client
├── @miqro/runner     cluster manager
├── @miqro/test       test runner
└── @miqro/test-http  http test helper

@miqro/test and @miqro/request are being phased out in favor of node:test and built-in fetch.

package docs: @miqro/core · @miqro/query · @miqro/jsx · @miqro/jsx-dom · @miqro/jsx-node · @miqro/request · @miqro/runner · @miqro/test · @miqro/test-http · @miqro/parser

composition

services run sequentially. each service runs on every request until a route sends a response.

{
  "services": ["a/", "b/", "c/"]
}
request →
  a/ runs (auth.ts, middleware.ts, then http/ routes)
  if no route in a/ matched, b/ runs
  if no route in b/ matched, c/ runs

earlier services establish state. later services consume it.

a/auth.ts    → sets req.session
b/db.ts      → req.server.db.get("b") available
c/http/      → has req.session and req.server.db, handles routes

req.session.account is the tenant identifier. use it to isolate data:

// any handler in c/
const posts = await req.server.db.get("b")
  .select().from("posts")
  .eq("account", req.session.account)
  .yield();

seed shared state in server.ts using isPrimaryWorker():

// a/server.ts
export default {
  load: async (server) => {
    if (server.isPrimaryWorker()) {
      const config = await server.db.get("b").select().from("config").yield();
      server.cache.set("config", config);
    }
  }
}

swap a/ for a different implementation — b/ and c/ don't change. they read from req.session, not from a/ directly.

same service folders, different miqro.json:

{ "services": ["a1/", "b/", "c/"] }   // production auth
{ "services": ["a2/", "b/", "c/"] }   // test auth
{ "services": ["b/", "c/"] }          // no auth

service folders can be npm packages:

{
  "services": [
    "node_modules/@myorg/auth/service/",
    "node_modules/@myorg/db/service/",
    "services/app/"
  ]
}

installation

npm

npm install miqro

getting started

mkdir -p example/http

example/http/index.html.tsx

import JSX from "@miqro/jsx";

export default (req, res) => {
  return <html>
    <body>
      <h1>Hello World!</h1>
    </body>
  </html>
}
miqro --watch --service example/

open http://localhost:8080/index.html.

generate static files

miqro --inflate --inflate-dir build/ --service example/

output in build/example/static/. serve with any http server.

service folder

app/
  http/          endpoints
  static/        files served as-is
  migration/     db migrations
  test/          tests
  db.ts          database config
  ws.ts          websocket config
  auth.ts        session/auth config
  server.ts      lifecycle hooks
  log.ts         log transport config
  middleware.ts  pre/post route middleware
  catch.ts       error handlers
  miqro.json     multi-service composition

all files and folders are optional.

miqro --service app/
miqro --service app/ --editor

http folder

files are served by their path in the directory.

http/
  index.html.tsx      → GET /index.html
  posts/
    index.html.tsx    → GET /posts/index.html
    list.api.ts       → GET /posts/list
  js/
    app.min.tsx       → GET /js/app.js (minified bundle)

.html.tsx

server-rendered JSX. runs at request time or at build time with --inflate.

import JSX from "@miqro/jsx";

export default (req, res) => {
  return <html>
    <body>
      <h1>Hello</h1>
    </body>
  </html>
}

with database access:

import JSX from "@miqro/jsx";

export default async (req, res) => {
  const posts = await req.server.db.get("mydb")
    .select().from("posts").yield();
  
  return <html>
    <body>
      {posts.map(p => <h2>{p.title}</h2>)}
    </body>
  </html>
}

customize path/method with apiOptions:

import JSX from "@miqro/jsx";
import { APIOptions } from "miqro";

export const apiOptions: APIOptions = {
  path: ["/", "/index.html"],
  method: ["GET"]
};

export default (req, res) => { ... }

.min.tsx

bundled and minified client-side JavaScript. use to define Web Components.

import JSX, { useState } from "@miqro/jsx";
import { define } from "@miqro/jsx-dom";

function Counter() {
  const [n, setN] = useState(0);
  return <button onClick={() => setN(n + 1)}>count: {n}</button>;
}

define("my-counter", Counter, {
  observedAttributes: ["initial"],
  shadowInit: false
});

included in HTML:

export default (req, res) => (
  <html>
    <body>
      <my-counter initial="0" />
      <script src="/js/app.js" />
    </body>
  </html>
);

.api.ts

REST endpoint. file path is the route path.

export default (req, res) => {
  return res.json({ ok: true });
}

full declaration with validation and typing:

import { defineRoute } from "miqro";
import { JSONParser } from "@miqro/core";

export default defineRoute({
  name: "create post",
  method: "POST",
  middleware: [JSONParser()],
  request: {
    body: {
      title: "string",
      content: "string"
    }
  },
  response: {
    status: [200],
    body: {
      id: "number"
    }
  },
  handler: async (req, res) => {
    const post = await req.server.db.get("mydb")
      .insert("posts")
      .values({ title: req.body.title, content: req.body.content })
      .returning("id")
      .yield();
    return res.json({ id: post[0].id });
  }
});

full declaration with validation but no req.body typing:

import { APIRoute, JSONParser } from "@miqro/core";

export default {
  name: "create post",
  method: "POST",
  middleware: [JSONParser()],
  request: {
    body: {
      title: "string",
      content: "string"
    }
  },
  response: {
    status: [200],
    body: {
      id: "number"
    }
  },
  handler: async (req, res) => {
    const post = await req.server.db.get("mydb")
      .insert("posts")
      .values({ title: req.body.title, content: req.body.content })
      .returning("id")
      .yield();
    return res.json({ id: post[0].id });
  }
} as APIRoute;

method: "use" registers the handler for all methods (middleware pattern):

import { Router } from "@miqro/core";

const router = new Router();
router.use(myMiddleware);

export default {
  path: "/admin",
  method: "use",
  handler: router
}

.html.md

markdown file converted to HTML. served as text/html.

http/
  docs/
    guide.html.md    → GET /docs/guide

guide.html.md:

# Guide

some **markdown** content.

converted using showdown. also works with --inflate — outputs static HTML.

available as req.server.inflateMDtoHTML(str) from any handler.

static folder

files served as-is, by path.

static/
  logo.png     → GET /logo.png
  style.css    → GET /style.css

db.ts

import { DBConfig } from "miqro";

export default {
  dialect: "node:sqlite",       // node:sqlite | sqlite3 | pg
  name: "mydb",                 // req.server.db.get("mydb")
  storage: "./data.sqlite3",    // sqlite only
  // connectionString: "..."    // postgres
} as DBConfig;

migrations run automatically on startup (primary worker only).

migration folder

files named NNN_name.ts run in order.

// migration/001_create_posts.ts
import { MigrationModule } from "miqro";

export default {
  name: "001_create_posts",
  dbName: "mydb",
  up: async (db) => {
    await db.createTable("posts", {
      id: { type: "integer", primaryKey: true, autoIncrement: true },
      title: { type: "string" },
      content: { type: "string" }
    }).yield();
  }
} as MigrationModule;

run manually:

miqro --migrate-up --service app/
miqro --migrate-down --service app/

cors.ts

import { CORSOptions } from "miqro";

export default {
  origins: ["https://myapp.com", "https://staging.myapp.com"],
  methods: "GET,POST,PUT,DELETE"
} as CORSOptions;

without cors.ts all origins are allowed. with it only listed origins are accepted — requests from other origins get 400 Bad Request.

ORM

quick reference. full docs in @miqro/query.

import { defineModel } from "@miqro/query";

const Post = defineModel(db, "posts", {
  id:        { type: "integer", primaryKey: true, autoIncrement: true },
  title:     { type: "string" },
  published: { type: "boolean" },
  createdAt: { type: "datetime" }
});

// create
await Post.create({ title: "hello", published: false });

// findAll
const posts = await Post.findAll(
  Post.where().eq("published", true).order("createdAt", "DESC"),
  { limit: 10 }
);

// updateAll
await Post.updateAll({ published: true }, Post.where().eq("id", 1));

// deleteAll
await Post.deleteAll(Post.where().eq("id", 1));

// count
const n = await Post.count(Post.where().eq("published", true));

// sync (create table if not exists)
await Post.sync();

access the db directly:

const db = req.server.db.get("mydb");
const rows = await db.select().from("posts").eq("published", true).yield();
const raw = await db.query("SELECT * FROM posts WHERE id = ?", [1]);

ws.ts

import { WSConfig } from "miqro";

export default {
  path: "/updates",   // req.server.ws.get("/updates")
  disabled: false
} as WSConfig;

broadcast from a handler:

export default async (req, res) => {
  const ws = req.server.ws.get("/updates");
  await ws.broadcast(JSON.stringify({ type: "update", data: "..." }));
  return res.json({ ok: true });
}

WebSocket client (.min.tsx)

import JSX, { useState, useEffect } from "@miqro/jsx";
import { define } from "@miqro/jsx-dom";

function LiveFeed(props) {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const ws = new WebSocket(`ws://${location.host}/updates`);
    ws.onmessage = (e) => {
      const msg = JSON.parse(e.data);
      setMessages(prev => [...prev, msg]);
    };
    return () => ws.close();
  }, []);

  return <ul>{messages.map((m, i) => <li>{m.text}</li>)}</ul>;
}

define("live-feed", LiveFeed, { shadowInit: false });

broadcast from a handler:

// http/publish.api.ts
export default async (req, res) => {
  const ws = req.server.ws.get("/updates");
  await ws.broadcast(JSON.stringify({ text: req.body.text }));
  return res.json({ ok: true });
}

auth.ts

import { AuthConfig, jwt } from "miqro";
import { createSecretKey } from "node:crypto";

const secret = createSecretKey(Buffer.from(process.env.JWT_SECRET, "hex"));

export default {
  verify: async ({ token }) => {
    try {
      const payload = await jwt.verify(token, secret);
      return {
        account: payload.account as string,
        username: payload.username as string,
        groups: payload.groups as string[],
        token
      };
    } catch {
      return null;
    }
  }
} as AuthConfig;

session available on req.session:

req.session.account   // tenant identifier
req.session.username
req.session.groups
req.session.token

restrict by group using policy on any endpoint:

export default {
  policy: {
    groups: ["admin"],
    groupPolicy: "at_least_one"
  },
  handler: ...
}

server.ts

lifecycle hooks.

import { ServerConfig } from "miqro";

export default {
  preload: async (server) => {
    // runs before db connections
  },
  load: async (server) => {
    // runs after db connections, before listening
    // seed cache here
    const rows = await server.db.get("mydb").select().from("config").yield();
    server.cache.set("config", rows);
  },
  start: async (server) => {
    // runs after listening
  },
  stop: async (server) => {
    // runs on shutdown
  }
} as ServerConfig;

load runs on all workers. use server.isPrimaryWorker() to gate one-time operations.

middleware.ts

pre and post route middleware for the service.

import { MiddlewareConfig } from "miqro";

export default {
  middleware: [
    // runs before all routes
    async (req, res) => {
      req.startTime = Date.now();
    }
  ],
  post: [
    // runs after all routes
    async (req, res) => {
      req.logger.debug("took %dms", Date.now() - req.startTime);
    }
  ]
} as MiddlewareConfig;

catch.ts

error handlers.

import { ErrorConfig } from "miqro";

export default {
  catch: [
    async (err, req, res) => {
      req.logger.error(err);
      return res.json({ error: err.message }, {}, 500);
    }
  ]
} as ErrorConfig;

log.ts

custom log transport. called for every log message.

import { LogConfig } from "miqro";

export default {
  level: "error",              // only receive messages at this level
  replaceConsoleTransport: false,  // keep default console output
  replaceFileTransport: false,     // keep default file output
  write: async ({ out, level, identifier }) => {
    // out       — formatted log string
    // level     — "error" | "warn" | "info" | "debug" | "trace"
    // identifier — route/worker identifier
    await fetch("https://logs.example.com/ingest", {
      method: "POST",
      body: JSON.stringify({ out, level, identifier })
    });
  }
} as LogConfig;

log levels: errorwarninfodebugtracenone

default output: console + ./server.log (or LOG_FILE env var).

per-identifier level override via env:

LOG_LEVEL=info
LOG_LEVEL_POSTS_GET=debug    // debug only for GET /posts
LOG_LEVEL_WORKER_0=trace     // trace only for worker 0

cluster mode: each worker writes to the same log file independently. at high throughput use pipes instead of FileTransport:

miqro --service app/ 2>&1 | tee app.log

or send to an external aggregator via log.ts write.

doc.ts

publish API documentation as a static file or live endpoint.

import { DocConfig } from "miqro";

export default {
  publish: {
    "/api/docs":   { type: "MD" },    // markdown
    "/api/schema": { type: "JSON" },  // json
    "/api/docs.html": { type: "HTML" } // html
  }
} as DocConfig;

with --inflate the docs are written to build/ as static files.

at runtime the docs are served as live endpoints — useful for development.

generate docs via CLI:

miqro --generate-doc --generate-doc-out API.md --service app/
miqro --generate-doc --generate-doc-type JSON --generate-doc-out api.json --service app/

doc output is derived from APIRoute declarations — name, description, request, response, policy fields.

miqro.json

compose multiple services.

{
  "name": "myapp",
  "port": "3000",
  "services": [
    "a/",
    "b/",
    "c/"
  ],
  "inflateDir": "build/",
  "logFile": false,
  "browser": true
}

all fields optional except services.

name        server name — required in cluster mode
port        default: 8080
services    ordered list of service folders
inflateDir  default inflate output directory
logFile     false | true | "./path/server.log"
browser     open browser on start (true | false | "browser-name")

generate default miqro.json:

miqro --install-miqrojson

run with miqro.json:

miqro

or override:

miqro --service a/ --service b/

swap implementations by changing the services array. same service folders work across different compositions.

req.server

available on every handler.

req.server.db.get("name")          // Database | null
req.server.ws.get("/path")         // WebSocketServer | undefined
req.server.cache                   // ClusterCache — synced across all cluster workers via IPC
req.server.localCache              // LocalCache — in-memory, per worker only
req.server.cache.set("key", value)
req.server.cache.get("key")
req.server.cache.has("key")
req.server.cache.unset("key")
req.server.cache.set_add("key", value)   // set operations
req.server.cache.set_has("key", value)
req.server.cache.set_delete("key", value)

req.server.middleware.json()       // body parser → req.body
req.server.middleware.url()        // url-encoded body parser → req.body
req.server.middleware.text()       // text body parser → req.body
req.server.middleware.buffer()     // raw buffer → req.buffer
req.server.middleware.cors(opts)   // CORS middleware
req.server.middleware.session(opts)// auth middleware

req.server.jwt.sign(payload, secret, opts)
req.server.jwt.verify(token, secret, opts)
req.server.jwt.decode(token)

req.server.isPrimaryWorker()       // true on worker 0
req.server.getWorkerNumber()       // 0..n
req.server.getWorkerCount()        // total workers

req.server.reload()                // hot reload
req.server.restart()               // full restart
req.server.stop()                  // shutdown

req.server.encodeHTML(str)
req.server.inflateMDtoHTML(str)
req.server.newParser()
req.server.newClusterCache(name)
req.server.newLocalCache(name)
req.server.getLogger(identifier)

req

req.path          // normalized pathname
req.hash          // url hash fragment
req.searchParams  // URLSearchParams
req.query         // parsed query string { [key]: string | string[] }
req.params        // path parameters { [key]: string }
req.cookies       // parsed cookies { [name]: string }
req.body          // parsed body (requires body parser middleware)
req.buffer        // raw body buffer (requires ReadBuffer middleware)
req.session       // set by auth.ts
req.session.account   // tenant identifier
req.session.username
req.session.groups    // string[]
req.session.token
req.uuid          // unique request id
req.startMS       // request start timestamp ms
req.logger        // per-request logger — includes path/method/uuid/remoteAddress
req.results       // pipeline accumulator

test folder

test files named *.test.ts.

import { describe, it } from "node:test";
import { strictEqual } from "assert";

describe("posts", () => {
  it("GET /posts returns 200", async () => {
    const res = await test.request({
      url: "/posts",
      method: "GET",
      disableThrow: true
    });
    strictEqual(res.status, 200);
  });
});

test.request hits the running miqro server via Unix socket. no port needed.

test JSX components:

import JSX from "@miqro/jsx";
import { Counter } from "./counter.js";

it("renders counter", test.jsx.test(async (container, root, runtime) => {
  container.render(JSX.createElement(Counter, { initial: 0 }));
  strictEqual(root.innerHTML.includes("0"), true);
}));

run tests:

miqro --test --service app/

Miqro object

import { Miqro } from "miqro";

const app = new Miqro({
  services: ["app/"],
  port: "3000",
  name: "myapp",       // required in cluster mode
  hotreload: false,
  editor: false
});

await app.inflate({ inflateDir: "build/" });  // generate static files
await app.inflate();                           // inflate to memory
await app.start();                             // start server
await app.stop();                              // stop server
await app.reload();                            // hot reload
await app.restart();                           // full restart
await app.dispose();                           // cleanup cluster connections

trigger static generation from a running server:

export default {
  path: "/publish",
  method: "POST",
  handler: async (req, res) => {
    const generator = new Miqro({ services: ["app/"] });
    await generator.inflate({ inflateDir: "build/" });
    return res.json({ ok: true });
  }
}

cluster

npx miqro-cluster --service app/
CLUSTER_COUNT=4 npx miqro-cluster --service app/
npx miqro-cluster

to run arbitrary scripts in cluster mode:

npx miqro-runner server.js
CLUSTER_COUNT=4 npx miqro-runner server.js

--no-build

use tsc instead of esbuild. run tsc first, then miqro.

generate a tsconfig:

miqro --install-tsconfig

tsconfig.json:

{
  "compilerOptions": {
    "target": "es2022",
    "noEmit": true,
    "module": "NodeNext",
    "moduleResolution": "nodenext",
    "lib": ["es2021", "dom"],
    "jsx": "react",
    "jsxFactory": "JSX.createElement",
    "jsxFragmentFactory": "JSX.Fragment"
  }
}

noEmit: true — tsc type-checks only, esbuild handles transpilation at runtime. remove noEmit and set outDir to use tsc output with --no-build.

development with tsc + miqro:

tsc --watch &
miqro --no-build --watch --service app/

note: in --no-build mode module-level state is shared across routes within a process. the stateless coding guarantee that esbuild provides does not apply. avoid module-level mutable state.

static site generator

miqro --inflate --inflate-dir build/ --service app/

.html.tsx handlers run with a live db connection. output is static HTML written to build/.

miqro --inflate --inflate-dir build/ --service app/
python3 -m http.server 8080 build/app/static/

https

miqro --https --https-key server.key --https-cert server.cert --service app/

with http redirect:

miqro --https --https-key server.key --https-cert server.cert --https-redirect 8080 --service app/

starts an additional http server on port 8080 that redirects all requests to https.

via env or miqro.json:

{
  "https": true,
  "httpsKey": "./server.key",
  "httpsCert": "./server.cert",
  "httpsRedirect": "8080"
}

cli

miqro --service app/
miqro --watch --service app/
miqro --editor --service app/
miqro --test --service app/
miqro --migrate-up --service app/
miqro --migrate-down --service app/
miqro --inflate --inflate-dir build/ --service app/
miqro --generate-doc --generate-doc-out API.md --service app/
miqro --compile --service app/

flags:

--watch                 auto reload on file changes
--hot-reload            enable hot-reload with --watch
--test                  run tests
--migrate-up            run migrations up
--migrate-down          run migrations down
--inflate               generate static files
--inflate-dir           output directory (default: inflated/)
--editor                run with built-in editor
--generate-doc          generate API documentation
--generate-doc-out      output file (default: API.md)
--generate-doc-type     MD | JSON | HTML (default: MD)
--generate-doc-all      include all routes
--compile               build NODE:SEA binary
--no-build              skip esbuild, use pre-compiled files
--no-minify             skip minification
--inflate-only-assets   inflate assets only
--inflate-flat          inflate into dir directly
--inflate-sea           inflate with SEA compilation scripts
--install-tsconfig      create tsconfig.json
--install-miqrojson     create miqro.json
--install               install from binary cache (SEA only)
--disable-miqrojson     ignore miqro.json
--log-file              override LOG_FILE
--browser               override BROWSER
--config                override miqro.json path
--port                  override PORT
--name                  override server name
--https                 serve with https
--https-key             path to server.key
--https-cert            path to server.cert
--https-redirect        http redirect port
--inflate-parallel      max parallel esbuild instances (default: 1)

environment variables:

PORT                    default: 8080
BROWSER                 default browser, none to disable
LOG_FILE                default: ./server.log
LOG_LEVEL               error | warn | info | debug | trace | none
LOG_LEVEL_<IDENTIFIER>  per-route log level
DB                      enable db features
DB_STORAGE              sqlite storage path (default: ./db.sqlite3)
DB_DIALECT              node:sqlite | sqlite3 | pg
DB_CONNECTION           connection url (postgres)
CLEAR_JSX_CACHE         clear esbuild cache (default: 1)
JSX_TMP                 esbuild tmp dir (default: /tmp/jsx_tmp)
CLUSTER_COUNT           number of cluster workers

build

npm install
npm run build