High-performance, type-safe query string parser and stringifier with zero dependencies. Built from the ground up to surpass existing solutions in performance, security, and developer experience.
- 🚀 Blazing Fast - 2x faster than popular alternatives
- 🔒 Security First - Built-in XSS and prototype pollution protection
- 📦 Zero Dependencies - No external runtime dependencies
- 🎯 Full TypeScript Support - Complete type definitions with generics
- 🔧 Flexible Array Formats - Multiple array serialization options
- 🏗️ QueryBuilder API - Fluent interface for building queries
- 📐 Schema Validation - Runtime type validation with detailed errors
- 🔌 Plugin System - Extensible architecture
- 🛡️ Security Features - DoS protection, input sanitization
- 📋 100% Test Coverage - Comprehensive test suite
- 🎨 Multiple Formats - CommonJS and ESM support
npm install @oxog/querystring
yarn add @oxog/querystring
pnpm add @oxog/querystring
import querystring from '@oxog/querystring';
// Parse a query string
const parsed = querystring.parse('name=John&age=30&tags=js&tags=ts');
console.log(parsed);
// { name: 'John', age: '30', tags: ['js', 'ts'] }
// Stringify an object
const stringified = querystring.stringify({
name: 'John',
age: 30,
tags: ['js', 'ts']
});
console.log(stringified);
// 'name=John&age=30&tags=js&tags=ts'
querystring.parse(query: string, options?: ParseOptions): ParsedQuery
delimiter
(string): Delimiter to use (default: '&')depth
(number): Maximum object depth (default: 5)arrayFormat
(string): How to parse arrays (default: 'repeat')parseNumbers
(boolean): Parse numeric values (default: false)parseBooleans
(boolean): Parse boolean values (default: false)parseDates
(boolean): Parse ISO date strings (default: false)allowDots
(boolean): Enable dot notation (default: false)decode
(boolean): Decode values (default: true)charset
('utf-8' | 'iso-8859-1'): Character encoding
// Parse with type coercion
const result = querystring.parse('page=1&limit=10&active=true', {
parseNumbers: true,
parseBooleans: true
});
// { page: 1, limit: 10, active: true }
// Parse with dot notation
const result = querystring.parse('user.name=John&user.age=30', {
allowDots: true
});
// { user: { name: 'John', age: '30' } }
// Parse arrays with different formats
const result = querystring.parse('tags[]=js&tags[]=ts', {
arrayFormat: 'brackets'
});
// { tags: ['js', 'ts'] }
querystring.stringify(obj: object, options?: StringifyOptions): string
delimiter
(string): Delimiter to use (default: '&')encode
(boolean): Encode values (default: true)encodeValuesOnly
(boolean): Only encode values, not keysarrayFormat
(string): How to stringify arraysindices
(boolean): Include array indicessort
(boolean | function): Sort keysfilter
(array | function): Filter keysskipNulls
(boolean): Skip null valuesaddQueryPrefix
(boolean): Add '?' prefixallowDots
(boolean): Use dot notationcharset
('utf-8' | 'iso-8859-1'): Character encodingformat
('RFC1738' | 'RFC3986'): URI encoding format
// Stringify with array format
const result = querystring.stringify(
{ tags: ['js', 'ts', 'node'] },
{ arrayFormat: 'brackets' }
);
// 'tags[]=js&tags[]=ts&tags[]=node'
// Stringify with custom sorting
const result = querystring.stringify(
{ z: 1, a: 2, m: 3 },
{ sort: true }
);
// 'a=2&m=3&z=1'
// Stringify with filtering
const result = querystring.stringify(
{ name: 'John', password: 'secret', age: 30 },
{ filter: ['name', 'age'] }
);
// 'name=John&age=30'
A fluent interface for building query strings:
import { QueryBuilder } from '@oxog/querystring';
const query = new QueryBuilder()
.add('search', 'typescript')
.addArray('tags', ['tutorial', 'guide'])
.addObject('filters', {
difficulty: 'beginner',
duration: '< 30min'
})
.when(isLoggedIn, (q) => q.add('userId', userId))
.build();
// With method chaining
const url = QueryBuilder
.create()
.add('page', 1)
.add('limit', 20)
.merge('sort=name&order=asc')
.toUrl('https://api.example.com/items');
add(key, value)
- Add a single key-value pairaddMultiple(object)
- Add multiple key-value pairsaddArray(key, array)
- Add an array valueaddObject(key, object)
- Add a nested objectappend(key, value)
- Append to existing value (creates array)merge(query)
- Merge another query string or objecthas(key)
- Check if key existsget(key)
- Get value by keydelete(key)
- Remove a keyclear()
- Remove all keyswhen(condition, callback)
- Conditional buildingtransform(callback)
- Transform the entire query objectbuild(options)
- Build the query stringtoUrl(baseUrl, options)
- Build complete URL
Runtime type validation with detailed error messages:
import { q, schema } from '@oxog/querystring';
// Define a schema
const searchSchema = schema({
q: q.string().min(1).max(100),
page: q.number().min(1).default(1),
limit: q.number().max(100).default(20),
sort: q.enum('relevance', 'date', 'popularity').default('relevance'),
filters: q.object().shape({
category: q.string().optional(),
minPrice: q.number().min(0).optional(),
maxPrice: q.number().min(0).optional()
}).optional()
});
// Parse and validate
const query = querystring.parse(req.url);
const validated = searchSchema.parse(query);
// Throws detailed errors if validation fails
// Safe parsing (doesn't throw)
const result = searchSchema.safeParse(query);
if (result.success) {
console.log(result.data);
} else {
console.log(result.errors);
}
q.string()
- String validation with length, pattern, format checksq.number()
- Number validation with min, max, integer checksq.boolean()
- Boolean validation with string coercionq.date()
- Date validation with min, max checksq.array()
- Array validation with item schemasq.object()
- Object validation with shape definitionq.enum()
- Enum validationq.union()
- Union type validationq.literal()
- Literal value validationq.optional()
- Make any schema optionalq.nullable()
- Make any schema nullableq.default()
- Provide default values
Multiple array serialization formats are supported:
const obj = { items: ['a', 'b', 'c'] };
// repeat (default): items=a&items=b&items=c
querystring.stringify(obj, { arrayFormat: 'repeat' });
// brackets: items[]=a&items[]=b&items[]=c
querystring.stringify(obj, { arrayFormat: 'brackets' });
// indices: items[0]=a&items[1]=b&items[2]=c
querystring.stringify(obj, { arrayFormat: 'indices' });
// comma: items=a,b,c
querystring.stringify(obj, { arrayFormat: 'comma' });
// separator: items=a|b|c
querystring.stringify(obj, {
arrayFormat: 'separator',
arrayFormatSeparator: '|'
});
// json: items=["a","b","c"]
querystring.stringify(obj, { arrayFormat: 'json' });
Extend functionality with plugins:
import { PluginManager, createPlugin } from '@oxog/querystring';
// Create a custom plugin
const uppercasePlugin = createPlugin({
name: 'uppercase',
beforeStringify: (obj) => {
const upper = {};
for (const [key, value] of Object.entries(obj)) {
upper[key.toUpperCase()] = value;
}
return upper;
}
});
// Register plugin
const plugins = new PluginManager();
plugins.register(uppercasePlugin);
// Use with parser/stringifier
const result = querystring.stringify(
{ foo: 'bar' },
{ plugins }
);
// 'FOO=bar'
timestampPlugin
- Add timestamps to queriessortKeysPlugin
- Sort object keyslowercaseKeysPlugin
- Convert keys to lowercasefilterEmptyPlugin
- Remove empty valuesbase64Plugin
- Base64 encode/decodecompressPlugin
- URL compressionnormalizePlugin
- Type normalization
Built-in protection against common vulnerabilities:
// Create a secure parser
const secureParser = querystring.createSecureParser({
maxKeys: 100, // Prevent DoS
maxDepth: 5, // Prevent deep nesting attacks
allowPrototypes: false, // Prevent prototype pollution
sanitize: true // XSS protection
});
// Use the secure parser
try {
const result = secureParser(untrustedInput);
// Safe to use
} catch (error) {
// Security violation detected
console.error('Security error:', error.message);
}
maxKeys
- Maximum number of keys (DoS protection)maxDepth
- Maximum object depthallowPrototypes
- Allow proto, constructor, prototype keyssanitize
- Enable XSS sanitization
Command-line interface for quick operations:
# Install globally
npm install -g @oxog/querystring
# Parse a query string
querystring parse "name=John&age=30"
# Stringify JSON
querystring stringify '{"name":"John","age":30}'
# Convert between formats
querystring convert brackets "items=1&items=2" --format comma
# Output: items=1,2
# Validate against schema
querystring validate 'q.object().shape({name: q.string()})' "name=John"
# Read from stdin
echo "foo=bar" | querystring parse --from-stdin
Benchmarked against popular alternatives:
Operation | @oxog/querystring | qs | query-string |
---|---|---|---|
Parse (simple) | 1.00x | 0.45x | 0.62x |
Parse (complex) | 1.00x | 0.38x | 0.55x |
Stringify (simple) | 1.00x | 0.52x | 0.71x |
Stringify (complex) | 1.00x | 0.41x | 0.63x |
Run benchmarks locally:
npm run benchmark
// qs
import qs from 'qs';
const parsed = qs.parse('a[b]=c');
// @oxog/querystring
import querystring from '@oxog/querystring';
const parsed = querystring.parse('a[b]=c', { allowDots: false });
// query-string
import queryString from 'query-string';
const parsed = queryString.parse('?foo=bar', { parseNumbers: true });
// @oxog/querystring
import querystring from '@oxog/querystring';
const parsed = querystring.parse('?foo=bar', {
ignoreQueryPrefix: true,
parseNumbers: true
});
Full TypeScript support with generics:
interface SearchParams {
q: string;
page: number;
filters?: {
category: string;
price: { min: number; max: number };
};
}
// Type-safe parsing
const params = querystring.parse<SearchParams>(url);
params.q // string
params.page // number
params.filters?.category // string | undefined
// Type-safe builder
const builder = new QueryBuilder<SearchParams>();
builder.add('q', 'search term');
builder.add('page', 1);
// TypeScript ensures type safety
Contributions are welcome! Please read our Contributing Guide for details.
MIT © Ersin KOÇ
This package was built to address limitations in existing query string libraries while providing a modern, secure, and performant solution for the JavaScript ecosystem.