@i.un/nuxt-api

A robust, production-ready API client module for Nuxt 4. Designed for enterprise-grade authentication handling, SSR compatibility, and seamless token refreshing.
5,372
Install
npm install @i.un/nuxt-api
Latest Version:2.0.3
Vue:3
License:MIT
Last Updated:Oct 1, 2026
Links: npm
Maintainer: zhyswan

@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

Related Packages

nuxt-ssr-api-logger

Nuxt module for logging SSR API requests.

22,791 11
nuxt-basic-authentication-module

Basic Authentication Module for Nuxt 3

5,381 4
@x-app/nuxt-api

Configurable API composable for Nuxt 3 and Nuxt 4

393
nuxt-define

One utility function for module authors to define compiler constants for all bui...

13,412,511
@lg2/nuxt3-module-create-dato-map

nuxt3-module-create-dato-map

2,869 0

Version History

Version Released Vue License
2.0.3 ^3.0 MIT
2.0.2 ^3.0 MIT
2.0.1 ^3.0 MIT
2.0.0 ^3.0 MIT
1.1.2 ^3.0 MIT