express-like
v1.2.1
Published
express like api style
Maintainers
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.sendFile用fs自实现,不依赖send等包。 - ESM(
.mjs):request.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.query 由 query 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.range、req.fresh、req.stale、req.xhr、req.ip、req.ips、req.host、req.hostname、req.subdomains、req.protocol、req.secure、req.params、req.app、res.links、res.format、res.attachment、res.vary、res.render、res.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 | 设置头;val 为 null 或对象式中值为 null → 删除该头。 |
| res.cookie(name, value, options?) | 同步,返回 this | 设置 Set-Cookie;value 为 null/undefined → 清除 cookie(Expires=epoch、Max-Age=0)。 |
| res.redirect(url) / res.redirect(status, url) | 同步,返回 this | 设 Location + 状态码并 end(),不输出 body。 |
| res.send(body) | 同步,返回 this | 立即写出:string→html、number/boolean→html 字符串、Buffer→octet-stream、对象→json;204/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.secret,s: + HMAC-SHA256 签名)、expires、path、domain、secure、httpOnly、sameSite(true → Lax)、encode、overwrite(先移除同名 Set-Cookie)、priority、partitioned、comment。
实现备注
- 所有自定义 getter / method 均以「对象」形式收集(
reqGetters/reqMethods、responseGetters/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 超时 |
