@soluble/dsn-parser
DSN parser, validation utilities, and query string helper in a light and modern package.

Install
$ npm install @soluble-dsn-parser
$ yarn add @soluble/dsn-parser
$ pnpm add @soluble/dsn-parser
Features
Quick start
parseDsnOrThrow
Usage with exceptions
import { parseDsnOrThrow } from "@soluble/dsn-parser";
const dsn = "redis://user:p@/ssword@localhost:6379/0?ssl=true";
try {
const parsedDsn = parseDsnOrThrow(dsn);
assert.deepEqual(parsedDsn, {
driver: "redis",
pass: "p@/ssword",
host: "localhost",
user: "user",
port: 6379,
db: "0",
params: {
ssl: true,
},
});
} catch (e) {
}
parseDsn
Usage with discriminated union.
import { parseDsn } from "@soluble/dsn-parser";
const dsn = "redis://user:p@/ssword@localhost:6379/0?ssl=true";
const parsed = parseDsn(dsn);
if (parsed.success) {
assert.deepEqual(parsed.value, {
driver: "redis",
pass: "p@/ssword",
host: "localhost",
user: "user",
port: 6379,
db: "0",
params: {
ssl: true,
},
});
} else {
assert.deepEqual(parsed, {
success: false,
reason: "INVALID_PORT",
message: "Invalid http port: 12345678",
});
}
Options
const dsn = "mySql://localhost:6379/db";
const parsed = parseDsn(dsn, {
lowercaseDriver: true,
overrides: {
db: "db3",
port: undefined,
},
});
assert.deepEqual(parsed.value, {
driver: "mysql",
host: "localhost",
db: "db3",
});
Params | Type | Description |
---|
lowercaseDriver | <boolean> | Driver name in lowercase, default false |
overrides | DSN must be a string | |
Assertion
import { assertParsableDsn, ParsableDsn } from "@soluble/dsn-parser";
try {
assertParsableDsn("redis:/");
} catch (e) {
assert.equal(e.message, "Cannot parse DSN (PARSE_ERROR)");
}
Typeguard
import { isParsableDsn, type ParsableDsn } from "@soluble/dsn-parser";
const dsn = "postgresql://localhost:6379/db";
if (isParsableDsn(dsn)) {
}
DSN parsing
Requirements
The minimum requirement for dsn parsing is to have a host and
a driver (/[a-z0-9]+/i)
defined. All other options are optional.
export type ParsedDsn = {
driver: string;
host: string;
user?: string;
pass?: string;
port?: number;
db?: string;
params?: Record<string, number | string | boolean>;
};
DSN support
Things like:
const validExamples = [
"postgresql://postgres:@localhost:5432/prisma-db",
"redis://us_er-name:P@ass-_:?/ssw/rd@www.example.com:6379/0?cache=true",
];
should work.
Query parameters
Simple query parameters are supported (no arrays, no nested). For convenience
it will cast 'true'
and 'false'
to booleans,
parse numeric string to numbers if possible. When a query
parameter does not contain a value, it will be returned as true
.
const dsn = "redis://host?index=1&compress=false&ssl";
const parsed = parseDsn(dsn);
assert.deepEqual(parsed.value.params, {
index: 1,
compress: false,
ssl: true,
});
Portability
parseDsn
won't make any assumptions on default values (i.e: default port for mysql...).
Validation
parseDsn
wraps its result in a discriminated union
to allow the retrieval of validation errors. No try... catch
needed and full typescript support.
Reason codes are guaranteed in semantic versions and messages does not
leak credentials
const parsed = parseDsn("redis://localhost:65636");
assert.deepEqual(parsed, {
success: false,
reason: "INVALID_PORT",
message: "Invalid port: 65636",
});
if (!parsed.success) {
log(parsed.reason);
}
Reason | Message | Comment |
---|
'PARSE_ERROR' | Cannot parse DSN | Regexp failed |
'INVALID_ARGUMENT' | DSN must be a string | |
'EMPTY_DSN' | DSN cannot be empty | |
'INVALID_PORT' | Invalid port: ${port} | [1-65535] |
Faq
Zod integration example
The isParsableDsn
can be easily plugged into zod custom validation. Example:
import { z } from "zod";
export const serverEnvSchema = z.object({
PRISMA_DATABASE_URL: z.custom(
(dsn) => isParsableDsn(dsn),
"Invalid DSN format."
),
});
serverEnvSchema.parse(process.env);
Why '/' in password matters
Some libs (ioredis...) still might fail parsing a password containing '/',
unfortunately they're pretty convenient, i.e:
openssl rand 60 | openssl base64 -A
If you are enjoying some of my OSS guides or libs for your company, I'd really appreciate a sponsorship, a coffee or a dropped star. That gives me a tasty morning boost and help me to make some of my ideas come true 🙏
Special thanks