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

@i.un/nuxt-api

v2.0.3

Published

A robust, production-ready API client module for Nuxt 4. Designed for enterprise-grade authentication handling, SSR compatibility, and seamless token refreshing.

Readme

@i.un/nuxt-api

npm version npm downloads License Nuxt

面向 Nuxt 4 的生产级 API 客户端模块,专注于企业级鉴权处理、SSR 兼容与无感 Token 刷新。

A robust, production-ready API client module for Nuxt 4. Designed for enterprise-grade authentication handling, SSR compatibility, and seamless token refreshing.


特性 / Features

  • 🔐 智能 Token 刷新 / Smart Token Refresh

    • 自动拦截 401 并刷新 Token。 Automatic 401 interception and token refreshing.
    • 请求排队:避免并发触发多次刷新,挂起的请求在拿到新 Token 后自动重放。 Request Queueing: Prevents multiple refresh requests; pending requests retry automatically with the new token.
    • SSR 支持:在 SSR 与后端 API 之间正确透传 Cookie。 SSR Support: Perfectly handles cookie passing between SSR server and backend API.
    • 跨域 / 子域支持:自动识别并修正 Cookie 域,实现跨子域无感鉴权。 Cross-Domain & Subdomain Support: Auto-detects and fixes cookie domains for seamless cross-subdomain authentication.
  • 🧬 类型化路径绑定(可选)/ Typed Path Binding (optional)

    • 基于 openapi-typescript 生成的 paths 类型,把 URL → query/body/path/响应类型 一次性绑定到位。 Bind URL → query/body/path/response types directly from an openapi-typescript paths definition.
    • 路径模板(/users/{id})自动插值;当 spec 声明了 path 参数时,path 字段会变为必填。 Path templates (/users/{id}) auto-interpolated; path becomes required when the spec declares one.
    • URL 前缀和信封字段名可配置,库本身不假设任何项目约定。 Configurable URL prefix and envelope data key — zero project assumptions baked in.
  • 🛡 安全优先 / Security First

    • 尊重 HttpOnly Cookie。 Respects HttpOnly cookies.
    • 不会覆盖后端设置的安全标志,除非必要。 Does not overwrite backend security flags unless necessary.
  • 🛠 开发者友好 / Developer Friendly

    • 完整 TypeScript 支持。 Full TypeScript support.
    • 可配置的 Debug 日志。 Configurable Debug Logging.
    • 可选择返回完整信封或仅返回业务数据。 Return full envelope or just unwrapped data.

安装 / Installation

# npm
npm install @i.un/nuxt-api

# pnpm
pnpm add @i.un/nuxt-api

# yarn
yarn add @i.un/nuxt-api

配置 / Configuration

在 nuxt.config.ts 中注册模块:

Add the module to your nuxt.config.ts:

export default defineNuxtConfig({
  modules: ['@i.un/nuxt-api'],

  iunApi: {
    // 后端 API 地址 / Backend API URL
    apiBase: 'https://api.example.com',
    // 可选:SSR 专用地址(例如 Docker 内网)
    // Optional: Different URL for SSR (e.g. internal docker network)
    apiBaseServer: 'http://backend-service:8080',

    // 鉴权 / Authentication
    cookieName: 'access_token', // 默认值 / Default

    // 接口路径 / Endpoints
    endpoints: {
      // 刷新端点。设 false 禁用,或填路径(如 '/auth/refresh')。
      // 触发刷新的业务码 unauthorizedCode、token 提取容器 dataKey 在 useApi options 里配。
      // Refresh endpoint. false to disable, or a path (e.g. '/auth/refresh').
      // The trigger code (unauthorizedCode) and token container (dataKey) live in useApi options.
      refresh: false, // 默认 / default
    },

    // 跳转 / Redirects
    redirects: {
      // 设为 false 禁用跳转,或填入自定义登录路径(如 '/auth/login')
      // Set to false to disable redirect,
      // or provide a custom redirect path (e.g. '/auth/login')
      login: false, // 默认 / default
    },

    // 调试 / Debugging
    debug: process.env.NODE_ENV === 'development',
  },
});

使用 / Usage

1. 基础请求 / Basic Requests (useApi)

useApi 在 Nuxt 的 $fetch 之上封装了自动鉴权与错误处理,默认如实返回后端响应(不解包)。所有参数(query / body / path / headers / timeout …)统一放在调用 options 对象里。

useApi wraps Nuxt's $fetch with auto-auth and error handling, and returns the backend response as-is by default (no unwrapping). All knobs (query, body, path, headers, timeout, …) live in a single call options object.

<script setup lang="ts">
const api = useApi();

// GET 请求,默认如实返回(不解包);解包是 opt-in,见下方「类型化 API」
// GET request — returns as-is by default; unwrapping is opt-in (see Typed API).
const users = await api.get<User[]>('/users');

// POST 请求(body 放在 options 里)
// POST request (body in options).
await api.post('/posts', { body: { title: 'Hello' } });

// Query + 原生 $fetch 选项一并传入
// Query params + native $fetch options.
const profile = await api.get<Profile>('/profile', {
  query: { include: 'avatar' },
  headers: { 'X-Custom': 'value' },
  timeout: 5000,
});
</script>

2. 完整信封 vs 解包 / Full envelope vs unwrap

默认不解包 —— api.get(...) 直接拿到后端完整响应(含 code / message / data)。要让库自动剥出 data,在 wrapper 里显式开启解包(下一节);开启解包后仍想拿完整信封,就再建一个不解包的 wrapper。

No unwrap by default — api.get(...) gives you the full backend response (code / message / data). To have the library peel out data, opt in in your wrapper (next section); if you still need full envelopes after that, make a second no-unwrap wrapper.

信封字段名与码值都可在 useApi options 里配,默认 { code, message, data } + 成功码 0 / 未授权码 401:codeKey / messageKey / dataKey / successCode / unauthorizedCode。后端用别的约定时覆盖对应项即可。

Envelope field names and codes are all configurable in useApi options — defaulting to { code, message, data } with success 0 / unauthorized 401: codeKey / messageKey / dataKey / successCode / unauthorizedCode. Override whichever ones your backend differs on.

3. 类型化 API / Typed API with OpenAPI

用 openapi-typescript 生成 paths 类型,把它作为第一个泛型传给 useApi。endpoint 字符串、query / body / path、以及响应类型,全部从 spec 推断 —— 不再需要每次手写 <T>。

Generate a paths type with openapi-typescript, then pass it as the first generic to useApi. The endpoint string, query, body, path, and the response type all flow from the spec — no per-call <T> needed.

pnpm dlx openapi-typescript http://localhost:3000/api/openapi.json -o types/openapi.d.ts
// composables/useApi.ts —— 在项目里只定义一次的 wrapper
// composables/useApi.ts — your project's wrapper, defined once.
import type { paths } from '~/types/openapi';

// 泛型:<Paths, Prefix, DataKey>
//   Prefix  —— 从 spec 路径里剥掉的前缀(默认 '')
//   DataKey —— never(默认) = 不解包;传字段名(如 'data') = 解包该字段,
//              且 options.dataKey 必须 === DataKey(类型强制一致)
// Generics: <Paths, Prefix, DataKey>
//   Prefix  — string segment stripped from spec paths (default '').
//   DataKey — never (default) = no unwrap; a field name = unwrap it,
//             and options.dataKey must === DataKey (type-enforced).

// 不解包(默认):useApi<paths, '/api'>()
// 解包信封 data:显式传 dataKey,类型强制与 DataKey 一致 ↓
export const useTypedApi = () => useApi<paths, '/api', 'data'>({ dataKey: 'data' });
<script setup lang="ts">
const api = useTypedApi();

// endpoint 自动补全;响应类型自动推断
// Endpoint autocompletes from spec; response type is inferred.
const users = await api.get('/users');
//    ^? User[]

// path 参数:spec 声明了路径参数时,path 字段变为必填
// Path params: `path` becomes required when the spec declares one.
const user = await api.get('/users/{id}', { path: { id: '42' } });
//    ^? User

// body 与 query 同样具有类型
// Body and query are also typed.
await api.post('/posts', {
  body: { title: 'Hello', tags: ['nuxt'] },
});
</script>

项目场景 / Project scenarios

| 后端约定 / Backend | 推荐写法 / Generic call | | ---------------------------------------------------- | -------------------------------------------- | | 无前缀、无信封(裸响应,默认)/ raw, no envelope (default) | useApi<paths, '/api'>() | | NestJS 全局 /api + data 信封(解包) | useApi<paths, '/api', 'data'>({ dataKey: 'data' }) | | 版本化 /v1/... + payload 信封(解包) | useApi<paths, '/v1', 'payload'>({ dataKey: 'payload' }) | | 未类型化(旧代码 / 混合)/ Untyped (legacy / mixed) | useApi() —— 任意 string,用 <T> 指定返回 |

类型工具集 / Type Kit Pattern

useApi wrapper 解决「调用」处的类型推断;当业务侧需要显式标注响应/请求类型时(例如声明 export type UserBalance = …),用库导出的类型原语在项目里组装一组短别名。两组工具共用同一份 <Paths, Prefix, DataKey> 配置,保持类型口径一致。

The useApi wrapper handles inference at call sites. When you need to declare response/request types explicitly (e.g. export type UserBalance = …), use the type primitives exported by the library to build a small set of short aliases in your project. Both layers share the same <Paths, Prefix, DataKey> constants, so the type story stays consistent.

库导出的原语 / Exported primitives:

import type {
  // 给定 (path, method) 推导 / derived from (path, method):
  EndpointFor,             // 含某 method 的路径并集 / paths union for a given method
  OperationFor,            // 单个 operation / one operation
  QueryFor,                // query 类型 / query type
  BodyFor,                 // body 类型 / body type
  PathParamsFor,           // path 参数类型 / path params type
  ResponseFor,             // 解包后的响应类型 / unwrapped response type
  OptionsArgsFor,          // 可变参数元组 / rest options tuple
  // HTTP method 调用签名 / HTTP method call signature:
  ApiMethod,               // 单个 method 的完整函数签名 / full function signature for one method
  // 独立工具 / standalone helpers:
  StripPrefix,             // 剥前缀映射 / strip-prefix mapping
  CallOptions,             // useApi 调用选项 / call options
  RequiredPathCallOptions, // path 必填变体 / variant with required path
} from '@i.un/nuxt-api';

项目侧的"类型 kit"(与 wrapper 同一份配置常量):

The project-side "type kit" (using the same constants as the wrapper):

// types/api-kit.ts —— 在项目里只定义一次
// types/api-kit.ts — defined once per project.
import type { paths } from '~/types/openapi';
import type {
  ResponseFor,
  BodyFor,
  QueryFor,
  PathParamsFor,
  StripPrefix,
} from '@i.un/nuxt-api';

// 复用库工具剥掉与 wrapper 一致的前缀
// Reuse the library's strip-prefix util so the alias surface matches the wrapper.
type ApiPaths = StripPrefix<paths, '/api'>;

/** ApiResponse<'/me/credits'> → 解包后的响应类型 / unwrapped response type */
export type ApiResponse<
  P extends keyof ApiPaths,
  M extends keyof ApiPaths[P] & string = 'get',
> = ResponseFor<paths, '/api', P, M, unknown, 'data'>;

/** ApiBody<'/posts', 'post'> → 请求 body 类型 / request body type */
export type ApiBody<
  P extends keyof ApiPaths,
  M extends keyof ApiPaths[P] & string = 'post',
> = BodyFor<paths, '/api', P, M>;

/** ApiQuery<'/users'> → query 参数类型 / query params type */
export type ApiQuery<
  P extends keyof ApiPaths,
  M extends keyof ApiPaths[P] & string = 'get',
> = QueryFor<paths, '/api', P, M>;

/** ApiPathParams<'/users/{id}'> → path 参数类型 / path params type */
export type ApiPathParams<
  P extends keyof ApiPaths,
  M extends keyof ApiPaths[P] & string = 'get',
> = PathParamsFor<paths, '/api', P, M>;

业务侧使用(与后端 schema 名解耦,不必关心 DTO 命名):

Usage in business code (decoupled from backend schema names):

import type { ApiResponse, ApiBody } from '~/types/api-kit';

// 响应类型从 URL 派生
// Response type derived from URL.
export type UserBalance       = ApiResponse<'/me/credits'>;
export type TransactionList   = ApiResponse<'/me/credits/transactions'>;
export type CreditTransaction = NonNullable<TransactionList['items']>[number];

// 请求体类型也从 spec 派生
// Request body types come from the spec too.
export type CreateRechargeDto = ApiBody<'/me/credits/recharges'>;

为什么不把这些短别名直接放进库 —— ApiResponse<P> 隐含三个项目侧约定(Prefix / DataKey / 默认 method),把它做进库就需要再开 3 个泛型槽,项目侧用前还要再 wrap 一层,反而更绕。把"用一次项目常量"留在项目侧,正好和 wrapper composable 对称:一处定义、两处复用。

Why these aliases aren't shipped from the library directly — ApiResponse<P> bakes in three project-level decisions (Prefix / DataKey / default method). Shipping it would force 3 more generic slots and an extra project-side wrap. Keeping the "apply project constants once" step in userland keeps it symmetric with the wrapper composable: one place defines the constants, two places consume them.

信封 / 非信封混用 / Mixed envelope / non-envelope

像 /health、/version 这类接口往往返回裸 JSON,没有信封。解包过程用 keyof 做了守卫:

Some endpoints (/health, /version, …) return raw JSON without an envelope. The unwrap step uses a keyof guard, so:

  • 响应中包含 DataKey 字段 → 解包成该字段的类型。 Responses with the DataKey field → unwrapped to that field's type.
  • 响应中不包含 → 原样返回,类型不丢精度。 Responses without it → returned as-is with full type precision.
const me     = await api.get('/me');     // User(从信封中解包 / unwrapped from envelope)
const health = await api.get('/health'); // { status: 'ok' | 'down' }(裸响应 / raw)

非 JSON 响应 / Non-JSON responses

ofetch 按响应的 content-type 自动解析:text/* 成 string(text/event-stream 除外,是流),二进制成 Blob;解包遇到非对象响应原样返回。类型跟着 spec 走 —— 声明了 text/* 的接口(如 CSV 导出)直接推成 string,不用 as;二进制给 unknown。

ofetch parses by the response content-type: text/* into a string (except text/event-stream, a stream), binary into a Blob; unwrapping returns non-object responses as-is. Types follow the spec — an endpoint declaring text/* (e.g. a CSV export) is typed as a string, no as; binary gives unknown.

const csv = await api.get('/contacts/export', { query: { format: 'csv' } }); // string

要按次改解析方式,透传 ofetch 的 responseType('blob' / 'text' / …);它不改返回类型,需自行 as。

To override parsing per call, pass ofetch's responseType ('blob' / 'text' / …); it doesn't change the return type, so cast it yourself.

配置一致性 / Configuration consistency

类型层的 Prefix 泛型必须与模块运行时的 apiBase(路径部分)保持一致 —— 两者错配属于配置 bug,运行时不做校验。DataKey 与 options.dataKey 则由类型强制一致(DataKeyOption),传错会直接编译报错,无需手动对齐。

The type-level Prefix generic must match the runtime apiBase (path portion) — a mismatch is a configuration bug, not checked at runtime. DataKey and options.dataKey, by contrast, are type-enforced to match (DataKeyOption): getting them out of sync is a compile error, so no manual alignment is needed.

4. 手动 Token 管理 / Manual Token Management (useTokenRefresh)

通常无需手动操作,库会自动处理;如需手动控制可调用。参数 dataKey 指明后端 refresh 响应里 token 所在的容器字段(如 'data');token 在顶层、或响应本身就是 token 串时可省略。

Usually handled automatically, but exposed if needed. The dataKey argument tells it which container field holds the token in the refresh response (e.g. 'data'); omit it when the token sits at the top level or the response is the raw token string.

const { refreshToken } = useTokenRefresh('data');

const doRefresh = async () => {
  const newToken = await refreshToken();
  if (newToken) {
    console.log('Token refreshed manually');
  }
};

许可证 / License

MIT