mrkeyoor.com_
Sat 08 Aug 21:56 UTC
npmWeb Backendupdated 08 Aug 2026

micro

micro is a thin Node.js HTTP service wrapper from Vercel. You export one request handler, and its CLI opens a TCP port, Unix socket, or Windows named pipe; the handler can return a value or write directly to the standard Node response. Helpers buffer JSON, text, and binary bodies, send typed responses, and turn thrown errors into HTTP status codes. It intentionally has no router or middleware system and is designed for containerized single-purpose services, not Vercel serverless functions.

Verdict

micro is still understandable and useful for an existing one-handler container, but its frozen release, separate development tool, and missing web-framework basics make it a poor default for a new service. Native Node HTTP is nearly as direct, while Hono, Fastify, or Koa add the application structure most teams soon need.

API stability4/5The public surface is tiny and has barely moved: one handler contract plus `serve`, `run`, `send`, `sendError`, `createError`, `buffer`, `text`, and `json`. Version 10 retained the familiar CommonJS usage while its CLI learned to load a default export. That frozen shape is predictable for existing code, although ambiguous return semantics and minor README-to-type mismatches weaken the contract for new TypeScript consumers.
Docs3/5The README covers CLI endpoints, body limits, response types, error composition, programmatic use, and testing with runnable examples. It is also unusually clear that micro is for containers rather than serverless. Some details no longer match version 10 source: development stack responses are described but not implemented by `sendError`, and `createError` examples omit the original Error required by the declaration. There is no separate maintained reference site.
Maintenance2/5The repository is not archived and was pushed in May 2026, with five open issues when pull requests are excluded. However, the 2026 change only switched the repository package manager, the previous commit was a documentation edit in 2024, and the latest npm release remains 10.0.1 from November 2022. Exact-pinned runtime dependencies and documentation drift make this look like low-touch maintenance, not an evolving server framework.
Ecosystem3/5The package recorded 3,398,893 downloads in the measured week and its repository has 10,622 stars, evidence of a large installed base and historical influence. It works with standard Node HTTP tooling and handler wrappers because it does not invent request objects. The deliberate lack of routing and middleware also caps the ecosystem: integrations live as independent wrapper functions, and development depends on the separate micro-dev project.

Use it if

  • You want one small HTTP handler per container and prefer standard Node request and response objects
  • You need a CLI that can listen on TCP, Unix sockets, or Windows named pipes with almost no framework setup
  • You want opt-in body parsing with a default 1 MB limit and cached raw request bodies
  • You are maintaining an existing micro 10 service whose composition model is already understood
Skip it if

Setup reality

Install `micro` and export a handler from the file named by `package.json` `main`, from `index.js`, or from an explicit CLI argument. Version 10.0.1 requires Node 16 or newer and includes TypeScript declarations, three pinned runtime dependencies, and a `micro` binary. The CLI defaults to `0.0.0.0:3000`; `-l tcp://host:port`, `-l unix:/path.sock`, and Windows `pipe:` endpoints are supported, and repeated `-l` flags open multiple listeners. Shell substitution in `micro -l tcp://0.0.0.0:$PORT` is a shell feature, so JSON scripts need platform-aware quoting and the README's fallback syntax only works in Bash. The project calls the CLI production-only and directs local work to the separate `micro-dev` tool, adding another package and workflow. Body parsing is deliberately opt-in. `json`, `text`, and `buffer` aggregate the request with a 1 MB default cap, cache the first raw body in a WeakMap, return 413 when the cap is exceeded, and return 400 for invalid input. They do not validate the resulting JSON; the TypeScript return type is `unknown`. A handler returning `null` produces 204, returning another value sends it, and returning `undefined` means you must eventually end the response yourself. Streams are piped as octet-stream, but the README makes stream error handling your responsibility. There is no router, middleware stack, request schema, access logging, CORS policy, graceful timeout policy, or TLS configuration. Compose those concerns around the handler or put them in a proxy. The README and published source also disagree in small but important places, so use the version 10 declarations and source as the final authority.

Patterns

Return a response from a CommonJS handlerexport-basic-handler

// index.js
module.exports = async (req, res) => {
  return { ok: true, method: req.method };
};

An object return value is serialized as JSON with status 200. The CLI uses `package.json` `main` first, then `index.js`.

Use an ESM default exportexport-esm-handler

// service.mjs
export default async function handler(req, res) {
  return 'ready';
}

// start with: micro service.mjs

The version 10 CLI dynamically imports the entry file and reads its default export; a named export alone is rejected.

Parse and validate a bounded JSON bodyparse-json-body

const { createError, json } = require('micro');

module.exports = async (req) => {
  const body = await json(req, { limit: '64kb' });
  if (!body || typeof body !== 'object' || !('email' in body)) {
    throw createError(422, 'email is required', new Error('invalid payload'));
  }
  return { accepted: body.email };
};

`json()` returns `unknown` in the declarations and only parses syntax. Schema validation is your responsibility.

Reuse the cached request body in two formsread-text-and-buffer

const { buffer, text } = require('micro');

module.exports = async (req) => {
  const raw = await buffer(req, { limit: 1024 * 1024 });
  const value = await text(req);
  return { bytes: raw.length, preview: value.slice(0, 40) };
};

The first helper buffers the stream and caches it by request, so later body-helper calls do not try to consume the stream again.

Set an explicit status and response bodysend-status-response

const { send } = require('micro');

module.exports = async (req, res) => {
  if (req.method !== 'POST') {
    res.setHeader('Allow', 'POST');
    send(res, 405, { error: 'method not allowed' });
    return;
  }
  send(res, 202, { queued: true });
};

After calling `send`, return `undefined`; returning another value would make micro attempt a second response.

Use the return-value contract deliberatelyreturn-no-content

module.exports = async (req, res) => {
  if (req.method === 'DELETE') return null; // 204
  if (req.method === 'HEAD') {
    res.statusCode = 200;
    res.end();
    return undefined;
  }
  return 'ok';
};

`null` means 204, a defined value is sent, and `undefined` tells micro that your code owns response completion.

Attach an HTTP status to a thrown errorthrow-http-error

const { createError } = require('micro');

async function requireUser(req) {
  try {
    return await authenticate(req);
  } catch (cause) {
    throw createError(401, 'unauthorized', cause);
  }
}

The shipped version 10 TypeScript signature requires the original Error as the third argument, even though older README examples omit it.

Wrap a handler with custom error outputcompose-error-handler

const { send } = require('micro');

const withErrors = (handler) => async (req, res) => {
  try {
    return await handler(req, res);
  } catch (error) {
    console.error(error);
    send(res, error.statusCode || 500, { error: error.message });
  }
};

module.exports = withErrors(async () => {
  throw new Error('database unavailable');
});

A wrapper is the middleware substitute. Do not expose stack traces or internal messages to clients in production.

Send a readable stream safelystream-response

const { createReadStream } = require('node:fs');
const { send } = require('micro');

module.exports = async (req, res) => {
  const stream = createReadStream('/srv/files/report.pdf');
  stream.on('error', (error) => {
    console.error(error);
    if (!res.headersSent) res.statusCode = 500;
    res.destroy(error);
  });
  res.setHeader('Content-Type', 'application/pdf');
  send(res, 200, stream);
};

micro pipes streams but does not install your stream error policy; the README explicitly leaves that responsibility to the handler.

Mount a micro handler on a Node serverserve-programmatically

const http = require('node:http');
const { serve } = require('micro');

const server = http.createServer(serve(async (req) => ({
  path: req.url,
  uptime: process.uptime(),
})));

server.listen(3000);
for (const signal of ['SIGTERM', 'SIGINT']) {
  process.once(signal, () => server.close(() => process.exit(0)));
}

Programmatic use gives you control over server timeouts and shutdown. `serve()` itself only adapts the async handler to a Node request listener.

Select a CLI listen endpointlisten-on-endpoint

micro -l tcp://127.0.0.1:4000 service.js
micro -l unix:/tmp/orders.sock service.js
# Bash-only environment fallback:
micro -l tcp://0.0.0.0:${PORT-3000} service.js

The `${PORT-3000}` fallback is Bash syntax. Use a cross-platform script or explicit environment handling when developers run other shells.

Alternatives

PackageRegistryPick it when
fastifynpmYou want routes, plugins, request schemas, validation, and structured logging in a performance-focused Node server
hononpmYou want a small router that can target Node, edge runtimes, and serverless platforms
koanpmYou like a minimal Node core but still need a mature async middleware composition model
expressnpmCompatibility, middleware availability, and familiar routing matter more than a single-function design