这些库解决了原生 URL API 无法覆盖的场景,包括查询参数序列化、RFC 标准合规性解析、路径拼接以及 URI 模板展开。query-string 专注于查询字符串的解析与生成;uri-js 提供严格的 RFC 3986 合规性处理;url-join 简化路径拼接;url-parse 提供轻量级的完整 URL 解析;url-template 支持 RFC 6570 标准的模板展开。开发者应根据具体需求选择,避免过度依赖单一库处理所有 URL 相关逻辑。
在现代 Web 开发中,处理 URL 是日常任务之一。虽然浏览器提供了原生的 URL 和 URLSearchParams API,但在复杂场景下,它们往往不够用或过于繁琐。本文对比五个主流 npm 包:query-string、uri-js、url-join、url-parse 和 url-template,帮助你在架构设计中做出正确选择。
这五个库并非直接竞争对手,而是解决了 URL 生命周期的不同环节。
query-string 专注于查询参数。
?key=value 部分。// query-string 示例
import queryString from 'query-string';
const parsed = queryString.parse('?foo=1&foo=2&bar=3');
// { foo: [1, 2], bar: '3' }
const stringified = queryString.stringify({ foo: [1, 2] });
// 'foo=1&foo=2'
uri-js 专注于标准合规性。
// uri-js 示例
import { parse, normalize } from 'uri-js';
const parsed = parse('http://example.com/path');
// { scheme: 'http', host: 'example.com', path: '/path' }
const normalized = normalize('HTTP://EXAMPLE.COM');
// 'http://example.com'
url-join 专注于路径拼接。
/ 的重复或缺失。// url-join 示例
import urlJoin from 'url-join';
const url = urlJoin('http://example.com', '/api/', 'users', '/1');
// 'http://example.com/api/users/1'
url-parse 专注于完整结构解析。
// url-parse 示例
import UrlParse from 'url-parse';
const url = new UrlParse('https://user:pass@example.com:8080/path?query#hash');
console.log(url.protocol); // 'https:'
console.log(url.hostname); // 'example.com'
console.log(url.port); // '8080'
url-template 专注于模板展开。
// url-template 示例
import urlTemplate from 'url-template';
const template = urlTemplate.parse('/users/{id}{?page,limit}');
const url = template.expand({ id: 123, page: 1, limit: 10 });
// '/users/123?page=1&limit=10'
在单页应用(SPA)中,同步 URL 状态到组件状态是常见需求。
query-string 是首选。它自动处理数组和对象的编码,避免手动拼接错误。url-parse 也可以用,但需要额外提取 .query 属性再解析,步骤更多。// 推荐:query-string
const params = queryString.parse(location.search);
// 备选:url-parse + 原生 API
const url = new UrlParse(location.href);
const params = new URLSearchParams(url.query);
微前端或模块化项目中,不同模块可能贡献路径片段。
url-join 最安全。它确保不会出现 // 或遗漏 /。replace 逻辑。// 推荐:url-join
const basePath = urlJoin(process.env.API_HOST, '/v1', endpoint);
// 不推荐:手动拼接
const basePath = `${process.env.API_HOST.replace(/\/$/, '')}/${endpoint.replace(/^\//, '')}`;
当用户输入 URL 或需要存储标准化链接时。
uri-js 提供最强的标准化能力。// 推荐:uri-js
const safeUrl = normalize(userInput);
if (safeUrl !== userInput) {
// 提示用户 URL 已被修正
}
构建 SDK 时,接口路径往往带有参数占位符。
url-template 是行业标准解法。// 推荐:url-template
const template = parse('/repos/{owner}/{repo}');
const url = template.expand({ owner: 'npm', repo: 'cli' });
现代浏览器和 Node.js 已支持原生 URL 类。对于简单场景,原生 API 足够且无需依赖。
// 原生 API 示例
const url = new URL('https://example.com/path?foo=bar');
console.log(url.searchParams.get('foo')); // 'bar'
但在以下情况,第三方库仍不可替代:
URL 兼容性虽好但非无限)。URL 必须有 base)。url-parse 比完整 URL polyfill 小)。不同库对空格、特殊字符的编码方式略有不同。
query-string 默认将空格编码为 +,符合表单提交习惯。uri-js 严格遵循 RFC,可能编码为 %20。// 编码差异示例
// query-string
stringify({ q: 'hello world' }); // 'q=hello+world'
// uri-js (serialize)
// 可能输出 'q=hello%20world' 取决于配置
| 特性 | query-string | uri-js | url-join | url-parse | url-template |
|---|---|---|---|---|---|
| 主要用途 | 查询参数处理 | URI 标准化/验证 | 路径拼接 | 完整 URL 解析 | URI 模板展开 |
| RFC 合规性 | 实用主义 | 严格 RFC 3986 | 无 | 高 | 严格 RFC 6570 |
| 支持协议 | 主要 HTTP | 多协议 (URN 等) | 无限制 | 多协议 | 主要 HTTP |
| 浏览器兼容 | 优秀 | 优秀 | 优秀 | 优秀 | 优秀 |
| 依赖复杂度 | 低 | 中 | 极低 | 低 | 低 |
| 典型场景 | 前端路由状态 | 数据验证/清洗 | API 路径构建 | 日志/分析解析 | SDK 开发 |
在实际工程架构中,不要试图用一个库解决所有问题。
query-string 配合框架路由(如 React Router)。它处理数组和对象最方便。url-join 构建基础 URL,使用 url-template 定义资源路径。这能让 API 定义更清晰。uri-js 进行标准化和清洗,确保数据一致性。url-parse 解析访问日志,它性能好且提取字段方便。最终建议:
query-string + 原生 URL 通常足够。uri-js 和 url-template 是更专业的选择。url-join 虽小但能减少大量边界情况错误。选择合适的工具能让 URL 处理逻辑更清晰 — 减少调试时间,提升代码可维护性。
选择 query-string 当你需要频繁处理查询参数(query params)的解析、序列化或修改时。它完美支持数组、对象编码,且对 URL 编码细节处理得当,适合 React 或 Vue 等前端框架中的路由参数管理。
选择 uri-js 当你需要严格的 RFC 3986 合规性,或者处理非 HTTP 协议(如 URN、UUID)时。它在验证和标准化 URI 方面表现优异,常用于 JSON Schema 验证等底层基础设施。
选择 url-join 当你只需要简单地将多个路径片段拼接成完整 URL 时。它自动处理斜杠重复或缺失的问题,代码可读性高,适合构建 API 基础路径或静态资源链接。
选择 url-parse 当你需要在浏览器或 Node.js 环境中轻量级地解析完整 URL 结构(协议、主机、端口等)时。它比原生 URL 对象更轻量,且对相对路径处理更灵活。
选择 url-template 当你需要实现 RFC 6570 标准的 URI 模板功能时。适合构建动态 API 客户端,其中 URL 路径包含可变参数表达式,如 /users/{id}。
Parse and stringify URL query strings
npm install query-string
[!WARNING] Remember the hyphen! Do not install the deprecated
querystringpackage!
For browser usage, this package targets the latest version of Chrome, Firefox, and Safari.
[!TIP] Consider using
URLSearchParamsfor simple use cases. It's a native browser API that handles basic query string operations.
import queryString from 'query-string';
console.log(location.search);
//=> '?foo=bar'
const parsed = queryString.parse(location.search);
console.log(parsed);
//=> {foo: 'bar'}
console.log(location.hash);
//=> '#token=bada55cafe'
const parsedHash = queryString.parse(location.hash);
console.log(parsedHash);
//=> {token: 'bada55cafe'}
parsed.foo = 'unicorn';
parsed.ilike = 'pizza';
const stringified = queryString.stringify(parsed);
//=> 'foo=unicorn&ilike=pizza'
location.search = stringified;
// note that `location.search` automatically prepends a question mark
console.log(location.search);
//=> '?foo=unicorn&ilike=pizza'
Parse a query string into an object. Leading ? or # are ignored, so you can pass location.search or location.hash directly.
The returned object is created with Object.create(null) and thus does not have a prototype.
queryString.parse('?foo=bar');
//=> {foo: 'bar'}
queryString.parse('#token=secret&name=jhon');
//=> {token: 'secret', name: 'jhon'}
Type: object
Type: boolean
Default: true
Decode the keys and values. URL components are decoded with decode-uri-component.
Type: string
Default: 'none'
'bracket': Parse arrays with bracket representation:import queryString from 'query-string';
queryString.parse('foo[]=1&foo[]=2&foo[]=3', {arrayFormat: 'bracket'});
//=> {foo: ['1', '2', '3']}
'index': Parse arrays with index representation:import queryString from 'query-string';
queryString.parse('foo[0]=1&foo[1]=2&foo[3]=3', {arrayFormat: 'index'});
//=> {foo: ['1', '2', '3']}
'comma': Parse arrays with elements separated by comma:import queryString from 'query-string';
queryString.parse('foo=1,2,3', {arrayFormat: 'comma'});
//=> {foo: ['1', '2', '3']}
'separator': Parse arrays with elements separated by a custom character:import queryString from 'query-string';
queryString.parse('foo=1|2|3', {arrayFormat: 'separator', arrayFormatSeparator: '|'});
//=> {foo: ['1', '2', '3']}
'bracket-separator': Parse arrays (that are explicitly marked with brackets) with elements separated by a custom character:import queryString from 'query-string';
queryString.parse('foo[]', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'});
//=> {foo: []}
queryString.parse('foo[]=', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'});
//=> {foo: ['']}
queryString.parse('foo[]=1', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'});
//=> {foo: ['1']}
queryString.parse('foo[]=1|2|3', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'});
//=> {foo: ['1', '2', '3']}
queryString.parse('foo[]=1||3|||6', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'});
//=> {foo: ['1', '', 3, '', '', '6']}
queryString.parse('foo[]=1|2|3&bar=fluffy&baz[]=4', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'});
//=> {foo: ['1', '2', '3'], bar: 'fluffy', baz:['4']}
'colon-list-separator': Parse arrays with parameter names that are explicitly marked with :list:import queryString from 'query-string';
queryString.parse('foo:list=one&foo:list=two', {arrayFormat: 'colon-list-separator'});
//=> {foo: ['one', 'two']}
'none': Parse arrays with elements using duplicate keys:import queryString from 'query-string';
queryString.parse('foo=1&foo=2&foo=3');
//=> {foo: ['1', '2', '3']}
Type: string
Default: ','
The character used to separate array elements when using {arrayFormat: 'separator'}.
Type: Function | boolean
Default: true
Supports both Function as a custom sorting function or false to disable sorting.
Type: boolean
Default: false
import queryString from 'query-string';
queryString.parse('foo=1', {parseNumbers: true});
//=> {foo: 1}
Parse the value as a number type instead of string type if it's a number.
Type: boolean
Default: false
import queryString from 'query-string';
queryString.parse('foo=true', {parseBooleans: true});
//=> {foo: true}
Parse the value as a boolean type instead of string type if it's a boolean.
Type: object
Default: {}
Specifies a schema for parsing query values with explicit type declarations. When defined, the types provided here take precedence over general parsing options such as parseNumbers, parseBooleans, and arrayFormat.
Use this option to explicitly define the type of a specific parameter—particularly useful in cases where the type might otherwise be ambiguous (e.g., phone numbers or IDs).
You can also provide a custom function to transform the value. The function will receive the raw string and should return the desired parsed result. When used with array formats (like comma, separator, bracket, etc.), the function is applied to each array element individually.
Supported Types:
'boolean': Parse flagged as a boolean (overriding the parseBooleans option):queryString.parse('?isAdmin=true&flagged=true&isOkay=0', {
parseBooleans: false,
types: {
flagged: 'boolean',
isOkay: 'boolean',
},
});
//=> {isAdmin: 'true', flagged: true, isOkay: false}
Note: The 'boolean' type also converts '0' and '1' to booleans, and treats valueless keys (e.g. ?flag) as true.
'string': Parse phoneNumber as a string (overriding the parseNumbers option):import queryString from 'query-string';
queryString.parse('?phoneNumber=%2B380951234567&id=1', {
parseNumbers: true,
types: {
phoneNumber: 'string',
}
});
//=> {phoneNumber: '+380951234567', id: 1}
'number': Parse age as a number (even when parseNumbers is false):import queryString from 'query-string';
queryString.parse('?age=20&id=01234&zipcode=90210', {
types: {
age: 'number',
}
});
//=> {age: 20, id: '01234', zipcode: '90210'}
'string[]': Parse items as an array of strings (overriding the parseNumbers option):import queryString from 'query-string';
queryString.parse('?age=20&items=1%2C2%2C3', {
parseNumbers: true,
types: {
items: 'string[]',
}
});
//=> {age: 20, items: ['1', '2', '3']}
'number[]': Parse items as an array of numbers (even when parseNumbers is false):import queryString from 'query-string';
queryString.parse('?age=20&items=1%2C2%2C3', {
types: {
items: 'number[]',
}
});
//=> {age: '20', items: [1, 2, 3]}
'Function': Provide a custom function as the parameter type. The parameter's value will equal the function's return value. When used with array formats (like comma, separator, bracket, etc.), the function is applied to each array element individually.import queryString from 'query-string';
queryString.parse('?age=20&id=01234&zipcode=90210', {
types: {
age: value => value * 2,
}
});
//=> {age: 40, id: '01234', zipcode: '90210'}
// With arrays, the function is applied to each element
queryString.parse('?scores=10,20,30', {
arrayFormat: 'comma',
types: {
scores: value => Number(value) * 2,
}
});
//=> {scores: [20, 40, 60]}
NOTE: Array types (string[], number[]) are ignored if arrayFormat is set to 'none'.
queryString.parse('ids=001%2C002%2C003&foods=apple%2Corange%2Cmango', {
arrayFormat: 'none',
types: {
ids: 'number[]',
foods: 'string[]',
},
}
//=> {ids:'001,002,003', foods:'apple,orange,mango'}
import queryString from 'query-string';
queryString.parse('?age=20&id=01234&zipcode=90210', {
types: {
age: value => value * 2,
}
});
//=> {age: 40, id: '01234', zipcode: '90210'}
Parse the value as a boolean type instead of string type if it's a boolean.
Stringify an object into a query string and sorting the keys.
Supported value types: string, number, bigint, boolean, null, undefined, and arrays of these types. Other types like Symbol, functions, or objects (except arrays) will throw an error.
Type: object
Type: boolean
Default: true
Strictly encode URI components. It uses encodeURIComponent if set to false. You probably don't care about this option.
Type: boolean
Default: true
URL encode the keys and values.
Type: string
Default: 'none'
'bracket': Serialize arrays using bracket representation:import queryString from 'query-string';
queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'bracket'});
//=> 'foo[]=1&foo[]=2&foo[]=3'
'index': Serialize arrays using index representation:import queryString from 'query-string';
queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'index'});
//=> 'foo[0]=1&foo[1]=2&foo[2]=3'
'comma': Serialize arrays by separating elements with comma:import queryString from 'query-string';
queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'comma'});
//=> 'foo=1,2,3'
queryString.stringify({foo: [1, null, '']}, {arrayFormat: 'comma'});
//=> 'foo=1,,'
// Note that typing information for null values is lost
// and `.parse('foo=1,,')` would return `{foo: [1, '', '']}`.
'separator': Serialize arrays by separating elements with a custom character:import queryString from 'query-string';
queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'separator', arrayFormatSeparator: '|'});
//=> 'foo=1|2|3'
'bracket-separator': Serialize arrays by explicitly post-fixing array names with brackets and separating elements with a custom character:import queryString from 'query-string';
queryString.stringify({foo: []}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'});
//=> 'foo[]'
queryString.stringify({foo: ['']}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'});
//=> 'foo[]='
queryString.stringify({foo: [1]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'});
//=> 'foo[]=1'
queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'});
//=> 'foo[]=1|2|3'
queryString.stringify({foo: [1, '', 3, null, null, 6]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'});
//=> 'foo[]=1||3|||6'
queryString.stringify({foo: [1, '', 3, null, null, 6]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|', skipNull: true});
//=> 'foo[]=1||3|6'
queryString.stringify({foo: [1, 2, 3], bar: 'fluffy', baz: [4]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'});
//=> 'foo[]=1|2|3&bar=fluffy&baz[]=4'
'colon-list-separator': Serialize arrays with parameter names that are explicitly marked with :list:import queryString from 'query-string';
queryString.stringify({foo: ['one', 'two']}, {arrayFormat: 'colon-list-separator'});
//=> 'foo:list=one&foo:list=two'
'none': Serialize arrays by using duplicate keys:import queryString from 'query-string';
queryString.stringify({foo: [1, 2, 3]});
//=> 'foo=1&foo=2&foo=3'
Type: string
Default: ','
The character used to separate array elements when using {arrayFormat: 'separator'}.
Type: Function | boolean
Supports both Function as a custom sorting function or false to disable sorting.
import queryString from 'query-string';
const order = ['c', 'a', 'b'];
queryString.stringify({a: 1, b: 2, c: 3}, {
sort: (a, b) => order.indexOf(a) - order.indexOf(b)
});
//=> 'c=3&a=1&b=2'
import queryString from 'query-string';
queryString.stringify({b: 1, c: 2, a: 3}, {sort: false});
//=> 'b=1&c=2&a=3'
If omitted, keys are sorted using Array#sort(), which means, converting them to strings and comparing strings in Unicode code point order.
Skip keys with null as the value.
Note that keys with undefined as the value are always skipped.
Type: boolean
Default: false
import queryString from 'query-string';
queryString.stringify({a: 1, b: undefined, c: null, d: 4}, {
skipNull: true
});
//=> 'a=1&d=4'
import queryString from 'query-string';
queryString.stringify({a: undefined, b: null}, {
skipNull: true
});
//=> ''
Skip keys with an empty string as the value.
Type: boolean
Default: false
import queryString from 'query-string';
queryString.stringify({a: 1, b: '', c: '', d: 4}, {
skipEmptyString: true
});
//=> 'a=1&d=4'
import queryString from 'query-string';
queryString.stringify({a: '', b: ''}, {
skipEmptyString: true
});
//=> ''
A function that transforms key-value pairs before stringification.
Type: function
Default: undefined
Similar to the replacer parameter of JSON.stringify(), this function is called for each key-value pair and can be used to transform values before they are stringified. The function receives the key and value, and should return the transformed value. Returning undefined will omit the key-value pair from the resulting query string.
This is useful for custom serialization of non-primitive types like Date:
import queryString from 'query-string';
queryString.stringify({
date: new Date('2024-01-15T10:30:00Z'),
name: 'John'
}, {
replacer: (key, value) => {
if (value instanceof Date) {
return value.toISOString();
}
return value;
}
});
//=> 'date=2024-01-15T10%3A30%3A00.000Z&name=John'
You can also use it to filter out keys:
import queryString from 'query-string';
queryString.stringify({
a: 1,
b: null,
c: 3
}, {
replacer: (key, value) => value === null ? undefined : value
});
//=> 'a=1&c=3'
Extract a query string from a URL that can be passed into .parse().
queryString.extract('https://foo.bar?foo=bar');
//=> 'foo=bar'
Extract the URL and the query string as an object.
Returns an object with a url and query property.
If the parseFragmentIdentifier option is true, the object will also contain a fragmentIdentifier property.
import queryString from 'query-string';
queryString.parseUrl('https://foo.bar?foo=bar');
//=> {url: 'https://foo.bar', query: {foo: 'bar'}}
queryString.parseUrl('https://foo.bar?foo=bar#xyz', {parseFragmentIdentifier: true});
//=> {url: 'https://foo.bar', query: {foo: 'bar'}, fragmentIdentifier: 'xyz'}
Type: object
The options are the same as for .parse().
Extra options are as below.
Parse the fragment identifier from the URL.
Type: boolean
Default: false
import queryString from 'query-string';
queryString.parseUrl('https://foo.bar?foo=bar#xyz', {parseFragmentIdentifier: true});
//=> {url: 'https://foo.bar', query: {foo: 'bar'}, fragmentIdentifier: 'xyz'}
Stringify an object into a URL with a query string and sorting the keys. The inverse of .parseUrl()
The options are the same as for .stringify().
Returns a string with the URL and a query string.
Query items in the query property overrides queries in the url property.
The fragmentIdentifier property overrides the fragment identifier in the url property.
queryString.stringifyUrl({url: 'https://foo.bar', query: {foo: 'bar'}});
//=> 'https://foo.bar?foo=bar'
queryString.stringifyUrl({url: 'https://foo.bar?foo=baz', query: {foo: 'bar'}});
//=> 'https://foo.bar?foo=bar'
queryString.stringifyUrl({
url: 'https://foo.bar',
query: {
top: 'foo'
},
fragmentIdentifier: 'bar'
});
//=> 'https://foo.bar?top=foo#bar'
Type: object
Type: string
The URL to stringify.
Type: object
Query items to add to the URL.
Pick query parameters from a URL.
Returns a string with the new URL.
import queryString from 'query-string';
queryString.pick('https://foo.bar?foo=1&bar=2#hello', ['foo']);
//=> 'https://foo.bar?foo=1#hello'
queryString.pick('https://foo.bar?foo=1&bar=2#hello', (name, value) => value === 2, {parseNumbers: true});
//=> 'https://foo.bar?bar=2#hello'
Exclude query parameters from a URL.
Returns a string with the new URL.
import queryString from 'query-string';
queryString.exclude('https://foo.bar?foo=1&bar=2#hello', ['foo']);
//=> 'https://foo.bar?bar=2#hello'
queryString.exclude('https://foo.bar?foo=1&bar=2#hello', (name, value) => value === 2, {parseNumbers: true});
//=> 'https://foo.bar?foo=1#hello'
Type: string
The URL containing the query parameters to filter.
Type: string[]
The names of the query parameters to filter based on the function used.
Type: (key, value) => boolean
A filter predicate that will be provided the name of each query parameter and its value. The parseNumbers and parseBooleans options also affect value.
Type: object
Parse options and stringify options.
This module intentionally doesn't support nesting as it's not spec'd and varies between implementations, which causes a lot of edge cases.
You're much better off just converting the object to a JSON string:
import queryString from 'query-string';
queryString.stringify({
foo: 'bar',
nested: JSON.stringify({
unicorn: 'cake'
})
});
//=> 'foo=bar&nested=%7B%22unicorn%22%3A%22cake%22%7D'
However, there is support for multiple instances of the same key:
import queryString from 'query-string';
queryString.parse('likes=cake&name=bob&likes=icecream');
//=> {likes: ['cake', 'icecream'], name: 'bob'}
queryString.stringify({color: ['taupe', 'chartreuse'], id: '515'});
//=> 'color=taupe&color=chartreuse&id=515'
Sometimes you want to unset a key, or maybe just make it present without assigning a value to it. Here is how falsy values are stringified:
import queryString from 'query-string';
queryString.stringify({foo: false});
//=> 'foo=false'
queryString.stringify({foo: null});
//=> 'foo'
queryString.stringify({foo: undefined});
//=> ''
+ as a space?See this answer.