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

@lark-apaas/http-client

v0.1.2

Published

HTTP client with Axios-style interceptors, built on native fetch API

Readme

@lark-apaas/http-client

技术栈无关的 HTTP 客户端,基于原生 fetch API,拦截器模式遵循 Axios 标准。

特性

  • 零依赖: 基于 Node.js 原生 fetch API (Node 18+)
  • 技术栈无关: 可在任何 Node.js 环境使用(NestJS, Express, Koa, 纯 Node)
  • Fetch 风格 API: 返回标准 Response 对象,原生支持流式响应
  • Axios 风格拦截器: 拦截器模式与 Axios 完全一致,AI 模型和开发者都熟悉
  • 流式响应支持: 完美支持 SSE、OpenAI Streaming API、大文件下载等场景
  • 完整类型支持: 100% TypeScript 编写
  • 请求超时: 支持配置请求超时时间
  • 请求取消: 支持 AbortController,可与超时结合使用
  • Query Params: 自动处理查询参数
  • baseURL 支持: 支持配置基础 URL
  • 平台 JWT 鉴权: 可选启用 AK/SK → JWT 方案,自动注入 Authorization 与 x-api-key
  • JWT 缓存: 自动缓存 token,在过期前自动刷新,支持多租户和多用户
  • 安全特性: 敏感信息脱敏、协议白名单、响应体大小限制

安装

npm install @lark-apaas/http-client

或使用 yarn:

yarn add @lark-apaas/http-client

环境要求

  • Node.js >= 18.0.0 (需要原生 fetch 支持)

快速开始

基础使用

import { HttpClient } from '@lark-apaas/http-client';

const client = new HttpClient({
  baseURL: 'https://api.example.com',
  timeout: 5000,
});

// GET 请求
const response = await client.get('/users');
const data = await response.json();
console.log(data);

// POST 请求
const newUserResponse = await client.post('/users', {
  name: 'John Doe',
  email: '[email protected]',
});
const newUser = await newUserResponse.json();
console.log(newUser);

响应格式

所有请求都返回标准的 Response 对象(原生 fetch API):

const response = await client.get('/users');

// 响应属性
console.log(response.status);      // HTTP 状态码 (例如: 200)
console.log(response.ok);          // 布尔值,状态码 200-299 为 true
console.log(response.statusText);  // 状态文本 (例如: "OK")
console.log(response.headers);     // Headers 对象

// 解析响应体
const jsonData = await response.json();        // 解析 JSON
const textData = await response.text();        // 解析文本
const blobData = await response.blob();        // 解析 Blob
const arrayBuffer = await response.arrayBuffer(); // 解析 ArrayBuffer

流式响应支持

Response 对象原生支持流式读取,适用于 SSE、实时 API、大文件下载等场景:

// OpenAI Streaming API 示例
const response = await client.post('/v1/chat/completions', {
  model: 'gpt-4',
  messages: [{ role: 'user', content: 'Hello!' }],
  stream: true,
});

const reader = response.body!.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  const chunk = decoder.decode(value);
  console.log(chunk); // 实时输出每个数据块
}
// Server-Sent Events (SSE) 示例
const response = await client.get('/events');
const reader = response.body!.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  const text = decoder.decode(value);
  const events = text.split('\n\n');

  for (const event of events) {
    if (event.startsWith('data: ')) {
      const data = JSON.parse(event.slice(6));
      console.log('Event:', data);
    }
  }
}
// 大文件下载进度示例
const response = await client.get('/large-file.zip');
const contentLength = parseInt(response.headers.get('content-length') || '0');
const reader = response.body!.getReader();

let receivedLength = 0;
const chunks: Uint8Array[] = [];

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  chunks.push(value);
  receivedLength += value.length;

  const progress = (receivedLength / contentLength) * 100;
  console.log(`Downloaded: ${progress.toFixed(2)}%`);
}

const blob = new Blob(chunks);
console.log('Download complete!', blob.size);

请求取消

http-client 支持通过 AbortController 来取消请求,这与原生的 fetch API 和新版 axios 保持一致。

你可以在请求配置中传入一个 AbortSignal。当该 signal 被触发时,请求会被中断。

import { HttpClient } from '@lark-apaas/http-client';

const client = new HttpClient();
const controller = new AbortController();

async function fetchWithCancellation() {
  try {
    const promise = client.get('https://api.example.com/long-running-task', {
      signal: controller.signal,
    });

    // 2秒后取消请求
    setTimeout(() => controller.abort(), 2000);

    const response = await promise;
    const data = await response.json();
    console.log('Response:', data);
  } catch (error) {
    // HttpError 的 message 会是 'Request aborted'
    console.error('Error:', error.message);
  }
}

fetchWithCancellation();

该机制可以与 timeout 配置结合使用,任何一个先触发(超时或外部取消)都会中断请求。

拦截器

拦截器模式完全遵循 Axios 标准,API 与 Axios 一致。

请求拦截器

请求拦截器接收并返回完整的 RequestConfig 对象:

// 添加请求拦截器
const requestInterceptorId = client.interceptors.request.use(
  (config) => {
    // 在请求发送前修改配置
    config.headers = config.headers || {};
    config.headers['Authorization'] = `Bearer ${getToken()}`;
    config.headers['X-Request-ID'] = generateRequestId();
    return config;
  },
  (error) => {
    // 处理请求错误
    console.error('Request error:', error);
    return Promise.reject(error);
  }
);

// 移除拦截器
client.interceptors.request.eject(requestInterceptorId);

响应拦截器

响应拦截器接收并返回标准的 Response 对象:

// 添加响应拦截器
client.interceptors.response.use(
  (response) => {
    // 处理响应
    console.log(`Response status: ${response.status}`);

    // 可以直接返回 Response,让业务代码自行解析
    return response;
  },
  async (error) => {
    // 处理响应错误
    if (error.response) {
      // 请求已发出,服务器返回错误状态码
      const status = error.response.status;

      if (status === 401) {
        // 处理未授权
        redirectToLogin();
      } else if (status === 500) {
        // 处理服务器错误
        const errorData = await error.response.json();
        console.error('Server error:', errorData);
      }
    } else {
      // 网络错误或请求超时
      console.error('Network error:', error.message);
    }

    return Promise.reject(error);
  }
);

完整示例

在 NestJS 中使用

import { Injectable } from '@nestjs/common';
import { HttpClient } from '@lark-apaas/http-client';

@Injectable()
export class ApiService {
  private client: HttpClient;

  constructor() {
    this.client = new HttpClient({
      baseURL: process.env.API_BASE_URL,
      timeout: 10000,
    });

    // 配置请求拦截器
    this.client.interceptors.request.use(
      (config) => {
        config.headers = config.headers || {};
        config.headers['X-Service-Name'] = 'my-service';
        return config;
      }
    );

    // 配置响应拦截器
    this.client.interceptors.response.use(
      (response) => response,
      async (error) => {
        if (error.response?.status === 401) {
          // 刷新 token 并重试
          await this.refreshToken();
          return this.client.request(error.config);
        }
        return Promise.reject(error);
      }
    );
  }

  async getUsers() {
    const response = await this.client.get('/users');
    return response.json();
  }

  async createUser(userData: any) {
    const response = await this.client.post('/users', userData);
    return response.json();
  }
}

在纯 Node.js 中使用

import { HttpClient } from '@lark-apaas/http-client';

const client = new HttpClient({
  baseURL: 'https://jsonplaceholder.typicode.com',
  timeout: 5000,
  headers: {
    'User-Agent': 'MyApp/1.0',
  },
});

// 添加日志拦截器
client.interceptors.request.use((config) => {
  console.log(`[Request] ${config.method} ${config.url}`);
  return config;
});

client.interceptors.response.use(
  (response) => {
    console.log(`[Response] ${response.status} ${response.statusText}`);
    return response;
  },
  (error) => {
    console.error(`[Error] ${error.message}`);
    return Promise.reject(error);
  }
);

async function main() {
  try {
    // GET 请求
    const usersResponse = await client.get('/users', {
      params: { page: 1, limit: 10 }
    });
    const users = await usersResponse.json();
    console.log('Users:', users);

    // POST 请求
    const newPostResponse = await client.post('/posts', {
      title: 'Hello World',
      body: 'This is a test post',
      userId: 1,
    });
    const newPost = await newPostResponse.json();
    console.log('New post:', newPost);
  } catch (error) {
    console.error('Request failed:', error);
  }
}

main();

平台 JWT 鉴权

在平台内部署的服务可以开启内置 JWT 鉴权能力,SDK 会:

  • 读取 apaas_ak / apaas_sk(名称可通过配置覆盖)
  • 使用 HS256 生成短期 JWT(默认 30 分钟有效)
  • 自动在请求头中注入 Authorization: Bearer <token>x-api-key
  • 自动缓存 token,在过期前 5 分钟自动刷新(可配置)

启用方式

const client = new HttpClient({
  platform: {
    enabled: true,
    domainEnv: 'FORCE_AUTHN_INNERAPI_DOMAIN', // 默认即可省略
    refreshBeforeMs: 5 * 60 * 1000, // 可选:过期前多久刷新(默认 5 分钟)
    defaultClaims: {
      tenant_id: 1001,
      app_id: 'demo-app',
      app_env: 'prod',
    },
  },
});

// baseURL 将默认为 process.env.FORCE_AUTHN_INNERAPI_DOMAIN
await client.get('/secure-resource');

也可以在单次请求中覆写 platformAuth,用于注入当前用户等动态信息:

await client.post('/secure-resource', body, {
  platformAuth: {
    customClaims: {
      user_id: currentUserId,
    },
  },
});

⚠️ 默认情况下该能力关闭;若 AK/SK 缺失或 JWT 生成失败,会直接抛错阻断请求。 注意:平台默认/请求级 customClaims 禁止设置 access_key,该字段始终由 SDK 根据 AK 自动填充。

性能优化:Token 缓存

平台插件会自动缓存生成的 JWT token,避免每次请求都重新生成:

  • 缓存策略:基于 claims 的组合(支持多租户和多用户场景)
  • 自动刷新:在 token 过期前 5 分钟自动刷新(可通过 refreshBeforeMs 配置)
  • 多租户支持:不同 tenant_iduser_id 会生成不同的 token 并分别缓存
const client = new HttpClient({
  platform: {
    enabled: true,
    refreshBeforeMs: 3 * 60 * 1000, // 过期前 3 分钟刷新
  },
});

安全特性

严格模式

启用严格模式后,会自动开启所有安全检查:

const client = new HttpClient({
  security: {
    strictMode: true, // 启用严格模式
  },
});

严格模式包含:

  • URL 协议限制:仅允许 http:https:,防止 SSRF 攻击
  • 响应体大小限制:默认 50MB,防止内存耗尽攻击
  • 敏感信息自动脱敏:错误对象中的敏感 headers 会被替换为 [REDACTED]

自定义安全配置

您也可以单独配置各项安全特性:

const client = new HttpClient({
  security: {
    allowedProtocols: ['https:'], // 仅允许 HTTPS
    maxResponseSize: 10 * 1024 * 1024, // 限制 10MB
  },
});

敏感信息保护

HttpError 对象会自动清理敏感的 headers,保护您的凭证安全:

try {
  await client.get('/api', {
    headers: {
      Authorization: 'Bearer secret-token-12345',
      'x-api-key': 'my-secret-api-key',
    },
  });
} catch (error) {
  console.log(error.config.headers.Authorization); // '[REDACTED]'
  console.log(error.config.headers['x-api-key']); // '[REDACTED]'
  // 其他非敏感 headers 保持不变
}

以下 headers 会被自动脱敏:

  • Authorization
  • x-api-key
  • Cookie
  • x-secret

API 文档

HttpClient

构造函数

new HttpClient(config?: HttpClientConfig)

请求方法

所有请求方法都返回 Promise<Response>(标准 fetch Response 对象):

// GET 请求
client.get(url: string, config?: RequestConfig): Promise<Response>

// POST 请求
client.post(url: string, data?: any, config?: RequestConfig): Promise<Response>

// PUT 请求
client.put(url: string, data?: any, config?: RequestConfig): Promise<Response>

// DELETE 请求
client.delete(url: string, config?: RequestConfig): Promise<Response>

// PATCH 请求
client.patch(url: string, data?: any, config?: RequestConfig): Promise<Response>

// 通用请求
client.request(config: RequestConfig): Promise<Response>

拦截器

// 请求拦截器
client.interceptors.request.use(
  onFulfilled?: (config: RequestConfig) => RequestConfig | Promise<RequestConfig>,
  onRejected?: (error: any) => any
): number

// 响应拦截器
client.interceptors.response.use(
  onFulfilled?: (response: Response) => Response | Promise<Response>,
  onRejected?: (error: any) => any
): number

// 移除拦截器
client.interceptors.request.eject(id: number): void
client.interceptors.response.eject(id: number): void

RequestConfig

interface RequestConfig extends RequestInit {
  url?: string;
  params?: Record<string, any>;  // Query 参数
  timeout?: number;               // 超时时间(毫秒)
  baseURL?: string;               // 基础 URL
  platformAuth?: {
    customClaims?: {
      tenant_id?: number;
      user_id?: string;
      app_id?: string;
      app_env?: string;
      sandbox_id?: string;
    };
  };
}

interface SecurityConfig {
  /**
   * 允许的 URL 协议白名单
   * 默认: null (不限制)
   * 设置为 ['http:', 'https:'] 可限制只允许 HTTP/HTTPS
   */
  allowedProtocols?: string[] | null;

  /**
   * 最大响应体大小(字节)
   * 默认: 0 (不限制)
   */
  maxResponseSize?: number;

  /**
   * 是否启用严格模式(启用所有安全检查)
   * 默认: false
   * 启用后会自动设置:
   * - allowedProtocols = ['http:', 'https:']
   * - maxResponseSize = 50MB
   */
  strictMode?: boolean;
}

interface HttpClientConfig extends RequestConfig {
  platform?: {
    enabled?: boolean;            // 开启后自动注册平台插件
    baseURL?: string;             // 直接指定平台域名
    domainEnv?: string;           // 域名环境变量(默认 FORCE_AUTHN_INNERAPI_DOMAIN)
    accessKeyEnv?: string;        // AK 环境变量名称(默认 FORCE_AUTHN_ACCESS_KEY)
    secretKeyEnv?: string;        // SK 环境变量名称(默认 FORCE_AUTHN_ACCESS_SECRET)
    accessKey?: string;           // 直接传入 AK(多用于本地测试)
    secretKey?: string;           // 直接传入 SK(多用于本地测试)
    expireTimeMs?: number;        // JWT 过期时间,默认 30 分钟
    refreshBeforeMs?: number;     // Token 刷新时机,默认过期前 5 分钟
    defaultClaims?: {             // 默认 Claim,可被每次请求覆盖
      tenant_id?: number;
      user_id?: string;
      app_id?: string;
      app_env?: string;
      sandbox_id?: string;
    };
  };
  security?: SecurityConfig;      // 安全配置
}

HttpError

class HttpError extends Error {
  response: Response | undefined;      // 响应对象(如果有)
  config: SafeRequestConfig;           // 已清理敏感信息的请求配置
}

注意:为了安全考虑,error.config 中的敏感 headers(如 Authorization, x-api-key, Cookie)会被自动替换为 [REDACTED]

设计理念

为什么采用 Fetch 风格的响应格式?

  1. 原生支持流式响应: Response.bodyReadableStream,完美支持 SSE、实时 API、大文件下载
  2. Web 标准: 遵循 WHATWG Fetch API 标准,与浏览器和 Deno 行为一致
  3. 更灵活: 业务代码可根据需要选择 .json(), .text(), .blob(), .arrayBuffer()
  4. 社区趋势: 现代 HTTP 客户端(ky, ofetch, undici)都采用 fetch 风格
// Fetch 风格 - 灵活且原生支持流
const response = await client.get('/data');
const data = await response.json();  // 或 .text(), .blob(), .arrayBuffer()

// 流式读取(Axios 风格无法原生支持)
const reader = response.body!.getReader();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  console.log(value); // 实时处理数据块
}

为什么遵循 Axios 的拦截器模式?

  1. 最广泛使用: Axios 是最流行的 HTTP 客户端,几乎所有开发者都熟悉
  2. AI 模型友好: 训练数据中有大量 Axios 使用示例,模型更容易生成正确的代码
  3. 清晰的职责分离: 请求拦截器、响应拦截器、错误处理分别处理
  4. 成熟可靠: 经过多年生产环境验证的模式
  5. 可预测: 开发者知道拦截器的执行顺序和行为

为什么基于 fetch 而不是 axios?

  1. 零依赖: 使用 Node.js 原生 API,无需额外依赖
  2. 更小体积: 代码体积小,打包后更轻量
  3. 技术栈无关: 不依赖特定框架,可在任何环境使用
  4. 现代化: fetch 是 Web 标准,未来趋势

与社区其他方案的对比

| 方案 | 响应风格 | 流式支持 | 拦截器模式 | 依赖 | 体积 | 熟悉度 | |------|---------|---------|-----------|------|------|--------| | @lark-apaas/http-client | Fetch | ✅ 原生 | Axios 风格 | 零依赖 | 小 | 高(Axios API) | | axios | Axios | ❌ 需封装 | Axios 风格 | 有依赖 | 35.6KB | 最高 | | ky | Fetch | ✅ 原生 | Hooks 风格 | 零依赖 | ~6KB | 中 | | ofetch | Fetch | ✅ 原生 | 简单 | 零依赖 | ~4KB | 中 | | undici | Fetch | ✅ 原生 | 简单 | Node专用 | 大 | 中 | | wretch | Fetch | ✅ 原生 | 中间件风格 | 零依赖 | ~2KB | 低 | | got | 自定义 | ⚠️ 部分 | Hooks 风格 | 有依赖 | 大 | 中 |

最佳实践

1. 创建专用的客户端实例

不要在每个请求中重新配置拦截器:

// ❌ 不好
async function fetchUser() {
  const client = new HttpClient();
  client.interceptors.request.use(/* ... */);
  const response = await client.get('/user');
  return response.json();
}

// ✅ 好
const apiClient = new HttpClient({
  baseURL: process.env.API_URL,
});

apiClient.interceptors.request.use(/* ... */);

async function fetchUser() {
  const response = await apiClient.get('/user');
  return response.json();
}

2. 统一错误处理

在响应拦截器中统一处理错误:

client.interceptors.response.use(
  (response) => response,
  async (error) => {
    // 统一错误日志
    logger.error('HTTP Error', {
      url: error.config?.url,
      status: error.response?.status,
      message: error.message,
    });

    // 统一错误转换
    throw new ApplicationError(error);
  }
);

3. 请求重试

使用拦截器实现请求重试:

const MAX_RETRIES = 3;

client.interceptors.response.use(
  (response) => response,
  async (error) => {
    const config = error.config;
    config.retryCount = config.retryCount || 0;

    if (config.retryCount < MAX_RETRIES && shouldRetry(error)) {
      config.retryCount += 1;
      await sleep(1000 * config.retryCount);
      return client.request(config);
    }

    return Promise.reject(error);
  }
);

4. 处理流式响应时的错误

流式响应需要特殊的错误处理:

async function streamData() {
  const response = await client.post('/stream', data);

  if (!response.ok) {
    // 即使是错误响应,也可能是流式的
    const errorText = await response.text();
    throw new Error(`Stream failed: ${errorText}`);
  }

  const reader = response.body!.getReader();
  const decoder = new TextDecoder();

  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;

      const chunk = decoder.decode(value);
      processChunk(chunk);
    }
  } catch (error) {
    // 处理流读取错误
    reader.cancel();
    throw error;
  }
}

从 Axios 风格迁移

如果你之前使用的是 response.data 风格的 API,迁移很简单:

// 旧代码(Axios 风格)
const response = await client.get('/users');
console.log(response.data);
console.log(response.status);

// 新代码(Fetch 风格)
const response = await client.get('/users');
const data = await response.json();  // 手动解析 JSON
console.log(data);
console.log(response.status);

拦截器中的变化:

// 旧代码(Axios 风格)
client.interceptors.response.use((response) => {
  console.log(response.data);  // data 已解析
  return response;
});

// 新代码(Fetch 风格)
client.interceptors.response.use((response) => {
  // response 是原生 Response 对象,未解析
  // 拦截器中通常不解析,让业务代码决定如何解析
  return response;
});

详见 TEST_MIGRATION.md 获取完整迁移指南。

许可证

MIT

关键字

  • http-client
  • fetch
  • axios
  • interceptor
  • typescript
  • technology-agnostic