# luch-request v4: a Promise based HTTP client with a documented config merge

> luch-request v4 is a TypeScript-first HTTP client for uni-app that wraps the platform request API in Promises, and its most distinctive feature is a precisely documented rule set for how instance defaults and per-request config combine.

**lei-mu/luch-request** — luch-request 是一个基于Promise 开发的uni-app跨平台、项目级别的请求库，它有更小的体积，易用的api，方便简单的自定义能力。

- Repository: https://github.com/lei-mu/luch-request
- Website: https://luch-request.quanzhan.co
- Stars: 668 · Forks: 98
- Language: TypeScript
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/lei-mu-luch-request

## A request library scoped to the uni-app platform

luch-request is described as a Promise based, project level request library for uni-app, with a smaller footprint, an easy API and room for simple customisation. The v4 line narrows that further: the README calls it a TypeScript-first request library for traditional uni-app. The audience is therefore specific, front end developers writing uni-app applications who want a request layer that speaks the platform's own uni.request API rather than a generic HTTP client with a platform adapter bolted on. The project topics list dcloud, uni, uni-app and wechat, which reflects the target runtimes, and the package description in the manifest calls it a TypeScript-first HTTP client for uni-app. The licence is MIT and the homepage documentation is hosted at luch-request.quanzhan.co. The repository layout is compact, with src/ for the source, test/ for the vitest suite, docs/, an example/luch-request-v4-test/ playground, a rollup build configuration, and separate MIGRATION.md and COMPATIBILITY.md files that matter a great deal for v4 specifically.

## v4 is ESM only, and says so plainly

The first thing the README does is state a hard constraint: v4 provides ESM only. The import list shows the public surface, and it is a named export set with no default export.

```ts
import {
  CancellationMode,
  createCancelSource,
  createLuchRequest,
  isLuchRequestError,
  LuchRequestError
} from 'luch-request'
```

The package manifest agrees, with type set to module and an exports map that only publishes types and an import condition, so there is no require path. The build output uses ES2017 syntax, and the README states that no CommonJS, UMD, ES5 legacy bundle or runtime polyfill is provided, with COMPATIBILITY.md as the place to check whether your toolchain copes. If your uni-app project still consumes dependencies through CommonJS, that decision is made for you. The feature list also includes a unified LuchRequestError type, request and response interceptors, and cancellation support that propagates through a signal, with a cross platform createCancelSource helper.

## Install and make the first call

Installation is one npm command, and the library is a normal package on npm.

```bash
npm install luch-request
```

A first call goes through an instance created with createLuchRequest, which takes defaults, and the instance exposes the HTTP verbs. The generics on the shortcut methods are worth reading closely, because they are the main reason to pick this library over a raw uni.request wrapper. A get call takes the response type and the query parameter type, and a post call takes the response type, the body type and the query parameter type, so a typo in a field name is a compile error rather than a runtime surprise.

```ts
const list = await http.get<User[], UserListParams>('/users', {
  params: {
    page: 1,
    pageSize: 20,
    tags: ['new', 'active']
  }
})

const created = await http.post<User, CreateUser>(
  '/users',
  {
    name: 'Ada'
  }
)
```

The full request method takes the same four type parameters in order, response data, request body, query parameters and the per request nativeOptions.

```ts
const response = await http.request<
  User,
  LoginBody,
  LoginParams
>({
  url: '/user/login',
  method: 'POST',
  data: {
    username: 'demo',
    password: 'secret'
  },
  params: {
    source: 'uni-app'
  }
})
```

## The merge rules are the substance of the library

Most request libraries have a config merge, and most document it loosely. luch-request v4 documents it in tables, and that is the most quotable part of the project. The priority is fixed: built in behaviour defaults, then instance defaults, then the per request config, with later effective values overriding earlier ones. But each key has its own rule. Headers merge case insensitively by name, and a per request value of null or undefined deletes an inherited header. luchMeta merges shallowly at the first level, so a nested object like auth is replaced wholesale rather than merged. luchOptions merges per library feature, with the jsonParsing object merged field by field while its include array is replaced and isNativeAbortError replaced entirely. nativeOptions merges shallowly for requests, and upload and download use only their own per call config. Meanwhile baseURL, params, paramsSerializer, validateStatus, signal and timeout are replaced wholesale by a per request value, and data, formData and files are never recursively merged with instance values.

```ts
const http = createLuchRequest({
  params: {
    language: 'zh',
    pageSize: 20
  },
  luchMeta: {
    source: 'app',
    auth: {
      required: true,
      role: 'user'
    }
  },
  nativeOptions: {
    platformOption: {
      cache: true
    }
  }
})
```

The documented outcome of that example is that params becomes just the per request object, luchMeta keeps its first level source but its auth is replaced entirely, and nativeOptions.platformOption is replaced entirely.

## Interceptors cannot pollute your defaults

The copy semantics are unusual enough to be worth understanding before you write interceptors. The README states that the merge process never writes into the passed in default config or per request config, and that plain objects and arrays get a private structural copy. The consequence is that an interceptor mutating a nested plain config in place will not pollute other instances, the instance defaults, the caller's input, or other concurrent requests. The same section is explicit that this is not a general deep clone: signal, functions, Date, ArrayBuffer, class instances and platform objects keep their reference identity, and the README warns that interceptors should not modify the internal state of those opaque objects. A second detail is that method and validateStatus are filled in before the request interceptor runs, so an interceptor can rely on them being present, while baseURL stays undefined internally and is only treated as an empty string when the full URL is built. The README also notes that fullURL is generated internally and cannot be passed as config, and that success, fail and complete are reserved for an internal callback adapter.

## What the defaults do when you do not say anything

The default behaviour table answers the question every adapter author has to guess at otherwise. With no config at all, baseURL is treated as an empty string and the request url is used directly. The method defaults to GET for request() when the instance has none, while the shortcut methods use their own fixed method. No headers are added, no query parameters are appended, and paramsSerializer falls back to the built in rules. validateStatus accepts only 200 through 299, which is why the example instance overrides it. JSON parsing is set to auto for upload and not applied to other operations, and isNativeAbortError uses a built in narrow rule to make a best effort at recognising an abort failure from the native Task. The signal is not listened to unless you pass one, and timeout along with other uni native config is left unset so the target platform applies its own defaults. One upload special case is worth flagging: when an upload does not set Content-Type itself, the instance default for that header is not inherited, so the platform can generate the multipart type and boundary, while every other default header is inherited normally.

## The v4 line is still an alpha, and that is the main risk

The release history is where caution belongs. The stable line ends at 3.1.1 from 2023-08-02, then two alpha tags appear in August 2026, 4.0.0-alpha.1 on 2026-08-04 and 4.0.0-alpha.2 on 2026-08-10, and the last push to the repository was on 2026-09-11. The manifest version is 4.0.0, and the package manager is pinned to pnpm 11.17.0 with a pnpm lockfile in the repository. So the current state of the project is a rewrite in progress, with the API surface visible in the README and the migration guidance separated into MIGRATION.md and COMPATIBILITY.md. The build tooling is modern, with Rollup for the bundle, tsc for the type declarations and a separate tsconfig.build.json, vitest for tests, and a check script that runs type checking, tests, clean, bundle and types in sequence, with prepublishOnly wired to that check. The package publishes under dist, README, CHANGELOG, MIGRATION and COMPATIBILITY, and the publish scripts distinguish alpha, beta and release tags. For a production application, the practical question is whether you are willing to ride an alpha line, and the honest answer for most teams is to wait for a stable 4.0.0.

## Conclusion

Adopt luch-request v4 if you build uni-app applications in TypeScript and want typed generics for response, body and query parameters, plus interceptors and cancellation without a large dependency. Do not adopt it if your project is on CommonJS or needs an ES5 build, since v4 ships ESM only with no legacy bundle or runtime polyfill. Verify first whether the 4.0.0 alpha line is acceptable for you, given the last stable release was 3.1.1 in 2023 and the alpha tags only appeared in August 2026.

## FAQ

### How do I install luch-request v4 and make a request?

Run npm install luch-request, then create an instance with createLuchRequest({ baseURL, timeout }) and call a shortcut method such as http.get or http.post. The instance defaults apply to every call, and each call can override them.

### Does luch-request v4 support CommonJS?

No. The README states that v4 provides ESM only, with ES2017 syntax in the build output, and no CommonJS, UMD, ES5 legacy bundle or runtime polyfill. The package manifest publishes only an import condition with its types.

### How does luch-request merge instance defaults with per request config?

The priority is built in defaults, then instance defaults, then per request config. Each key has its own rule: headers merge case insensitively, luchMeta and nativeOptions merge shallowly at the first level, while baseURL, params, paramsSerializer, validateStatus, signal and timeout are replaced wholesale by a per request value.

### Is luch-request v4 stable or still a prerelease?

It is still an alpha. The release history shows the last stable version as 3.1.1 from 2023-08-02, followed by 4.0.0-alpha.1 on 2026-08-04 and 4.0.0-alpha.2 on 2026-08-10, with the manifest version at 4.0.0.

## Sources

- [lei-mu/luch-request on GitHub](https://github.com/lei-mu/luch-request)
- [License: MIT](https://github.com/lei-mu/luch-request/blob/master/LICENSE)
- [Project website](https://luch-request.quanzhan.co)
- [README](https://github.com/lei-mu/luch-request/blob/master/README.md)
- [Releases](https://github.com/lei-mu/luch-request/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/lei-mu-luch-request
