📌 Topic: TypeScript Generics — the feature that makes your code truly reusable Most devs use TypeScript to add types. Few use it to write code that is flexible AND fully type-safe at the same time. That's exactly what Generics give you. Without Generics, you end up doing this: 🚫 Any kills all type safety function getFirstItem(arr: any[]): any { return arr[0]; } const num = getFirstItem([1, 2, 3]); // type: any ❌ const str = getFirstItem(['a', 'b']); // type: any ❌ With Generics: ✅ Type flows through — no any, no casting function getFirstItem<T>(arr: T[]): T { return arr[0]; } const num = getFirstItem([1, 2, 3]); // type: number ✅ const str = getFirstItem(['a', 'b']); // type: string ✅ Real-world usage — Generic API response wrapper: interface ApiResponse<T> { data: T; status: number; message: string; } // Reuse across your entire app type UserResponse = ApiResponse<User>; type ProductResponse = ApiResponse<Product>; type OrderResponse = ApiResponse<Order[]>; One interface. Infinite reuse. Zero repetition. Constrained Generics — when T shouldn't be just anything: T must have an 'id' property function findById<T extends { id: number }>(items: T[], id: number): T | undefined { return items.find(item => item.id === id); } findById(users, 1); // ✅ works — User has id findById(products, 5); // ✅ works — Product has id findById([1, 2, 3], 1); // ❌ compile error — number has no id Generic utility types TypeScript ships out of the box: Partial<T> → makes all properties optional Required<T> → makes all properties required Readonly<T> → prevents mutation Pick<T, K> → extract only the keys you need Omit<T, K> → exclude keys you don't want Record<K, V> → build key-value map types The difference between a codebase with Generics and one without is simple: Without Generics → duplicate types everywhere, any sprinkled around, runtime errors TypeScript should have caught With Generics → one source of truth, full type safety, errors caught at compile time not in production If you're writing TypeScript without Generics — you're using 40% of the language. 💡 Which utility type do you reach for the most? 👇 #TypeScript #JavaScript #FullStackDev #WebDevelopment #SoftwareEngineering #Angular #React #CleanCode
TypeScript Generics for Reusable Code
More Relevant Posts
-
𝗧𝘆𝗽𝗲𝘀𝗰𝗿𝗶𝗽𝘁 𝗧𝘆𝗽𝗲𝘀: 𝗕𝗲𝘆𝗼𝗻𝗱 𝗕𝗮𝘀𝗶𝗰𝘀 You write TypeScript. You avoid any types. You see green squiggles. But errors happen at runtime. TypeScript missed them. Many developers face this. Basic types seem enough. But TypeScript can catch more. It makes code stronger. Use these patterns. - Branded types: Give strings unique names. UserId and Email are both strings but different types. This stops mix-ups. type UserId = string & { brand: unique symbol }; function createUserId(id: string): UserId { return id as UserId; } function sendEmail(userId: UserId, email: Email) {} sendEmail(createUserId('123'), 'test@example.com'); // Error - Template literals and infer: Parse string patterns. Extract parts from paths. type Route = `/users/${string}/posts/${string}`; type Params<T> = T extends `/users/${infer U}/posts/${infer P}` ? {userId: U; postId: P} : never; Params<'/users/alice/posts/99'> is {userId: 'alice', postId: '99'} - as const: Make objects deeply immutable. Get exact types. const config = { env: 'production', retries: 3 } as const; // config.env is 'production' not string - keyof and mapped types: Generate types from keys. Create Partial or Readonly. type PartialUser = { [K in keyof User]?: User[K]; }; type UserUpdate = { [K in keyof User as K extends 'id' ? never : K]?: User[K]; }; - Conditional types: Add logic to types. type Awaited<T> = T extends Promise<infer U> ? U : T; type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T; Build a type-safe API client. const API_ROUTES = { getUser: '/users/:id' } as const; type ParamsFor<K extends keyof typeof API_ROUTES> = Record<ExtractParamNames<typeof API_ROUTES[K]>, string>; async function fetchFromApi<K extends keyof typeof API_ROUTES>(key: K, params: ParamsFor<K>) { // Fetch with correct params } This finds param errors early. Advanced TypeScript shifts your mindset. Design types before code. Start with one pattern. Apply it today. Catch bugs early. Refactor with confidence. What patterns do you use? Share your stories below. Source: https://lnkd.in/g__X8SbJ
To view or add a comment, sign in
-
Zod is the best thing to happen to TypeScript APIs since TypeScript itself. I spent 3 years writing manual validation logic in Node.js APIs. Checking if req.body.email is a string. Checking if it's actually an email. Checking if req.body.age is a number and not negative. Writing the error message manually. Remembering to do this on every route. Then I found Zod. I genuinely don't know how I shipped APIs without it. WHAT ZOD DOES Zod lets you define a schema once. That schema does three things: 1. Validates the data at runtime 2. Infers the TypeScript type automatically 3. Produces clean, structured error messages // One schema. Three things at once. import { z } from 'zod' const CreateOrderSchema = z.object({ userId: z.string().uuid(), items: z.array(z.object({ productId: z.string().uuid(), quantity: z.number().int().min(1).max(100) })).min(1, 'Order must have at least one item'), deliveryDate: z.string().datetime().optional(), promoCode: z.string().toUpperCase().optional() }) // TypeScript type — inferred automatically, no duplication type CreateOrder = z.infer USING IT IN AN EXPRESS / NESTJS API const result = CreateOrderSchema.safeParse(req.body) if (!result.success) { return res.status(422).json({ errors: result.error.flatten().fieldErrors }) // Returns exactly which field failed and why // { items: ['Order must have at least one item'] } } // result.data is now fully typed — no casting, no assertions const order = await orderService.create(result.data) 3 ZOD PATTERNS I USE ON EVERY PROJECT 1. .transform() — sanitise on parse, not separately z.string().trim().toLowerCase().email() 2. .refine() — custom logic type-safety can't express z.string().refine(s => isValidIBAN(s), 'Invalid IBAN') 3. Shared schemas between frontend and backend One package, one source of truth, zero API contract drift Zod replaced about 400 lines of manual validation in the last codebase I cleaned up. 400 lines that were inconsistent, untested, and spread across 30 files. One Zod schema file. Consistent everywhere. #TypeScript #NodeJS #WebDevelopment #BackendDevelopment #SoftwareEngineering
To view or add a comment, sign in
-
-
TypeScript : Part 2 – Loops & Functions! I'm excited to share the next chapter of my TypeScript learning journey. In this part, I dived deep into the core building blocks: Control Flow (Loops) and Type-Safe Functions. Understanding how to automate tasks and write reusable code is a game-changer! Here’s a breakdown of what I covered with examples: 1. Loops Loops help us run the same block of code multiple times. ■ For Loop: Perfect when you know exactly how many times to repeat. Example: for(let num:number=1; num<=target; num++){ console.log("For Prints: "+ num); } ■ While Loop: Best when you want to run as long as a condition is true Example: let num:number=1; while(num <= target){ console.log("While Prints: "+ num); num++; } ■ Do-While Loop: Unique because it guarantees the code runs at least once, even if the condition is false initially. Example: let num:number=1; do{ console.log("Do-While Prints: "+ num); num++; }while(num <= target); 2. Types of Functions in TypeScript ■ Named Function This is the traditional way to write a function. It has a specific name and can be called anywhere in your code Example: function sayHello(name: string): void { console.log("Hello, " + name + "!"); } sayHello("Akhila"); ■ Anonymous Function A function without a name! Usually, we assign it to a variable. It’s great for logic that doesn't need to be reused globally. Example: let square = function (num: number): number { return num * num; } console.log("square of 5: " + square(5)); ■ Arrow Function (Lambda) The modern, shorthand way to write functions using the => syntax. It’s clean, concise, and very popular in modern web development. Example: const add = (x: number, y: number): number => x + y; ■ Void Function A function that does not return a value. We use the void type to tell TypeScript that this function just performs an action (like logging to the console) and finishes. Example: let greet = function (): void { console.log("Welcome to TypeScript!"); } greet(); Ram Shankar Darivemula | Frontlines EduTech (FLM) Key Takeaway: Using types in functions helps catch bugs during development rather than at runtime. It makes the code much more predictable and easier to read! What's Next? Stay tuned for Part 3 of TypeScript, where I'll be exploring Complex types #TypeScript #WebDevelopment #SoftwareEngineering #Programming #JavaScript #WebDev #DotNet
To view or add a comment, sign in
-
🚀 Day 36 – Starting Node.js | Backend Journey Begins I’m now stepping into the backend world with Node.js to strengthen my journey towards becoming a Full Stack Developer As a frontend developer, I’ve always consumed APIs to display data in the UI. But today, I started understanding what actually happens behind the scenes — how APIs are built, how requests are handled, and how data flows from the frontend to the server and then to the database. --- What is Node.js? Node.js is a JavaScript runtime environment that allows us to execute JavaScript outside the browser. It is built on Chrome’s V8 engine and is designed for building fast and scalable backend applications. One of the most interesting things I learned is that Node.js uses a single-threaded, non-blocking, event-driven architecture, which makes it highly efficient in handling multiple requests without waiting for one task to complete. --- ⚙️ Key Concepts I Learned Today: 🔹 Running JavaScript using Node - Executed ".js" files using "node app.js" - Understood how backend logic runs independently from the browser 🔹 Global Objects - Used "__dirname" to get the current directory path - Used "__filename" to get the current file path - Practiced "console" for logging and debugging 🔹 Module System (CommonJS) - Created reusable functions using "module.exports" - Imported them using "require()" - Understood how backend applications are structured into modules 🔹 Asynchronous Behavior (Introduction) - Used "setTimeout()" to simulate async operations - Observed non-blocking execution - Learned how Node.js can handle multiple operations efficiently --- Key Takeaway: Node.js doesn’t wait for one task to complete before moving to the next. Instead, it handles operations asynchronously, which is very useful in real-time applications like APIs, data fetching, and file handling. --- 💻 What I Practiced: ✔ node -v → Check Node version ✔ npm -v → Check npm version ✔ node app.js → Run application ✔ npm init -y → Initialize project ✔ npm install express → Install package ✔ npx nodemon app.js → Auto-restart server --- Example: console.log("Start"); setTimeout(() => { console.log("Fetching data..."); }, 2000); console.log("End"); 👉 Output: Start End Fetching data... 🌍 Real-Time Understanding: As a frontend developer, when I call an API from Angular, I now understand that Node.js plays a key role in: 👉 Receiving the request 👉 Processing business logic 👉 Interacting with the database (MongoDB) 👉 Sending the response back to the UI This gives me a complete picture of how full-stack applications work. 🚀 What’s Next? ➡️ Deep dive into Async Programming & Event Loop ➡️ Building server using HTTP module ➡️ Learning Express.js ➡️ Creating REST APIs ➡️ Connecting Node.js with MongoDB #NodeJS #MEANStack #Angular #MongoDB #LearningJourney
To view or add a comment, sign in
-
𝗧𝘆𝗽𝗲𝗦𝗰𝗿𝗶𝗽𝘁 𝟲.𝟬 𝗞𝗲𝘆 𝗙𝗲𝗮𝘁𝘂𝗿𝗲𝘀 𝗳𝗼𝗿 𝟮𝟬𝟮𝟲 TypeScript 6.0 is here. It sets up major changes for TypeScript 7.0. Here is what matters for your code. **Small fix, big help** TypeScript 6.0 now understands method syntax better. Functions that do not use `this` get better type guesses. Your code works consistently now. **Cleaner imports** You can now use `#/` as an import prefix. This matches bundler tools. Write `import utils from "#/utils.js"` instead of `../../../utils.js`. **Predictable output** A new flag `--stableTypeOrdering` makes union types appear in a fixed order. Your build files will not change randomly. Note: this can slow compilation. **Modern dates** The Temporal API is now built-in. It fixes many problems with JavaScript's Date object. Use it for time zones and durations. **Easier Map updates** Maps have two new methods: - `getOrInsert(key, default)` sets a value if missing. - `getOrInsertComputed(key, function)` runs a function only if the key is missing. **Safer regex** Use `RegExp.escape(userInput)` to safely turn user text into a pattern. You avoid injection bugs. **Breaking changes** New defaults in `tsconfig.json`: - `strict: true` - `module: esnext` - `target: es2025` (floating) - `types: []` (empty, add needed types yourself) You must now list required types explicitly. Options like `baseUrl` are deprecated. Use full paths in `paths` instead. **Your action plan** Immediate: - Update your `tsconfig.json` - Add needed types to the `types` array - Set `rootDir` if your source folder is not the project root Medium term: - Move off `target: es5` - Change module resolution to `nodenext` or `bundler` - Stop using AMD or UMD modules Long term: - Test the `--stableTypeOrdering` flag - Plan for parallel builds in CI when TypeScript 7.0 arrives These changes prepare your project for faster builds. The default settings cut startup time. Source: https://lnkd.in/gu9jP4tb Which feature will you try first? Share your migration steps below.
To view or add a comment, sign in
-
TypeScript ships with 20+ utility types. Most developers use 3. Here are the ones I actually reach for in production: ───────────────────────────── Partial<T> When you want optional updates without a new interface. type UpdateUser = Partial<User> // All fields optional — perfect for PATCH requests ───────────────────────────── Pick<T, K> Expose only what the consumer needs. type UserPreview = Pick<User, 'id' | 'name' | 'avatar'> // No accidental exposure of sensitive fields ───────────────────────────── ReturnType<T> Infer the type from a function — not the other way around. type ApiResponse = ReturnType<typeof fetchUser> // Single source of truth: the function itself ───────────────────────────── NonNullable<T> Strip null/undefined before passing down. type SafeId = NonNullable<User['id']> // No optional chaining hell downstream ───────────────────────────── Parameters<T> Extract function arguments as a tuple. type LogArgs = Parameters<typeof logger> // Wrap functions without redefining their signatures ───────────────────────────── Awaited<T> Unwrap Promise types cleanly. type UserData = Awaited<ReturnType<typeof fetchUser>> // No more Promise<Promise<...>> nesting ───────────────────────────── The underrated combo: type SafePartialUpdate<T> = Partial<Pick<T, 'name' | 'email'>> Composing utility types is where TypeScript really pays off. Which utility type do you reach for most? #TypeScript #React #JavaScript #WebDevelopment #SoftwareArchitecture #DeveloperProductivity #NodeJS
To view or add a comment, sign in
-
-
I avoided TypeScript generics for a long time in my early days with TypeScript. I'd copy one from Stack Overflow, it worked, and I moved on without fully understanding why. That's a problem, because generics are not just advanced TypeScript; they're a core part of what makes TypeScript powerful. A generic is just a type that takes another type as a parameter. That's it. Instead of writing a function that accepts a string and returns a string, you write one that accepts a T and returns a T, where T is whatever the caller passes in. The type is preserved and reused instead of being hardcoded. function identity<T>(value: T): T { return value; } No magic. Just a placeholder that gets filled in at the call site. Without generics, you often end up with two bad options: hardcode a type and lose flexibility, or use any and lose safety entirely. Generics give you both. A function that wraps an API response, a utility that transforms an array, a service that handles paginated results - all of these can be written once, typed correctly, and reused across every data shape in your codebase. Constraints are where it gets useful. Raw generics accept anything. Constraints narrow that down. function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } Now, K isn't just any string. It has to be an actual key of T. TypeScript will catch invalid keys at compile time. That's the kind of safety that pays for itself the first time it catches a typo before it reaches production. Stop reading <T> as intimidating syntax. Read it as a question: 'What type is the caller going to pass in?' The generic is just your way of saying 'I don't know yet, but I'll be consistent about it'. Once that clicks, generics stop being something you avoid and start being something you reach for.
To view or add a comment, sign in
-
-
🚀 Day 7 of My Angular Journey – Services & Dependency Injection Today I learned about Services and Dependency Injection (DI) in Angular — a core concept for building scalable applications. 🔹 What are Services? Services are used to share data and logic across components. -> They help in: • Fetching data from APIs • Sharing data between components • Keeping components clean and focused 🔹 Why use Services? Instead of writing logic inside components, we move it to services. -> This makes code: • Reusable • Maintainable • Organized 🔹 Creating a Service We can create a service using Angular CLI: ng generate service my-service 🔹 Example Service import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class DataService { getMessage() { return "Hello from Service!"; } } 🔹 What is Dependency Injection? Dependency Injection means providing a service to a component instead of creating it manually. -> Angular automatically creates and manages the service instance. 🔹 Using Service in Component import { Component } from '@angular/core'; import { DataService } from './data.service'; @Component({ selector: 'app-root', template: `<p>{{ message }}</p>` }) export class AppComponent { message: string = ''; constructor(private dataService: DataService) { this.message = this.dataService.getMessage(); } } 🔹 How it works • Angular creates the service • Injects it into the constructor • You use it directly 🔹 Key Benefits -> Reusable logic -> Cleaner components -> Easy testing -> Better project structure 🔹 Key Learning Services + Dependency Injection help separate business logic from UI, making Angular apps scalable and maintainable. Grateful to our mentor Ram Shankar Darivemula sir for clear explanation. ✨ Getting more clarity on Angular architecture! Next step → HTTP Client & API Integration 🌐🔥 #Angular #FrontendDevelopment #WebDevelopment #Day7
To view or add a comment, sign in
-
🚀 Page Object Model (POM) in Playwright with TypeScript — Simple Explanation Page Object Model (POM) is a design pattern used in automation testing to separate page logic from test logic. 👉 In simple words: Keep locators & actions in page files Keep tests clean & readable Make code reusable & maintainable 🎯 Without Page Object Model All code written inside test file ❌ Hard to maintain ❌ Code duplication ❌ Not scalable 🟥 Example (Without POM) await page.goto('https://example.com/login') await page.fill('#username', 'admin') await page.fill('#password', '123') await page.click('#login') If login UI changes → You must update everywhere 😓 🎯 With Page Object Model With POM, we: ✔ Create separate page files ✔ Store locators & actions in page class ✔ Use them in test files 📁 Folder Structure (POM) tests/ pages/ login.page.ts tests/ login.spec.ts pages/ → Contains Page Classes tests/ → Contains Test Files 📁 Step 1 — Create Page Class Create login.page.ts Store locators and actions 🟦 login.page.ts import { Page, Locator } from '@playwright/test'; export class LoginPage { readonly page: Page; readonly username: Locator; readonly password: Locator; readonly loginButton: Locator; constructor(page: Page) { this.page = page; this.username = page.locator('#username'); this.password = page.locator('#password'); this.loginButton = page.locator('#login'); } async login(user: string, pass: string) { await this.username.fill(user); await this.password.fill(pass); await this.loginButton.click(); } } 📁 Step 2 — Use in Test File 🟦 login.spec.ts import { test } from '@playwright/test'; import { LoginPage } from '../pages/login.page'; test('Login Test', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.login('admin', '123'); }); 🎯 Why Use Page Object Model? ✅ Clean test files ✅ Reusable code ✅ Easy maintenance ✅ Scalable framework ✅ Industry best practice POM makes your automation clean, professional, and easy to manage 🚀 #Playwright #AutomationTesting #TypeScript #QA #SoftwareTesting #TestAutomation
To view or add a comment, sign in
-
I spent several weeks refactoring a React Native + Expo + Supabase codebase. A 2,461 line lib/supabase.ts file everyone was importing from, god screens over 1,400 lines, zero tests, 74 as any in the worst file, manual useState + useEffect + fetch all over the place, realtime subs inlined directly in components. Ended up being 87 PRs. Zero regressions. Monolith went to 11 domain modules, added 88 tests, extracted a bunch of hooks and components along the way. At some point the methodology got specific enough that I figured it was worth packaging. So I wrote it up as a Claude Code plugin. The core idea is structure preserving migration. I wanted to keep the same behavior, w a better organization. Never touch business logic during a move. Re exports keep consumers working during the split phase, then you strip them and audit at the end. What's in it: 8 phases with explicit skip conditions, progressive disclosure so only the active phase loads into context. 4 level rollback, commit, sub branch, phase, full reset. A code-validator agent (suuuper useful tbh, proud of this one) that gates every commit, auto detects the stack (RN, Next, Vite, plain TS), runs type check + lint + tests + some refactor specific static review for orphaned imports, circular deps, dynamic imports that grep misses. Plus reference docs on team coordination (solo vs small team vs 7+ dev squad), DB migrations with expand migrate contract, and the common pitfalls I hit. What it's not: Not for codebases under ~10k LOC, manual is just faster. Not a rewrite tool, it preserves behavior, doesn't rethink it. Not for non TS/JS monoliths since the re export pattern depends on ESM. Not for ship this week type deadlines, you budget in weeks not days. (tbc how efficient can be coding agents with it, my hot take is that I used Opus 4.6 on that, not 4.7) The numbers above are from one specific project. Your codebase is different, your numbers will differ. The method works, the specific counts won't copy paste. Repo: https://lnkd.in/e-rZEvTz Feedback welcome, PRs appreciated. For those who'll use it only, curious about: Non Supabase users, does the realtime extraction translate cleanly to WebSocket / Firestore / EventSource etc. ? Monorepo users, where do the audit scripts break? Anyone who's done a similar refactor differently, what did I miss? Again feedbacks and PRs appreciated thx
To view or add a comment, sign in
More from this author
Explore content categories
- Career
- Productivity
- Finance
- Soft Skills & Emotional Intelligence
- Project Management
- Education
- Technology
- Leadership
- Ecommerce
- User Experience
- Recruitment & HR
- Customer Experience
- Real Estate
- Marketing
- Sales
- Retail & Merchandising
- Science
- Supply Chain Management
- Future Of Work
- Consulting
- Writing
- Economics
- Artificial Intelligence
- Employee Experience
- Workplace Trends
- Fundraising
- Networking
- Corporate Social Responsibility
- Negotiation
- Communication
- Engineering
- Hospitality & Tourism
- Business Strategy
- Change Management
- Organizational Culture
- Design
- Innovation
- Event Planning
- Training & Development