{
"compilerOptions": {
"target": "esnext", //Output JS Version (newer version -> less polyfills but also less browser compatibility)
"module": "esnext", //see note below
"declaration": true, //Generate .d.ts files
"outDir": "dist",
"strict": true,
"esModuleInterop": true, //Clean import of "old" CommonJS Modules (the require() syntax)
"moduleResolution": "node", //see note below
"skipLibCheck": true,
},
"include": ["src"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
Node.js (CJS): "CommonJS" + "type": "commonjs" in package.json → Uses require, works in most current Node apps.
Node.js (ESM): "ESNext" or "NodeNext" + "type": "module" in package.json → Uses import, future-proof, but requires .js in import paths.
Bundlers (Vite, Webpack, Rollup): "ESNext" is best – clean output for tree shaking.
Browser without bundler: "System" or "UMD" if loading with <script type="module">.
- "bundler" ✅ Best for ESM libraries and bundler-based builds. Assumes extension remapping (like .ts → .js) happens in bundler.
- "node" Good for Node.js/CommonJS projects. Follows Node's module resolution rules. Requires you to manually fix import paths/extensions in ESM.
- "node16" / "nodenext" Only needed if you're using "module": "NodeNext" and targeting pure native Node.js resolution without a bundler.
- "classic" Old default for pre-ESM days. Almost never used anymore.
The Record<K, T>
utility type in TypeScript constructs an object type whose property keys are K and property values are T.
// Creates an object with string keys and number values
const scores: Record<string, number> = {
alice: 10,
bob: 15,
};
To describe a datatype minus a specific property in TypeScript, use the Omit<T, K>
utility type.
It creates a new type by removing property K from type T.
type User = {
id: number;
name: string;
email: string;
};
// Remove the 'email' property
type UserWithoutEmail = Omit<User, 'email'>;
In TypeScript (and JavaScript), array destructuring is a syntax that lets you unpack values from arrays into separate variables in a concise way.
Example:
const arr = [1, 2, 3];
//The variables on the left ([a, b, c]) match the order of elements in the array.
const [a, b, c] = arr; // a=1, b=2, c=3
// Skip elements
const [, second] = arr; // second=2
Use the mysql2
npm package with promises (mysql
package is deprecated)
import mysql from "mysql2/promise";
const userId = 1;
const conn = await mysql.createConnection({ host: "localhost", user: "root", database: "test" });
//conn.execute() returns an array with two items. The interesting one is the first item that is selected with array destructing [rows] = ...
const [rows] = await conn.execute("SELECT * FROM users WHERE id = ?", [userId]);
console.log(rows);
Typescript cannot know whats the datatype of the query's result. For example that can be a set of data from your DB (eg. for a SELECT statement) or some operational feedback (eg. for an INSERT statement). You can use a type parameter in typescript to define whats the query's return type:
const queryCmd = `SELECT * FROM projects WHERE id = ?`;
const [rows] = await conn.execute<RowDataPacket[]>(queryCmd, [projectId]);
return rows[0]; //Returns data of the first result row
const queryCmd = `INSERT INTO projects (title, description, notes) VALUES (?, ?, ?)`;
const [result] = await conn.execute<ResultSetHeader>(queryCmd, [title, description, notes]);
return result.insertId; //Returns the id of the inserted element
This sets up a symlink between your local package and your consuming project. 🔄 Changes in my-lib will be reflected in main-app without reinstalling.
Steps:
#Go to the package folder:
cd path/to/my-lib
npm link
#In the project that uses the lib:
cd path/to/main-app
npm link my-lib
# -> Now your app uses a symlink to your local my-lib.
#To undo:
npm unlink my-lib
To export multiple static utility classes from your TypeScript-based npm library, you should create a public API entry point (usually src/index.ts) that re-exports your classes clearly.
my-lib/
├── src/
│ ├── string-utils.ts
│ ├── date-utils.ts
│ └── index.ts ← Public API
├── package.json
└── tsconfig.json
This file re-exports your classes:
export { StringUtils } from './string-utils';
export { DateUtils } from './date-utils';
//or this way
export * from './string-utils';
export * from './date-utils';
//Or if you want to group them:
import { StringUtils } from './string-utils';
import { DateUtils } from './date-utils';
export {
StringUtils,
DateUtils
};
After installing your package (npm install my-lib), users can do:
import { StringUtils, DateUtils } from 'my-lib';
console.log(StringUtils.capitalize('hello'));
console.log(DateUtils.formatISO(new Date()));
Make sure your package.json points to the correct compiled entry:
{
"name": "my-lib",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
}
}