用 Node.js 对接快递查询接口:原生 fetch 与 Express 代理
2026-07-26 · 阅读约 7 分钟
给后台、电商系统或小程序后端加一个「查快递」能力,用 Node.js 对接现成的快递查询接口是最省事的路子。
Node 18 起内置了全局 fetch,连 axios 都不用装。本文从最小请求讲到批量查询、TypeScript 类型,
再到最关键的一步——用 Express 包一层代理端点,把密钥牢牢留在服务端。
一、为什么 Node 18+ 不用再装 axios
过去在 Node 里发 HTTP 请求要装 axios 或 node-fetch;从 Node 18 起,fetch、Headers、
Response 都是全局可用的标准 API,写法和浏览器一致。除 Express 那节外,下面的示例只依赖 Node 18+ 自带能力。
先把密钥放进环境变量:
# .env —— 放在服务端,切勿提交仓库、切勿打包进前端
KUAIDI_PK=pk_xxx
KUAIDI_SK=sk_xxx
认证格式是 Bearer API_KEY:SECRET_KEY——把 pk_ 开头的 API Key 与
sk_ 开头的 Secret Key 用英文冒号拼接成一个字符串。
二、最小可用示例
查询接口是 POST /v1/tracking/trace,请求体里放一个 items 数组。先查单个运单号:
// chaxun.mjs —— Node 18+ 自带全局 fetch,无需安装 axios
const AUTH = 'Bearer ' + process.env.KUAIDI_PK + ':' + process.env.KUAIDI_SK;
async function chaxun(courierCode, trackingNumber) {
const res = await fetch('https://api.kuaidichaxunapi.com/v1/tracking/trace', {
method: 'POST',
headers: {
'Authorization': AUTH,
'Content-Type': 'application/json',
},
body: JSON.stringify({ items: [{ courierCode, trackingNumber }] }),
});
const json = await res.json();
return json.data.results[0];
}
const r = await chaxun('cainiao', 'CGD0000123456');
console.log(r.success ? r.data.deliveryStatusText : r.error.code); r.success 为 true 时读 r.data,为 false 时读 r.error。
deliveryStatusText 是已归一好的中文状态,可直接展示。
三、批量查询(一次最多 10 个)
items 一次最多放 10 个运单号。返回里除了 results,还有一个 summary,
汇总了 total(总数)、successful(成功)、failed(失败)、billable(实际计费条数):
// 一次最多 10 个运单号;成功条目计费,COURIER_PREPARING 与参数错误不计费
async function piliang(items) {
const res = await fetch('https://api.kuaidichaxunapi.com/v1/tracking/trace', {
method: 'POST',
headers: { 'Authorization': AUTH, 'Content-Type': 'application/json' },
body: JSON.stringify({ items }),
});
const json = await res.json();
return json.data; // { results, summary }
}
const data = await piliang([
{ courierCode: 'cainiao', trackingNumber: 'CGD0000123456' },
{ courierCode: 'zto', trackingNumber: 'ZT0000000001' },
]);
console.log(data.summary); // { total, successful, failed, billable }
计费只按成功查询计算:COURIER_PREPARING(该公司查询功能开发中)与参数类错误都不消耗额度,
看 summary.billable 就知道这批实际计费了几条。
四、加上 TypeScript 类型
用 TypeScript 的话,给返回值定义类型能少踩不少字段拼写的坑。成功与失败是两种结构, 用可辨识联合(discriminated union)最合适:
// 返回类型(简化版,够日常使用);成功与失败是两种结构
interface Progress {
dateTime: string;
location: string;
status: string;
statusCode: string;
description: string;
}
interface TraceOk {
success: true;
data: {
trackingNumber: string;
courierName: string;
deliveryStatus: string;
deliveryStatusText: string;
isDelivered: boolean;
dateDelivered: string | null;
progresses: Progress[];
};
}
interface TraceErr {
success: false;
error: { code: string; message: string; billable: boolean };
}
type TraceResult = TraceOk | TraceErr;
判断 success 之后,TypeScript 会自动把类型收窄到 TraceOk 或 TraceErr,
访问 data 或 error 都不会报错。
五、关键:用 Express 代理,密钥只留在服务端
Secret Key 一旦打包进前端(网页 JS、小程序包),任何人打开开发者工具或抓包都能读到,然后拿你的额度随便刷。 正确做法是:前端只调用你自己的后端,后端再带着密钥去请求快递 API。
// server.mjs —— npm i express;密钥只存在于服务端环境变量
import express from 'express';
const app = express();
app.use(express.json());
const AUTH = 'Bearer ' + process.env.KUAIDI_PK + ':' + process.env.KUAIDI_SK;
// 前端只调用这个端点,永远看不到 pk_ / sk_
app.post('/api/kuaidi', async (req, res) => {
const items = (req.body.items || []).slice(0, 10);
const upstream = await fetch('https://api.kuaidichaxunapi.com/v1/tracking/trace', {
method: 'POST',
headers: { 'Authorization': AUTH, 'Content-Type': 'application/json' },
body: JSON.stringify({ items }),
});
const json = await upstream.json();
res.json(json.data); // 只透传 data,不外泄 demoUsage 等信息
});
app.listen(3000); 前端这样调用,全程接触不到 pk_ / sk_:
// 浏览器 / 前端:只知道自家后端地址,从不接触密钥
const res = await fetch('/api/kuaidi', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items: [{ courierCode: 'cainiao', trackingNumber: 'CGD0000123456' }] }),
});
const { results } = await res.json(); 六、健壮性:网络异常与逐条失败分开处理
两类错误要分开:一类是网络层面(超时、5xx),用 try/catch 兜住;
另一类是单条运单查询失败,藏在 results 里逐条判断 success。
别让一条查不到就让整个请求崩掉:
// 把网络异常与逐条业务失败分开处理
async function anquanChaxun(items) {
let json;
try {
const res = await fetch('https://api.kuaidichaxunapi.com/v1/tracking/trace', {
method: 'POST',
headers: { 'Authorization': AUTH, 'Content-Type': 'application/json' },
body: JSON.stringify({ items }),
});
if (!res.ok) throw new Error('HTTP ' + res.status);
json = await res.json();
} catch (e) {
console.error('网络或服务异常:', e.message);
return [];
}
return json.data.results.map((r) => {
if (r.success) return { trackingNumber: r.data.trackingNumber, status: r.data.deliveryStatus };
// NOT_FOUND / COURIER_PREPARING / INVALID_TRACKING_NUMBER…
return { trackingNumber: r.error.trackingNumber, error: r.error.code };
});
} 对接的认证、字段与错误码细节见接口文档,各家快递的编码与运单号格式见快递公司页。 想先不写代码跑一遍,可以用首页的在线演示(公开测试密钥),或直接注册拿自己的密钥。