@knsdev/node-utils
v3.4.1
Published
TypeScript utilities for Node.js: JWT, bcrypt, Express cookies/helpers, OAuth, Turnstile, validation, Zod schemas, pagination, themes, and more (ESM).
Downloads
1,037
Maintainers
Readme
@knsdev/node-utils
TypeScript utilities for Node.js APIs: JWT (HS256) via JwtUtils, bcrypt passwords, Express cookies and small Express helpers, OAuth (Google / Facebook), Cloudflare Turnstile, HMAC, validation helpers, Zod schemas, pagination/sleep/theme helpers (getValidTheme), shared types (including UI themes), and math/random utilities. Published as ES modules ("type": "module").
Requirements
- Node.js with native
fetch(or a compatible runtime). - Express is a peer-style dependency for
CookieUtilstypings andres.cookie; installexpressin your app if you use cookies.
Installation
pnpm add @knsdev/node-utils
# or
npm install @knsdev/node-utilsBuild from source (for contributors). build runs clean first so dist/ never keeps stale files after renames.
pnpm install
pnpm run build
# optional: pnpm run cleanHow to import
The package exposes per-file subpaths (no root barrel in exports):
import { JwtUtils } from "@knsdev/node-utils/JwtUtils.js";
import { tryCatch } from "@knsdev/node-utils/tryCatch.js";Use the .js extension in import specifiers (NodeNext / TypeScript convention for ESM). Paths map to compiled files under dist/ (e.g. JwtUtils.ts → JwtUtils.js).
For local monorepos, you can also re-export from the included src/index.ts if your bundler resolves it.
Modules and usage
tryCatch / tryCatch2
Async and sync helpers that return a tuple [data, error] instead of throwing.
import { tryCatch, tryCatch2 } from "@knsdev/node-utils/tryCatch.js";
const [user, err] = await tryCatch(fetch("https://api.example.com/user"));
if (err) {
console.error(err);
} else {
console.log(await user.json());
}
const [parsed, parseErr] = tryCatch2(() => JSON.parse('{"a":1}'));getEnvv
Read process.env with a small schema: string, int, float, boolean, or appEnv (dev | test | prod). On failure, throws one Error whose message has up to two sections separated by a line of ================================: Not found keys: … and/or Value errors: ….
import { getEnvv } from "@knsdev/node-utils/getEnvv.js";
const env = getEnvv({
PORT: "int",
DATABASE_URL: "string",
APP_ENV: "appEnv",
DEBUG: "boolean",
});
// env.PORT is number, env.APP_ENV is 'dev' | 'test' | 'prod', etc.JwtUtils
HS256 sign/verify via jsonwebtoken. Throwing variants use your API error types (InternalServerError, Unauthorized, BadRequest).
import { JwtUtils } from "@knsdev/node-utils/JwtUtils.js";
type Session = { sub: string; role: string };
const token = JwtUtils.signOrThrow<Session>(
process.env.JWT_SECRET!,
{ sub: "user-1", role: "user" },
15 * 60 * 1000, // 15 minutes (ignored if payload already has `exp`)
);
const payload = JwtUtils.verifyOrThrow<Session>(process.env.JWT_SECRET!, token);
const signResult = JwtUtils.sign<Session>(secret, payload, 3600_000);
if (signResult.error) {
/* handle */
}PasswordUtils
Bcrypt hash/compare and simple length checks.
import { PasswordUtils } from "@knsdev/node-utils/PassUtils.js";
const hashResult = await PasswordUtils.hashPassword(
"correct horse battery staple",
);
if (hashResult.success) {
console.log(hashResult.hashedPassword);
}
const cmp = await PasswordUtils.comparePassword("plain", storedHash);
if (cmp.success) {
/* logged in */
} else {
/* cmp.error — e.g. BadRequest for wrong password */
}
try {
await PasswordUtils.comparePasswordOrThrow("plain", storedHash); // true on match; throws on mismatch / bcrypt error
} catch (e) {
/* handle */
}
if (!PasswordUtils.validateStrength(pw, 8, 128)) {
/* reject */
}
if (PasswordUtils.isBcryptHash(value)) {
/* already a hash */
}CookieUtils (Express)
HTTP-only cookies with secure defaults: secure follows environment (dev allows insecure unless sameSite === 'none').
import type { Response } from "express";
import { CookieUtils } from "@knsdev/node-utils/Cookies.js";
function setSessionCookie(res: Response, token: string) {
CookieUtils.setCookie(
res,
"session",
token,
7 * 24 * 60 * 60 * 1000,
".example.com",
"prod",
"/",
"lax",
);
}
function logout(res: Response) {
CookieUtils.clearCookie(res, "session", ".example.com", "prod");
}express (helpers)
Typed res.json wrapper and async route wrapper that forwards errors to next.
import type { RequestHandler } from "express";
import { CatchExpress, SendRes } from "@knsdev/node-utils/express.js";
type Health = { ok: true };
const health: RequestHandler = (req, res) => {
SendRes<Health>(res, { ok: true });
};
app.get("/health", CatchExpress(health));utils
sleep(ms) and PageLimit(query) for list endpoints (page, limit, skip, sort, optional searchTerm, remaining keys as filtersData). getValidTheme(unknown) coerces untrusted theme input to a valid TThemes ('light' | 'dark' | 'system'), defaulting to 'light' when the value is missing or invalid (see also AThemes in types).
import {
getValidTheme,
PageLimit,
sleep,
} from "@knsdev/node-utils/utils.js";
await sleep(500);
const { page, limit, skip, sortBy, sortOrder, searchTerm, filtersData } =
PageLimit(req.query);
const theme = getValidTheme(req.query.theme);HMACUtils
SHA-256 HMAC (hex) and timing-safe comparison.
import { HMACUtils } from "@knsdev/node-utils/HMACUtils.js";
const { data: digest, error } = HMACUtils.hash(
process.env.WEBHOOK_SECRET!,
rawBody,
);
const valid = digest !== null && HMACUtils.compare(digest, signatureFromHeader);IDUtils / validateUUID
Crypto random UUID (v4) and validation; validateOrThrow raises BadRequest with 'ID__INVALID'.
import { IDUtils } from "@knsdev/node-utils/IDUtils.js";
import { validateUUID } from "@knsdev/node-utils/validators.js";
const id = IDUtils.gen();
if (IDUtils.validate(req.params.id)) {
/* narrowed to string */
}
IDUtils.validateOrThrow(req.params.id); // throws BadRequest if invalid
if (validateUUID(someInput)) {
/* someInput is string */
}Rand
Random integers/floats (crypto vs Math.random), UUIDs, and Base58 strings.
import {
randInt,
randUuid,
randStr,
srandInt,
} from "@knsdev/node-utils/Rand.js";
const code = randInt(100000, 999999);
const apiKey = randStr(32);
const id = randUuid();
const quickDemo = srandInt(1, 6); // non-crypto, for low-stakes UINums
Clamping, safe parsing, formatting, percentages.
import {
clamp,
parseIntSafe,
formatWithCommas,
percentage,
toNumber,
} from "@knsdev/node-utils/Nums.js";
const n = clamp(Number(input), 0, 100);
const page = parseIntSafe(query.page, 10) ?? 1;
const label = formatWithCommas(12345.67, "en-US", 2);
const pct = percentage(3, 10, 1); // 30.0
const x = toNumber(unknownValue, 0);HttpCodes / ErrorClasses / ECApiCodes
HTTP status constants and errors for APIs: ApiError plus BadRequest, Unauthorized, Forbidden, NotFound, TooManyRequests, InternalServerError. Error message values use the ECApiCodes string union—machine-readable codes for clients (including auth/JWT/refresh, captcha, password and field validation, slug/username/profile fields, and simple success-style API codes such as FETCHED or CREATED).
import { HttpCodes } from "@knsdev/node-utils/HttpCodes.js";
import {
BadRequest,
Unauthorized,
NotFound,
} from "@knsdev/node-utils/ErrorClasses.js";
import type { ECApiCodes } from "@knsdev/node-utils/ECApiCodes.js";
function handler() {
throw new BadRequest("EMAIL__INVALID");
}
function requireUser(token: string | undefined) {
if (!token) throw new Unauthorized("UNAUTHORIZED");
}
// Example Express error middleware pattern:
// if (err instanceof ApiError) res.status(err.status).json({ code: err.message });GoogleUtils
Verify Google ID tokens with jose, or exchange an authorization code for tokens and then resolve the user.
import { GoogleUtils } from "@knsdev/node-utils/GoogleUtils.js";
const fromIdToken = await GoogleUtils.getUserFromJwt(
idToken,
process.env.GOOGLE_CLIENT_ID!,
);
const fromCode = await GoogleUtils.getUserFromCode(
code,
process.env.GOOGLE_CLIENT_ID!,
process.env.GOOGLE_CLIENT_SECRET!,
"https://your.app/oauth/callback",
);
// Returns { oauth_id, name, email, picture } or null on failureFacebookUtils
Validates a Facebook user access token (not a JWT) via Graph API debug_token, then loads /me. Requests use POST bodies so tokens and app secrets stay out of query strings. Returns { oauth_id, name, email, picture } or null. The Graph API version used by this module is defined in code as a single constant (currently v19.0) for easy upgrades.
import { FacebookUtils } from "@knsdev/node-utils/FBUtils.js";
const profile = await FacebookUtils.getUserFromAccessToken(
accessToken,
process.env.FB_APP_ID!,
process.env.FB_APP_SECRET!,
);TurnstileValidator
Server-side Cloudflare Turnstile verification. Tuple API or throwing BadRequest with 'CAPTCHA__VALIDATION_FAILED'.
import { TurnstileValidator } from "@knsdev/node-utils/TurnstileValidator.js";
const [success, failure] = await TurnstileValidator.validate(
process.env.TURNSTILE_SECRET!,
tokenFromClient,
req.ip,
{ expectedAction: "login", expectedHostname: "your.app" },
10_000,
"prod",
);
if (failure) {
console.error(failure["error-codes"]);
} else {
console.log(success.hostname, success.challenge_ts);
}
// Or throw on any failure (last arg is reserved for API stability / future use; pass your app env):
const result = await TurnstileValidator.validateOrThrow(
secret,
tokenFromClient,
req.ip,
{},
10_000,
"prod",
);bnUtils
Bengali digit substitution and English month → Bengali month names.
import { bnNum, monthToBn } from "@knsdev/node-utils/bnUtils.js";
bnNum(2026); // Bengali digits for "2026"
monthToBn("january");
monthToBn(3);types
Shared string unions (app envs, roles, user status/gender, visibility, months, themes, etc.) plus AThemes (the runtime array of valid theme literals) and the NoInfer<T> helper type for generics.
import type { TAppEnvs, TThemes, TUserRoles } from "@knsdev/node-utils/types.js";
import { AThemes } from "@knsdev/node-utils/types.js";
function configure(env: TAppEnvs) {
/* ... */
}
const isTheme = (v: string): v is TThemes => AThemes.includes(v as TThemes);zod (optional)
Prebuilt Zod schemas aligned with CLengths and machine-friendly error codes (TZodCodes / ECApiCodes style): z_PASSWORD, z_GENERAL_TOKEN, z_CODE_6_DIGITS, z_SLUG, z_EMAIL, z_FIRST_NAME, z_LAST_NAME, z_TURNSTILE_TOKEN, z_LOGOUT_OTHER_SESSIONS, z_REMEMBER_ME.
import { z_EMAIL, z_PASSWORD } from "@knsdev/node-utils/zod.js";
import { z } from "zod";
const LoginBody = z.object({
email: z_EMAIL,
password: z_PASSWORD,
});Dependencies (summary)
| Area | Packages |
| ------------------------- | ------------------------------ |
| JWT | jsonwebtoken |
| Google ID token | jose |
| Passwords | bcrypt |
| Schemas | zod |
| Cookies + Express helpers | express (types / Response) |
License
MIT — see LICENSE.
