└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # TypeScript Best Practices 2 | 3 | [![GitHub stars](https://img.shields.io/github/stars/seanpmaxwell/Typescript-Best-Practices?style=flat-square)](https://github.com/seanpmaxwell/Typescript-Best-Practices/stargazers) 4 | 5 | Patterns and best practices for **procedural TypeScript / JavaScript development**, guided by the **Rule of 4** principle. 6 | 7 | > This guide is intentionally opinionated. It prioritizes clarity, consistency, and long-term maintainability over abstraction or novelty. 8 | 9 | --- 10 | 11 | ## Table of Contents 12 | 13 | - [Fundamental Concepts](#fundamental-concepts) 14 | - [Script Types](#script-types) 15 | - [File Organization](#file-organization) 16 | - [Core Language Features](#core-language-features) 17 | - [Primitives](#primitives) 18 | - [Functions](#functions) 19 | - [Objects](#objects) 20 | - [Object Literals](#object-literals) 21 | - [Classes](#classes) 22 | - [Enums](#enums) 23 | - [Types](#types) 24 | - [Naming Conventions](#naming-conventions) 25 | - [Comments](#comments) 26 | - [Imports](#imports) 27 | - [Examples](#examples) 28 | - [Style Guidelines](#style-guidelines) 29 | - [Testing](#testing) 30 | - [Organizing Shared Code](#organizing-shared-code) 31 | - [Philosophy](#philosophy) 32 | 33 | --- 34 | 35 | ## Fundamental Concepts 36 | 37 | This guide revolves around four fundamental language features: 38 | 39 | - **Primitives** 40 | - **Functions** 41 | - **Objects** 42 | - **Types** 43 | 44 | These concepts form the foundation of all JavaScript and TypeScript programs. Mastering them—and using them consistently—results in code that is easier to read, reason about, and maintain. 45 | 46 | --- 47 | 48 | ## Script Types 49 | 50 | Every file should have a clear purpose. Most scripts fall into one of the following categories: 51 | 52 | - **Declaration** 53 | Exports a single declared item (e.g., a constant, enum, or configuration object). 54 | 55 | - **Modular Object** 56 | Exports a default object literal that groups closely related logic. 57 | 58 | - **Inventory** 59 | Exports multiple independent declarations, such as shared types or utilities. 60 | 61 | - **Linear** 62 | Executes a series of commands, often for setup or initialization. 63 | 64 | --- 65 | 66 | ## File Organization 67 | 68 | Files should generally be organized into clearly defined regions: 69 | 70 | 1. Constants 71 | 2. Types 72 | 3. Setup / Execution 73 | 4. Components (`.jsx` / `.tsx`) 74 | 5. Functions 75 | 6. Exports 76 | 77 | Place `export default` at the **very bottom** of the file to make the public API immediately obvious. 78 | 79 | Separate regions with: 80 | 81 | ```ts 82 | /****************************************************************************** 83 | RegionName (i.e. Constants) 84 | ******************************************************************************/ 85 | ``` 86 | 87 | Regions can be divided further into sections: 88 | 89 | ```ts 90 | function getUserName(userId: number) { isValidUser(id) ...do stuff } 91 | function getUserEmail(userId: number) { isValidUser(id) ...do stuff } 92 | 93 | // **** Shared Helper Functions **** // 94 | 95 | function isValidUser(id: number) { ...do stuff } 96 | ``` 97 | 98 | Comments in functions: 99 | - Generally you should not put spaces in functions and separate chunks of logic with a single inline comment. 100 | - If you have a really large function that can't can't be broken up (i.e. React Component or a linear script with a bunch of async/await line) the you can further separate functions with a space and `// ** "Info" ** //` 101 | 102 | ```ts 103 | /** 104 | * Normal everyday javascript function. 105 | */ 106 | function normalFunction() { 107 | // Do stuff 108 | foo(); 109 | bar(); 110 | // Do more stuff 111 | blah(); 112 | whatever(); 113 | } 114 | 115 | // Self executing startup script that needs to be wrapped 116 | // in and async function so we use away 117 | (async () => { 118 | try { 119 | // ** Do stuff ** // 120 | foo(); 121 | bar(); 122 | ...several more lines of code 123 | 124 | // ** Do more stuff **// 125 | blah(); 126 | whatever(); 127 | ...several more lines of code 128 | 129 | } catch (err) { 130 | handleErrorObject(err); 131 | } 132 | })() 133 | ``` 134 | 135 | --- 136 | 137 | ## Core Language Features 138 | 139 | ### Primitives 140 | 141 | JavaScript primitives include: 142 | 143 | `null`, `undefined`, `boolean`, `number`, `string`, `symbol`, and `bigint`. 144 | 145 | Understand **type coercion**: when calling methods on primitives, JavaScript temporarily wraps them in their object counterparts (`String`, `Number`, `Boolean`). 146 | 147 | `symbol` is particularly useful for defining unique object keys in shared or library code. 148 | 149 | --- 150 | 151 | ### Functions 152 | 153 | - Prefer **function declarations** at the file level to take advantage of hoisting. 154 | - Use **arrow functions** for callbacks and inline logic. 155 | 156 | ```ts 157 | function parentFn(param: string) { 158 | const childFn = value => doSomething(value); 159 | const childFn2 = (a, b) => doSomethingElse(a, b); 160 | } 161 | ``` 162 | 163 | Use object-literal methods when `this` should refer to the object itself. 164 | 165 | --- 166 | 167 | ### Objects 168 | 169 | Objects are collections of key/value pairs created via: 170 | 171 | - Object literals 172 | - Classes 173 | - Enums 174 | 175 | Avoid legacy constructor functions (`new Fn()`) in favor of modern class syntax. 176 | 177 | #### Object Literals 178 | 179 | Object literals are ideal for organizing related logic and are often preferable to classes. 180 | 181 | ```ts 182 | export default { 183 | foo, 184 | bar, 185 | } as const; 186 | ``` 187 | 188 | #### Classes 189 | 190 | Use classes only when they satisfy the **M.I.N.T. principle**: 191 | 192 | - **Multiple instances** 193 | - **Not serialized** 194 | - **Tightly coupled data and behavior** 195 | 196 | Avoid classes used solely as namespaces. 197 | 198 | #### Enums 199 | 200 | Enums emit runtime JavaScript and are discouraged in modern TypeScript configurations. Prefer bi-directional objects instead: 201 | 202 | ```ts 203 | const USER_ROLES = { 204 | Basic: 0, 205 | Admin: 1, 206 | Owner: 2, 207 | 0: "Basic", 208 | 1: "Administrator", 209 | 2: "Owner", 210 | } as const; 211 | ``` 212 | 213 | --- 214 | 215 | ### Types 216 | 217 | - Prefer `interface` for object shapes. 218 | - Use `type` for unions, primitives, and utility types. 219 | - Place type aliases above interfaces. 220 | 221 | ```ts 222 | type TRole = "basic" | "admin"; 223 | 224 | interface IUser { 225 | id: number; 226 | name: string; 227 | role: TRole; 228 | } 229 | ``` 230 | 231 | --- 232 | 233 | ## Naming Conventions 234 | 235 | - **Folders**: `kebab-case` 236 | - **Files**: 237 | - **Inventory / Linear scripts**: `kebab-case` 238 | - **Modular object / Declaration scripts**: name after the item being exported 239 | - **Constants**: `UPPER_SNAKE_CASE` 240 | - **Variables**: `camelCase` 241 | - **Classes / Types**: `PascalCase` 242 | - **Interfaces**: prefix with an `I` 243 | - **Type Aliases**: 244 | - **Utility types**: `PascalCase` 245 | - **All others**: prefix with a `T` 246 | - **Booleans**: prefix with `is` or `has` 247 | 248 | --- 249 | 250 | ## Comments 251 | 252 | - Place `/** */` above all function declarations. 253 | - Use `//` for inline explanations. 254 | - Capitalize and punctuate comments. 255 | - Separate logical regions clearly. 256 | 257 | --- 258 | 259 | ## Imports 260 | 261 | - Group imports by origin: libraries → application → local. 262 | - Split long import lists across multiple lines. 263 | 264 | --- 265 | 266 | ## Examples 267 | 268 | ### Modular Object 269 | 270 | ```ts 271 | const mailer = someThirdPartyMailerLib("your settings"); 272 | 273 | function sendMail(options: MailerOptions): Promise { 274 | await mailer.send(options); 275 | } 276 | 277 | function sendSupportStaffEmail(options: MailerOptions): Promise { 278 | await mailer.send({ ...options, to: process.env.SUPPORT_STAFF_EMAIL }); 279 | } 280 | 281 | export default { 282 | sendMail, 283 | sendSupportStaffEmail, 284 | } as const; 285 | ``` 286 | 287 | ### Inventory 288 | 289 | ```tsx 290 | export function SubmitButton() { 291 | return ; 292 | } 293 | 294 | export function CancelButton() { 295 | return ; 296 | } 297 | 298 | export function CloseButton() { 299 | return ; 300 | } 301 | ``` 302 | 303 | ### Linear Script 304 | 305 | ```ts 306 | import express from 'express'; 307 | 308 | const app = express(); 309 | 310 | app.use(middleware1); 311 | app.use(middleware2); 312 | 313 | doSomething(); 314 | doSomethingElse(); 315 | 316 | export default app; 317 | ``` 318 | 319 | 320 | ### Declaration 321 | 322 | ```typescript 323 | // ENV_VARS.ts 324 | 325 | export default { 326 | port: process.env.PORT, 327 | host: process.env.Host, 328 | databaseUsername: process.env.DB_USERNAME, 329 | ...etc, 330 | } as const; 331 | ``` 332 | 333 | --- 334 | 335 | ## Testing 336 | 337 | - Unit-test all user-driven behavior. 338 | - Developers should write their own tests. 339 | - Integration tests should be focused and minimal early on. 340 | - Tests improve readability as well as correctness. 341 | 342 | --- 343 | 344 | ## Organizing shared code 345 | - In a directory with shared content create a subfolder named `common/`. 346 | - Start off by adding the following files as needed 347 | - `utils.ts`: logic that needs to be executed (standalone functions or modular-objects) 348 | - `constants.ts`: static items 349 | - `types.ts`: types only no values 350 | - Depending on the nature of your project you could have more. A react app for example could also include: 351 | - `components/` 352 | - `hooks/` 353 | - If any of these files becomes too large, create a folder of the same name, rename the file to `index.ts` and place it there along with its adjacent content: 354 | - `common/` 355 | - `constants.ts` 356 | - `utils/` <-- `utils.ts` grew too large so we separated it into `index.ts` and `StringUtils.ts` 357 | - `index.ts` 358 | - `StringUtils.ts` 359 | - `types.ts` 360 | - If you have something that isn't shared but you don't want it to go in the file that it is used in for whatever reason create another subfolder called `aux/`and place it there. 361 | - `routes/` 362 | - `aux/` 363 | - `postToPdf.ts` <-- Only used in the `PostRoutes.ts` file but large enough to separate out. 364 | - `support/` 365 | - `PostRoutes.ts` 366 | - `UserRoutes.ts` 367 | - Try to avoid giving folders names like `misc/`, `helpers/`, `shared/` etc. as these can quickly become dumping grounds. 368 | 369 | --- 370 | 371 | ## Philosophy 372 | 373 | This guide favors: 374 | 375 | - Explicitness over cleverness 376 | - Simplicity over abstraction 377 | - Consistency over novelty 378 | 379 | It is designed to scale with real-world TypeScript applications. 380 | 381 | --- 382 | --------------------------------------------------------------------------------