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

express-like

v1.2.1

Published

express like api style

Readme

express-like —— 轻型 HTTP 辅助接口

一个混合了 Express/Hyper-Express API 风格、提供异步接口风格不依赖 Express 运行时零外部依赖的辅助库。

通过 init(req, res) 用「替换原型链」的方式,把白名单 API 挂到任意原生 http.IncomingMessage / http.ServerResponse 上(即 Express 对 app.request / app.response 的思路,但直接作用于你传入的对象)。

  • 异步风格:请求体读取(req.json / req.text / req.buffer / req.form)与文件发送(res.sendFile)均为 async / 返回 Promise
  • 零外部依赖:仅使用 node: 内置模块;res.sendFilefs 自实现,不依赖 send 等包。
  • ESM(.mjsrequest.mjs(req 原型)、response.mjs(res 原型)、init.mjs(入口)。
  • 可扩展:所有自定义 getter / method 以对象形式收集后遍历挂到原型,按需自行增补。

基础使用

import http from 'node:http';
import init from 'express-like';

const SERVER_HOST = process.env.SERVER_HOST || '0.0.0.0';
const SERVER_PORT = process.env.SERVER_PORT || 80;

main();

async function main() {
  const server = http.createServer(async (req, res) => {
    init(req, res); // 仅替换原型链

    try {
      if (req.url === '/api') {
        const body = await req.json(); // 异步读 JSON body
        res.cookie('sid', 'abc', { httpOnly: true });
        res.json({ echo: body }); // 同步写出(内部调用 res.end)
        return;
      }
      // 其他路由 …
    } catch (err) {
      res.status(500).json({ error: err.message });
    }
  });

  const { promise, resolve, reject } = Promise.withResolvers();
  server.listen(SERVER_PORT, SERVER_HOST, resolve);
  server.on('error', reject);
  await promise;
  console.log(`server start at http://${SERVER_HOST}:${SERVER_PORT}`);
}

调用 init(req, res) 之后,req / res 即拥有下方「本库提供的 API」里的所有属性与方法。

扩展属性

import { request, response, defineLazyGetters } from 'express-like';

// 扩展计算属性
defineLazyGetters(request, {
  proto() {
    return this.headers['x-forwarded-proto'] || this.headers['x-client-proto'] || 'http';
  },
  host() {
    let host = this.headers['x-forwarded-host'] || this.headers['host'] || `${SERVER_HOST}:${SERVER_PORT}`;
    if (host.endsWith(':80')) {
      host = host.slice(0, -3);
    }
    return host;
  },
  origin() {
    return `${this.proto}://${this.host}`;
  },
});

// 扩展属性
Object.assign(request, {
  // 修改请求体长度最大10k
  MAX_BODY_SIZE: 1024 * 10,

  // 新增req.fetchApi()方法
  async fetchApi(url, options) {
    const res = await await fetch(url, options)
    return await res.json()
  },
});

// 扩展 sendFile,支持 ETag + 304
import fs from 'node:fs';

const rawSendFile = response.sendFile;
Object.assign(response, {
  async sendFile(filePath) {
    const stat = await fs.promises.stat(filePath);
    const etag = `"${stat.mtime.getTime().toString(16)}"`;
    this.setHeader('ETag', etag);
    const reqEtag = this.req?.headers?.['if-none-match']?.replace(/^W\//, '');
    if (reqEtag === etag) {
      this.status(304).end();
      return;
    }
    return rawSendFile.call(this, filePath);
  },
});

调用 init(req, res) 之后,req / res 即拥有下方「本库提供的 API」里的所有属性与方法。


与 Express 的差异

| 维度 | 本库 | Express | | --------------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | 运行时 | 不依赖 Express,自己 init(req, res) | 由 Express app/router 驱动,含中间件、路由、视图引擎 | | 模块系统 | ESM(.mjs),零外部依赖 | CommonJS,依赖 send/cookie/mime-types 等 | | init | 仅 Object.setPrototypeOf不 return、不互设 req.res/res.req | app.request/app.response 为原型,请求处理时互设 req.res/res.req | | 请求体解析 | req.json()/text()/buffer()/form()异步方法,直接读流 | body 解析由 express.json()/express.urlencoded()/multer 等中间件完成(req.body 同步可用) | | res.sendFile | 自实现 fs 流式(无 send 包),async 返回 Promise | 基于 send 包,回调式 (err) => {} | | res.send 等 | 经类型判断后直接调用原生 res.end() 写出 | 内部同样调用 end,但还附带视图、res.format 等机制 | | 内容协商 | req.accepts* / res.format / req.range / req.fresh / req.xhr | 均提供 | | 请求元信息 | req.params / req.ip / req.ips / req.hostname / req.subdomains / req.protocol / req.secure | 均提供(依赖 trust proxy 配置) | | res.render / res.locals | (白名单不含视图) | 提供 | | req.app / res.req / req.res | (未互设) | 提供 | | res.cookie 签名 | signed 必须显式传 options.secret(无 req.secret) | 默认读 req.secret(来自 cookie-parser) | | req.query/path/cookies/search | lazy getter,首次访问后缓存为实例属性 | Express 为每次计算的 getter / req.queryquery parser 解析 | | req.form | 仅 urlencoded;multipart 直接抛错 | express.urlencoded() 仅 urlencoded;multipart 用 multer | | HEAD 处理 | res.sendFile 已不做 req/HEAD 校验(因 res.req 未设) | res.sendFile 等按 req.method === 'HEAD' 跳过 body |

刻意裁剪

白名单之外的 Express API 均不提供:req.accepts*req.rangereq.freshreq.stalereq.xhrreq.ipreq.ipsreq.hostreq.hostnamereq.subdomainsreq.protocolreq.securereq.paramsreq.appres.linksres.formatres.attachmentres.varyres.renderres.locals 等。需要时请自行在 req / res 原型上按相同方式扩展。


API 参考

本库提供的 API

Request(req

| API | 类型 | 说明 | | ----------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | req.params | 属性(lazy getter) | 默认为 {}首次访问后缓存为实例属性,之后读为 O(1)。 | | req.cookies | 属性(lazy getter) | 从 Cookie 头解析为对象;无则 {}首次访问后缓存为实例属性,之后读为 O(1)。 | | req.path | 属性(lazy getter) | URL pathname(不含查询串);首次访问后缓存。 | | req.query | 属性(lazy getter) | 查询串解析为对象;首次访问后缓存。 | | req.search | 属性(lazy getter) | 查询串部分(如 ?foo=bar);无则空串;首次访问后缓存。 | | req.json() | async | 读请求体一次并 JSON.parse;空体返回 undefined,非法 JSON 抛错。 | | req.text() | async | 读请求体一次返回 utf8 字符串。 | | req.buffer() | async | 读请求体一次返回 Buffer。超 request.MAX_BODY_SIZE(默认 1MB)则 reject 并 destroy。 | | req.form() | async | 仅处理 application/x-www-form-urlencoded(用 querystring.parse);遇到 multipart/form-data 直接抛错。 |

请求体读取:以上 4 个 body 方法共享同一次读取(实例上缓存的 Promise),幂等;空体或已被消费时解析为空 Buffer

大小限制request.MAX_BODY_SIZE 默认 1MB(1024 * 1024),超限抛错并 destroy();设为 0 不限制。

Response(res

| API | 类型 | 说明 | | ------------------------------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------- | | res.headers | 属性(getter) | 每次返回 res.getHeaders() 的快照(Node 的 kOutHeaders 不可外部引用,故走 getHeaders)。 | | res.cookies | 属性(getter) | 已通过 res.cookie() 排入的 cookie 只读快照(实例上的 res._cookies)。 | | res.status(code) | 同步,返回 this | 设置状态码。 | | res.type(type) | 同步,返回 this | 设置 Content-Type(扩展名或完整 mime)。 | | res.header(field, val) / res.header(obj) | 同步,返回 this | 设置头;valnull 或对象式中值为 null → 删除该头。 | | res.cookie(name, value, options?) | 同步,返回 this | 设置 Set-Cookievaluenull/undefined → 清除 cookie(Expires=epochMax-Age=0)。 | | res.redirect(url) / res.redirect(status, url) | 同步,返回 this | 设 Location + 状态码并 end()不输出 body。 | | res.send(body) | 同步,返回 this | 立即写出:string→htmlnumber/boolean→html 字符串Buffer→octet-stream、对象→json204/304 去 body。 | | res.html(body) | 同步,返回 this | text/html 快捷。 | | res.json(obj) | 同步,返回 this | application/json 快捷。 | | res.text(body) | 同步,返回 this | text/plain 快捷。 | | res.buffer(body) | 同步,返回 this | application/octet-stream 快捷。 | | res.charset | 属性,默认 utf-8 | Content-Type 字符串拼接时使用的 charset;由 res.type() 内部读取。 | | res.sendFile(path) | async,返回 Promise | 流式发送文件;完成 resolve、出错 reject(ENOENT → 404);按扩展名推断 Content-Type。 |

res.cookie 的 options(参照 Express):maxAge(ms,自动转秒并设 Expires)、signed(需 options.secrets: + HMAC-SHA256 签名)、expirespathdomainsecurehttpOnlysameSitetrue → Lax)、encodeoverwrite(先移除同名 Set-Cookie)、prioritypartitionedcomment

实现备注

  • 所有自定义 getter / method 均以「对象」形式收集(reqGetters/reqMethodsresponseGetters/responseMethods),再遍历挂到原型。
  • req.cookies / req.path / req.query / req.search 为 lazy getter:首次访问计算并把结果写成实例上的普通属性,后续读取不再重算。
  • res.cookies 来自实例上的 res._cookies 对象(由 res.cookie() 写入)。
  • res.send / json / html / text / buffer 经类型判断后直接调用原生 res.end() 写出;res.redirect 设头后 end()无 body
  • res.sendFile 流式 pipe 到响应,完成 resolve / 出错 reject。

常用的 Node 原生 API(与本库不重复的)

以下仅列出功能未被本库覆盖的 Node 原生 API。已被本库覆盖的(如 setHeader / getHeader / getHeaders / removeHeader / appendHeader / statusCode / end / write 等,已分别由本库的 res.header / res.headers / res.status / res.send 等提供)不再重复列出。

Request(继承自 http.IncomingMessage

| API | 说明 | | ------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | req.method | HTTP 方法(GET/POST…) | | req.headers | 请求头对象(原生,本库不重新实现) | | req.url | 原始请求 URL(含查询串) | | req.httpVersion | HTTP 版本 | | req.socket / req.connection | 底层 TCP socket | | req.complete / req.readableEnded | 请求体是否已接收完 | | req.on(...) / req.once(...) / req.pipe(...) / req.destroy() | Readable / EventEmitter 能力(内部 body 读取即基于 data / end 事件) |

Response(继承自 http.ServerResponse

| API | 说明 | | ------------------------ | ---------------- | | res.statusMessage | 状态文本 | | res.headersSent | 响应头是否已发出 | | res.flushHeaders() | 立即发送响应头 | | res.setTimeout(ms, cb) | 设置 socket 超时 |