<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged dx</title>
<link>https://coderlegion.com/tag/dx</link>
<description>Powered by Question2Answer</description>
<item>
<title>TypeScript DeepPath: Achieving Indestructible Types in Nested Objects</title>
<link>https://coderlegion.com/16090/typescript-deeppath-achieving-indestructible-types-in-nested-objects</link>
<description>&lt;p&gt;If you're writing TypeScript code but still using &lt;code&gt;string&lt;/code&gt; to reference keys in nested objects (like translation files or configurations), you're missing out on half of the compiler's power.&lt;/p&gt;
&lt;p&gt;Mastering &lt;strong&gt;Recursive Template Literal Types&lt;/strong&gt; allows you to create strict accesses that the IDE understands perfectly, rooting out silent &quot;undefined&quot; errors at runtime. It's not about over-typing; it's about designing a developer experience (DX) where the editor won't let you make mistakes.&lt;/p&gt;
&lt;h4 id=&quot;why-you-need-deeppath&quot;&gt;Why you need DeepPath&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Real Validation:&lt;/strong&gt; &lt;code&gt;&quot;api.endpionts.users&quot;&lt;/code&gt; stops compiling automatically.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Full path autocompletion:&lt;/strong&gt; The IDE natively suggests all possible combinations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maintenance:&lt;/strong&gt; If you change a key in the config, the compiler breaks wherever it's used.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-silent-bug&quot;&gt;The silent bug&lt;/h3&gt;
&lt;p&gt;Before looking at the solution, let's see the problem. Using &lt;code&gt;string&lt;/code&gt; to access deep properties hides bugs that TypeScript should easily catch:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// Typical in i18n or configs
function getConfig(path: string) { /* ... */ }

// Compiles perfectly, fails in production
const userUrl = getConfig(&quot;api.enpoints.users&quot;);
// The error? You wrote &quot;enpoints&quot; instead of &quot;endpoints&quot;. 
// TS doesn't warn you, and your app returns undefined at runtime.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-pattern-recursive-deeppath&quot;&gt;The pattern: Recursive DeepPath&lt;/h3&gt;
&lt;p&gt;The secret lies in combining recursion with &lt;code&gt;Template Literal Types&lt;/code&gt;. See how we can extract all possible paths from an object as if they were a dot-joined string:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;type DeepPath&amp;lt;T&amp;gt; = T extends object
  ? {
      [K in keyof T &amp;amp; string]: T[K] extends object
        ? `${K}` | `${K}.${DeepPath&amp;lt;T[K]&amp;gt;}`
        : `${K}`;
    }[keyof T &amp;amp; string]
  : never;

// Usage example:
interface AppConfig {
  api: {
    endpoints: {
      users: string;
      posts: string;
    };
    timeout: number;
  };
  theme: &quot;dark&quot; | &quot;light&quot;;
}

type ConfigPaths = DeepPath&amp;lt;AppConfig&amp;gt;;
// Result: &quot;api&quot; | &quot;theme&quot; | &quot;api.endpoints&quot; | &quot;api.timeout&quot; | &quot;api.endpoints.users&quot; | &quot;api.endpoints.posts&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;developer-evolution-using-satisfies&quot;&gt;Developer Evolution: Using &lt;code&gt;satisfies&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;I often see configurations annotated manually (&lt;code&gt;const config: AppConfig = ...&lt;/code&gt;). This &quot;flattens&quot; the type and erases the specific inference of your actual data.&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;DeepPath&amp;lt;T&amp;gt;&lt;/code&gt; to work perfectly, it needs to know the exact literal structure. That's where the &lt;code&gt;satisfies&lt;/code&gt; operator comes in: it validates that you meet the interface but &lt;strong&gt;keeps the literals intact&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// The &quot;Wizard&quot; Path
const config = {
  api: {
    endpoints: {
      users: &quot;/users&quot;,
      posts: &quot;/posts&quot;
    },
    timeout: 5000
  },
  theme: &quot;dark&quot;
} satisfies AppConfig;

// 'config.theme' remains as the literal &quot;dark&quot;
// and we can navigate any path with full safety:
function getProp(path: DeepPath&amp;lt;AppConfig&amp;gt;) {
  // implementation logic...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;bonus-track-return-type-inference-deepvalue-&quot;&gt;Bonus Track: Return Type Inference (DeepValue)&lt;/h3&gt;
&lt;p&gt;Validating the input string is great, but true level 500 is inferring the exact &lt;strong&gt;return type&lt;/strong&gt; based on that string. If I request &lt;code&gt;&quot;api.timeout&quot;&lt;/code&gt;, TypeScript should know it returns a &lt;code&gt;number&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We combine &lt;code&gt;DeepPath&lt;/code&gt; with another recursive type called &lt;code&gt;DeepValue&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;type DeepValue&amp;lt;T, P&amp;gt; = P extends `${infer K}.${infer Rest}`
  ? K extends keyof T
    ? DeepValue&amp;lt;T[K], Rest&amp;gt;
    : never
  : P extends keyof T
    ? T[P]
    : never;

// Pure magic:
function getConfig&amp;lt;P extends DeepPath&amp;lt;AppConfig&amp;gt;&amp;gt;(path: P): DeepValue&amp;lt;AppConfig, P&amp;gt; {
  // implementation (e.g., using lodash/get)
  return {} as any; 
}

const url = getConfig(&quot;api.endpoints.users&quot;); // inferred as string
const timeout = getConfig(&quot;api.timeout&quot;); // inferred as number
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-cost-of-magic-trade-offs-&quot;&gt;The cost of magic (Trade-offs)&lt;/h3&gt;
&lt;p&gt;No advanced pattern comes for free. In production, abusing this recursion has real technical limits that will hit you if the object grows too large.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Pros &lt;/th&gt;&lt;th&gt;Cons&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Zero typo-related runtime errors. &lt;/td&gt;&lt;td&gt;Compiler performance: generates massive unions that can slow down the IDE.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;100% safe and traceable refactoring. &lt;/td&gt;&lt;td&gt;Recursion limits: TS might halt if the object is excessively deep.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Native autocompletion without extra plugins. &lt;/td&gt;&lt;td&gt;Complexity: Dynamic arrays make inference much harder.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;This technique drastically reduces headaches. By implementing these types, you force the compiler to validate the actual structure of your data before the build. If you work on mid-sized projects with predictable configurations or i18n systems, this pattern saves you hours of debugging.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you're interested in taking type safety to the next level, check out my other posts on &lt;a rel=&quot;nofollow&quot; href=&quot;https://campa.dev/en/til/typescript-deep-paths/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=typescript-deep-paths&quot;&gt;campa.dev&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/16090/typescript-deeppath-achieving-indestructible-types-in-nested-objects</guid>
<pubDate>Tue, 28 Apr 2026 18:02:12 +0000</pubDate>
</item>
<item>
<title>Too Many Chefs, One Kitchen</title>
<link>https://coderlegion.com/6869/too-many-chefs-one-kitchen</link>
<description>&lt;p&gt;Most teams don’t start with architecture problems. They start by building. Fast. A small product, a tight team, a system that does just enough. And for a while, that’s the right choice. You’re shipping. You’re learning. You’re moving.&lt;/p&gt;
&lt;p&gt;But at some point, usually when the business starts to grow, something shifts. Releases become slower. Teams start stepping on each other. And what once felt nimble now feels stuck.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The instinctive answer is technical debt — maybe the system just needs refactoring. Or maybe it’s a process problem. But often, the real issue is more structural.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Your architecture no longer matches your organization’s needs.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A common sign of this mismatch is &lt;strong&gt;deployment friction&lt;/strong&gt;. One team wants to ship a small feature. But they have to wait. Wait for another team to finish testing. Wait for someone else’s changes to stabilize. Wait because even a tiny change means releasing everything.&lt;/p&gt;
&lt;p&gt;The system works — technically. But it’s like sharing one kitchen with five different chefs.&lt;br&gt;
One team’s baking. Another’s cleaning fish. Someone else just spilled oil on the floor.&lt;br&gt;
You can coordinate. You can take turns. But you can’t move freely. And eventually, the kitchen becomes the bottleneck.&lt;/p&gt;
&lt;p&gt;This is what monoliths often feel like: &lt;strong&gt;centralized&lt;/strong&gt;, &lt;strong&gt;coupled&lt;/strong&gt;, and &lt;strong&gt;fragile under pressure&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Microservices don’t solve everything, but they restructure the problem. By giving each team their own “station,” they reduce the coordination tax. Teams can deploy independently. They can fail independently. They can learn faster, without always waiting for the rest of the kitchen to finish cooking.&lt;/p&gt;
&lt;p&gt;That doesn’t mean microservices are always better. Just that at a certain scale — of product, of people, of change — they become a way to &lt;strong&gt;align architecture with the shape of the organization&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;What follows is a simple example — not to show how to “do microservices,” but to make clear what kind of friction they solve, and why it starts to matter more as teams grow. Think of it as a zoom-in on what happens when structure starts slowing teams down — not because of failure, but because of how things are bundled.&lt;/p&gt;
&lt;p&gt;⸻&lt;/p&gt;
&lt;p&gt;Imagine a SaaS platform offering &lt;strong&gt;payment processing and loyalty features&lt;/strong&gt; for small businesses. It’s been growing steadily, and the codebase — a monolith — has carried the company through its early traction. Releases are coordinated, large, and slow, but manageable.&lt;/p&gt;
&lt;p&gt;Then a new opportunity arises.&lt;/p&gt;
&lt;p&gt;The product team identifies a small pricing optimization — tweaking loyalty point accrual rules to encourage repeat purchases. It only affects &lt;em&gt;existing users&lt;/em&gt;. Forecast models suggest it could increase &lt;strong&gt;annual revenue by 2%&lt;/strong&gt;. It’s a focused change, with minimal risk, and the team wants to run a fast A/B test to validate it.&lt;/p&gt;
&lt;p&gt;But it can’t ship.&lt;/p&gt;
&lt;p&gt;It’s bundled into the same release as a compliance update — one that applies exclusively to &lt;em&gt;new customer onboarding&lt;/em&gt; and affects shared reporting logic. The loyalty change, meanwhile, is for &lt;strong&gt;existing users&lt;/strong&gt;. The two have nothing to do with each other, but because they live in the same monolith, they move together. &lt;em&gt;You just want to add a bit of seasoning and send the plate out. But the kitchen won’t let anything leave until the whole menu is rewritten.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So the loyalty experiment waits. And each day of delay costs more than just projected revenue — it delays validation. No test runs. No feedback arrives. The team can’t iterate. &lt;em&gt;Momentum slows.&lt;/em&gt; The problem isn’t the work. It’s the structure that forces unrelated changes to move together.&lt;/p&gt;
&lt;p&gt;Microservices would let the loyalty team ship on their own terms. They could release early, run the test, learn what works, and improve the feature before the week’s over. And perhaps more importantly — they’d own the outcome from end to end.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Architecture changes behavior.&lt;/strong&gt;  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When teams can move without waiting, they start testing more, listening sooner, and improving faster. Not because of a mandate — but because the system allows it.&lt;br&gt;
But that freedom doesn’t come for free.&lt;/p&gt;
&lt;p&gt;⸻&lt;/p&gt;
&lt;p&gt;It’s easy to see microservices as the fix: split the kitchen, give each team their own space, and let them move fast. And when done right, it works.&lt;/p&gt;
&lt;p&gt;But it also comes with a new kind of burden.&lt;/p&gt;
&lt;p&gt;Every kitchen now needs its own tools, its own cleanup, its own supply of ingredients. And to make something great, those ingredients need to be fresh, high-quality, and well-managed — and that adds not just effort, but real operational cost.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Microservices give you autonomy — but only if you're mature enough to handle it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That means good testing. Clear ownership. Reliable observability. Solid deployment pipelines. If any of those fall short, the service breaks — or worse, breaks others silently.&lt;/p&gt;
&lt;p&gt;Monoliths concentrate complexity in one place. Microservices distribute it.&lt;br&gt;
Which means &lt;strong&gt;you don’t reduce complexity — you just move it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If the teams aren’t ready — if the ingredients aren’t right — you won’t get autonomy. You’ll get inconsistency. Drift. And a lot of mess to clean up.&lt;/p&gt;
&lt;p&gt;And so the question becomes: &lt;strong&gt;when&lt;/strong&gt; do you make the shift?&lt;/p&gt;
&lt;p&gt;You don’t need five kitchens on day one.&lt;br&gt;
A single shared space works just fine — until it doesn’t.&lt;/p&gt;
&lt;p&gt;Microservices aren’t a milestone to reach.&lt;br&gt;
They’re a response to something felt: friction, delays, coordination overhead that’s no longer manageable. If your teams are shipping smoothly, if ownership is clear, if feedback loops are tight — stay where you are.&lt;/p&gt;
&lt;p&gt;But when the line to cook is longer than the cooking itself — when teams spend more time waiting than building — it’s worth asking whether the kitchen still fits.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Structure should serve the pace and shape of your product.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;If it starts slowing you down, it’s time to reshape it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The best teams aren’t just fast.&lt;br&gt;
They’re intentional about the systems they build around them — not just in code, but in how they move.&lt;/p&gt;
&lt;p&gt;Architecture is just one part of that system. But it’s powerful.&lt;br&gt;
Because when it’s right, it doesn’t just unblock delivery.&lt;br&gt;
It &lt;strong&gt;unlocks behavior&lt;/strong&gt;.&lt;/p&gt;
</description>
<category>Collaboration Tools</category>
<guid isPermaLink="true">https://coderlegion.com/6869/too-many-chefs-one-kitchen</guid>
<pubDate>Sun, 26 Oct 2025 07:20:05 +0000</pubDate>
</item>
</channel>
</rss>