<?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: whchi</title>
    <description>The latest articles on DEV Community by whchi (@whchi).</description>
    <link>https://dev.to/whchi</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F898116%2F7bd554c0-b828-4bd7-9bdb-c457501bbf1a.jpeg</url>
      <title>DEV Community: whchi</title>
      <link>https://dev.to/whchi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/whchi"/>
    <language>en</language>
    <item>
      <title>4 Design Dimensions of Agentic Workflows</title>
      <dc:creator>whchi</dc:creator>
      <pubDate>Tue, 23 Jun 2026 16:03:14 +0000</pubDate>
      <link>https://dev.to/whchi/4-design-dimensions-of-agentic-workflows-1i0m</link>
      <guid>https://dev.to/whchi/4-design-dimensions-of-agentic-workflows-1i0m</guid>
      <description>&lt;p&gt;AI agent, AI workflow, and agentic workflow are terms that often get mixed together. Some people call any LLM that can use tools an agent. Others say it only counts if the system can run on its own for a long time. When definitions don't match, asking "is this agentic or not?" usually gets you nowhere.&lt;/p&gt;

&lt;p&gt;A more practical approach is to break the system down into a few observable design choices: who decides the next step, how fixed the execution path is, how multiple agents work together, and where humans step in.&lt;/p&gt;

&lt;p&gt;The four dimensions below are not industry standards, and they are not four mutually exclusive quadrants. They are just a set of coordinates I use to understand agentic workflows. The same system can land in different positions at different levels, and it will shift depending on the task and the risk involved.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, Let's Separate Workflow from Agent
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.anthropic.com/engineering/building-effective-agents" rel="noopener noreferrer"&gt;Anthropic's classification&lt;/a&gt; is a good starting point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Workflow&lt;/strong&gt;: The LLM and tools run along a pre-defined code path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent&lt;/strong&gt;: The LLM dynamically decides its own execution process and how to use tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not the only definition. The community is still debating where the line between agent and workflow sits, and real-world systems are almost always a mix of both. For example, code might lock in the broad structure of a research process, while the LLM decides what to search for and which tools to call at each stage.&lt;/p&gt;

&lt;p&gt;So instead of putting one label on the whole system, it's more useful to clearly describe how it is controlled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dimension 1: Who Controls Orchestration?
&lt;/h2&gt;

&lt;p&gt;The first question is: who decides what happens next?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Code-driven&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Code controls the sequence, branching, retries, and stop conditions; the LLM only handles specific nodes&lt;/td&gt;
&lt;td&gt;Clear rules, high cost of errors, needs stable reproducibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Model-driven&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The LLM selects tools, plans steps, or delegates tasks based on the current goal and state&lt;/td&gt;
&lt;td&gt;Open-ended tasks, high input variety, paths that are hard to define in advance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hybrid&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Code sets the outer frame and safety boundaries; the LLM makes decisions within a limited scope&lt;/td&gt;
&lt;td&gt;Most practical agentic systems&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;a href="https://openai.github.io/openai-agents-python/multi_agent/" rel="noopener noreferrer"&gt;OpenAI Agents SDK&lt;/a&gt; also separates orchestration into LLM-driven and code-driven modes, and explicitly notes that both can be used together.&lt;/p&gt;

&lt;p&gt;Note that this dimension has nothing to do with which framework you use. LangGraph, AutoGen, and CrewAI can all produce multiple orchestration styles. You cannot determine the architecture just by knowing the framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dimension 2: How Fixed Is the Execution Path?
&lt;/h2&gt;

&lt;p&gt;The second question is: does every run follow the same path?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fixed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Steps and order are defined in advance&lt;/td&gt;
&lt;td&gt;Fetch data → summarize → review → publish&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Conditional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Branches, retries, or skipped steps based on intermediate results&lt;/td&gt;
&lt;td&gt;Trigger a deep investigation only when an anomaly is detected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Adaptive&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The next step is decided at runtime; the path may keep changing&lt;/td&gt;
&lt;td&gt;Open-ended research, debugging, exploratory coding&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Conditional branching does not require an LLM — a regular if-else works fine. Adaptive does not mean uncontrolled; a good adaptive system still has budgets, permissions, stop conditions, and tool boundaries to keep it in check.&lt;/p&gt;

&lt;p&gt;One more thing: Fixed does not equal DAG. As soon as there are retries, corrections, or an evaluator-optimizer loop, the path can cycle back. The real question is "who decides the path, and when?" — not "is this a DAG?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Dimension 3: How Do Agents Collaborate?
&lt;/h2&gt;

&lt;p&gt;The third question actually covers two things: how many independent agents are in the system, and how they hand work to each other.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Main cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Single Agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One agent uses multiple tools or loads different skills within the same task state&lt;/td&gt;
&lt;td&gt;Context can grow too large; role boundaries are weaker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Manager-Worker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A central agent breaks down tasks, calls workers, and integrates the results&lt;/td&gt;
&lt;td&gt;The manager can become a bottleneck or a single point of failure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Handoff&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One agent passes control to another specialized agent&lt;/td&gt;
&lt;td&gt;Requires careful handling of context transfer and responsibility boundaries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Peer / Decentralized&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multiple agents collaborate through a shared protocol with no single central decision-maker&lt;/td&gt;
&lt;td&gt;Hardest to debug; highest coordination cost&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Multi-agent does not mean "calling the LLM many times." A single agent can have many model calls. What makes something multi-agent is whether each unit has its own distinct instructions, context, tools, state, or responsibility boundary.&lt;/p&gt;

&lt;p&gt;Frameworks are also not one-to-one with a topology. &lt;a href="https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/tutorial/teams.html" rel="noopener noreferrer"&gt;AutoGen Teams&lt;/a&gt;, for instance, offers round-robin, model selector, swarm, and graph flow all at once. Assuming AutoGen or CrewAI means "no center, behavior emerges naturally" is simply wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dimension 4: Where Do Humans Step In?
&lt;/h2&gt;

&lt;p&gt;Autonomy should not be measured by how many steps an agent can take in a row. Three tool calls might delete production data; thirty read-only searches might carry almost no risk. A better measure is: which actions require approval, and how recoverable are mistakes?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Common controls&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Human-triggered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Humans decide each major action; AI provides suggestions or drafts&lt;/td&gt;
&lt;td&gt;Preview, step-by-step confirmation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Checkpointed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The system can proceed on its own but waits for approval at high-risk nodes&lt;/td&gt;
&lt;td&gt;Confirmation before financial transactions, deletions, or external publishing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Goal-driven within guardrails&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Humans set the goal and boundaries; the system completes the work within defined permissions, budgets, and stop conditions&lt;/td&gt;
&lt;td&gt;Sandbox, allowlist, spending limits, audit log, rollback&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Even a highly autonomous system does not mean "no one is responsible." Observability, failure handling, permission isolation, and post-run auditing are still required in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four Common Combinations
&lt;/h2&gt;

&lt;p&gt;When you put all four dimensions together, you will notice that what appears in practice is not a single maturity ladder, but a few combinations matched to different task types.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Reviewable Research Pipeline
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Single Agent × Hybrid × Mostly Fixed × Checkpointed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Code or documents define the research stages. The LLM handles searching, analysis, and writing at each stage. Humans review sources, conclusions, and publishing. This design does not aim for maximum autonomy, but it is easy to reproduce, version-control, and resume after interruptions.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Deterministic Batch Processing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Single Agent × Code-driven × Conditional × Goal-driven within guardrails
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Code handles scheduling, retries, and data flow. The LLM only does classification, extraction, or text judgment. Good for customer service routing, document classification, and contract field extraction.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Single Adaptive Agent
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Single Agent × Model-driven × Adaptive × Checkpointed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent reads error messages, edits code, runs tests, and decides the next step based on the results. This works well for exploration and debugging, but it should still pause for human confirmation before destructive operations, external publishing, or sensitive data.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Manager-Worker Parallel Research
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Multi-Agent × Model-driven Manager × Adaptive × Checkpointed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A central agent splits a research question into parts and sends them to multiple workers to handle in parallel, then integrates and fills the gaps. Anthropic's public multi-agent research system uses exactly this orchestrator-worker architecture — not a fully decentralized swarm.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does the Community and Research Say?
&lt;/h2&gt;

&lt;p&gt;There is no consensus that "multi-agent is always better," but a few signals keep appearing across official documentation, research papers, and developer discussions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start with the Simplest Architecture
&lt;/h3&gt;

&lt;p&gt;Anthropic recommends finding the simplest working solution first and only adding agentic complexity when you actually need it. AutoGen's documentation also suggests optimizing a single agent first, and only moving to a team after confirming the single agent is not enough.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://news.ycombinator.com/item?id=42470541" rel="noopener noreferrer"&gt;The Hacker News discussion on Anthropic's article&lt;/a&gt; mostly agreed with simple, composable workflows, but added two points: Human-in-the-Loop introduces long wait times, retries, and repeated execution; and production systems cannot rely on just a prompt and a model loop — durable execution, state management, and observability are unavoidable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Agent Has a Coordination Tax
&lt;/h3&gt;

&lt;p&gt;Adding another agent is not just one more model call. It also brings context transfer, result integration, error propagation, latency, and higher debugging cost. The cases where it actually pays off are usually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The task can be clearly split and run in parallel.&lt;/li&gt;
&lt;li&gt;Different subtasks each need a large, isolated block of context.&lt;/li&gt;
&lt;li&gt;Different agents need different tools, permissions, or responsibility boundaries.&lt;/li&gt;
&lt;li&gt;A single agent has already started degrading because it has too many tools or too long a context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A 2025 paper, &lt;a href="https://arxiv.org/abs/2512.08296" rel="noopener noreferrer"&gt;Towards a Science of Scaling Agent Systems&lt;/a&gt;, compared 260 configurations and found that multi-agent setups may benefit parallelizable tasks, but can actually regress on sequential reasoning tasks due to coordination overhead. This is a controlled result across six agentic benchmarks with a fixed compute budget — it should not be taken as a universal rule for all products. The more reliable takeaway is: match the architecture to the nature of the task; the number of agents is not a measure of capability.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Fully Automated" Is Not the Only Goal
&lt;/h3&gt;

&lt;p&gt;In finance, healthcare, legal, payments, and formal data modification scenarios, Checkpointed is often not a temporary compromise — it is the right product choice from the start. The appropriate level of autonomy should be determined by risk, reversibility, and where responsibility lies, not by how high the autonomy score is.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Real Example: My Investment Research Repo
&lt;/h2&gt;

&lt;p&gt;My own &lt;a href="https://github.com/whchi/tw-stock-researcher" rel="noopener noreferrer"&gt;tw-stock-researcher&lt;/a&gt; is closest to this combination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Single Agent × Hybrid × Mostly Fixed × Checkpointed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Skills are defined in Markdown documents; the LLM loads only the capabilities the task needs.&lt;/li&gt;
&lt;li&gt;The overall direction is fixed: case init → data fetching → analysis → conclusions → market observation, in that order.&lt;/li&gt;
&lt;li&gt;Within each stage, the LLM can still decide which tools to use and how deep to go based on data quality.&lt;/li&gt;
&lt;li&gt;Output is saved as structured Markdown for easy human review, version control, and resumption across sessions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I originally described it as &lt;code&gt;LLM-as-Orchestrator × Fixed DAG&lt;/code&gt;, which was not quite right. A more accurate description is: the macro flow is fixed, the local paths are left to the LLM; documents handle constraints, code and humans guard the boundaries. This hybrid design makes sense for research tasks, because "reviewable" is usually more important than "fully automated."&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Choose?
&lt;/h2&gt;

&lt;p&gt;When designing an agentic workflow, you can work through six questions in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Can this be reliably solved with regular code?&lt;/strong&gt; Hand the deterministic parts to deterministic code first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How much variation is there in inputs and paths?&lt;/strong&gt; Low variation means a fixed flow; high variation means letting the model plan dynamically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can the task actually be split up?&lt;/strong&gt; The extra cost of multi-agent only makes sense when tasks can run in parallel, need isolation, or require different permissions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What is the cost of failure, and how reversible is it?&lt;/strong&gt; High-risk or irreversible actions must have approval checkpoints and permission restrictions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When something breaks, can you tell where?&lt;/strong&gt; Every step should have visible inputs, outputs, state, and stop reasons.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is there an eval showing the complexity is worth it?&lt;/strong&gt; Compare success rates, cost, latency, and manual correction effort — do not judge by how impressive the demo looks.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;"Agentic" is not an architecture name, and it is not a badge of maturity you pin to a system. The four questions that actually carry information are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the next step decided by code, a model, or both together?&lt;/li&gt;
&lt;li&gt;Is the path fixed, conditional, or generated dynamically at runtime?&lt;/li&gt;
&lt;li&gt;Is it a single agent, manager-worker, handoff, or decentralized collaboration?&lt;/li&gt;
&lt;li&gt;At which risk points do humans step in, and what guardrails does the system have?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you can answer these clearly, the system's cost, risk, and maintainability become readable. A good agentic workflow is not the one with the most autonomy or the most agents — it is the one that stays flexible where the task needs flexibility, and stays deterministic where it does not.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.anthropic.com/engineering/building-effective-agents" rel="noopener noreferrer"&gt;Anthropic: Building effective agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.anthropic.com/engineering/built-multi-agent-research-system" rel="noopener noreferrer"&gt;Anthropic: How we built our multi-agent research system&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openai.github.io/openai-agents-python/multi_agent/" rel="noopener noreferrer"&gt;OpenAI Agents SDK: Agent orchestration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/tutorial/teams.html" rel="noopener noreferrer"&gt;AutoGen: Teams&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://langchain-ai.github.io/langgraph/tutorials/multi_agent/multi-agent-collaboration/" rel="noopener noreferrer"&gt;LangChain: Multi-agent patterns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2512.08296" rel="noopener noreferrer"&gt;Towards a Science of Scaling Agent Systems&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=42470541" rel="noopener noreferrer"&gt;Hacker News: Building Effective Agents discussion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>Prisma-Docker Problem: Why `migrate deploy` Installs Binaries Again</title>
      <dc:creator>whchi</dc:creator>
      <pubDate>Tue, 23 Jun 2026 16:01:25 +0000</pubDate>
      <link>https://dev.to/whchi/prisma-docker-problem-why-migrate-deploy-installs-binaries-again-1ali</link>
      <guid>https://dev.to/whchi/prisma-docker-problem-why-migrate-deploy-installs-binaries-again-1ali</guid>
      <description>&lt;p&gt;If you use &lt;strong&gt;pnpm&lt;/strong&gt; and &lt;strong&gt;Prisma&lt;/strong&gt; inside a multi-stage Docker build, you might see a strange thing: Prisma tries to download its binary files (like &lt;code&gt;linux-musl-openssl-3.0.x&lt;/code&gt;) again, even though you ran &lt;code&gt;pnpm prisma generate&lt;/code&gt; in an earlier stage.&lt;/p&gt;

&lt;p&gt;This is a &lt;strong&gt;common issue&lt;/strong&gt;. It is not a mistake in your code. It is a problem with how Prisma checks its files and how Docker handles time when copying files.&lt;/p&gt;

&lt;h3&gt;
  
  
  🪤 The Problem: The Timestamp Trap
&lt;/h3&gt;

&lt;p&gt;Prisma is built to check if its Client is &lt;strong&gt;old&lt;/strong&gt; (or "stale").&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prisma's Rule:&lt;/strong&gt; Prisma checks the &lt;strong&gt;last modified time (timestamp)&lt;/strong&gt; of two important files:

&lt;ol&gt;
&lt;li&gt; Your &lt;code&gt;schema.prisma&lt;/code&gt; file (the source code).&lt;/li&gt;
&lt;li&gt; The generated Prisma Client in &lt;code&gt;node_modules/.prisma/client&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Decision:&lt;/strong&gt; If the &lt;code&gt;schema.prisma&lt;/code&gt; file's time is &lt;strong&gt;newer&lt;/strong&gt; than the Client's time, Prisma decides that the Client is old.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Action:&lt;/strong&gt; It &lt;strong&gt;automatically runs &lt;code&gt;prisma generate&lt;/code&gt; again&lt;/strong&gt;. This action includes checking and downloading the binary files you listed in &lt;code&gt;binaryTargets&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  How Docker Creates This Problem
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Builder Stage (Time T1 - Old Time):&lt;/strong&gt; You run &lt;code&gt;pnpm prisma generate&lt;/code&gt;. The generated Client gets a timestamp of &lt;strong&gt;T1&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Runner Stage (Time T2 - New Time):&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;You copy the Client from the builder: &lt;code&gt;COPY --from=builder ... .prisma&lt;/code&gt;. This Client &lt;strong&gt;keeps the old T1 timestamp&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Trap:&lt;/strong&gt; You copy your &lt;code&gt;schema.prisma&lt;/code&gt; file from your local computer: &lt;code&gt;COPY ./prisma /app/prisma&lt;/code&gt;. This new file gets a timestamp of &lt;strong&gt;T2&lt;/strong&gt; (the time the Docker layer is built).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; When you run &lt;code&gt;pnpm prisma migrate deploy&lt;/code&gt;, Prisma sees that &lt;strong&gt;T2&lt;/strong&gt; (schema) is &lt;strong&gt;newer&lt;/strong&gt; than &lt;strong&gt;T1&lt;/strong&gt; (Client). It thinks the Client is outdated and runs &lt;code&gt;generate&lt;/code&gt;, which causes the binary to be downloaded again.&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 Three Ways to Fix This
&lt;/h3&gt;

&lt;p&gt;The best solutions prevent Prisma from making this "new vs. old" comparison.&lt;/p&gt;

&lt;h4&gt;
  
  
  Solution 1 (The Best Way): Do Not Copy the Source Schema
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;prisma migrate deploy&lt;/code&gt; command &lt;strong&gt;does not need&lt;/strong&gt; the &lt;code&gt;schema.prisma&lt;/code&gt; file itself. It only needs the migration files inside the &lt;code&gt;migrations&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change the &lt;code&gt;runner&lt;/code&gt; stage:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runner&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Copy the built Prisma Client (needed for your application or seeders)&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/node_modules/.prisma /app/node_modules/.prisma&lt;/span&gt;

&lt;span class="c"&gt;# ✅ CORRECT: Only copy the migration files&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./prisma/migrations /app/prisma/migrations&lt;/span&gt;

&lt;span class="c"&gt;# ❌ REMOVE THIS LINE: This is the problem file!&lt;/span&gt;
&lt;span class="c"&gt;# COPY ./prisma /app/prisma &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; Prisma cannot check the timestamp if the &lt;code&gt;schema.prisma&lt;/code&gt; file is not in the final image.&lt;/p&gt;

&lt;h4&gt;
  
  
  Solution 2: Copy the Full &lt;code&gt;node_modules&lt;/code&gt; from the Builder
&lt;/h4&gt;

&lt;p&gt;If your &lt;code&gt;builder&lt;/code&gt; stage successfully generated the client, copying the entire &lt;code&gt;node_modules&lt;/code&gt; folder (which includes the client) from that stage might help ensure that all files within that folder have consistent timestamps from &lt;strong&gt;T1&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change the &lt;code&gt;runner&lt;/code&gt; stage:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runner&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# ✅ Copy the whole node_modules, which now includes the generated client from T1.&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/node_modules /app/node_modules&lt;/span&gt;

&lt;span class="c"&gt;# You still need to use Solution 1 and avoid copying the schema file!&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./prisma/migrations /app/prisma/migrations&lt;/span&gt;
&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Solution 3 (Quick Fix): Tell Prisma to Skip the Check
&lt;/h4&gt;

&lt;p&gt;You can use an Environment Variable to make Prisma ignore the time check and assume the binary is already there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add this line to your &lt;code&gt;runner&lt;/code&gt; stage:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runner&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PRISMA_SKIP_BINARY_CHECK=1&lt;/span&gt;

&lt;span class="c"&gt;# ... other COPY commands ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This is fast, but if your &lt;code&gt;builder&lt;/code&gt; stage ever fails to download the binary, this fix will hide the real error until the program starts.&lt;/p&gt;

</description>
      <category>prisma</category>
    </item>
    <item>
      <title>Managing Object Ownership in PostgreSQL Migrations</title>
      <dc:creator>whchi</dc:creator>
      <pubDate>Sun, 03 Aug 2025 13:27:46 +0000</pubDate>
      <link>https://dev.to/whchi/managing-object-ownership-in-postgresql-migrations-2d0o</link>
      <guid>https://dev.to/whchi/managing-object-ownership-in-postgresql-migrations-2d0o</guid>
      <description>&lt;p&gt;Migrating a PostgreSQL database involves more than just copying data and schema—it also requires careful handling of ownership and privileges. Failure to properly manage these aspects can lead to permission errors, particularly when roles need to &lt;code&gt;ALTER&lt;/code&gt;, &lt;code&gt;TRUNCATE&lt;/code&gt;, or &lt;code&gt;DROP&lt;/code&gt; objects. This guide outlines solutions to ensure a smooth migration process with proper object ownership.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Owner" in PostgreSQL
&lt;/h2&gt;

&lt;p&gt;In PostgreSQL, every database object—such as &lt;strong&gt;tables&lt;/strong&gt;, &lt;strong&gt;views&lt;/strong&gt;, &lt;strong&gt;sequences&lt;/strong&gt;, &lt;strong&gt;functions&lt;/strong&gt;, and even &lt;strong&gt;schemas&lt;/strong&gt;—is owned by a role. The &lt;strong&gt;owner&lt;/strong&gt; is the only role that can perform certain high-level operations on the object unless privileges are explicitly reassigned.&lt;/p&gt;

&lt;p&gt;PostgreSQL roles are essentially accounts, and they can either be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Superusers&lt;/strong&gt;: These are roles with unrestricted access. They can bypass ownership rules but should be used cautiously.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-superuser roles&lt;/strong&gt;: These require explicit ownership or privileges to modify objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For production environments, it’s a best practice to &lt;strong&gt;delegate ownership to a specific application role&lt;/strong&gt; and avoid using the default superuser for schema management or migrations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Ownership Matters in Migration
&lt;/h2&gt;

&lt;p&gt;When you dump and restore a database (e.g., using &lt;code&gt;pg_dump&lt;/code&gt; and &lt;code&gt;pg_restore&lt;/code&gt;), all objects retain their original ownership unless explicitly changed. If the original owner doesn't exist or isn't appropriate in the target environment, you'll encounter permission issues.&lt;/p&gt;

&lt;p&gt;Common symptoms of incorrect ownership:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ERROR: must be owner of table&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Inability to run schema-altering migrations (e.g., &lt;code&gt;ALTER TABLE&lt;/code&gt;, &lt;code&gt;DROP&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;li&gt;Unexpected behavior in CI/CD pipelines or database initialization scripts&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Solutions For Handling Ownership in Migration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. use &lt;code&gt;pg_dump&lt;/code&gt; &amp;amp; &lt;code&gt;pg_restore&lt;/code&gt; correctly
&lt;/h3&gt;

&lt;p&gt;Recommended when migrating to a different environment or when role structures differ between source and target databases.&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;# Export the database without OWNER and GRANT statements&lt;/span&gt;
pg_dump &lt;span class="nt"&gt;--no-owner&lt;/span&gt; &lt;span class="nt"&gt;--no-privileges&lt;/span&gt; &lt;span class="nt"&gt;-Fc&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; db.dump your_db
&lt;span class="c"&gt;# Restore into a new database as the desired application role&lt;/span&gt;
pg_restore &lt;span class="nt"&gt;--role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_app_role &lt;span class="nt"&gt;-d&lt;/span&gt; your_new_db db.dump
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that all objects are created under your_app_role without retaining the original roles or grants from the source database.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Set Owner During Database Creation
&lt;/h3&gt;

&lt;p&gt;When provisioning a new database, you can set the owner from the beginning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;your_new_db&lt;/span&gt; &lt;span class="k"&gt;OWNER&lt;/span&gt; &lt;span class="n"&gt;your_owner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="n"&gt;your_new_db&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;your_owner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;my_schema&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures all subsequently created objects inherit the correct owner.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Use a One-Time Ownership Migration Script
&lt;/h3&gt;

&lt;p&gt;If ownership needs to be updated after initialization, use a PL/pgSQL block to update all object types in one go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- The following script automates ownership reassignment for all tables in a database, ensuring consistency across schemas."&lt;/span&gt;

&lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt;
    &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="n"&gt;RECORD&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="c1"&gt;-- Alter tables&lt;/span&gt;
    &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; 
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt; 
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_class&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
        &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_namespace&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relnamespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relkind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;
          &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'pg_%'&lt;/span&gt; 
          &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'information_schema'&lt;/span&gt;
    &lt;span class="n"&gt;LOOP&lt;/span&gt;
        &lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="s1"&gt;'ALTER TABLE '&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;quote_ident&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'.'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;quote_ident&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;' OWNER TO your_new_owner'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;RAISE&lt;/span&gt; &lt;span class="n"&gt;NOTICE&lt;/span&gt; &lt;span class="s1"&gt;'Changed owner of table %.% to your_new_owner'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;-- Repeat for sequences, views, enums, domains, functions, schemas, and other objects you want&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach ensures all relevant objects are properly reassigned to the desired role, preventing future permission issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Use &lt;code&gt;GRANT&lt;/code&gt; When Ownership Change Isn't Required
&lt;/h3&gt;

&lt;p&gt;You can delegate permissions via &lt;code&gt;GRANT&lt;/code&gt;, but this does not allow operations like &lt;code&gt;DROP&lt;/code&gt;, &lt;code&gt;ALTER&lt;/code&gt;, or &lt;code&gt;TRUNCATE&lt;/code&gt;. It’s a more limited but safer approach for read/write access&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- schema&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;my_schema&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;new_role&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- table&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;my_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_table&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;new_role&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;my_schema&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;new_role&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- sequence&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;SEQUENCE&lt;/span&gt; &lt;span class="n"&gt;my_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_sequence&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;new_role&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;SEQUENCES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;my_schema&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;new_role&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- view&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;my_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_view&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;new_role&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- enum&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="n"&gt;my_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_enum&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;new_role&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- function&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;my_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;new_role&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;FUNCTIONS&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;my_schema&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;new_role&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;GRANT&lt;/code&gt; when you want multiple roles to collaborate without transferring ownership.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Plan ownership early: It’s easier to assign correct owners during initialization than to fix them later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoid superuser for app logic: Delegate to application-specific roles with scoped permissions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automate ownership reassignment: Use PL/pgSQL to enforce consistency in your CI/CD pipeline or database bootstrap scripts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Audit before migration: Run queries to list current owners and identify mismatches before migrating.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By handling ownership carefully, you ensure your PostgreSQL migrations remain predictable, secure, and maintainable.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>programming</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Introduction to access control model: ACL, RBAC, ABAC</title>
      <dc:creator>whchi</dc:creator>
      <pubDate>Tue, 06 May 2025 02:43:00 +0000</pubDate>
      <link>https://dev.to/whchi/introduction-to-access-control-model-acl-rbac-abac-4m1k</link>
      <guid>https://dev.to/whchi/introduction-to-access-control-model-acl-rbac-abac-4m1k</guid>
      <description>&lt;p&gt;In system design, access control is a critical mechanism to ensure data security and correct authorization of functionalities. Depending on the complexity of the system, the variety of user roles, and the flexibility needed in resource management, there are three common access control models: ACL (Access Control List), RBAC (Role-Based Access Control), and ABAC (Attribute-Based Access Control). Below is a brief comparison of these three models, outlining their design logic, characteristics, and application scenarios to help clarify the selection and implementation considerations.&lt;/p&gt;

&lt;h3&gt;
  
  
  ACL
&lt;/h3&gt;

&lt;p&gt;Direct authorization, fine-grained, but difficult to manage&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Design Logic&lt;/strong&gt;:
User → Resource + Operation&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If a user has read and update permissions for the development department's file list, they can view all the data under that department and make changes to it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  RBAC
&lt;/h3&gt;

&lt;p&gt;Simplified management, clear structure, but limited flexibility&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Design Logic&lt;/strong&gt;:
Role → Resource + Operation; User is assigned to a role&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If a user’s role is a department manager, they can perform CRUD operations on all resources within that department.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  ABAC
&lt;/h3&gt;

&lt;p&gt;Highest flexibility, dynamic condition evaluation based on the user's, resource's, and environment's attributes to determine authorization.&lt;br&gt;
Some variants include ReBAC (Relationship-Based Access Control), LBAC (Label-Based Access Control).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Design Logic&lt;/strong&gt;:
Access is determined based on attributes.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If a user’s department attribute is "HR Department," the resource's attribute is "personnel data," and the current time is within working hours, access is granted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Comparison Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;ACL (Access Control List)&lt;/th&gt;
&lt;th&gt;RBAC (Role-Based Access Control)&lt;/th&gt;
&lt;th&gt;ABAC (Attribute-Based Access Control)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;📘 Authorization Target&lt;/td&gt;
&lt;td&gt;User → Resource&lt;/td&gt;
&lt;td&gt;Role → Resource&lt;/td&gt;
&lt;td&gt;User attributes, resource attributes, environment attributes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👤 User Binding Method&lt;/td&gt;
&lt;td&gt;User is directly granted access&lt;/td&gt;
&lt;td&gt;User is assigned a role&lt;/td&gt;
&lt;td&gt;User attributes are used to check access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🔄 Authorization Method&lt;/td&gt;
&lt;td&gt;Static (each rule is defined clearly)&lt;/td&gt;
&lt;td&gt;Static (role-based)&lt;/td&gt;
&lt;td&gt;Dynamic condition-based (using multiple attributes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🧱 Database Design Simplicity&lt;/td&gt;
&lt;td&gt;Simple (but hard to scale)&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Complex (requires extra logic for attributes and rules)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⚙️ Flexibility / Detail Control&lt;/td&gt;
&lt;td&gt;High (per user and resource)&lt;/td&gt;
&lt;td&gt;Medium (role-level)&lt;/td&gt;
&lt;td&gt;Very high (can use any attribute condition)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;📈 Maintenance Cost (Large Systems)&lt;/td&gt;
&lt;td&gt;High (hard to manage when users grow)&lt;/td&gt;
&lt;td&gt;Low to medium (if roles are well designed)&lt;/td&gt;
&lt;td&gt;High (complex rules and attributes need engine support)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🔐 Permission Sharing / Inheritance&lt;/td&gt;
&lt;td&gt;Low (granted per user)&lt;/td&gt;
&lt;td&gt;High (shared roles, hierarchical roles)&lt;/td&gt;
&lt;td&gt;High (attributes are reusable)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🛡️ Suitable Resource Scale&lt;/td&gt;
&lt;td&gt;Small (few users and resources)&lt;/td&gt;
&lt;td&gt;Medium to large (with stable role structure)&lt;/td&gt;
&lt;td&gt;Large and complex (many rules, multi-tenant)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🧠 Learning / Implementation Difficulty&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low to medium&lt;/td&gt;
&lt;td&gt;High (requires abstract thinking, policy language like XACML or engines like OPA)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🧭 Suitable Use Cases&lt;/td&gt;
&lt;td&gt;Personal systems, sensitive data with fine control&lt;/td&gt;
&lt;td&gt;Enterprise backends, SaaS, shared platforms&lt;/td&gt;
&lt;td&gt;Banks, government, insurance, dynamic access control, zero-trust&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🧩 Typical Examples&lt;/td&gt;
&lt;td&gt;Dropbox, Google Drive “share with someone”&lt;/td&gt;
&lt;td&gt;Internal company systems (e.g., Admin, Manager)&lt;/td&gt;
&lt;td&gt;Data classification access, policy-driven API Gateway&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👮🏻‍♂️ Security&lt;/td&gt;
&lt;td&gt;High (fine control, but human error risk)&lt;/td&gt;
&lt;td&gt;Medium (depends on good role design)&lt;/td&gt;
&lt;td&gt;High (granular control, but needs accurate rules/attributes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🏎️ Performance&lt;/td&gt;
&lt;td&gt;Decreases with scale (each access is checked)&lt;/td&gt;
&lt;td&gt;High (simple role lookup)&lt;/td&gt;
&lt;td&gt;Low (many attributes and rules to evaluate)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🎲 Standard Support&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;NIST RBAC Model&lt;/td&gt;
&lt;td&gt;XACML&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>security</category>
      <category>basic</category>
    </item>
    <item>
      <title>Setting up TLS connection for containerized PostgreSQL database</title>
      <dc:creator>whchi</dc:creator>
      <pubDate>Mon, 24 Feb 2025 06:52:40 +0000</pubDate>
      <link>https://dev.to/whchi/setting-up-tls-connection-for-containerized-postgresql-database-1kmh</link>
      <guid>https://dev.to/whchi/setting-up-tls-connection-for-containerized-postgresql-database-1kmh</guid>
      <description>&lt;p&gt;When connecting your database to external services, especially SaaS platforms, you often can't restrict access by IP address or domain. In these cases, enabling TLS (Transport Layer Security) encryption helps keep your data safe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PostgreSQL version: 16&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding PostgreSQL Client Connection Types
&lt;/h2&gt;

&lt;p&gt;PostgreSQL clients can connect in six different ways&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;disabled&lt;/code&gt;: No encryption at all. Only safe for local networks.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;allow&lt;/code&gt;: Prefers unencrypted connections but will use encryption if the server requires it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prefer&lt;/code&gt;: (Default for most clients) Tries to use encryption first but accepts unencrypted connections if necessary.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;require&lt;/code&gt;: Must use encryption. Won't connect without it but doesn't verify certificates.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;verify_ca&lt;/code&gt;: Uses encryption and checks if the server's certificate is signed by a trusted authority.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;verify_full&lt;/code&gt;: The most secure option. Checks encryption, certificates, and ensures the server name matches the certificate.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The most secure options are &lt;code&gt;verify_ca&lt;/code&gt; and &lt;code&gt;verify_full&lt;/code&gt;, but they require more setup in development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one-way verification: You need the root CA certificate&lt;/li&gt;
&lt;li&gt;two-way(mTLS) verification: You need both the client certificate and key in your connection string, &lt;strong&gt;the certificate/key MUST signed by root CA and key.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting Up the Server(One-Way Verification)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create Certificate and Key
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set to 100 years for development use&lt;/span&gt;
openssl req &lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="nt"&gt;-newkey&lt;/span&gt; rsa:4096 &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="nt"&gt;-nodes&lt;/span&gt; &lt;span class="nt"&gt;-keyout&lt;/span&gt; key.pem &lt;span class="nt"&gt;-out&lt;/span&gt; cert.pem &lt;span class="nt"&gt;-days&lt;/span&gt; 36500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Configure PostgreSQL
&lt;/h3&gt;

&lt;p&gt;Add these lines to &lt;code&gt;postgresql.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;ssl&lt;/span&gt; = &lt;span class="n"&gt;on&lt;/span&gt;
&lt;span class="n"&gt;ssl_cert_file&lt;/span&gt; = &lt;span class="s1"&gt;'/var/lib/postgresql/cert.pem'&lt;/span&gt;
&lt;span class="n"&gt;ssl_key_file&lt;/span&gt; = &lt;span class="s1"&gt;'/var/lib/postgresql/key.pem'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Update Access Rules
&lt;/h3&gt;

&lt;p&gt;Add these lines to &lt;code&gt;pg_hba.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;hostssl&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;scram&lt;/span&gt;-&lt;span class="n"&gt;sha&lt;/span&gt;-&lt;span class="m"&gt;256&lt;/span&gt;
&lt;span class="n"&gt;hostnossl&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;reject&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Set Up Docker Compose
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16.3&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;5432:5432'&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=postgres&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=postgres&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=postgres&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/postgres/config/pg_hba.conf:/etc/postgresql/pg_hba.conf&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/postgres/config/postgresql.conf:/etc/postgresql/postgresql.conf&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/postgres/certs/cert.pem:/var/lib/postgresql/cert.pem:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/postgres/certs/key.pem:/var/lib/postgresql/key.pem:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-c&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;config_file=/etc/postgresql/postgresql.conf&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-c&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;hba_file=/etc/postgresql/pg_hba.conf&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Connecting to Your Database
&lt;/h3&gt;

&lt;p&gt;After setting everything up, just add &lt;code&gt;sslmode=require&lt;/code&gt; to your connection string:&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="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"postgresql://postgres:postgres@postgres:5432/database?sslmode=require"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all you need to establish an encrypted connection to your PostgreSQL database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Server(mTLS)
&lt;/h2&gt;

&lt;p&gt;For mTLS (mutual TLS), both the server and client need valid certificates signed by a trusted Certificate Authority (CA). Here's how to set it up&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create certifications for server and client
&lt;/h3&gt;

&lt;p&gt;Your server.csr &lt;code&gt;CN&lt;/code&gt; MUST be your postgreSQL hostname&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;# 1. Create Root CA&lt;/span&gt;
openssl genrsa &lt;span class="nt"&gt;-out&lt;/span&gt; root.key 4096
openssl req &lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="nt"&gt;-new&lt;/span&gt; &lt;span class="nt"&gt;-nodes&lt;/span&gt; &lt;span class="nt"&gt;-key&lt;/span&gt; root.key &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="nt"&gt;-days&lt;/span&gt; 36500 &lt;span class="nt"&gt;-out&lt;/span&gt; root.crt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/C=US/ST=California/L=San Francisco/O=MyCompany/OU=IT/CN=PostgreSQL Root CA"&lt;/span&gt;

&lt;span class="c"&gt;# 2. create server cert&lt;/span&gt;
openssl genrsa &lt;span class="nt"&gt;-out&lt;/span&gt; server.key 4096
openssl req &lt;span class="nt"&gt;-new&lt;/span&gt; &lt;span class="nt"&gt;-key&lt;/span&gt; server.key &lt;span class="nt"&gt;-out&lt;/span&gt; server.csr &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/C=US/ST=California/L=San Francisco/O=MyCompany/OU=Database/CN=postgres"&lt;/span&gt;

&lt;span class="c"&gt;# This is important that SAN MUST contains all your possible DNS/IP &lt;/span&gt;
openssl x509 &lt;span class="nt"&gt;-req&lt;/span&gt; &lt;span class="nt"&gt;-in&lt;/span&gt; server.csr &lt;span class="nt"&gt;-CA&lt;/span&gt; root.crt &lt;span class="nt"&gt;-CAkey&lt;/span&gt; root.key &lt;span class="nt"&gt;-CAcreateserial&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-out&lt;/span&gt; server.crt &lt;span class="nt"&gt;-days&lt;/span&gt; 36500 &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-extfile&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"subjectAltName=DNS:localhost,DNS:postgres,IP:127.0.0.1,IP:192.168.0.23"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# 3. create client cert - CN here MUST same as the postgres user you want&lt;/span&gt;
openssl genrsa &lt;span class="nt"&gt;-out&lt;/span&gt; client.key 4096
openssl req &lt;span class="nt"&gt;-new&lt;/span&gt; &lt;span class="nt"&gt;-key&lt;/span&gt; client.key &lt;span class="nt"&gt;-out&lt;/span&gt; client.csr &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/C=US/ST=California/L=San Francisco/O=MyCompany/OU=Developers/CN=postgres"&lt;/span&gt;

openssl x509 &lt;span class="nt"&gt;-req&lt;/span&gt; &lt;span class="nt"&gt;-in&lt;/span&gt; client.csr &lt;span class="nt"&gt;-CA&lt;/span&gt; root.crt &lt;span class="nt"&gt;-CAkey&lt;/span&gt; root.key &lt;span class="nt"&gt;-CAcreateserial&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-out&lt;/span&gt; client.crt &lt;span class="nt"&gt;-days&lt;/span&gt; 36500 &lt;span class="nt"&gt;-sha256&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want your client CN using custom map, you should use &lt;code&gt;pg_ident.conf&lt;/code&gt; and adjust setting in &lt;code&gt;pg_hba.conf&lt;/code&gt; to archive that&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# pg_hba.conf
hostssl all all 0.0.0.0/0 cert map=my_map
# pg_ident.conf
my_map /CN=client postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Configure PostgreSQL
&lt;/h3&gt;

&lt;p&gt;Add these lines to &lt;code&gt;postgresql.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;ssl&lt;/span&gt; = &lt;span class="n"&gt;on&lt;/span&gt;
&lt;span class="n"&gt;ssl_cert_file&lt;/span&gt; = &lt;span class="s1"&gt;'/var/lib/postgresql/server.crt'&lt;/span&gt;
&lt;span class="n"&gt;ssl_key_file&lt;/span&gt; = &lt;span class="s1"&gt;'/var/lib/postgresql/server.key'&lt;/span&gt;
&lt;span class="n"&gt;ssl_ca_file&lt;/span&gt; = &lt;span class="s1"&gt;'/var/lib/postgresql/root.crt'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Update Access Rules
&lt;/h3&gt;

&lt;p&gt;Add these lines to &lt;code&gt;pg_hba.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;hostssl&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;cert&lt;/span&gt; &lt;span class="n"&gt;clientcert&lt;/span&gt;=&lt;span class="n"&gt;verify&lt;/span&gt;-&lt;span class="n"&gt;full&lt;/span&gt;
&lt;span class="n"&gt;hostnossl&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;reject&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Update docker-compose.yml
&lt;/h3&gt;

&lt;p&gt;Update volume bindings&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/postgres/config/pg_hba.conf:/etc/postgresql/pg_hba.conf&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/postgres/config/postgresql.conf:/etc/postgresql/postgresql.conf&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/postgres/certs/server.crt:/var/lib/postgresql/server.crt:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/postgres/certs/server.key:/var/lib/postgresql/server.key:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/postgres/certs/root.crt:/var/lib/postgresql/root.crt:ro&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Connecting to Your Database
&lt;/h3&gt;

&lt;p&gt;After setting everything up, add the following options to your connection string&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sslmode=verify-full&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sslcert=client.crt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sslkey=client.key&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sslrootcert=root.crt&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"postgresql://postgres:postgres@postgres:5432/database?sslmode=verify-full&amp;amp;sslcert=client.crt&amp;amp;sslkey=client.key&amp;amp;sslrootcert=root.crt"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>postgres</category>
      <category>database</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Setting up TLS connection for containerized PostgreSQL database</title>
      <dc:creator>whchi</dc:creator>
      <pubDate>Sat, 22 Feb 2025 08:27:43 +0000</pubDate>
      <link>https://dev.to/whchi/setting-up-encrypted-postgresql-connection-on-aws-ec2-4m3g</link>
      <guid>https://dev.to/whchi/setting-up-encrypted-postgresql-connection-on-aws-ec2-4m3g</guid>
      <description>&lt;p&gt;When you need to allow external connections to your database while keeping costs low, encrypting the connection is essential. This guide shows you the simplest way to set up encrypted connections for your development environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Do We Need Encryption?
&lt;/h2&gt;

&lt;p&gt;When connecting your database to external services, especially SaaS platforms, you often can't restrict access by IP address or domain. In these cases, enabling TLS (Transport Layer Security) encryption helps keep your data safe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PostgreSQL version: 16&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding PostgreSQL Client Connection Types
&lt;/h2&gt;

&lt;p&gt;PostgreSQL clients can connect in six different ways&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;disabled&lt;/code&gt;: No encryption at all. Only safe for local networks.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;allow&lt;/code&gt;: Prefers unencrypted connections but will use encryption if the server requires it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prefer&lt;/code&gt;: (Default for most clients) Tries to use encryption first but accepts unencrypted connections if necessary.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;require&lt;/code&gt;: Must use encryption. Won't connect without it but doesn't verify certificates.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;verify_ca&lt;/code&gt;: Uses encryption and checks if the server's certificate is signed by a trusted authority.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;verify_full&lt;/code&gt;: The most secure option. Checks encryption, certificates, and ensures the server name matches the certificate.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The most secure options are &lt;code&gt;verify_ca&lt;/code&gt; and &lt;code&gt;verify_full&lt;/code&gt;, but they require more setup in development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one-way verification: You need the root CA certificate&lt;/li&gt;
&lt;li&gt;two-way(mTLS) verification: You need both the client certificate and key in your connection string, &lt;strong&gt;the certificate/key MUST signed by root CA and key.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guide focuses on setting up one-way verification for development environments since it's simpler while still providing good security.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Server(One-Way Verification)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create Certificate and Key
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set to 100 years for development use&lt;/span&gt;
openssl req &lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="nt"&gt;-newkey&lt;/span&gt; rsa:4096 &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="nt"&gt;-nodes&lt;/span&gt; &lt;span class="nt"&gt;-keyout&lt;/span&gt; key.pem &lt;span class="nt"&gt;-out&lt;/span&gt; cert.pem &lt;span class="nt"&gt;-days&lt;/span&gt; 36500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Configure PostgreSQL
&lt;/h3&gt;

&lt;p&gt;Add these lines to &lt;code&gt;postgresql.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;ssl&lt;/span&gt; = &lt;span class="n"&gt;on&lt;/span&gt;
&lt;span class="n"&gt;ssl_cert_file&lt;/span&gt; = &lt;span class="s1"&gt;'/var/lib/postgresql/cert.pem'&lt;/span&gt;
&lt;span class="n"&gt;ssl_key_file&lt;/span&gt; = &lt;span class="s1"&gt;'/var/lib/postgresql/key.pem'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Update Access Rules
&lt;/h3&gt;

&lt;p&gt;Add these lines to &lt;code&gt;pg_hba.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;hostssl&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;scram&lt;/span&gt;-&lt;span class="n"&gt;sha&lt;/span&gt;-&lt;span class="m"&gt;256&lt;/span&gt;
&lt;span class="n"&gt;hostnossl&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;reject&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Set Up Docker Compose
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16.3&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;5432:5432'&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=postgres&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=postgres&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=postgres&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/postgres/config/pg_hba.conf:/etc/postgresql/pg_hba.conf&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/postgres/config/postgresql.conf:/etc/postgresql/postgresql.conf&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/postgres/certs/cert.pem:/var/lib/postgresql/cert.pem:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/postgres/certs/key.pem:/var/lib/postgresql/key.pem:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-c&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;config_file=/etc/postgresql/postgresql.conf&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-c&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;hba_file=/etc/postgresql/pg_hba.conf&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Connecting to Your Database
&lt;/h3&gt;

&lt;p&gt;After setting everything up, just add &lt;code&gt;sslmode=require&lt;/code&gt; to your connection string:&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="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"postgresql://postgres:postgres@postgres:5432/database?sslmode=require"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all you need to establish an encrypted connection to your PostgreSQL database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Server(mTLS)
&lt;/h2&gt;

&lt;p&gt;For mTLS (mutual TLS), both the server and client need valid certificates signed by a trusted Certificate Authority (CA). Here's how to set it up&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create certifications for server and client
&lt;/h3&gt;

&lt;p&gt;Your server.csr &lt;code&gt;CN&lt;/code&gt; MUST be your postgreSQL hostname&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;# 1. Create Root CA&lt;/span&gt;
openssl genrsa &lt;span class="nt"&gt;-out&lt;/span&gt; root.key 4096
openssl req &lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="nt"&gt;-new&lt;/span&gt; &lt;span class="nt"&gt;-nodes&lt;/span&gt; &lt;span class="nt"&gt;-key&lt;/span&gt; root.key &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="nt"&gt;-days&lt;/span&gt; 36500 &lt;span class="nt"&gt;-out&lt;/span&gt; root.crt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/C=US/ST=California/L=San Francisco/O=MyCompany/OU=IT/CN=PostgreSQL Root CA"&lt;/span&gt;

&lt;span class="c"&gt;# 2. create server cert&lt;/span&gt;
openssl genrsa &lt;span class="nt"&gt;-out&lt;/span&gt; server.key 4096
openssl req &lt;span class="nt"&gt;-new&lt;/span&gt; &lt;span class="nt"&gt;-key&lt;/span&gt; server.key &lt;span class="nt"&gt;-out&lt;/span&gt; server.csr &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/C=US/ST=California/L=San Francisco/O=MyCompany/OU=Database/CN=postgres"&lt;/span&gt;

&lt;span class="c"&gt;# This is important that SAN MUST contains all your possible DNS/IP &lt;/span&gt;
openssl x509 &lt;span class="nt"&gt;-req&lt;/span&gt; &lt;span class="nt"&gt;-in&lt;/span&gt; server.csr &lt;span class="nt"&gt;-CA&lt;/span&gt; root.crt &lt;span class="nt"&gt;-CAkey&lt;/span&gt; root.key &lt;span class="nt"&gt;-CAcreateserial&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-out&lt;/span&gt; server.crt &lt;span class="nt"&gt;-days&lt;/span&gt; 36500 &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-extfile&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"subjectAltName=DNS:localhost,DNS:postgres,IP:127.0.0.1,IP:192.168.0.23"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# 3. create client cert - CN here MUST same as the postgres user you want&lt;/span&gt;
openssl genrsa &lt;span class="nt"&gt;-out&lt;/span&gt; client.key 4096
openssl req &lt;span class="nt"&gt;-new&lt;/span&gt; &lt;span class="nt"&gt;-key&lt;/span&gt; client.key &lt;span class="nt"&gt;-out&lt;/span&gt; client.csr &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/C=US/ST=California/L=San Francisco/O=MyCompany/OU=Developers/CN=postgres"&lt;/span&gt;

openssl x509 &lt;span class="nt"&gt;-req&lt;/span&gt; &lt;span class="nt"&gt;-in&lt;/span&gt; client.csr &lt;span class="nt"&gt;-CA&lt;/span&gt; root.crt &lt;span class="nt"&gt;-CAkey&lt;/span&gt; root.key &lt;span class="nt"&gt;-CAcreateserial&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-out&lt;/span&gt; client.crt &lt;span class="nt"&gt;-days&lt;/span&gt; 36500 &lt;span class="nt"&gt;-sha256&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want your client CN using custom map, you should use &lt;code&gt;pg_ident.conf&lt;/code&gt; and adjust setting in &lt;code&gt;pg_hba.conf&lt;/code&gt; to archive that&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# pg_hba.conf
hostssl all all 0.0.0.0/0 cert map=my_map
# pg_ident.conf
my_map /CN=client postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Configure PostgreSQL
&lt;/h3&gt;

&lt;p&gt;Add these lines to &lt;code&gt;postgresql.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;ssl&lt;/span&gt; = &lt;span class="n"&gt;on&lt;/span&gt;
&lt;span class="n"&gt;ssl_cert_file&lt;/span&gt; = &lt;span class="s1"&gt;'/var/lib/postgresql/server.crt'&lt;/span&gt;
&lt;span class="n"&gt;ssl_key_file&lt;/span&gt; = &lt;span class="s1"&gt;'/var/lib/postgresql/server.key'&lt;/span&gt;
&lt;span class="n"&gt;ssl_ca_file&lt;/span&gt; = &lt;span class="s1"&gt;'/var/lib/postgresql/root.crt'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Update Access Rules
&lt;/h3&gt;

&lt;p&gt;Add these lines to &lt;code&gt;pg_hba.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;hostssl&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;cert&lt;/span&gt; &lt;span class="n"&gt;clientcert&lt;/span&gt;=&lt;span class="n"&gt;verify&lt;/span&gt;-&lt;span class="n"&gt;full&lt;/span&gt;
&lt;span class="n"&gt;hostnossl&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;reject&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Update docker-compose.yml
&lt;/h3&gt;

&lt;p&gt;Update volume bindings&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/postgres/config/pg_hba.conf:/etc/postgresql/pg_hba.conf&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/postgres/config/postgresql.conf:/etc/postgresql/postgresql.conf&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/postgres/certs/server.crt:/var/lib/postgresql/server.crt:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/postgres/certs/server.key:/var/lib/postgresql/server.key:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/postgres/certs/root.crt:/var/lib/postgresql/root.crt:ro&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Connecting to Your Database
&lt;/h3&gt;

&lt;p&gt;After setting everything up, add the following options to your connection string&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sslmode=verify-full&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sslcert=client.crt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sslkey=client.key&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sslrootcert=root.crt&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"postgresql://postgres:postgres@postgres:5432/database?sslmode=verify-full&amp;amp;sslcert=client.crt&amp;amp;sslkey=client.key&amp;amp;sslrootcert=root.crt"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>postgres</category>
      <category>security</category>
      <category>database</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Integrate Self-host Infisical into your NestJS project</title>
      <dc:creator>whchi</dc:creator>
      <pubDate>Wed, 11 Sep 2024 15:26:34 +0000</pubDate>
      <link>https://dev.to/whchi/integrate-self-host-infisical-into-your-nestjs-project-552h</link>
      <guid>https://dev.to/whchi/integrate-self-host-infisical-into-your-nestjs-project-552h</guid>
      <description>&lt;h2&gt;
  
  
  Why centralized secret management is necessary
&lt;/h2&gt;

&lt;p&gt;In modern software development, especially in containerized and collaborative environments, centralized secret management has become increasingly important. Here are&lt;/p&gt;

&lt;h3&gt;
  
  
  Flexibility in containerized deployment
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Real-time environment variable updates: In containerized deployments, centralized secret management allows us to easily update environment variables without rebuilding or redeploying containers. This greatly improves system flexibility and security.&lt;/li&gt;
&lt;li&gt;Environment consistency: It ensures all container instances use the same up-to-date secrets, reducing problems caused by environment inconsistencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Convenience in multi-developer scenarios
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Avoiding &lt;code&gt;.env&lt;/code&gt; file transfers: Traditionally, developers might need to send &lt;code&gt;.env&lt;/code&gt; files via email or messaging apps, which is not only insecure but can also lead to version confusion.&lt;/li&gt;
&lt;li&gt;Permission management: Centralized management allows us to set different access permissions for different team members, enhancing security.&lt;/li&gt;
&lt;li&gt;Version control: You can track the change history of secrets, making audits and rollbacks easier. two main reasons:&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A little about Infisical
&lt;/h2&gt;

&lt;p&gt;Infisical is a secret management service similar to HashiCorp Vault, but it focuses more on the developer experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages of Infisical
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;User-friendly: Offers an intuitive web interface and CLI tools, making secret management simple.&lt;/li&gt;
&lt;li&gt;Integration with development workflows: Provides SDKs in multiple languages, making it easy to integrate into existing projects.&lt;/li&gt;
&lt;li&gt;Team collaboration: Supports secure sharing and management of secrets among team members.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Paid features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Advanced audit logs&lt;/li&gt;
&lt;li&gt;Custom roles and more granular permission controls&lt;/li&gt;
&lt;li&gt;SAML single sign-on&lt;/li&gt;
&lt;li&gt;Advanced key rotation strategies&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Writing a NestJS Module to integrate Infisical
&lt;/h2&gt;

&lt;p&gt;First, install the necessary dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @infisical/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, create a new &lt;code&gt;infisical.module.ts&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DynamicModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Global&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Module&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="s1"&gt;@nestjs/common&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;InfisicalClient&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="s1"&gt;@infisical/sdk&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;InfisicalService&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="s1"&gt;./infisical.service&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;InfisicalModuleOptions&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="s1"&gt;./infisical-module-options.type&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;ConfigModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ConfigService&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="s1"&gt;@nestjs/config&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="nd"&gt;Global&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Module&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;class&lt;/span&gt; &lt;span class="nc"&gt;InfisicalModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;forRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InfisicalModuleOptions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;DynamicModule&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="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="c1"&gt;// fallback to dotenv&lt;/span&gt;
        &lt;span class="nx"&gt;ConfigModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forRoot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;envFilePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fallbackFile&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;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InfisicalModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;providers&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;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INFISICAL_OPTIONS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;useValue&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="nx"&gt;options&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;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InfisicalClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;useFactory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ConfigService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;new&lt;/span&gt; &lt;span class="nc"&gt;InfisicalClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
              &lt;span class="na"&gt;siteUrl&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="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INFISICAL_SITE_URL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;universalAuth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="na"&gt;clientId&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="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INFISICAL_CLIENT_ID&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="na"&gt;clientSecret&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="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INFISICAL_CLIENT_SECRET&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="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;inject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ConfigService&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nx"&gt;InfisicalService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;InfisicalService&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;The &lt;code&gt;infisical.service.ts&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Inject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OnModuleInit&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="s1"&gt;@nestjs/common&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;ConfigService&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="s1"&gt;@nestjs/config&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;InfisicalClient&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="s1"&gt;@infisical/sdk&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;InfisicalModuleOptions&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="s1"&gt;./infisical-module-options.type&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="nd"&gt;Injectable&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;class&lt;/span&gt; &lt;span class="nc"&gt;InfisicalService&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnModuleInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;logger&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;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;InfisicalService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;fallbackToConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;initializationPromise&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;PROCESS_ENVS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DATABASE_URL&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="s1"&gt;GOOGLE_APPLICATION_CREDENTIALS&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;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ConfigService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InfisicalClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INFISICAL_OPTIONS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InfisicalModuleOptions&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initializationPromise&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="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;onModuleInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&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;initializationPromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;init&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="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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INFISICAL_SITE_URL&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&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="s1"&gt;Use config from ConfigService&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="nx"&gt;fallbackToConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;try&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;secrets&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listSecrets&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;environment&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INFISICAL_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="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;projectId&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INFISICAL_PROJECT_ID&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="na"&gt;path&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;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// path to infisical project's path&lt;/span&gt;
        &lt;span class="na"&gt;includeImports&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="p"&gt;});&lt;/span&gt;
      &lt;span class="nx"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secretKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secretValue&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PROCESS_ENVS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secretKey&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// ENVs where should load directly into process&lt;/span&gt;
          &lt;span class="c1"&gt;// like prisma's DATABASE_URL &amp;amp; google cloud credential&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;secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secretKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secretValue&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&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="s1"&gt;Secrets loaded from Infisical&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&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="nx"&gt;logger&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;Failed to fetch secrets from Infisical, falling back to ConfigService&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fallbackToConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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="k"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;T&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fallbackToConfig&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;T&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="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&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;secrets&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&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;secrets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;T&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;value&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;secrets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&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;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;T&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;value&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;T&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;The &lt;code&gt;infisical-module-options.type&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;InfisicalModuleOptions&lt;/span&gt; &lt;span class="o"&gt;=&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;fallbackFile&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;string&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;h2&gt;
  
  
  Use it
&lt;/h2&gt;

&lt;p&gt;Write env in your dotenv&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFISICAL_ENV=dev # the slot of environments
INFISICAL_PROJECT_ID=&amp;lt;your-infisical-project-id&amp;gt;
INFISICAL_SITE_URL=&amp;lt;your-infisical-site-url&amp;gt;
INFISICAL_CLIENT_ID=&amp;lt;your-infisical-client-id&amp;gt;
INFISICAL_CLIENT_SECRET=&amp;lt;your-infisical-client-secret&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;INFISICAL_ENV&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqgefx88cm51unw26q13z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqgefx88cm51unw26q13z.png" alt="INFISICAL_ENV" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;INFISICAL_PROJECT_ID&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcx4e3u6153jwrxv8abrx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcx4e3u6153jwrxv8abrx.png" alt="INFISICAL_PROJECT_ID" width="800" height="206"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;INFISICAL_CLIENT_ID and INFISICAL_CLIENT_SECRET&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm23zablo8vskomys0qph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm23zablo8vskomys0qph.png" alt="id and secret" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And import it into your &lt;code&gt;app.module.ts&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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;InfisicalModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forRoot&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, you can use it as &lt;code&gt;ConfigService&lt;/code&gt; of nestjs&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="nx"&gt;infisicalService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_ENV_SETUP_IN_INFISICAL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is&lt;/p&gt;

</description>
      <category>infisical</category>
      <category>nestjs</category>
      <category>security</category>
      <category>dx</category>
    </item>
    <item>
      <title>Enhance your python code security using bandit</title>
      <dc:creator>whchi</dc:creator>
      <pubDate>Thu, 29 Feb 2024 03:11:38 +0000</pubDate>
      <link>https://dev.to/whchi/enhance-your-python-code-security-using-bandit-14gb</link>
      <guid>https://dev.to/whchi/enhance-your-python-code-security-using-bandit-14gb</guid>
      <description>&lt;p&gt;In the constantly evolving realm of technology, ensuring the security of your code is also an important part of software development.&lt;/p&gt;

&lt;p&gt;Here, I am using Bandit, a tool designed to find common security issues in Python code, to improve my project's security.&lt;/p&gt;

&lt;h2&gt;
  
  
  Severity vs Confidence
&lt;/h2&gt;

&lt;p&gt;In the context of Information Security, severity and confidence are two important metrics. Both of them are leveled into Low, Medium and High.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Severity&lt;/strong&gt;, it measures the seriousness of the consequences that may arise if the security issue is exploited or left unaddressed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confidence&lt;/strong&gt;, it reflects how well the information is validated, verified, or understood.&lt;/p&gt;

&lt;h2&gt;
  
  
  Call to action
&lt;/h2&gt;

&lt;p&gt;The result of a Bandit scan is a detailed report that outlines potential security issues in the code. This report includes the severity and confidence of each issue, as well as the part of the code where the issue was detected. The report can be &lt;br&gt;
output in several formats, including CSV, HTML, JSON, text, XML, and YAML. This allows developers to easily parse and analyze the results and take appropriate action to improve the security of their code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5u2qddo1hlfrjf3dtiwu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5u2qddo1hlfrjf3dtiwu.png" alt="Bandit scanning result" width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The following are some simple judgment criteria after scan.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;High Severity, High Confidence&lt;/strong&gt;: Immediate action is typically taken due to a well-understood and verified security threat with potentially severe consequences.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;High Severity, Low Confidence&lt;/strong&gt;: Caution is exercised, and further investigation is needed to increase confidence in the assessment before taking decisive action.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Low Severity, High Confidence&lt;/strong&gt;: Proactive measures may be taken even for low-severity issues if there is high confidence in the assessment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Low Severity, Low Confidence&lt;/strong&gt;: Ongoing monitoring and investigation are required to either confirm the low risk or gather additional information.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;pre-commit&lt;/code&gt; you can integrate bandit into your python project very easily&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;pyproject.toml: skip folders you don't want to be scanned
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tool.bandit]&lt;/span&gt;
&lt;span class="py"&gt;exclude_dirs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;".venv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;".git"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"__pycache__"&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;2 .pre-commit-config.yml: add pre-commit hook&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/PyCQA/bandit&lt;/span&gt;
    &lt;span class="na"&gt;rev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.7.7&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bandit&lt;/span&gt;
        &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pyproject.toml"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-r"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;."&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;additional_dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bandit[toml]"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Speed up your pull request review with CodiumAI PR agent</title>
      <dc:creator>whchi</dc:creator>
      <pubDate>Tue, 12 Dec 2023 16:03:33 +0000</pubDate>
      <link>https://dev.to/whchi/speed-up-your-pull-request-review-with-codiumai-pr-agent-1n90</link>
      <guid>https://dev.to/whchi/speed-up-your-pull-request-review-with-codiumai-pr-agent-1n90</guid>
      <description>&lt;p&gt;Pull Request (PR) is an essential process in software development that ensures code quality and collaboration among developers. PRs are also a way to learn from others and get better at coding, a good PR process helps people work together better and keeps the code easy to use and understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problems with PR
&lt;/h2&gt;

&lt;p&gt;However, PRs can be a bottleneck in software development lifecycle. Developers often face challenges such as large codebases, time constraints, and the need for thorough code analysis.&lt;/p&gt;

&lt;p&gt;Here are some common problems of PR:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Time Consumption(No time to review)&lt;/strong&gt;: Reviewers who are familiar with the codebase often do not have enough time to conduct a thorough review in a short period.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context Switching&lt;/strong&gt;: Reviewers may have to switch contexts frequently, especially if they are reviewing multiple PRs from different parts of the codebase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedback Fatigue&lt;/strong&gt;: Reviewers may experience fatigue, especially when dealing with a high volume of PRs, which can lead to less thorough reviews.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technical Debt and Bugs&lt;/strong&gt;: Identifying deeper issues like technical debt or potential bugs can be challenging, especially under time constraints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unclear Descriptions&lt;/strong&gt;: If the PR descriptions are not clear or detailed enough, it becomes difficult for reviewers to understand the purpose and scope of the changes, leading to inefficient reviews.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coding style&lt;/strong&gt;: Inconsistencies in coding style and lack of adherence to best practices can lead to future maintenance issues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Large PRs&lt;/strong&gt;: ****Very large PRs can be overwhelming to review, often resulting in missed issues or superficial reviews.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are several tools available to address these issues, such as &lt;strong&gt;linters&lt;/strong&gt;, &lt;strong&gt;automated tests&lt;/strong&gt;, and &lt;strong&gt;contribution guidelines&lt;/strong&gt;. Additionally, &lt;strong&gt;training sessions&lt;/strong&gt; can be beneficial if you are part of a well-resourced company (although they might not be as common in open-source projects)&lt;/p&gt;

&lt;h2&gt;
  
  
  How AI Can Help
&lt;/h2&gt;

&lt;p&gt;In November 2022, OpenAI launched ChatGPT, showcasing how AI, particularly through Large Language Model (LLM) APIs, can empower individuals and enhance the PR review experience.&lt;/p&gt;

&lt;p&gt;Subsequently, numerous AI tools based on Large Language Models (LLMs) were developed to address the aforementioned issues. These tools have been created by both large enterprises, such as &lt;em&gt;GitHub&lt;/em&gt;, &lt;em&gt;AWS&lt;/em&gt;, and &lt;em&gt;Microsoft&lt;/em&gt;, and startups, including &lt;em&gt;CodiumAI&lt;/em&gt;, &lt;em&gt;BLACKBOX AI&lt;/em&gt;, and &lt;em&gt;Tabnine&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In this section, I will provide an extensive comparison of some of the most notable tools designed to facilitate PR reviews. This comparison will focus specifically on two key examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From an enterprise perspective: &lt;strong&gt;GitHub Copilot&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;From a startup perspective: &lt;strong&gt;CodiumAI PR Agent&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Comparison of CodiumAI PR agent and Github Copilot
&lt;/h3&gt;

&lt;p&gt;I’m still in the waiting list of Github Copilot, so the Github part in this comparison table might change&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;CodiumAI PR Agent&lt;/th&gt;
&lt;th&gt;Github Copilot&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;pricing&lt;/td&gt;
&lt;td&gt;free&lt;/td&gt;
&lt;td&gt;paid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;opensource&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PR review&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/review&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;copilot:summary&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PR description&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/describe&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;copilot:walkthrough&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;code suggestion&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/improve&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;interestring tricks&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/ask "your-question"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;copilot:poem&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;support code hosting platforms&lt;/td&gt;
&lt;td&gt;github, gitlab, bitbucket, AWS CodeCommit, azure devops, gerrit&lt;/td&gt;
&lt;td&gt;github&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Granularity&lt;/td&gt;
&lt;td&gt;focus in PR&lt;/td&gt;
&lt;td&gt;more general&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;website&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/Codium-ai/pr-agent" rel="noopener noreferrer"&gt;https://github.com/Codium-ai/pr-agent&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://githubnext.com/projects/copilot-for-pull-requests" rel="noopener noreferrer"&gt;https://githubnext.com/projects/copilot-for-pull-requests&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;CodiumAI PR Agent also includes commands such as &lt;code&gt;/update_changelog&lt;/code&gt; for writing release logs, &lt;code&gt;/similiar_issue&lt;/code&gt; to find identical issues in large codebases, &lt;code&gt;/add_docs&lt;/code&gt; for more detailed descriptions, and &lt;code&gt;/generate_labels&lt;/code&gt; to enhance documentation clarity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real world example using CodiumAI PR Agent
&lt;/h3&gt;

&lt;p&gt;Here's an example of package development, focusing only on the tools listed in the comparison table.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;/review&lt;/code&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnqy63rjck5dn81757321.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnqy63rjck5dn81757321.png" alt="review command" width="800" height="739"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;/describe&lt;/code&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7l45a1f9blbc4lzqie4u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7l45a1f9blbc4lzqie4u.png" alt="describe command" width="800" height="714"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;/improve&lt;/code&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq8umvg8otkmzl0ru6ek8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq8umvg8otkmzl0ru6ek8.png" alt="improve command" width="800" height="556"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;/ask "what is the biggest changes? what is the latest commit doing?"&lt;/code&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcq0gz3xtajrjk5kos1ac.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcq0gz3xtajrjk5kos1ac.png" alt="ask command" width="800" height="661"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've tested all the commands of CodiumAI PR Agent except for &lt;strong&gt;&lt;code&gt;/similar_issue&lt;/code&gt;&lt;/strong&gt;. That's a total of &lt;strong&gt;7 features&lt;/strong&gt;, which cost me &lt;strong&gt;$0.86&lt;/strong&gt; for using the OpenAI API, and it takes me only &lt;strong&gt;5&lt;/strong&gt; minutes to review &lt;strong&gt;17&lt;/strong&gt; files. &lt;/p&gt;

&lt;p&gt;That’s a huge improvement.&lt;/p&gt;

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

&lt;p&gt;AI is revolutionizing individual performance across various fields. I currently use tools like ChatGPT, Bard, and claude.ai for scripting codes, solving math problems, and creating tests. These tools often generate an initial proof of concept (POC) or even a minimum viable product (MVP) in under a minute, significantly boosting my overall productivity.&lt;/p&gt;

&lt;p&gt;I'm also exploring the use of PR agents in my work. So far, I find them suitable for open-source projects, and as AI technology advances, I anticipate their integration into enterprise environments in the near future.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>Mocking Async API Calls in FastAPI Tests</title>
      <dc:creator>whchi</dc:creator>
      <pubDate>Sat, 02 Dec 2023 08:48:13 +0000</pubDate>
      <link>https://dev.to/whchi/testing-fastapi-async-function-and-httpx-asyncclient-1glc</link>
      <guid>https://dev.to/whchi/testing-fastapi-async-function-and-httpx-asyncclient-1glc</guid>
      <description>&lt;p&gt;When engaging in software development, there is often a need to send SMS, emails, or make API calls to other systems. When testing such programs, it is common to mock these interactions to ensure that the testing process does not fail due to an unavailable network connection.&lt;/p&gt;

&lt;p&gt;In FastAPI, there are two ways to define functions: using &lt;code&gt;def&lt;/code&gt; and &lt;code&gt;async def&lt;/code&gt;. According to the official documentation, testing these functions requires the use of &lt;code&gt;TestClient&lt;/code&gt; for &lt;code&gt;def&lt;/code&gt; and &lt;code&gt;httpx.AsyncClient&lt;/code&gt; for &lt;code&gt;async def&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;This article provides a practical method for mocking the API caller within &lt;code&gt;async def&lt;/code&gt; functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  code
&lt;/h2&gt;

&lt;p&gt;Here the API endpoint makes an API call to 3rd party&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# example_router.py
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;

&lt;span class="nd"&gt;@router.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/examples&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_example&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Any&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://3rd-party-api.example.com&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                  &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;()})&lt;/span&gt;
            &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JsonResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We should mock the &lt;code&gt;httpx.AsyncClient&lt;/code&gt; inside &lt;code&gt;add_example&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, make a fixture&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# conftest.py
&lt;/span&gt;
&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;async_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http://localhost:1234&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then write the test with mock, the key points are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Uses the &lt;code&gt;with patch&lt;/code&gt; to mock the &lt;code&gt;httpx.AsyncClient&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Mock your calling method, here is &lt;code&gt;post&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You cannot patch the whole &lt;code&gt;httpx.AsyncClient&lt;/code&gt; using &lt;code&gt;@patch&lt;/code&gt; decorator because it will mock all &lt;code&gt;httpx.AsyncClient&lt;/code&gt; including the one declared in your test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# test_example_router.py
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unittest.mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsyncMock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsyncClient&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.mark.asyncio&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_contact_us&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;async_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;httpx.AsyncClient&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;mock_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;mock_post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AsyncMock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;mock_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__aenter__&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mock_post&lt;/span&gt;

        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;async_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/api/examples&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                           &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                                               &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;desc&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;desc&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                               &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Brid Pett&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                               &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;phone&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;123456789&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                               &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;test@cc.cc&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                           &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="n"&gt;mock_post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert_called_once&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;status_code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all.&lt;/p&gt;

</description>
      <category>fastapi</category>
      <category>python</category>
      <category>testing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Reading notes | Software Engineer-The Soft Part</title>
      <dc:creator>whchi</dc:creator>
      <pubDate>Fri, 20 Oct 2023 02:46:37 +0000</pubDate>
      <link>https://dev.to/whchi/reading-notes-software-engineer-the-soft-part-bkm</link>
      <guid>https://dev.to/whchi/reading-notes-software-engineer-the-soft-part-bkm</guid>
      <description>&lt;p&gt;This is a very pragmatic book, and it is also quite thin. The author is Addy Osmani, the person in charge of the Google Chrome project. The content is a compilation of his past experiences and soft skills guidance.&lt;/p&gt;

&lt;p&gt;I will note down some parts of the book that I personally found impactful, there are 33 points.&lt;/p&gt;

&lt;h1&gt;
  
  
  notes
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Mastery of technology means delivering high value per hour worked. This means you can identify which tasks bring value and help the team focus on those tasks. It also means knowing how to avoid work that does not add value. The best engineers can even guide the entire team to avoid work that does not add value.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Critical thinking is thinking purposefully to form your own conclusions. It allows you to focus more on root causes and avoid potential future problems.&lt;br&gt;
Avoid falling into the trap of assuming correlation implies causation. Someone with critical thinking may question such assumptions and ask why we believe it to be true.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The long-term value of learning fundamentals is their transferability, while the short-term value is helping you make better decisions. Transferable skills have macro and micro aspects. Macroscopically, the basic principles of programming languages are mostly the same, and frequently applying the basic principles can generate tremendous value. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Microscopically, syntax may be familiar but the skills are not transferable. You should also not overfocus on fundamentals, the key is to learn for application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Having a solid foundation of knowledge allows you to make better decisions and go further, such as considering frameworks or avoiding choosing the wrong technologies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"You haven't mastered a tool until you understand when it&lt;br&gt;
should not be used." - @kelseyhightower&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Focus on the user/developer experience, don't make decisions based on specific solutions (e.g. just using Laravel because it's good).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use boring (proven) technologies, then consciously choose the tools most suited to solving the problem. FOMO may not be an effective approach in tech - understanding how to use things is important, but the focus should still be on delivering value, unless those technologies can increase the value you produce.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The best time to learn new technologies is by using new projects - use the Feynman learning technique to accelerate learning.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To enable a project to evolve, become its caretaker rather than its owner - document it well so it can continue when you're gone.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The value of software lies in the accumulated knowledge of its producers, not the code itself - don't rewrite code for no reason, you're discarding collective knowledge.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;YAGNI (You aren't gonna need it) means don't add features until needed, focus on what really delivers value. But maintain appropriate abstraction - avoid premature abstraction (AHA). Decoupling solutions for specific problems while trying to extract reusability is better.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The best modules are those with maximum benefit and minimum cost. Interfaces should be narrow, functionality deep.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A good mindset for legacy code is to not assume every line is relevant - understand it then remove unnecessary parts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Defining "done" is important when tackling problems - avoids unnecessary future revisions. This could include meeting acceptance criteria, passing tests, documentation, etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Debug messages show the truth, don't keep making small changes trying to fix issues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Design docs are part of software engineering, not an afterthought. Coordinate reviews of design docs, compare to original as it evolves to validate addressing issues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Focus communication on kindness - may take more time but makes others more willing to communicate for effective outcomes. Avoid jargon, match vocabulary to audience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Saying no is better than over promising.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A senior engineer should be skilled at designing software systems and human/team systems. You can lead a diverse engineering team, delegate tasks, guide them to focus on code quality, performance and simplicity. Provide feedback when needed, defend them when necessary. You should also be able to market yourself, your work, and problem-solving abilities for exposure in the organization. Overall, manage relationships well internally and with management.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Guide junior engineers to ask the right questions in a way that lets them know you're glad they asked.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Strongly defend your views but re-examine assumptions when new evidence arrives.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Teach the team to fish - coordinate internally and externally, aim to increase team output. More senior means focusing more on managing relationships with management and the team.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Team efficiency is efficiency - accept imposter syndrome, it can motivate you to learn.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Understand the business model, your code hugely impacts business logic. Insight into commercial software is key to influencing, as you're closer to the market so naturally do impactful things.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Start from the user experience and work backwards to required tech.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mentoring is guiding - suggest ideas for mentees to try. If they can't solve a problem, show your approach and thinking for reference e.g. debugging steps rather than the answer directly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Don't try to do too much at once, focused deep work time is very important.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Estimates can be updated anytime, software estimates are inherently complex until requirements are deeply understood, then they become more accurate. A better way is rough estimate, detailed estimate, staged rollout.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A leader confidently admitting not knowing something is powerful. This confidence reduces expectations for senior engineers to know everything. You absolutely don't need all the answers, but admitting you're human and committed to solving problems together is most important.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tech debt repayment should be regular time allocated by tech leads for cleanup - prevention over cure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Focus on the problem - issues in other projects may also be issues in yours.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"Surround yourself by excellence and work with people who are&lt;br&gt;
the best as what they do" - Brian Staufenbiel&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  source
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://addyosmani.com/blog/software-engineering-soft-parts/" rel="noopener noreferrer"&gt;https://addyosmani.com/blog/software-engineering-soft-parts/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>beginners</category>
      <category>productivity</category>
      <category>career</category>
    </item>
    <item>
      <title>Localize your FastAPI validation message</title>
      <dc:creator>whchi</dc:creator>
      <pubDate>Thu, 17 Aug 2023 06:14:04 +0000</pubDate>
      <link>https://dev.to/whchi/localize-your-fastapi-validation-message-38h4</link>
      <guid>https://dev.to/whchi/localize-your-fastapi-validation-message-38h4</guid>
      <description>&lt;p&gt;When working on website frontend development, a significant amount of time is spent on handling form validation. If your service caters to a global audience (not only within your country), it's advisable to incorporate internationalization (i18n) error messages into it.&lt;/p&gt;

&lt;p&gt;If the situation involves frontend-backend separation, managing the validation i18n message communication becomes a troublesome task. &lt;/p&gt;

&lt;p&gt;The question arises: should the validation messages be managed on the backend or the frontend?&lt;/p&gt;

&lt;p&gt;Generally, messages should be managed closer to the rendering (frontend mostly) interface for better organization. A common approach is to use error codes paired with corresponding messages.&lt;/p&gt;

&lt;p&gt;However, there are numerous validation methods for forms, and if error codes were applied to forms, maintenance would become quite challenging. Therefore, it's best to centrally manage form-type validation by either the frontend or the backend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FastAPI&lt;/strong&gt; employs &lt;strong&gt;Pydantic&lt;/strong&gt; to encapsulate most scenarios of form validation and provides corresponding error messages. This works well in a purely English environment, but if we require i18n, it's clear that this approach falls short.&lt;/p&gt;

&lt;p&gt;This article will work through the whole concept of how to integrate i18n into FastAPI's request validation error messages with pseudo code.&lt;/p&gt;

&lt;h2&gt;
  
  
  middleware
&lt;/h2&gt;

&lt;p&gt;Firstly we need to know what locale it is, we can archive this using middleware&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LocaleMiddleware&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_next&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
       &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;incoming_locale&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;call_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LocaleMiddleware&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  exception handler
&lt;/h2&gt;

&lt;p&gt;Then we register our own exception handler to handle &lt;code&gt;RequestValidationError&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;i18n_exception_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
     &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;make_i18n_msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;errors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;422&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_exception_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RequestValidationError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i18n_exception_handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  translate
&lt;/h2&gt;

&lt;p&gt;Finally, we make translate action, the exc of FastAPI is object of object, so we need to extract the message recursively&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_i18n_msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstanceof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wrapper&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;make_i18n_msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Translator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trans.file.key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we need a translation file looks like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;zh-TW.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"field required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"欄位必填"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"extra fields not permitted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"不允許額外的欄位"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ja-JP.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"field required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"フィールドは必須です"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"extra fields not permitted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"余分なフィールドは許可されていません"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then when you send with locale to your API pydantic validation, you will get something like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "errors": [
    {
      "loc": [
        "body",
        "string"
      ],
      "msg": "フィールドは必須です",
      "type": "value_missing.field_required"
    },
   {
      "loc": [
        "body",
        "string"
      ],
      "msg": "余分なフィールドは許可されていません",
      "type": "value_error.extra_fields_are_not_permitted"
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the essence of it, and I've written a package for it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://pypi.org/project/fastapi-validation-i18n/" rel="noopener noreferrer"&gt;&lt;strong&gt;fastapi-validation-i18n&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can accomplish this by adhering to the documentation, contributions are also encouraged and welcome.&lt;/p&gt;

</description>
      <category>fastapi</category>
      <category>webdev</category>
      <category>python</category>
    </item>
  </channel>
</rss>
