<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ustun Ozgur</title>
    <description>The latest articles on DEV Community by Ustun Ozgur (@ustun).</description>
    <link>https://dev.to/ustun</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3162%2F94374.jpeg</url>
      <title>DEV Community: Ustun Ozgur</title>
      <link>https://dev.to/ustun</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ustun"/>
    <language>en</language>
    <item>
      <title>A Practical Guide to Moving from AWS App Runner to ECS Express Mode</title>
      <dc:creator>Ustun Ozgur</dc:creator>
      <pubDate>Wed, 08 Apr 2026 10:26:03 +0000</pubDate>
      <link>https://dev.to/ustun/a-practical-guide-to-moving-from-aws-app-runner-to-ecs-express-mode-1fe3</link>
      <guid>https://dev.to/ustun/a-practical-guide-to-moving-from-aws-app-runner-to-ecs-express-mode-1fe3</guid>
      <description>&lt;p&gt;Most teams hear "App Runner is closing to new customers" and think: swap the&lt;br&gt;
runtime resource, point DNS at the new service, done.&lt;/p&gt;

&lt;p&gt;In practice, the migration is bigger than that.&lt;/p&gt;

&lt;p&gt;AWS now recommends Amazon ECS Express Mode as the migration path. App Runner&lt;br&gt;
closes to new customers on April 30, 2026. Existing services keep running, but&lt;br&gt;
no new features are planned. If you are still on App Runner, the clock is&lt;br&gt;
ticking.&lt;/p&gt;

&lt;p&gt;The reason the migration feels deceptively small is that App Runner often sits&lt;br&gt;
in the middle of several things operators rely on: custom domains, TLS&lt;br&gt;
certificates, WAF rules, CI/CD assumptions, health checks, deployment and&lt;br&gt;
rollback habits. Change the runtime and you may discover that half of those were&lt;br&gt;
tightly coupled to it.&lt;/p&gt;

&lt;p&gt;This is the playbook I would hand someone starting this migration today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the Right Mental Model
&lt;/h2&gt;

&lt;p&gt;Do not treat this as a resource rename.&lt;/p&gt;

&lt;p&gt;Treat it as a platform migration with at least four moving parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;runtime&lt;/li&gt;
&lt;li&gt;public edge&lt;/li&gt;
&lt;li&gt;deployment workflow&lt;/li&gt;
&lt;li&gt;operational recovery paths&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The runtime is only one layer. Teams lose time when they update the service&lt;br&gt;
first and only later discover that deploys, health checks, certificates, DNS,&lt;br&gt;
or WAF behavior were tightly coupled to App Runner.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Usually Carries Over Cleanly
&lt;/h2&gt;

&lt;p&gt;The easiest migrations already have these characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the application already runs from a container image&lt;/li&gt;
&lt;li&gt;images are already published to ECR or another registry&lt;/li&gt;
&lt;li&gt;environment variables and secrets are already separated&lt;/li&gt;
&lt;li&gt;the application exposes a stable health endpoint&lt;/li&gt;
&lt;li&gt;the team already has separate app-only and infra-only operational flows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you already build an image and ship that image, you are in good shape. That&lt;br&gt;
means the migration is mostly about runtime and edge behavior, not packaging.&lt;/p&gt;

&lt;p&gt;One note: if your App Runner service currently deploys from source code rather&lt;br&gt;
than a container image, you will need to add a build step that produces an image&lt;br&gt;
and pushes it to a registry like ECR. Plan for that work separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Changes in ECS Express Mode
&lt;/h2&gt;

&lt;p&gt;ECS Express Mode keeps the simple "give AWS an image and let it run" model, but&lt;br&gt;
it does so by creating ECS and load-balancer infrastructure in your account. You&lt;br&gt;
gain visibility into underlying resources, but you also inherit integration&lt;br&gt;
points around them.&lt;/p&gt;

&lt;p&gt;This is where many migrations get harder than expected. Your new runtime may be&lt;br&gt;
simple to create, but your operational tooling now has to coexist with&lt;br&gt;
load-balancer, listener, target-group, certificate, and routing behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observability changes: X-Ray is no longer built-in
&lt;/h3&gt;

&lt;p&gt;App Runner had native X-Ray integration. You toggled a setting in the&lt;br&gt;
observability configuration, and App Runner handled the rest. The ADOT&lt;br&gt;
collector ran behind the scenes without you managing a sidecar or daemon.&lt;/p&gt;

&lt;p&gt;ECS does not work that way. To get X-Ray tracing on ECS (including Express&lt;br&gt;
Mode), you need to run the X-Ray daemon as a sidecar container in your task&lt;br&gt;
definition, instrument your application with the ADOT or X-Ray SDK, and&lt;br&gt;
configure the appropriate IAM permissions on your task role. It is not&lt;br&gt;
difficult, but it is not free either. Plan for an extra container in your task&lt;br&gt;
definition, the associated CPU and memory overhead, and the time to verify that&lt;br&gt;
traces are actually flowing.&lt;/p&gt;

&lt;p&gt;If your team relied on App Runner's built-in tracing for debugging production&lt;br&gt;
issues, do not leave this until after the migration. Set it up during the&lt;br&gt;
parallel validation phase so you have the same visibility on the new service&lt;br&gt;
before you shift traffic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost changes: the ALB you did not used to pay for
&lt;/h3&gt;

&lt;p&gt;App Runner included load balancing in its pricing. You did not see a separate&lt;br&gt;
ALB line item on your bill.&lt;/p&gt;

&lt;p&gt;ECS Express Mode creates an Application Load Balancer in your account. An ALB&lt;br&gt;
costs roughly $16-25/month just to exist, even with zero traffic. For teams&lt;br&gt;
running multiple environments (dev, staging, production), that adds $50-75/month&lt;br&gt;
in load balancer costs before any compute charges.&lt;/p&gt;

&lt;p&gt;Express Mode can share a single ALB across up to 25 services with compatible&lt;br&gt;
networking configurations, which helps. But if your services have different&lt;br&gt;
networking shapes or you need isolation, you may end up with multiple ALBs.&lt;/p&gt;

&lt;p&gt;For small, low-traffic services where App Runner's pricing was attractive&lt;br&gt;
precisely because of its simplicity, the additional ALB cost is worth&lt;br&gt;
modeling before you migrate. It may not change your decision, but it should not&lt;br&gt;
be a surprise on your first bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Migration Plan I Recommend
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Inventory the App Runner service before changing anything
&lt;/h3&gt;

&lt;p&gt;Write down the current behavior, not just the current Terraform or console&lt;br&gt;
shape.&lt;/p&gt;

&lt;p&gt;Capture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;container image and tag strategy&lt;/li&gt;
&lt;li&gt;CPU and memory settings&lt;/li&gt;
&lt;li&gt;environment variables&lt;/li&gt;
&lt;li&gt;Secrets Manager references&lt;/li&gt;
&lt;li&gt;health endpoint and expected response&lt;/li&gt;
&lt;li&gt;custom domains&lt;/li&gt;
&lt;li&gt;certificate ownership&lt;/li&gt;
&lt;li&gt;WAF and allowlist behavior&lt;/li&gt;
&lt;li&gt;observability settings (X-Ray enabled or not, and how traces are consumed)&lt;/li&gt;
&lt;li&gt;deployment commands people actually use&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This becomes your migration acceptance checklist.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Preserve the image pipeline
&lt;/h3&gt;

&lt;p&gt;The safest migration keeps the build artifact exactly the same.&lt;/p&gt;

&lt;p&gt;If App Runner currently runs image &lt;code&gt;X&lt;/code&gt;, the first ECS Express deployment should&lt;br&gt;
also run image &lt;code&gt;X&lt;/code&gt;. Do not combine a runtime migration with a packaging change&lt;br&gt;
unless you absolutely have to.&lt;/p&gt;

&lt;p&gt;That one decision removes a large class of risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Bring up ECS Express in parallel
&lt;/h3&gt;

&lt;p&gt;Create the Express service with the same image, the same key environment, and&lt;br&gt;
the same secret references.&lt;/p&gt;

&lt;p&gt;Do not move public traffic yet.&lt;/p&gt;

&lt;p&gt;Validate the service using its generated endpoint first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;container starts&lt;/li&gt;
&lt;li&gt;app answers health checks&lt;/li&gt;
&lt;li&gt;database connectivity works&lt;/li&gt;
&lt;li&gt;Redis or other private dependencies work&lt;/li&gt;
&lt;li&gt;logs and metrics show up where you expect&lt;/li&gt;
&lt;li&gt;X-Ray traces are flowing (if you had tracing enabled on App Runner)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the point where you learn how Express behaves in your actual account and&lt;br&gt;
network shape.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Rebuild the edge deliberately
&lt;/h3&gt;

&lt;p&gt;This is where most hidden complexity lives.&lt;/p&gt;

&lt;p&gt;Plan for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ACM certificate creation and validation&lt;/li&gt;
&lt;li&gt;Route 53 record changes&lt;/li&gt;
&lt;li&gt;ALB listener and host-routing behavior&lt;/li&gt;
&lt;li&gt;WAF attachment and policy scope&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One of the easiest mistakes is assuming that "runtime healthy" means "public&lt;br&gt;
URL healthy." That is not always true, especially when WAF sits in front of the&lt;br&gt;
service.&lt;/p&gt;

&lt;p&gt;Use different signals for different questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;service rollout health&lt;/li&gt;
&lt;li&gt;target-group health&lt;/li&gt;
&lt;li&gt;public edge reachability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are related, but they are not interchangeable.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Shift traffic gradually
&lt;/h3&gt;

&lt;p&gt;Use a blue/green mindset, even if the final architecture is simple.&lt;/p&gt;

&lt;p&gt;The broad shape is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;keep App Runner serving production traffic&lt;/li&gt;
&lt;li&gt;stand up ECS Express in parallel&lt;/li&gt;
&lt;li&gt;shift a small percentage of traffic to ECS Express&lt;/li&gt;
&lt;li&gt;observe errors, latency, auth, and edge behavior&lt;/li&gt;
&lt;li&gt;complete the cutover&lt;/li&gt;
&lt;li&gt;keep App Runner around briefly for rollback confidence&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Weighted DNS is a good fit for this because it gives you a straightforward&lt;br&gt;
rollback path.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Re-verify operator workflows before deleting App Runner
&lt;/h3&gt;

&lt;p&gt;This step gets skipped surprisingly often.&lt;/p&gt;

&lt;p&gt;Before retiring App Runner, explicitly verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;app-only deploy&lt;/li&gt;
&lt;li&gt;infra-only apply&lt;/li&gt;
&lt;li&gt;full deploy&lt;/li&gt;
&lt;li&gt;secret rotation path&lt;/li&gt;
&lt;li&gt;manual recovery path for custom domains&lt;/li&gt;
&lt;li&gt;CI/CD workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your team had three deploy paths before the migration, you should assume&lt;br&gt;
people still need three deploy paths after it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Most Common Pitfalls
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Public health checks can lie
&lt;/h3&gt;

&lt;p&gt;If your public URL is protected by WAF or allowlists, a deploy script that&lt;br&gt;
simply curls the public health endpoint may fail even when the backend is&lt;br&gt;
perfectly healthy.&lt;/p&gt;

&lt;p&gt;A better deployment gate is usually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;service rollout complete&lt;/li&gt;
&lt;li&gt;target group healthy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then use the public health endpoint as a separate edge verification signal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shared ALBs reduce cost and increase coupling
&lt;/h3&gt;

&lt;p&gt;Express Mode can share Application Load Balancers across services that use the&lt;br&gt;
same networking shape. The cost benefit is real, but so are the tradeoffs: more&lt;br&gt;
host-based routing logic, more shared listener behavior, and more care required&lt;br&gt;
when updating custom-domain rules.&lt;/p&gt;

&lt;p&gt;If you have multiple public service surfaces, understand whether they will land&lt;br&gt;
behind one shared ALB or separate ones before you design your routing and WAF&lt;br&gt;
strategy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Terraform will not always tell you everything during plan
&lt;/h3&gt;

&lt;p&gt;This is especially true with newer resource types and services that create&lt;br&gt;
dependent infrastructure on your behalf.&lt;/p&gt;

&lt;p&gt;Be ready for cases where plan succeeds, validate succeeds, and apply reveals an&lt;br&gt;
API-side constraint or provider mismatch. That does not mean Terraform is&lt;br&gt;
failing you. It means you still need a real staging apply as part of the&lt;br&gt;
migration process.&lt;/p&gt;

&lt;p&gt;Also worth noting: the Terraform AWS provider has already opened issues to&lt;br&gt;
deprecate App Runner resources in a future major version. If you are managing&lt;br&gt;
App Runner with Terraform, expect provider-level changes in the coming months.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secret references and secret values are not the same thing
&lt;/h3&gt;

&lt;p&gt;Changing the ECS service definition is one class of update. Changing the value&lt;br&gt;
inside Secrets Manager without changing the reference is a different class of&lt;br&gt;
update.&lt;/p&gt;

&lt;p&gt;Make sure your team has a clear answer to both questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how does the service pick up a changed environment variable definition?&lt;/li&gt;
&lt;li&gt;how does the service pick up a rotated secret value behind the same ARN?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are often different mechanisms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do not let the migration silently change deploy contracts
&lt;/h3&gt;

&lt;p&gt;If &lt;code&gt;deploy-app&lt;/code&gt; meant "ship application code" before the migration, it should&lt;br&gt;
still mean that afterward.&lt;/p&gt;

&lt;p&gt;Migrations become much harder to validate when runtime behavior and operator&lt;br&gt;
behavior change at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  When ECS Express Mode Is a Good Fit
&lt;/h2&gt;

&lt;p&gt;ECS Express Mode is a strong choice when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you want to stay container-first&lt;/li&gt;
&lt;li&gt;you want AWS-managed defaults&lt;/li&gt;
&lt;li&gt;you want the underlying resources in your account&lt;/li&gt;
&lt;li&gt;you do not want to hand-build every ECS and ALB component&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is especially attractive for teams that have outgrown App Runner but still&lt;br&gt;
want a fairly managed experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  When You May Want Standard ECS Instead
&lt;/h2&gt;

&lt;p&gt;If you need deep control over deployment tuning, target-group behavior, listener&lt;br&gt;
layout, load balancer isolation, or blue/green strategy details, then standard&lt;br&gt;
ECS/Fargate with explicitly managed ALB resources may be a better fit.&lt;/p&gt;

&lt;p&gt;That is not a knock against Express Mode. It is a recognition that managed&lt;br&gt;
abstractions are most helpful when their defaults line up with your operational&lt;br&gt;
needs.&lt;/p&gt;

&lt;p&gt;It is also worth noting that ECS Express Mode does not currently support&lt;br&gt;
blue/green deployment. If that is a hard requirement for your team, standard&lt;br&gt;
ECS gives you more options.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Short Acceptance Checklist
&lt;/h2&gt;

&lt;p&gt;Before calling the migration done, verify all of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ECS Express is running the same image you expected&lt;/li&gt;
&lt;li&gt;direct service endpoints are healthy&lt;/li&gt;
&lt;li&gt;custom domains serve the correct service&lt;/li&gt;
&lt;li&gt;TLS certificates are valid&lt;/li&gt;
&lt;li&gt;WAF behavior still matches policy&lt;/li&gt;
&lt;li&gt;X-Ray tracing works (if previously enabled)&lt;/li&gt;
&lt;li&gt;app-only deploy works&lt;/li&gt;
&lt;li&gt;infra-only apply works&lt;/li&gt;
&lt;li&gt;full deploy works&lt;/li&gt;
&lt;li&gt;secret rotation works&lt;/li&gt;
&lt;li&gt;ALB cost is understood and budgeted&lt;/li&gt;
&lt;li&gt;rollback steps are documented and tested&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If even one of those is still "probably fine," the migration is not done yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Advice
&lt;/h2&gt;

&lt;p&gt;The best way to reduce risk is to separate the migration into phases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;create ECS Express&lt;/li&gt;
&lt;li&gt;validate it privately&lt;/li&gt;
&lt;li&gt;move traffic&lt;/li&gt;
&lt;li&gt;verify deploy workflows&lt;/li&gt;
&lt;li&gt;remove App Runner&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Teams lose the most time when they try to do all five in one move.&lt;/p&gt;

&lt;p&gt;If you plan the migration as a runtime swap, you will probably underestimate it.&lt;br&gt;
If you plan it as a platform-and-edge migration, you will make better decisions&lt;br&gt;
earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/apprunner/latest/dg/apprunner-availability-change.html" rel="noopener noreferrer"&gt;AWS App Runner availability change&lt;/a&gt; — Official announcement and migration guidance from AWS&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/express-service-overview.html" rel="noopener noreferrer"&gt;Amazon ECS Express Mode overview&lt;/a&gt; — How Express Mode works and what it creates in your account&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/express-service-create-full.html" rel="noopener noreferrer"&gt;Creating an Amazon ECS Express Mode service&lt;/a&gt; — Step-by-step service creation walkthrough&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/xray/latest/devguide/xray-daemon-ecs.html" rel="noopener noreferrer"&gt;Running the X-Ray daemon on Amazon ECS&lt;/a&gt; — Sidecar setup for distributed tracing on ECS&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>awsapprunner</category>
      <category>ecsexpressmode</category>
      <category>ecs</category>
    </item>
    <item>
      <title>Terraform HCL for Developers: Why It Feels Familiar and Strange</title>
      <dc:creator>Ustun Ozgur</dc:creator>
      <pubDate>Mon, 06 Apr 2026 20:48:07 +0000</pubDate>
      <link>https://dev.to/ustun/terraform-hcl-for-developers-why-it-feels-familiar-and-strange-2ek</link>
      <guid>https://dev.to/ustun/terraform-hcl-for-developers-why-it-feels-familiar-and-strange-2ek</guid>
      <description>&lt;p&gt;If you're coming to Terraform from Python, JavaScript, or Ruby, HCL can feel uncanny: familiar enough to read, strange enough to second-guess.&lt;/p&gt;

&lt;p&gt;You see list-like comprehensions, Ruby-ish blocks, and syntax that looks a bit like assignment without really behaving like a programming language. That is not accidental. HCL is a hybrid—a configuration language designed to be readable by humans and useful for describing infrastructure, relationships, and constraints.&lt;/p&gt;

&lt;p&gt;The mental shift that helps most is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You are not writing a script that runs top to bottom. You are declaring a desired shape of infrastructure, and Terraform turns that declaration into a dependency graph, a plan, and an execution strategy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once that clicks, most of HCL's weirdness starts to make sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. It's a graph, not a script
&lt;/h2&gt;

&lt;p&gt;In Python or JavaScript, source order usually matters because execution is fundamentally sequential. In Terraform, block order does not define execution order. References between objects—and, when needed, explicit &lt;code&gt;depends_on&lt;/code&gt; edges—do.&lt;/p&gt;

&lt;p&gt;That is why you can scatter related resources across multiple &lt;code&gt;.tf&lt;/code&gt; files without teaching Terraform what runs first. Terraform builds a dependency graph from the configuration and uses that graph to generate a plan and sequence operations.&lt;/p&gt;

&lt;p&gt;That does &lt;strong&gt;not&lt;/strong&gt; mean every fact is always known before &lt;code&gt;apply&lt;/code&gt;. In many cases Terraform can resolve values during planning, but some data sources may be deferred until apply if their inputs are not known yet.&lt;/p&gt;

&lt;p&gt;So the right mental model is not “top to bottom.” It is “describe the relationships, then let Terraform walk the graph.”&lt;/p&gt;

&lt;h2&gt;
  
  
  2. HCL is built from arguments and blocks
&lt;/h2&gt;

&lt;p&gt;The most important syntax distinction in Terraform is not really about operators. It is about &lt;strong&gt;arguments&lt;/strong&gt; and &lt;strong&gt;blocks&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ami-1234567890"&lt;/span&gt; &lt;span class="c1"&gt;# argument&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t3.micro"&lt;/span&gt;       &lt;span class="c1"&gt;# argument&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                         &lt;span class="c1"&gt;# argument whose value is an object&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;ebs_block_device&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;               &lt;span class="c1"&gt;# nested block&lt;/span&gt;
    &lt;span class="nx"&gt;device_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/dev/sda1"&lt;/span&gt;
    &lt;span class="nx"&gt;volume_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Arguments assign values to names. Blocks are containers for more configuration and usually represent schema-defined structure.&lt;/p&gt;

&lt;p&gt;They can look similar when you're new to HCL, but they are not interchangeable. Understanding that explains a lot of Terraform's “why does this parse but that doesn't?” moments.&lt;/p&gt;

&lt;p&gt;It also explains why &lt;code&gt;dynamic&lt;/code&gt; blocks exist. Expressions can compute argument values directly, but nested blocks are structural, so Terraform needs a dedicated construct to generate them.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The expression language is where HCL feels Pythonic
&lt;/h2&gt;

&lt;p&gt;Inside argument values, HCL has a compact expression language: functions, conditionals, indexing, attribute access, and &lt;code&gt;for&lt;/code&gt; expressions.&lt;/p&gt;

&lt;p&gt;This is the part that often feels most familiar to developers coming from general-purpose languages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;subnet_ids&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nx"&gt;subnet_map&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;for&lt;/code&gt; expressions are one of the cleanest parts of the language. Square brackets produce a tuple/list-like result. Curly braces produce an object/map-like result. In object &lt;code&gt;for&lt;/code&gt; expressions, &lt;code&gt;=&amp;gt;&lt;/code&gt; maps a computed key to a computed value.&lt;/p&gt;

&lt;p&gt;There is also only one real inline conditional form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is deliberate. HCL gives you enough expression power to transform data, but it stops well short of becoming a full programming language with arbitrary control flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Instance identity matters more than iteration
&lt;/h2&gt;

&lt;p&gt;If you want to understand why Terraform sometimes feels fussy, look at &lt;code&gt;for_each&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For resources and modules, &lt;code&gt;for_each&lt;/code&gt; accepts a map or a set of strings. Terraform identifies each instance by the map key or set member. That is the real reason &lt;code&gt;toset()&lt;/code&gt; shows up so often: it lets you create instance addresses based on stable values instead of numeric positions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;environments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;toset&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"staging"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"prod"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"env"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environments&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-app-${each.key}"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is safer than list-indexed identity, but it is not magic.&lt;/p&gt;

&lt;p&gt;Reordering the original list before converting it to a set does not matter. Changing the key &lt;strong&gt;does&lt;/strong&gt; matter, because Terraform uses that key as the instance identity. If you intentionally rename or move an object address, &lt;code&gt;moved&lt;/code&gt; blocks are the right way to preserve that refactor.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. State is the hidden half of the model
&lt;/h2&gt;

&lt;p&gt;Terraform is declarative, but it is not stateless.&lt;/p&gt;

&lt;p&gt;It persists state so it can map configuration objects to real infrastructure, track metadata, and make planning practical at scale. Terraform then compares your configuration with its state and the existing infrastructure to decide what needs to change.&lt;/p&gt;

&lt;p&gt;That is why HCL alone is never the whole story. Two identical-looking &lt;code&gt;.tf&lt;/code&gt; files can behave differently depending on what Terraform already believes it manages.&lt;/p&gt;

&lt;p&gt;This is also why backends, locking, drift, imports, and refactors matter so much operationally: they are all state-management problems wearing different costumes.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Where the chimera gets awkward
&lt;/h2&gt;

&lt;p&gt;HCL is excellent at describing structured infrastructure. It is much worse at acting like a general-purpose language.&lt;/p&gt;

&lt;p&gt;String manipulation gets unreadable quickly. Branching is intentionally limited. &lt;code&gt;dynamic&lt;/code&gt; blocks solve a real problem but can become cryptic fast.&lt;/p&gt;

&lt;p&gt;And while Terraform's validation and testing story is better than it used to be—you now have variable validation, preconditions, postconditions, check blocks, and &lt;code&gt;terraform test&lt;/code&gt;—it still feels different from application development. Complex module testing can still be heavier and less ergonomic than writing ordinary unit tests in an application language.&lt;/p&gt;

&lt;p&gt;That is not a flaw so much as a boundary. HCL works best when you let it describe infrastructure shape, identity, and dependency, and move business logic or heavy data wrangling elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;HCL feels hybrid because it is.&lt;/p&gt;

&lt;p&gt;It borrows just enough from programming languages to be expressive, but it stays rooted in configuration: blocks, arguments, expressions, and graphs.&lt;/p&gt;

&lt;p&gt;Once you stop reading it like a script and start reading it like a human-friendly language for declaring structure and dependency, most of its weirdness starts to look intentional.&lt;/p&gt;

&lt;p&gt;And that is usually the moment Terraform clicks.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>devops</category>
      <category>programming</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Find Plaintext Secrets Hiding in Your .env Files</title>
      <dc:creator>Ustun Ozgur</dc:creator>
      <pubDate>Wed, 25 Mar 2026 10:41:22 +0000</pubDate>
      <link>https://dev.to/ustun/find-plaintext-secrets-hiding-in-your-env-files-5dpl</link>
      <guid>https://dev.to/ustun/find-plaintext-secrets-hiding-in-your-env-files-5dpl</guid>
      <description>&lt;p&gt;This is a companion to my earlier post, &lt;a href="https://dev.to/ustun/a-small-hardening-trick-for-envlocal-dotenvx-os-keychain-2533"&gt;A Small Hardening Trick for &lt;code&gt;.env.local&lt;/code&gt;: &lt;code&gt;dotenvx&lt;/code&gt; + OS Keychain&lt;/a&gt;, where I described a pattern for encrypting local secrets with &lt;code&gt;dotenvx&lt;/code&gt; and storing the decryption key in the OS keychain.&lt;/p&gt;

&lt;p&gt;That post was about fixing the problem. This one is about finding it first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with "just gitignore it"
&lt;/h2&gt;

&lt;p&gt;If you have been developing locally for a while, you probably have &lt;code&gt;.env&lt;/code&gt; files&lt;br&gt;
scattered across dozens of project directories. Some are recent, some are from&lt;br&gt;
projects you have not touched in months. Some contain harmless config. Some&lt;br&gt;
contain database URLs, API keys, and auth secrets in plaintext.&lt;/p&gt;

&lt;p&gt;The trouble is that you do not always know which is which, especially once you&lt;br&gt;
have 10 or 20 repos checked out under &lt;code&gt;~/code&lt;/code&gt;. And if a supply chain attack or&lt;br&gt;
a compromised tool scans your filesystem, it does not care whether you remember&lt;br&gt;
what is in those files.&lt;/p&gt;

&lt;p&gt;I wanted a quick way to answer one question: &lt;strong&gt;where on my machine are plaintext&lt;br&gt;
secrets sitting in &lt;code&gt;.env&lt;/code&gt; files right now?&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The script
&lt;/h2&gt;

&lt;p&gt;I wrote a small bash script that walks a directory tree, finds &lt;code&gt;.env&lt;/code&gt; files, and&lt;br&gt;
reports lines that look like they contain secrets. It is deliberately simple,&lt;br&gt;
opinionated, and safe to run.&lt;/p&gt;

&lt;p&gt;The full script is at the end of this post. Here is what it does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it scans:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It looks for files named &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;.env.production&lt;/code&gt;, &lt;code&gt;.env.local&lt;/code&gt;, and&lt;br&gt;
&lt;code&gt;.env.local.secrets&lt;/code&gt;. It skips &lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;.git&lt;/code&gt;, build output, caches,&lt;br&gt;
and other directories you would never want to crawl.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it matches:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It uses &lt;code&gt;rg&lt;/code&gt; (ripgrep) to find lines matching a pattern. The default pattern is&lt;br&gt;
&lt;code&gt;token|api|key&lt;/code&gt;, which catches most secret-looking variable names without&lt;br&gt;
generating too much noise. You can override it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash find-env-secret-patterns.sh &lt;span class="nt"&gt;--pattern&lt;/span&gt; &lt;span class="s1"&gt;'token|secret|api|key|password'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What it skips:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lines containing &lt;code&gt;encrypted:&lt;/code&gt; (already handled by &lt;code&gt;dotenvx&lt;/code&gt;), &lt;code&gt;DOTENV_PUBLIC_KEY&lt;/code&gt;&lt;br&gt;
(not a secret), &lt;code&gt;USE_KEYCHAIN_FOR_DOTX&lt;/code&gt; (a config flag, not a credential), and&lt;br&gt;
the &lt;code&gt;dotenvx&lt;/code&gt; public-key header comment are all ignored. For &lt;code&gt;.env.local.secrets&lt;/code&gt;&lt;br&gt;
files specifically, it only reports lines that look like env-style assignments&lt;br&gt;
(&lt;code&gt;KEY=value&lt;/code&gt;), since the rest of an encrypted file is ciphertext noise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it outputs:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every match is printed as &lt;code&gt;filepath:line_number:KEY=[REDACTED]&lt;/code&gt;. The script&lt;br&gt;
redacts everything after the &lt;code&gt;=&lt;/code&gt; sign, so you can share the output or paste it&lt;br&gt;
into a ticket without leaking actual values.&lt;/p&gt;
&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# scan your entire home directory (the default)&lt;/span&gt;
bash find-env-secret-patterns.sh

&lt;span class="c"&gt;# scan only your code directory&lt;/span&gt;
bash find-env-secret-patterns.sh &lt;span class="nt"&gt;--root&lt;/span&gt; ~/code

&lt;span class="c"&gt;# use a broader pattern&lt;/span&gt;
bash find-env-secret-patterns.sh &lt;span class="nt"&gt;--pattern&lt;/span&gt; &lt;span class="s1"&gt;'token|secret|api|key|password|credential'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/Users/you/code/project-a/.env.local:3:GOOGLE_CLIENT_SECRET=[REDACTED]
/Users/you/code/project-a/.env.local:5:POSTGRES_URL=[REDACTED]
/Users/you/code/project-b/.env:2:STRIPE_API_KEY=[REDACTED]
/Users/you/code/old-project/.env.production:8:AUTH_TOKEN=[REDACTED]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of those lines is a plaintext secret sitting on your disk.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do with the results
&lt;/h2&gt;

&lt;p&gt;The output tells you where your exposure is. From there you have a few options&lt;br&gt;
depending on how much effort you want to put in:&lt;/p&gt;

&lt;p&gt;For active projects, this is a good prompt to adopt the &lt;code&gt;dotenvx&lt;/code&gt; + OS keychain&lt;br&gt;
pattern from the &lt;a href="https://dev.to/ustun/a-small-hardening-trick-for-envlocal-dotenvx-os-keychain-2533"&gt;first post&lt;/a&gt;: Split secrets into &lt;code&gt;.env.local.secrets&lt;/code&gt;, encrypt them, move the key into your keychain, and those files will stop showing up in future scans.&lt;/p&gt;

&lt;p&gt;For old projects you are not actively working on, consider whether those &lt;code&gt;.env&lt;/code&gt;&lt;br&gt;
files need to exist at all. If you have not touched a project in six months, the&lt;br&gt;
credentials in its &lt;code&gt;.env.local&lt;/code&gt; might be stale, but they might also still be&lt;br&gt;
valid. Deleting or encrypting them reduces your surface.&lt;/p&gt;

&lt;p&gt;For production env files (&lt;code&gt;.env.production&lt;/code&gt;), those should probably not be on&lt;br&gt;
your machine in plaintext at all. If they are showing up in the scan, that is&lt;br&gt;
worth a closer look at how your deployment pipeline distributes config.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it periodically
&lt;/h2&gt;

&lt;p&gt;You could add this to a cron job or a weekly reminder. The script exits with&lt;br&gt;
code 0 on success (whether or not it found matches) and code 1 on errors, so&lt;br&gt;
it plays well with automation. If you want to fail a CI check when plaintext&lt;br&gt;
secrets are found, you could adjust the exit code for the &lt;code&gt;FOUND_MATCH&lt;/code&gt; case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design decisions
&lt;/h2&gt;

&lt;p&gt;A few things I was deliberate about:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redaction by default.&lt;/strong&gt; The script never prints secret values. It shows you the&lt;br&gt;
variable name and the file location, which is enough to act on. This means you&lt;br&gt;
can pipe the output into a log or share it with a teammate without worrying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;rg&lt;/code&gt; over &lt;code&gt;grep&lt;/code&gt;.&lt;/strong&gt; ripgrep is faster on large directory trees and handles&lt;br&gt;
null-delimited input cleanly. If you do not have &lt;code&gt;rg&lt;/code&gt; installed, &lt;code&gt;brew install&lt;br&gt;
ripgrep&lt;/code&gt; or your package manager equivalent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skipping &lt;code&gt;dotenvx&lt;/code&gt; artifacts.&lt;/strong&gt; If you have already encrypted a file with&lt;br&gt;
&lt;code&gt;dotenvx&lt;/code&gt;, the script should not flag it. The skip patterns for &lt;code&gt;encrypted:&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;DOTENV_PUBLIC_KEY&lt;/code&gt; handle this. The &lt;code&gt;.env.local.secrets&lt;/code&gt; special casing is there&lt;br&gt;
because an encrypted file still has key-looking variable names in its ciphertext&lt;br&gt;
lines, but those are not plaintext secrets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;find&lt;/code&gt; with &lt;code&gt;-print0&lt;/code&gt; and null-delimited reads.&lt;/strong&gt; Filenames with spaces or&lt;br&gt;
special characters will not break the script. This matters less for &lt;code&gt;.env&lt;/code&gt; files&lt;br&gt;
specifically, but it is a good habit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full script
&lt;/h2&gt;

&lt;p&gt;The complete script is in this gist:&lt;br&gt;
&lt;a href="https://gist.github.com/ustun/ece8a17ab61028786c1366127cad1569" rel="noopener noreferrer"&gt;find-env-secret-patterns.sh&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thought
&lt;/h2&gt;

&lt;p&gt;Encrypting secrets is the fix. But knowing where unencrypted secrets are sitting&lt;br&gt;
is the prerequisite. You cannot harden what you have not inventoried.&lt;/p&gt;

&lt;p&gt;Run this once across your home directory and see what comes back. Then do the hardening steps in the previous post.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>devops</category>
      <category>security</category>
      <category>tooling</category>
    </item>
    <item>
      <title>A Small Hardening Trick for .env.local: dotenvx + OS Keychain</title>
      <dc:creator>Ustun Ozgur</dc:creator>
      <pubDate>Wed, 25 Mar 2026 08:53:02 +0000</pubDate>
      <link>https://dev.to/ustun/a-small-hardening-trick-for-envlocal-dotenvx-os-keychain-2533</link>
      <guid>https://dev.to/ustun/a-small-hardening-trick-for-envlocal-dotenvx-os-keychain-2533</guid>
      <description>&lt;p&gt;Most teams keep local secrets in &lt;code&gt;.env.local&lt;/code&gt; and add that file to &lt;code&gt;.gitignore&lt;/code&gt;.&lt;br&gt;
That is the bare minimum, and it does not address a more pressing risk: supply&lt;br&gt;
chain attacks and compromised local tooling that read &lt;code&gt;.env&lt;/code&gt; files as soon as&lt;br&gt;
they get repo access.&lt;/p&gt;

&lt;p&gt;Once a malicious dependency, postinstall script, editor extension, MCP server,&lt;br&gt;
AI coding tool, or other local helper can inspect your workspace, plain-text&lt;br&gt;
&lt;code&gt;.env.local&lt;/code&gt; files become low-effort, high-value targets.&lt;/p&gt;

&lt;p&gt;I wanted a low-friction way to reduce that blast radius without forcing the whole&lt;br&gt;
team onto a heavyweight secrets manager for day-to-day local development.&lt;/p&gt;

&lt;p&gt;This is the pattern I landed on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep non-secret local config in &lt;code&gt;.env.local&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;move actual secrets into &lt;code&gt;.env.local.secrets&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;encrypt &lt;code&gt;.env.local.secrets&lt;/code&gt; with &lt;code&gt;dotenvx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;move the decryption key out of disk and into macOS Keychain&lt;/li&gt;
&lt;li&gt;load &lt;code&gt;.env.local&lt;/code&gt; first, then only decrypt secrets when an explicit opt-in
flag says to&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Important distinction: I am &lt;strong&gt;not&lt;/strong&gt; using &lt;code&gt;dotenvx&lt;/code&gt; the way it is often&lt;br&gt;
marketed, where encrypted env files are committed to the repo and shared that&lt;br&gt;
way. This setup is local-only. The encrypted file and &lt;code&gt;.env.keys&lt;/code&gt; both stay&lt;br&gt;
uncommitted, and I prefer it that way. Committing encrypted env files is useful&lt;br&gt;
when you want team-wide encrypted config distribution, but that was not my goal.&lt;br&gt;
I wanted to reduce plaintext secrets on developer machines and raise the cost of&lt;br&gt;
tools that slurp local env files, while keeping the workflow simple enough that&lt;br&gt;
teammates actually use it.&lt;/p&gt;
&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;Start with a normal &lt;code&gt;.env.local&lt;/code&gt;, then split it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.env.local&lt;/code&gt;: safe local config, feature flags, non-secret defaults&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.env.local.secrets&lt;/code&gt;: secrets only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .env.local
BETTER_AUTH_URL=http://localhost:3000
USE_KEYCHAIN_FOR_DOTX=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .env.local.secrets
POSTGRES_URL=postgres://...
GOOGLE_CLIENT_SECRET=...
BETTER_AUTH_SECRET=...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure your &lt;code&gt;.gitignore&lt;/code&gt; covers all the pieces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.env.local
.env.local.secrets
.env.keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then encrypt the secrets file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm &lt;span class="nb"&gt;exec &lt;/span&gt;dotenvx encrypt &lt;span class="nt"&gt;-f&lt;/span&gt; .env.local.secrets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives you an encrypted &lt;code&gt;.env.local.secrets&lt;/code&gt; and a decryption key in&lt;br&gt;
&lt;code&gt;.env.keys&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At this point, you have improved at-rest security a bit, but the key is still on&lt;br&gt;
disk, which is not the end state we want.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why bother with the extra steps?
&lt;/h2&gt;

&lt;p&gt;There have been enough supply chain and developer tooling incidents lately that I&lt;br&gt;
no longer treat "it is gitignored" as a meaningful security boundary. Once&lt;br&gt;
something malicious lands in your development environment, one of the first&lt;br&gt;
profitable things it can do is read local env files and exfiltrate credentials.&lt;/p&gt;

&lt;p&gt;Encrypting local secrets at rest is not a complete defense, but it is a useful&lt;br&gt;
speed bump:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;secrets are no longer sitting in plaintext on disk&lt;/li&gt;
&lt;li&gt;the decryption key can live in the OS keychain instead of another dotfile&lt;/li&gt;
&lt;li&gt;accidental repo-wide file reads become less damaging&lt;/li&gt;
&lt;li&gt;you can keep the workflow mostly compatible with existing frameworks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This does &lt;strong&gt;not&lt;/strong&gt; protect secrets after your app starts. At runtime, the process&lt;br&gt;
still has decrypted environment variables in memory. But that is still better&lt;br&gt;
than leaving everything plaintext on disk all the time.&lt;/p&gt;
&lt;h2&gt;
  
  
  Move the key into macOS Keychain
&lt;/h2&gt;

&lt;p&gt;Copy the &lt;code&gt;DOTENV_PRIVATE_KEY_LOCAL_SECRETS&lt;/code&gt; value from &lt;code&gt;.env.keys&lt;/code&gt;, then store it&lt;br&gt;
in Keychain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;security add-generic-password &lt;span class="nt"&gt;-U&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-a&lt;/span&gt; LOCAL_SECRETS_DOTENVX_KEY &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-s&lt;/span&gt; LOCAL_SECRETS_DOTENVX_KEY &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-w&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;security&lt;/code&gt;, keeping &lt;code&gt;-w&lt;/code&gt; as the last argument makes it prompt you for the&lt;br&gt;
secret instead of putting it in your shell history.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;LOCAL_SECRETS_DOTENVX_KEY&lt;/code&gt; label is just an example. Pick any consistent&lt;br&gt;
Keychain item name you want, then use that same name everywhere in your scripts.&lt;/p&gt;

&lt;p&gt;Now you can delete &lt;code&gt;.env.keys&lt;/code&gt;. Before you do, stash the key somewhere safe&lt;br&gt;
outside the repo. A password manager like 1Password is a good choice. You will&lt;br&gt;
need it if you set up another machine or need to recover.&lt;/p&gt;

&lt;p&gt;With that, your decryption key is no longer sitting next to the repo in another&lt;br&gt;
plaintext file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Linux and Windows.&lt;/strong&gt; This post uses macOS Keychain, but the same idea&lt;br&gt;
applies elsewhere. On Linux, &lt;code&gt;secret-tool&lt;/code&gt; (backed by &lt;code&gt;libsecret&lt;/code&gt; and&lt;br&gt;
GNOME Keyring or KWallet) fills the same role. On Windows, you can use&lt;br&gt;
Credential Manager via PowerShell's &lt;code&gt;Get-StoredCredential&lt;/code&gt; /&lt;br&gt;
&lt;code&gt;New-StoredCredential&lt;/code&gt; cmdlets. The loading pattern stays the same; only&lt;br&gt;
the key retrieval command changes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  The loading pattern
&lt;/h2&gt;

&lt;p&gt;The subtle part is loader order.&lt;/p&gt;

&lt;p&gt;If you want a flag like &lt;code&gt;USE_KEYCHAIN_FOR_DOTX=true&lt;/code&gt; to live in &lt;code&gt;.env.local&lt;/code&gt;,&lt;br&gt;
your app needs to read &lt;code&gt;.env.local&lt;/code&gt; &lt;strong&gt;before&lt;/strong&gt; it decides whether to pull the&lt;br&gt;
decryption key from Keychain.&lt;/p&gt;

&lt;p&gt;That means the loader should do this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Load &lt;code&gt;.env&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Load &lt;code&gt;.env.local&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Check &lt;code&gt;USE_KEYCHAIN_FOR_DOTX&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If enabled, read &lt;code&gt;DOTENV_PRIVATE_KEY_LOCAL_SECRETS&lt;/code&gt; from Keychain&lt;/li&gt;
&lt;li&gt;Load &lt;code&gt;.env.local.secrets&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the core idea in TypeScript:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/ustun/1f5a9974394cc32bba066e5584243ada" rel="noopener noreferrer"&gt;Open as GitHub Gist&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;execFileSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:child_process&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;existsSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@dotenvx/dotenvx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadEnv&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.env&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.env.local&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;localSecretsPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.env.local.secrets&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localSecretsPath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;USE_KEYCHAIN_FOR_DOTX&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DOTENV_PRIVATE_KEY_LOCAL_SECRETS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;security&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;find-generic-password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;LOCAL_SECRETS_DOTENVX_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-s&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;LOCAL_SECRETS_DOTENVX_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-w&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to read decryption key from macOS Keychain. &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Make sure the LOCAL_SECRETS_DOTENVX_KEY item exists. &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;See scripts/store-keychain-key.sh for setup.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;localSecretsPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;overload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// The private key has done its job. Remove it from the environment so it&lt;/span&gt;
  &lt;span class="c1"&gt;// is not visible in process.env dumps or child process inheritance.&lt;/span&gt;
  &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DOTENV_PRIVATE_KEY_LOCAL_SECRETS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;execFileSync&lt;/code&gt; will throw on a non-zero exit, so the try/catch above turns
a cryptic child-process error into a clear setup instruction&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;delete&lt;/code&gt; at the end matters: once &lt;code&gt;dotenvx&lt;/code&gt; has decrypted the secrets
into their individual env vars, the private key has no further purpose; leaving
it in &lt;code&gt;process.env&lt;/code&gt; means any code that inspects the environment (logging,
diagnostics, error reporters) could leak the one key that decrypts the file&lt;/li&gt;
&lt;li&gt;do not log even partial key material during startup. That is easy to get
wrong when debugging the integration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One thing this does &lt;strong&gt;not&lt;/strong&gt; protect against: once your app is running, the&lt;br&gt;
decrypted secrets are plain strings in &lt;code&gt;process.env&lt;/code&gt;. Anyone who can attach a&lt;br&gt;
Node debugger to your process can inspect memory directly.&lt;/p&gt;

&lt;p&gt;Cross-process env snooping is more nuanced. On macOS 11+, SIP prevents&lt;br&gt;
processes from reading other processes' environment variables, so this vector&lt;br&gt;
is largely closed on a default macOS install. On Linux, &lt;code&gt;/proc/&amp;lt;pid&amp;gt;/environ&lt;/code&gt;&lt;br&gt;
is still readable by any process running as the same user. Either way, this&lt;br&gt;
pattern is about secrets at rest on disk, not secrets in a running process.&lt;/p&gt;
&lt;h2&gt;
  
  
  Next.js integration
&lt;/h2&gt;

&lt;p&gt;If you are using Next.js, you cannot just call &lt;code&gt;loadEnv()&lt;/code&gt; from anywhere and&lt;br&gt;
expect it to work. Next.js has its own env loading built in, and by the time&lt;br&gt;
your application code runs, it has already resolved which variables are&lt;br&gt;
available.&lt;/p&gt;

&lt;p&gt;The right place to hook this in is &lt;code&gt;instrumentation.ts&lt;/code&gt; (or &lt;code&gt;.js&lt;/code&gt;). Next.js&lt;br&gt;
calls the &lt;code&gt;register&lt;/code&gt; function exported from this file once when the server&lt;br&gt;
starts, before any routes or middleware run. That makes it the earliest&lt;br&gt;
reliable point to pull secrets from Keychain and inject them into &lt;code&gt;process.env&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// instrumentation.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;loadEnv&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./lib/load-env&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;loadEnv&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dynamic import is intentional. It keeps the Keychain and &lt;code&gt;dotenvx&lt;/code&gt; logic&lt;br&gt;
out of the client bundle and avoids top-level side effects that could run at&lt;br&gt;
the wrong time.&lt;/p&gt;

&lt;p&gt;Make sure &lt;code&gt;instrumentation.ts&lt;/code&gt; is at your project root (next to &lt;code&gt;next.config&lt;/code&gt;),&lt;br&gt;
and that you are on Next.js 15+ where the instrumentation hook is stable. On&lt;br&gt;
older versions (13.2 through 14.x) it works but requires setting&lt;br&gt;
&lt;code&gt;experimental.instrumentationHook: true&lt;/code&gt; in your Next config.&lt;/p&gt;
&lt;h2&gt;
  
  
  Helper scripts for temporary decrypt/re-encrypt
&lt;/h2&gt;

&lt;p&gt;I also like keeping two tiny helper scripts around so I can temporarily decrypt&lt;br&gt;
the file, edit it, and then re-encrypt it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# scripts/decrypt-local-secrets.sh&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;KEYCHAIN_ITEM_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"LOCAL_SECRETS_DOTENVX_KEY"&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DOTENV_PRIVATE_KEY_LOCAL_SECRETS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;
  security find-generic-password &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEYCHAIN_ITEM_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEYCHAIN_ITEM_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-w&lt;/span&gt;
&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

pnpm &lt;span class="nb"&gt;exec &lt;/span&gt;dotenvx decrypt &lt;span class="nt"&gt;-f&lt;/span&gt; .env.local.secrets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# scripts/encrypt-local-secrets.sh&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;KEYCHAIN_ITEM_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"LOCAL_SECRETS_DOTENVX_KEY"&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DOTENV_PRIVATE_KEY_LOCAL_SECRETS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;
  security find-generic-password &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEYCHAIN_ITEM_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEYCHAIN_ITEM_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-w&lt;/span&gt;
&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

pnpm &lt;span class="nb"&gt;exec &lt;/span&gt;dotenvx encrypt &lt;span class="nt"&gt;-f&lt;/span&gt; .env.local.secrets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives you a simple workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash scripts/decrypt-local-secrets.sh
&lt;span class="c"&gt;# edit .env.local.secrets&lt;/span&gt;
bash scripts/encrypt-local-secrets.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One risk here: if you decrypt the file, edit it, and forget to re-encrypt,&lt;br&gt;
your secrets are back to sitting in plaintext. A git pre-commit hook can catch&lt;br&gt;
this. Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# .husky/pre-commit or .git/hooks/pre-commit&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; 50 .env.local.secrets 2&amp;gt;/dev/null | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qv&lt;/span&gt; &lt;span class="s2"&gt;"^#/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ERROR: .env.local.secrets appears to be decrypted. Run:"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  bash scripts/encrypt-local-secrets.sh"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(&lt;code&gt;dotenvx&lt;/code&gt;-encrypted files start with a comment header like&lt;br&gt;
&lt;code&gt;#/-------------------[DOTENV]--------------------/&lt;/code&gt;, so checking for the&lt;br&gt;
absence of that prefix is a reasonable heuristic.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this helps, and where it doesn't
&lt;/h2&gt;

&lt;p&gt;This pattern helps with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;raising the cost of supply chain attacks that look for &lt;code&gt;.env&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;repo-wide local file scraping&lt;/li&gt;
&lt;li&gt;accidental plaintext secret exposure in local tooling&lt;/li&gt;
&lt;li&gt;reducing how many places secrets live on disk&lt;/li&gt;
&lt;li&gt;avoiding accidental exposure during screen sharing and pair programming&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does not solve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;malicious code running inside your process&lt;/li&gt;
&lt;li&gt;a debugger attached to your Node process (secrets are in memory as plain strings)&lt;/li&gt;
&lt;li&gt;cross-process env snooping on Linux (&lt;code&gt;/proc/&amp;lt;pid&amp;gt;/environ&lt;/code&gt;); macOS SIP blocks
this since Big Sur, but Linux does not&lt;/li&gt;
&lt;li&gt;secrets already exported into your shell&lt;/li&gt;
&lt;li&gt;logs or copy/paste leaks&lt;/li&gt;
&lt;li&gt;production secret management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it as one useful layer, not as a silver bullet.&lt;/p&gt;

&lt;h3&gt;
  
  
  A note on screen sharing
&lt;/h3&gt;

&lt;p&gt;If your secrets live in a&lt;br&gt;
plain-text &lt;code&gt;.env.local&lt;/code&gt;, it is very easy to accidentally flash them on screen&lt;br&gt;
during a Zoom call, a pair programming session, or a live demo. All it takes&lt;br&gt;
is opening the wrong file, running &lt;code&gt;cat&lt;/code&gt; on it, or having your editor preview&lt;br&gt;
it in a sidebar.&lt;/p&gt;

&lt;p&gt;With encrypted &lt;code&gt;.env.local.secrets&lt;/code&gt;, that file is just opaque ciphertext. Even&lt;br&gt;
if you open it on camera, nobody sees your database credentials or API keys.&lt;br&gt;
The decryption only happens at runtime, in memory, when your app starts, not&lt;br&gt;
when a human or a screen recording is looking at your filesystem.&lt;/p&gt;

&lt;p&gt;This is not a reason to adopt the pattern on its own, but it is a nice side&lt;br&gt;
effect that has already saved me at least once.&lt;/p&gt;

&lt;h2&gt;
  
  
  The developer-experience bar matters
&lt;/h2&gt;

&lt;p&gt;The reason I like this approach is that it is security work people may actually&lt;br&gt;
keep using.&lt;/p&gt;

&lt;p&gt;Once set up, the workflow is close to normal local development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep config in &lt;code&gt;.env.local&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;keep secrets in &lt;code&gt;.env.local.secrets&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;let the app pull the key from Keychain when needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is much more likely to stick than a system that feels "more secure" on paper&lt;br&gt;
but creates enough friction that everyone bypasses it.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want to adopt this
&lt;/h2&gt;

&lt;p&gt;My suggestions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with local-only encryption, not a big secret-sharing redesign.&lt;/li&gt;
&lt;li&gt;Separate non-secret config from secrets first.&lt;/li&gt;
&lt;li&gt;Make the Keychain path opt-in with a clear env flag.&lt;/li&gt;
&lt;li&gt;Ensure &lt;code&gt;.env.local&lt;/code&gt; loads before you evaluate that flag.&lt;/li&gt;
&lt;li&gt;Audit helper scripts too, not just the main app boot path.&lt;/li&gt;
&lt;li&gt;Back up your decryption key in a password manager before deleting &lt;code&gt;.env.keys&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add a pre-commit hook to catch unencrypted secrets files.&lt;/li&gt;
&lt;li&gt;Never print keys, even partially, while debugging the integration.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last point deserves repeating.&lt;/p&gt;

&lt;p&gt;Security improvements have a way of being partially undone by "temporary"&lt;br&gt;
debugging statements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related tools worth looking at
&lt;/h2&gt;

&lt;p&gt;If this pattern feels too lightweight for your needs, or you want something&lt;br&gt;
more structured, there are good adjacent tools in this space.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://getsops.io/" rel="noopener noreferrer"&gt;SOPS&lt;/a&gt; is a strong option when you want encrypted files as&lt;br&gt;
a first-class workflow, especially in teams already comfortable with cloud KMS,&lt;br&gt;
age, or GitOps-style config management.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dmno.dev/" rel="noopener noreferrer"&gt;DMNO&lt;/a&gt; goes in a different direction: schema-aware config,&lt;br&gt;
tooling around developer experience, and integrations with external secret&lt;br&gt;
stores. Their &lt;a href="https://dmno.dev/docs/plugins/1password/" rel="noopener noreferrer"&gt;1Password plugin&lt;/a&gt;&lt;br&gt;
is a good example if you want local development ergonomics tied more directly to&lt;br&gt;
a secrets manager instead of local encrypted &lt;code&gt;.env&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;I do not see these as mutually exclusive with the smaller pattern in this post.&lt;br&gt;
They just sit at different points on the complexity and capability curve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thought
&lt;/h2&gt;

&lt;p&gt;I do not think local &lt;code&gt;.env&lt;/code&gt; files are going away anytime soon.&lt;/p&gt;

&lt;p&gt;But I do think the threat model around them has changed.&lt;/p&gt;

&lt;p&gt;A small amount of structure, encryption at rest, and OS-managed key storage can&lt;br&gt;
go a surprisingly long way without making local development miserable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Followup&lt;/strong&gt;: I also wrote a companion script that scans your machine for plaintext secrets sitting in .env files. It pairs well with this post as a way to find what needs encrypting.&lt;br&gt;
&lt;a href="https://dev.to/ustun/find-plaintext-secrets-hiding-in-your-env-files-5dpl"&gt;Find Plaintext Secrets Hiding in Your .env Files&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>dotenv</category>
      <category>dotenvx</category>
      <category>secrets</category>
    </item>
    <item>
      <title>Object Oriented Functional Programming or How Can You Use Classes as Redux Reducers</title>
      <dc:creator>Ustun Ozgur</dc:creator>
      <pubDate>Mon, 20 Feb 2017 06:24:48 +0000</pubDate>
      <link>https://dev.to/ustun/object-oriented-functional-programming-or-how-can-you-use-classes-as-reduxreducers</link>
      <guid>https://dev.to/ustun/object-oriented-functional-programming-or-how-can-you-use-classes-as-reduxreducers</guid>
      <description>&lt;p&gt;&lt;em&gt;Note: This article originally appeared &lt;a href="https://medium.com/@ustunozgur/object-oriented-functional-programming-or-how-can-you-use-classes-as-redux-reducers-23462a5cae85#.815k9536e" rel="noopener noreferrer"&gt;Ustun Ozgur's blog on Medium&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;TL;DR You can use ImmutableJS Record classes with methods as Redux reducers, combining the best of FP and OOP. &lt;br&gt;
See the final result here: &lt;a href="https://gist.github.com/ustun/f55dc03ff3f7a0c169c517b459d59810" rel="noopener noreferrer"&gt;https://gist.github.com/ustun/f55dc03ff3f7a0c169c517b459d59810&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the last decade, functional programming has been steadily gaining&lt;br&gt;
popularity, while object oriented programming is being questioned more&lt;br&gt;
and more. The kingdom of nouns is now being threatened by the kingdom&lt;br&gt;
of verbs, and we can see this revolution best explained in Rich&lt;br&gt;
Hickey's talk Simple Made Easy.&lt;/p&gt;

&lt;p&gt;In the JavaScript frontend ecosystem, React broke the last functional&lt;br&gt;
frontier, UI development and ideas from the functional world such as&lt;br&gt;
immutability, higher order functions are now becoming common-place in&lt;br&gt;
the industry.&lt;/p&gt;

&lt;p&gt;The principal difference between object oriented programs and&lt;br&gt;
functional programs is their stance on handling data and&lt;br&gt;
state. Objects by their nature encapsulate data, whereas in functional&lt;br&gt;
programs, data is usually separated from the code. One additional&lt;br&gt;
vital difference is that, most OOP systems also incorporate identity&lt;br&gt;
tracking, that is, an object is not only the sum of its state (data)&lt;br&gt;
and methods (or functions in FP world), but also identity.&lt;/p&gt;

&lt;p&gt;So,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OOP out of the box gives you identity + state + methods.&lt;/li&gt;
&lt;li&gt;FP out of the box gives you data + functions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tracking the identity is left as exercise to the reader, which is a&lt;br&gt;
blessing and a curse; and as a consultant and trainer to multiple&lt;br&gt;
companies, the single most source of confusion people face when&lt;br&gt;
transitioning paradigms.&lt;/p&gt;

&lt;p&gt;Decoupling&lt;/p&gt;

&lt;p&gt;The fundamental idea in analyzing big systems is decoupling and layering. When confronted with state, functional programming basically asks the&lt;br&gt;
following question: What if we would take the three notions,&lt;br&gt;
state, identity and methods and decouple them?&lt;/p&gt;

&lt;p&gt;The advantage is that these different parts can be constructed and&lt;br&gt;
assembled separately. The disadvantage is you risk losing the cohesion&lt;br&gt;
of your abstractions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Functions andÂ Methods&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's start with methods for example. Most classes act as bags of&lt;br&gt;
methods, so if you have a few methods on your plate, you could&lt;br&gt;
actually have those as different functions that take the primary data&lt;br&gt;
being operated on as the first argument. Effectively, thing.doIt() becomes doIt(thing).&lt;/p&gt;

&lt;p&gt;Such functions can obviously take additional arguments, however most&lt;br&gt;
of the time, in a business application setting which follows the&lt;br&gt;
Domain Model pattern, the first argument of the function will be the&lt;br&gt;
domain model we are operating on.&lt;/p&gt;

&lt;p&gt;As the number of functions increases though, your program is in a&lt;br&gt;
danger of filling up with lots of functions scattered around. FP&lt;br&gt;
languages do not give much guidance here, effectively you are free to&lt;br&gt;
do whatever you prefer. Again a blessing and a curse.&lt;/p&gt;

&lt;p&gt;In an OOP world, where a function goes in is pretty much defined; in&lt;br&gt;
less flexible languages like Java (before Java 8) for example, the&lt;br&gt;
functions belonged to classes.&lt;/p&gt;

&lt;p&gt;In a more flexible language like JavaScript though, we could collect&lt;br&gt;
the functions related to a data structure in a module or an object&lt;br&gt;
literal.&lt;/p&gt;

&lt;p&gt;For example, if we have 3 different functions operating on a data&lt;br&gt;
structure like Person, we could collect three functions operating on&lt;br&gt;
Person datas as follows:&lt;/p&gt;

&lt;p&gt;PersonFunctions = {&lt;br&gt;
Â doThis(person,Â …) {Â … }&lt;br&gt;
Â doThat(person,Â …) {Â … }&lt;br&gt;
Â doBar(person,Â …) {Â … }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This is effectively solving the third part of the decoupling process,&lt;br&gt;
namely handling the placement of the methods.&lt;/p&gt;

&lt;p&gt;Another alternative here would be to create a JS module (a file&lt;br&gt;
actually) that has these functions at the top-level, as follows:&lt;br&gt;
in person_functions.js&lt;br&gt;
function doThis(person,Â …) {Â ….}&lt;br&gt;
function doThat(person,Â …) {Â ….}&lt;br&gt;
function doBar(person,Â …) {Â ….}&lt;/p&gt;

&lt;p&gt;(In a langugage like Clojure, for example, the equivalent would be to put these functions into namespaces.)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;State, Data andÂ Identity&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As mentioned before, functional programs effectively separate state&lt;br&gt;
(data) and identity. Most OOP systems operate the data in place,&lt;br&gt;
whereas the functional counterparts need to handle both the input and&lt;br&gt;
output of the data explicitly. Hence, in OOP, &lt;code&gt;this&lt;/code&gt; keyword offers a convenience to the following 3 steps in a functional program:&lt;/p&gt;

&lt;p&gt;aâ€Š–â€Šget data =&amp;gt; state as data&lt;br&gt;
bâ€Š–â€Štransform data =&amp;gt; some_function(data)&lt;br&gt;
câ€Š–â€Šput the data where you took it. =&amp;gt; state = some_function(data)&lt;/p&gt;

&lt;p&gt;In OOP world, steps a &amp;amp; c are automatic, if you access the state in&lt;br&gt;
the thing pointed by this keyword. This is the main decoupling here, OOP takes the position that most of the time, you will put the data from where you took it back, where FP takes the position that these three steps could be decoupled.&lt;/p&gt;

&lt;p&gt;If you want to track the identity in an FP system, you have to do it&lt;br&gt;
manually, though it is not as laborous as it sounds.&lt;/p&gt;

&lt;p&gt;For example, Clojure provides atoms, which effectively are more similar to objects in Java or JavaScript; which enclose the pure data.&lt;/p&gt;

&lt;p&gt;Any function call operating on an atom effectively sends the same call to the inner object, and writes the output object back.&lt;/p&gt;

&lt;p&gt;Let's say we have an atom that wraps some data.&lt;/p&gt;

&lt;p&gt;my_object = atom(data)&lt;br&gt;
swap(my_object, some_function)&lt;/p&gt;

&lt;p&gt;effectively becomes three operations:&lt;/p&gt;

&lt;p&gt;1- Extract the data from the object.&lt;br&gt;
2- Execute some function on the data.&lt;br&gt;
3- Write the data back into the object.&lt;/p&gt;

&lt;p&gt;As a result, if identity tracking is added, a FP system is&lt;br&gt;
equivalent to an OOP system.&lt;/p&gt;

&lt;p&gt;Redux&lt;/p&gt;

&lt;p&gt;And this is where Redux comes in. Redux is basically advertised as “a&lt;br&gt;
state container”, which wraps your data (state) in an object&lt;br&gt;
(store). And any transformation you do is a transforming function&lt;br&gt;
called “a reducer”.&lt;/p&gt;

&lt;p&gt;Excluding the fancy terms like state containment and reducing&lt;br&gt;
operation though, this is effectively just what OOP provides. OOP&lt;br&gt;
provides a container for your data, and provides some methods&lt;br&gt;
(equivalent to functions, reducers) that operate on that data, and put&lt;br&gt;
the result back into the place when the transformation is done.&lt;br&gt;
Hence, Redux reducers are equivalent to traditional Object Oriented&lt;br&gt;
Programming, with the following two differences:&lt;/p&gt;

&lt;p&gt;1- It does not give you dispatch by default, so you have to do if/else/switch to select the method to operate on.&lt;br&gt;
2- All the data is modelled as immutable data structures.&lt;/p&gt;

&lt;p&gt;So, the obvious question is this: Can we have our cake and eat it too?&lt;/p&gt;

&lt;p&gt;That is, how can someone proficient with object modeling reuse his&lt;br&gt;
skills in a Redux application?&lt;/p&gt;

&lt;p&gt;The Obligatory TodoÂ App&lt;/p&gt;

&lt;p&gt;Let's consider the following transform function for a TodoApp, a reducer. The basic domain modeling is as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can add, remove todos, toggle todos' completion state, and add a
Â temporary todo text that will be added when user presses
Â Submit. I'll just implement REMOVE_TODOS so that the code is
Â concise.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;todoAppReducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;:[],&lt;/span&gt; &lt;span class="na"&gt;newTodo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;â&lt;/span&gt;&lt;span class="err"&gt;€˜&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;}, action) {
    switch (action.type) {
    case â€˜REMOVE_TODO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;â&lt;/span&gt;&lt;span class="err"&gt;€˜&lt;/span&gt;&lt;span class="nx"&gt;ADD_TODO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:
    case â€˜TOGGLE_TODO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;â&lt;/span&gt;&lt;span class="err"&gt;€˜&lt;/span&gt;&lt;span class="nx"&gt;ADD_TEMP_TODO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:
    }
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The first refactoring results in the following, where we replace dispatch functions with an object bag of methods.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;todoAppReducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;:[],&lt;/span&gt; &lt;span class="na"&gt;newTodo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;â&lt;/span&gt;&lt;span class="err"&gt;€˜&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;}, action) {
    methods = {
    REMOVE_TODO: function (payload) return {…state, todos: state.todos.filter(todo=&amp;gt;todo.description != payload.description)},
    ADD_TODO: function () …,
    TOGGLE_TODO: function () …,
    ADD_TEMP_TODO: function ()
    }

    return methods[action.type](action.payload)
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, since the functions in methods object are inside the main function, all of them can access the variable named state. If we take the methods object out those, we have to pass the state explicitly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;methods&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;REMOVE_TODO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
    &lt;span class="na"&gt;ADD_TODO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;TOGGLE_TODO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ADD_TEMP_TODO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;todoAppReducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;:[],&lt;/span&gt; &lt;span class="na"&gt;newTodo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;â&lt;/span&gt;&lt;span class="err"&gt;€˜&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;}, action) {
    return methods[action.type](state, action.payload)
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, the object literal methods is starting to look more like a&lt;br&gt;
traditional bag of objects, a class. First, let's move them inside a&lt;br&gt;
proper class, where we do not make use of this for now. Effectively,&lt;br&gt;
this is a class of static methods that take â€˜state' as first variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Todo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nc"&gt;REMOVE_TODO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;ADD_TODO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this stage, we are almost midway between FP and OOP. Closer to FP in spirit, and closer to OOP in look. The generation of immutable values is quite ugly though, using spread operator and various tricks that will irk most newcomers.&lt;br&gt;
Enter ImmutableJS library, which makes these transformations natural. Getting a new version of an immutable object with all the fields, except one intact is as simple as just setting that field.&lt;br&gt;
For example, let's say we have object A, and want to get object B, but with name set to foo.&lt;/p&gt;

&lt;p&gt;B = A.set(â€˜name', â€˜foo')&lt;/p&gt;

&lt;p&gt;Effectively, as an OOP programmer, you can think of ImmutableJS as taking a clone of your current object without defining cloning operation and setting the different values.&lt;br&gt;
Want to have the same as in object A, but with name â€˜foo', and surname â€˜bar'?&lt;br&gt;
You could do it by setting those in succession:&lt;/p&gt;

&lt;p&gt;A.set(â€˜name', â€˜foo').set(â€˜surname', â€˜bar')&lt;/p&gt;

&lt;p&gt;or in one step by merging the second object like:&lt;/p&gt;

&lt;p&gt;A.merge({name: â€˜foo', surname: â€˜bar'})&lt;/p&gt;

&lt;p&gt;So, transforming our previous class to use ImmutableJs, we get the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Todo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;REMOVE_TODO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;â&lt;/span&gt;&lt;span class="err"&gt;€˜&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, state.todos.filter(todo=&amp;gt;todo.get(â€˜description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;ADD_TODO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;todoAppReducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;Immutable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromJS&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;:[],&lt;/span&gt; &lt;span class="na"&gt;newTodo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;â&lt;/span&gt;&lt;span class="err"&gt;€˜&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;}), action) {
    return Todo[action.type](state, action.payload)
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see that we are still passing state explicitly, whereas we would just use this to pass state explicitly in an OOP application.&lt;br&gt;
Enter Immutable Records, which give you the best of both worlds, where you can define methods that operate on this.&lt;br&gt;
Let's convert our Todo class to make use of Immutable Records.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Todo&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Immutable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Immutable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;newTodo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;â&lt;/span&gt;&lt;span class="err"&gt;€˜&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;}){
    REMOVE_TODO(payload) {
    return this.set(â€˜todos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;â&lt;/span&gt;&lt;span class="err"&gt;€˜&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;)!= payload.description));
    }

    ADD_TODO(payload) {

    }
}

function todoAppReducer(state=new Todo(), action) {
    return state[action.type](action.payload)
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See where we are going with this? Just a few cosmetic steps left.&lt;/p&gt;

&lt;p&gt;1- What to do about methods we don't recognize? In JS, this is easy, we could just access the proper state[action.type] and check whether it is a function or not.&lt;/p&gt;

&lt;p&gt;2- Ugly method names: In Redux apps, event names are usually CONSTANT_CASED and we want them camelCames. The transformation is easy thanks to lodash.camelcase.&lt;/p&gt;

&lt;p&gt;Now, let's extract the part where we take an Immutable Record class and we produce a compatible Redux reducer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Todo&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Immutable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Immutable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;newTodo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nf"&gt;removeTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;â&lt;/span&gt;&lt;span class="err"&gt;€˜&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, state.todos.filter(todo=&amp;gt;todo.get(â€˜description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;addTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;todoAppReducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;camelcase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;camelcase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)](&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// we don't recognize the method, return current state.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Final Product:&lt;br&gt;
You can get the final version of this pattern here &lt;a href="https://gist.github.com/ustun/a1b441d54894117e8b172065d14a849a" rel="noopener noreferrer"&gt;on Github&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;camelCase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lodash.camelcase&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;List&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;immutable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Todo&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;InitialTodoApp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;newTodo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;activeFilter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TodoApp&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;InitialTodoApp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// action methods: kind of like IBActions&lt;/span&gt;

    &lt;span class="nf"&gt;setTempTextAction&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setNewTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;removeTodoAction&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;addTodoAction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addTodo&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// other methods&lt;/span&gt;

    &lt;span class="nf"&gt;setNewTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newTodo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;newTodo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newTodo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;addTodo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addTodoFromDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newTodo&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;resetNewTodo&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;resetNewTodo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;newTodo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;addTodoFromDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newTodos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTodos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newTodos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;removeTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newTodos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTodos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newTodos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;setTodos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;todos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;setTodosFromJS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todosJS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;todos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;todosJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todoJS&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todoJS&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTodos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;incompleteTodos&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;nIncompleteTodos&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;incompleteTodos&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;completeTodos&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;nCompleteTodos&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;completeTodos&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;allTodos&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;toggleTodo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;newTodos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTodos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newTodos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toJS&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;incomplete todos&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nIncompleteTodos&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reducerFromRecordClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;camelCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;_ACTION&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;camelCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;_ACTION&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)](&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;camelCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You tried to call an action method, but no such action method provided.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;todoAppReducer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;reducerFromRecordClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TodoApp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;todoAppReducer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// main();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compared to a traditional OOP application, we can observe a few things:&lt;/p&gt;

&lt;p&gt;1- All setters have to return a new object.&lt;br&gt;
2- Identity tracking is done by redux.&lt;br&gt;
3- Redux actions are suffixed by “action (this is completely optional, just provided to separated methods that are invoked via redux from normal methods. Redux methods simply delegate to normal class methods.)&lt;/p&gt;

&lt;p&gt;Other than that,it is pretty much the best of both functional and object oriented worlds.Unlike most Redux applications which operate on an amorph, unnamed&lt;br&gt;
data structure called “state”, we have a real domain model which eases&lt;br&gt;
our mental data abstraction capabilities. We can also reuse this model&lt;br&gt;
elsewhere easily and even use other OOP techniques like inheritance to&lt;br&gt;
derive new classes.&lt;/p&gt;

&lt;p&gt;Unlike most OOP applications, this operates on immutable data as in FP&lt;br&gt;
and hence solves the tight coupling between state and identity.&lt;br&gt;
In this particular instance, identity tracking is left to Redux, but a&lt;br&gt;
simple stateful wrapper like a Clojure atom will bring you the&lt;br&gt;
identity tracking benefits of OOP.&lt;/p&gt;

&lt;p&gt;Acknowledgments:&lt;/p&gt;

&lt;p&gt;Thanks to Ahmet Akilli from T2 Yazilim for introducing me to JumpState, which basically implements the same idea, but without using Immutable Records. See more discussion here: &lt;a href="https://medium.com/@machnicki/why-redux-is-not-so-easy-some-alternatives-24816d5ad22d#.912ks1hij" rel="noopener noreferrer"&gt;https://medium.com/@machnicki/why-redux-is-not-so-easy-some-alternatives-24816d5ad22d#.912ks1hij&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Conclusion&lt;/p&gt;

&lt;p&gt;I hope this article provides guidance to you as you utilize hybrid paradigms in developing your applications. We believe FP and OOP paradigms can co-exist to build powerful products.&lt;/p&gt;

&lt;p&gt;If you need assistance, consulting and training, feel free to drop us a line at SkyScraper.Tech (&lt;a href="mailto:contact@skyscraper.tech"&gt;contact@skyscraper.tech&lt;/a&gt;) and we'll be pleased to help.&lt;br&gt;
We provide consultancy services, where we lead teams, and also&lt;br&gt;
write code. We also provide skeletons so that our customers' existing teams can continue from a good foundation.&lt;/p&gt;

&lt;p&gt;We support a number of platforms, ranging from Django to nodejs to&lt;br&gt;
Clojure apps, depending on the requirements. We also give trainings&lt;br&gt;
mainly on JavaScript (backend and frontend), but also on other&lt;br&gt;
platforms we support.&lt;/p&gt;

&lt;p&gt;See &lt;a href="http://skyscraper.tech" rel="noopener noreferrer"&gt;http://skyscraper.tech&lt;/a&gt; for more info.&lt;br&gt;
Discuss this article on HackerNews: &lt;a href="https://news.ycombinator.com/item?id=13578656" rel="noopener noreferrer"&gt;https://news.ycombinator.com/item?id=13578656&lt;/a&gt;&lt;/p&gt;

</description>
      <category>redux</category>
      <category>react</category>
      <category>immutablejs</category>
      <category>oop</category>
    </item>
  </channel>
</rss>
