query-string, uri-js, uri-template, url-parse, and whatwg-url are JavaScript libraries designed to handle various aspects of URI (Uniform Resource Identifier) manipulation, parsing, and construction. query-string specializes in encoding and decoding URL query parameters with support for arrays and objects. uri-js provides comprehensive URI parsing, validation, and resolution according to RFC 3986 and related standards. uri-template implements RFC 6570 for expanding URI templates with dynamic values. url-parse offers a lightweight, cross-environment URL parser that works consistently in both browsers and Node.js. whatwg-url is a spec-compliant implementation of the WHATWG URL Standard, providing URL and URLSearchParams APIs that match modern browser behavior, even in older JavaScript environments.
When building modern web applications — especially those that interact with APIs, handle routing, or manage client-side state via the URL — you’ll inevitably need to parse, construct, or manipulate URIs. While browsers provide native APIs like URL and URLSearchParams, they don’t cover every use case, particularly around templating, full RFC compliance, or lightweight parsing without side effects. That’s where specialized npm packages come in.
Let’s compare five widely used libraries: query-string, uri-js, uri-template, url-parse, and whatwg-url. Each serves a distinct purpose, and choosing the right one depends on your specific needs around standards compliance, feature scope, and browser compatibility.
query-stringFocused exclusively on parsing and stringifying query strings (the part after ? in a URL). It supports array/object serialization, custom delimiters, and decoding strategies. It does not parse full URLs — just the search component.
import queryString from 'query-string';
const parsed = queryString.parse('foo=bar&baz=qux');
// { foo: 'bar', baz: 'qux' }
const str = queryString.stringify({ x: [1, 2], y: 'hello' });
// 'x=1&x=2&y=hello'
uri-jsA full-featured URI parser and validator based strictly on RFC 3986. It can parse, resolve, normalize, and validate any URI component (scheme, authority, path, query, fragment). It also supports IRI (Internationalized Resource Identifiers) and URNs.
import * as URI from 'uri-js';
const parts = URI.parse('https://example.com/path?foo=bar#section');
// { scheme: 'https', host: 'example.com', path: '/path', query: 'foo=bar', fragment: 'section' }
URI.resolve('https://a.com/base/', '../other');
// 'https://a.com/other'
uri-templateSpecializes in expanding URI templates as defined by RFC 6570. You define a template like /users/{id}/posts{?page,size} and inject values to produce a concrete URL.
import UriTemplate from 'uri-template';
const template = UriTemplate.parse('/api/users/{userId}{?active,role}');
const url = template.expand({ userId: 123, active: true, role: 'admin' });
// '/api/users/123?active=true&role=admin'
url-parseA lightweight, cross-environment URL parser that works consistently in both Node.js and browsers. It parses full URLs into components and offers utilities for relative URL resolution and origin comparison.
import Url from 'url-parse';
const url = new Url('https://user:pass@sub.example.com:8080/path?query=1#frag');
console.log(url.hostname); // 'sub.example.com'
console.log(url.port); // '8080'
// Resolve relative URL
const base = new Url('https://example.com/base/');
const resolved = base.resolve('../other');
// 'https://example.com/other'
whatwg-urlA spec-compliant implementation of the WHATWG URL Standard, which is the same standard used by modern browsers. It provides URL and URLSearchParams classes that behave identically to their browser counterparts, even in older Node.js versions.
import { URL, URLSearchParams } from 'whatwg-url';
const url = new URL('https://example.com/path?foo=bar');
url.searchParams.append('baz', 'qux');
console.log(url.toString());
// 'https://example.com/path?foo=bar&baz=qux'
// Works exactly like browser's URL
This is a critical differentiator:
whatwg-url implements the WHATWG URL Standard — the living standard used by all modern browsers. If you want behavior that matches new URL() in Chrome or Firefox, this is your choice.uri-js follows RFC 3986 (and related RFCs like 6570 for templating via an add-on). This is more academic and strict, supporting edge cases like IPv6 literals, percent-encoding rules, and URN schemes.url-parse aims for practical compatibility, not strict RFC adherence. It handles real-world URLs well but may diverge from formal specs in ambiguous cases.query-string and uri-template each implement specific sub-standards: query-string uses common web conventions (similar to application/x-www-form-urlencoded), while uri-template strictly follows RFC 6570.⚠️ Note: The WHATWG standard and RFC 3986 are not identical. For example, WHATWG normalizes hosts to lowercase and enforces stricter port parsing. In most web apps, WHATWG behavior is preferred because it matches the browser.
| Package | Parses full URL? | Returns structured object? | Resolves relative URLs? |
|---|---|---|---|
query-string | ❌ No | ❌ Only query string | ❌ |
uri-js | ✅ Yes | ✅ Detailed parts | ✅ (resolve) |
uri-template | ❌ No | ❌ Template only | ❌ |
url-parse | ✅ Yes | ✅ Simple object | ✅ (resolve method) |
whatwg-url | ✅ Yes | ✅ URL instance | ❌ (use .href with base) |
Example: resolving a relative path
// uri-js
URI.resolve('https://a.com/base/', '../other'); // 'https://a.com/other'
// url-parse
new Url('https://a.com/base/').resolve('../other'); // 'https://a.com/other'
// whatwg-url (requires base URL as second arg)
new URL('../other', 'https://a.com/base/').href; // 'https://a.com/other'
Only query-string and whatwg-url (via URLSearchParams) offer rich query manipulation:
// query-string
queryString.parseUrl('https://x.com/?a=1&a=2').query;
// { a: ['1', '2'] } — arrays supported by default
// whatwg-url
const params = new URLSearchParams('a=1&a=2');
params.getAll('a'); // ['1', '2']
However, query-string supports nested objects and custom array formats out of the box:
queryString.stringify({ filter: { type: 'user', active: true } }, { arrayFormat: 'bracket' });
// 'filter[type]=user&filter[active]=true'
URLSearchParams does not support nested structures — it only handles flat key-value pairs.
Only uri-template handles RFC 6570 templates:
// uri-template
UriTemplate.parse('/search{?q,lang}').expand({ q: 'js', lang: 'en' });
// '/search?q=js&lang=en'
// Other packages cannot do this natively
If you’re consuming hypermedia APIs (like those using HAL or OpenAPI with templated links), this is essential.
whatwg-url is ideal when you need consistent URL behavior across Node.js versions, especially pre-v10 where native URL was incomplete. In modern environments (Node 14+, evergreen browsers), you might not need it — but it’s safe to use as a polyfill.url-parse shines in isomorphic apps where you can’t rely on the global URL (e.g., old browsers or strict CSP environments). It’s dependency-free and tiny.uri-js works everywhere but is heavier due to its comprehensive RFC support. Avoid if you only need basic parsing.query-string is pure utility — no environment assumptions. Perfect for query manipulation regardless of platform.uri-template is environment-agnostic and focused solely on templating.Don’t use query-string to parse full URLs. It will treat the entire string as a query, leading to bugs:
queryString.parse('https://example.com?foo=bar');
// { 'https://example.com?foo': 'bar' } ← wrong!
Don’t expect whatwg-url to handle URI templates. It’s strictly for concrete URLs.
Avoid mixing uri-js and whatwg-url in the same codebase unless you understand their spec differences. They may produce different results for edge-case URLs.
url-parse does not auto-decode percent-encoded components like whatwg-url does. You may need to call decodeURIComponent manually on parts like pathname.
| Use Case | Recommended Package(s) |
|---|---|
| Parse or build query strings with arrays/objects | query-string |
| Need exact browser-like URL behavior | whatwg-url (or native URL) |
| Work with RFC 6570 URI templates | uri-template |
| Lightweight, consistent parsing in all JS envs | url-parse |
| Full RFC 3986 compliance (e.g., for tooling) | uri-js |
| Validate or normalize complex URIs (IPv6, etc) | uri-js |
URL and URLSearchParams. Only reach for whatwg-url if you need to support older runtimes.query-string is unmatched.uri-template is the only correct choice.url-parse when you can’t trust the environment’s URL implementation but don’t need full RFC rigor.uri-js for tooling, validators, or systems requiring strict URI conformance — it’s overkill for typical app logic.These libraries aren’t competitors — they solve different layers of the URI problem. The key is matching the tool to the task.
Choose whatwg-url when you require exact parity with the browser’s native URL and URLSearchParams APIs in environments where they’re missing or incomplete (e.g., legacy Node.js). It’s the safest way to ensure your URL logic behaves identically across client and server. In modern runtimes, prefer the native APIs unless you need guaranteed consistency.
Choose uri-js when you need strict compliance with RFC 3986 for URI parsing, validation, normalization, or resolution — such as in tooling, linters, or systems that must handle edge cases like IPv6 literals, internationalized domain names, or non-http schemes. It’s overkill for typical web app routing but invaluable when correctness per formal standards is non-negotiable.
Choose url-parse when you need a lightweight, dependency-free URL parser that behaves consistently across all JavaScript environments (including older browsers and Node.js) without relying on global URL. It’s well-suited for isomorphic apps or libraries that must avoid native API inconsistencies, though it lacks advanced query handling or strict RFC compliance.
Choose query-string when your primary need is robust parsing and stringification of URL query strings, especially if you require support for nested objects, array formats (like bracket notation), or custom encoding/decoding strategies. It’s ideal for client-side state management via query parameters or API clients that serialize complex filters into URLs. Avoid it if you need to parse full URLs or handle URI templates.
Choose uri-template exclusively when working with RFC 6570 URI templates, commonly found in hypermedia-driven APIs (e.g., HAL, OpenAPI). It allows you to safely expand templates like /users/{id}{?active,role} into concrete URLs. Don’t use it for general URL parsing or query string manipulation — it serves one narrow but critical purpose.
whatwg-url is a full implementation of the WHATWG URL Standard. It can be used standalone, but it also exposes a lot of the internal algorithms that are useful for integrating a URL parser into a project like jsdom.
whatwg-url is currently up to date with the URL spec up to commit 3c83874 and the web platform tests up to commit 2a91c2e.
For file: URLs, whose origin is left unspecified, whatwg-url chooses to use a new opaque origin (which serializes to "null").
URL and URLSearchParams classesThe main API is provided by the URL and URLSearchParams exports, which follows the spec's behavior in all ways (including e.g. USVString conversion). Most consumers of this library will want to use these.
The following methods are exported for use by places like jsdom that need to implement things like HTMLHyperlinkElementUtils. They mostly operate on or return an "internal URL" or "URL record" type.
parseURL(input, { baseURL, encoding = "UTF-8" })basicURLParse(input, { baseURL, url, stateOverride, encoding = "UTF-8" })serializeURL(urlRecord, excludeFragment)serializeHost(hostFromURLRecord)serializePath(urlRecord)serializeInteger(number)serializeURLOrigin(urlRecord)setTheUsername(urlRecord, usernameString)setThePassword(urlRecord, passwordString)hasAnOpaquePath(urlRecord)cannotHaveAUsernamePasswordPort(urlRecord)percentDecodeBytes(uint8Array)percentDecodeString(string)The stateOverride parameter is one of the following strings:
"scheme start""scheme""no scheme""special relative or authority""path or authority""relative""relative slash""special authority slashes""special authority ignore slashes""authority""host""hostname""port""file""file slash""file host""path start""path""opaque path""query""fragment"The URL record type has the following API:
These properties should be treated with care, as in general changing them will cause the URL record to be in an inconsistent state until the appropriate invocation of basicURLParse is used to fix it up. You can see examples of this in the URL Standard, where there are many step sequences like "4. Set context object’s url’s fragment to the empty string. 5. Basic URL parse input with context object’s url as url and fragment state as state override." In between those two steps, a URL record is in an unusable state.
The return value of "failure" in the spec is represented by null. That is, functions like parseURL and basicURLParse can return either a URL record or null.
whatwg-url/webidl2js-wrapper moduleThis module exports the URL and URLSearchParams interface wrappers API generated by webidl2js.
First, install Node.js. Then, fetch the dependencies of whatwg-url, by running from this directory:
npm install
To run tests:
npm test
To generate a coverage report:
npm run coverage
To build and run the live viewer:
npm run prepare
npm run build-live-viewer
Serve the contents of the live-viewer directory using any web server.
The jsdom project (including whatwg-url) is a community-driven project maintained by a team of volunteers. You could support us by: