DEV Community: Jiri Spac The latest articles on DEV Community by Jiri Spac (@capaj). https://dev.to/capaj https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F39403%2F03610caa-65a8-4597-a1e1-7ec05fbe5ec5.jpeg DEV Community: Jiri Spac https://dev.to/capaj en The Right Time to Approve a Pull Request: Embracing Improvement Over Perfection Jiri Spac Fri, 09 Feb 2024 14:54:52 +0000 https://dev.to/capaj/the-right-time-to-approve-a-pull-request-embracing-improvement-over-perfection-2pm6 https://dev.to/capaj/the-right-time-to-approve-a-pull-request-embracing-improvement-over-perfection-2pm6 <p><a href="proxy.php?url=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%2Fbkvgvtt61rjuhptpixhy.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=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%2Fbkvgvtt61rjuhptpixhy.jpg" alt="PR review" width="800" height="800"></a></p> <p>In the ever-accelerating world of software development, the Pull Request (PR) review process stands as a crucial checkpoint. Especially in the time of AI. It's the confluence where innovation meets critique, quality is meticulously refined, and rubber meets the road. Yet, a persistent specter haunts many development teams: the unspoken belief that a PR must achieve an almost mythical state of perfection before earning the coveted "Approved" stamp. This well-intentioned pursuit of flawlessness, however, often clashes fundamentally with the iterative, adaptive spirit of Agile development.</p> <h3> Embracing Momentum: Improvement as the True North </h3> <p>Agile methodologies are built on a foundation of continuous improvement—a relentless cycle of building, measuring, and learning through small, manageable increments. This powerful philosophy should permeate every aspect of our work, especially how we evaluate PRs. A PR's primary mission shouldn't be to present a flawless masterpiece, but rather to represent a tangible step forward. If a proposed change demonstrably enhances the User Experience (UX) or smooths out friction in the Developer Experience (DX), it carries inherent value and generally warrants approval. Consider these dimensions:</p> <ol> <li><p><strong>Elevating the User Experience (UX):</strong><br> Improvements here can span the spectrum – from subtle interface adjustments that clarify user journeys, to significant feature enhancements that unlock new levels of satisfaction. Each positive modification builds towards a superior product. Holding back these valuable increments in pursuit of an unattainable "perfect" state risks stagnation. We delay tangible benefits for users, potentially leaving them grappling with known pain points longer than necessary, which can erode satisfaction and trust.</p></li> <li><p><strong>Refining the Developer Experience (DX):</strong><br> Often underestimated, the DX is paramount to a team's velocity and well-being. A PR that untangles complex logic, boosts code clarity, pays down technical debt, or introduces helpful tooling makes the entire development lifecycle more efficient and sustainable. While individual DX improvements might seem minor, their compound effect is profound. Approving such PRs keeps the development engine running smoothly, fosters motivation, combats burnout, and ultimately enables the team to deliver value faster.</p></li> </ol> <p>The Agile ethos thrives on adaptation, feedback loops, and incremental progress. Applying this lens to PR reviews cultivates a culture where collaboration flourishes, learning is continuous, and adaptability is paramount. The goal isn't necessarily immaculate code in every single PR, but rather collective forward motion, refining our creation with each contribution.</p> <h3> The Perils of Delay: When Good PRs Languish </h3> <p>Waiting for perfection can have tangible negative consequences. Imagine these scenarios:</p> <ul> <li> <strong>Scenario 1: The Lingering UX Annoyance:</strong> A developer submits a PR with a simple fix: clarifying the label on a confusing button that generates frequent user support tickets. The core logic is sound, but the reviewer blocks it, demanding minor stylistic changes or unrelated refactoring. Weeks pass. While the team debates stylistic nuances, users continue to stumble, support remains burdened, and a straightforward improvement sits gathering digital dust.</li> <li> <strong>Scenario 2: The DX Bottleneck:</strong> A thoughtful PR refactors a notoriously complex and brittle module, making it significantly easier to understand and modify. It passes all tests and achieves its goal. However, it's held up because a reviewer wants a different (but functionally equivalent) naming convention applied throughout. Meanwhile, two other developers need to build features touching that same complex module. They are forced to grapple with the old, difficult code, slowing their progress and increasing the risk of introducing new bugs – all because a beneficial refactoring was unnecessarily delayed.</li> </ul> <p>These examples highlight how demanding perfection over progress can actively hinder the team and negatively impact both users and developers.</p> <h3> Drawing the Line: When Approval is Not the Answer </h3> <p>While we champion progress, quality cannot be sacrificed wholesale. There are clear situations where rejecting a PR is not just appropriate, but essential for maintaining the integrity and stability of the product. The key is to approach rejection constructively, transforming it into a learning opportunity rather than a point of discouragement. Here are non-negotiable reasons for rejection:</p> <ol> <li> <strong>Continuous Integration (CI) Failures:</strong> If the automated CI pipeline flags errors (and these aren't attributable to flaky tests), it's a critical stop sign. A red build signals that the changes have introduced bugs, broken existing functionality, or failed to adhere to established project standards. Ensuring a green CI build is a fundamental prerequisite for merging code, safeguarding the application's baseline stability.</li> <li> <strong>Breaking the Primary Use Case:</strong> Any PR that compromises the core, essential functionality of the application must be rejected. These primary use cases represent the fundamental value delivered to users. Disrupting them, even unintentionally, can have severe repercussions. Rigorous verification that the main features remain fully operational is mandatory before approval.</li> <li> <strong>Introducing Critical Performance Regressions:</strong> Performance is often a key feature in itself. A PR that causes a measurable, significant slowdown in critical operations harms the user experience and can undermine the application's viability. Such regressions should be caught via performance benchmarks or profiling tools integrated into the development lifecycle. If a PR demonstrably degrades performance beyond acceptable thresholds, it needs revision.</li> </ol> <p>When rejection is necessary, clarity is paramount. Explain <em>precisely</em> what needs to be addressed. Utilizing standardized formats like <a href="proxy.php?url=https://conventionalcomments.org/" rel="noopener noreferrer">Conventional Comments</a> can significantly aid in providing actionable, unambiguous feedback.</p> <h3> The Unsung Hero: A Robust Testing Strategy </h3> <p>Underpinning this entire philosophy is the non-negotiable requirement of a comprehensive testing strategy. Unit tests, integration tests, end-to-end (E2E) tests, and potentially performance tests form the safety net that allows us to embrace incremental improvement with confidence. This robust test suite acts as an automated guardian, catching regressions and ensuring that approved changes don't inadvertently destabilize the application. Without solid testing, the "improvement over perfection" approach becomes significantly riskier.</p> <h3> Rejection as a Catalyst for Growth </h3> <p>A rejected PR shouldn't signify failure, but rather a need for course correction. It's an invaluable feedback loop. When rejecting, deliver clear, constructive criticism outlining the specific issues and suggesting pathways to resolution. Encourage the contributor to iterate and resubmit. Frame the review process as a collaborative partnership aimed at achieving shared quality goals, not as a gatekeeping exercise designed to block contributions.</p> <h3> Gauging Team Health: The Rejection Rate Metric </h3> <p>Does this mean approving virtually every PR? Ideally, in a perfectly aligned team, yes. But reality dictates rejections will happen. The <em>rate</em> of rejection, however, can be a fascinating indicator of team dynamics and process health.</p> <ul> <li> <strong>High Rejection Rate (e.g., &gt;40%):</strong> This often signals deeper issues. Is the team misaligned on quality expectations? Are requirements poorly defined? Is there a lack of mentorship for junior developers? Or, critically, are essential automated checks missing, allowing fundamentally broken PRs to even reach the review stage?</li> <li> <strong>Very Low Rejection Rate (e.g., &lt;5-6%):</strong> While seemingly positive, this <em>could</em> indicate rubber-stamping. Are reviewers engaging critically, or just clicking "Approve" to clear their queue? Thorough reviews are still vital, even with highly skilled teams.</li> <li> <strong>Healthy Range (e.g., 6-14%):</strong> Teams operating in this zone often demonstrate good alignment, strong testing practices, and effective collaboration. Rejections occur for valid reasons, and feedback loops are generally constructive.</li> </ul> <p>Remember, even within elite teams where code quality is consistently high, the PR process serves another vital function: knowledge dissemination. It combats information silos and ensures multiple team members gain familiarity with different parts of the codebase.</p> <h3> Finding the Balance </h3> <p>The decision to approve or reject a Pull Request hinges on a pragmatic balance. Prioritize tangible improvements to the product or the development process. Set clear, high-level bars for rejection based on critical failures, backed by a strong testing infrastructure. Foster a culture where feedback is constructive, collaboration is valued, and progress is consistently celebrated. Every thoughtfully approved PR represents forward momentum on your product's journey. Reject judiciously, approve progressively, and build better software, together.</p> codereviews pullrequests How to let lambda access the internet outside of your VPC Jiri Spac Sat, 14 Jan 2023 11:10:25 +0000 https://dev.to/capaj/how-to-let-lambda-access-the-internet-outside-of-your-vpc-2oki https://dev.to/capaj/how-to-let-lambda-access-the-internet-outside-of-your-vpc-2oki <p>This article should not exist. It's absolutely a huge problem when something as trivial, as mundane, as obvious generates such a confusion between your users.<br> When I was first greeted with this:</p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code> Error: There are no 'Private' subnet groups in this VPC. Available types: Public at LookedUpVpc.selectSubnetObjectsByType (/home/capaj/work-repos/top-secret/node_modules/aws-cdk-lib/aws-ec2/lib/vpc.js:1:4998) at LookedUpVpc.selectSubnetObjects (/home/capaj/work-repos/top-secret/node_modules/aws-cdk-lib/aws-ec2/lib/vpc.js:1:3724) at LookedUpVpc.selectSubnets (/home/capaj/work-repos/top-secret/node_modules/aws-cdk-lib/aws-ec2/lib/vpc.js:1:1454) at Function.configureVpc (/home/capaj/work-repos/top-secret/node_modules/aws-cdk-lib/aws-lambda/lib/function.js:1:18626) at new Function (/home/capaj/work-repos/top-secret/node_modules/aws-cdk-lib/aws-lambda/lib/function.js:1:5676) at /home/capaj/work-repos/top-secret/cdk/LambdaStack.ts:93:30 at Array.forEach (&lt;anonymous&gt;) at new LambdaStack (/home/capaj/work-repos/top-secret/cdk/LambdaStack.ts:88:17) at Object.&lt;anonymous&gt; (/home/capaj/work-repos/top-secret/bin/backend.ts:58:3) at Module._compile (node:internal/modules/cjs/loader:1159:14) </code></pre> </div> <p>I thought it's going to be easy. I'll just flip a switch somewhere and we will be done.<br> Nope. AWS Cloud had much more in store for me that day. Hell for a few more days.</p> <p>These were stack overflow answers I was desperately trying to understand:</p> <ul> <li><a href="proxy.php?url=https://stackoverflow.com/questions/52992085/why-cant-an-aws-lambda-function-inside-a-public-subnet-in-a-vpc-connect-to-the" rel="noopener noreferrer">https://stackoverflow.com/questions/52992085/why-cant-an-aws-lambda-function-inside-a-public-subnet-in-a-vpc-connect-to-the</a></li> <li><a href="proxy.php?url=https://stackoverflow.com/a/55267891/671457" rel="noopener noreferrer">https://stackoverflow.com/a/55267891/671457</a></li> <li><a href="proxy.php?url=https://stackoverflow.com/a/43669759/671457" rel="noopener noreferrer">https://stackoverflow.com/a/43669759/671457</a></li> <li> <a href="proxy.php?url=https://stackoverflow.com/a/50280780/671457" rel="noopener noreferrer">https://stackoverflow.com/a/50280780/671457</a> (this one is quite confusing with the ENI)</li> </ul> <p>This AWS CDK error shows up, when you try to deploy lambdas into your default VPC. If you created your VPC without a "Private" subnet like me and you have a TS infa code like this:</p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code> <span class="kd">const</span> <span class="nx">lambdaFunction</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">lambda</span><span class="p">.</span><span class="nc">Function</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="s2">`</span><span class="p">${</span><span class="nx">cron</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2">-cron`</span><span class="p">,</span> <span class="p">{</span> <span class="na">memorySize</span><span class="p">:</span> <span class="nx">cron</span><span class="p">.</span><span class="nx">memorySize</span> <span class="o">??</span> <span class="mi">2048</span><span class="p">,</span> <span class="na">timeout</span><span class="p">:</span> <span class="nx">cdk</span><span class="p">.</span><span class="nx">Duration</span><span class="p">.</span><span class="nf">minutes</span><span class="p">(</span><span class="mi">15</span><span class="p">),</span> <span class="na">runtime</span><span class="p">:</span> <span class="nx">lambda</span><span class="p">.</span><span class="nx">Runtime</span><span class="p">.</span><span class="nx">NODEJS_18_X</span><span class="p">,</span> <span class="na">architecture</span><span class="p">:</span> <span class="nx">Architecture</span><span class="p">.</span><span class="nx">ARM_64</span><span class="p">,</span> <span class="na">handler</span><span class="p">:</span> <span class="s2">`</span><span class="p">${</span><span class="nx">cron</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2">.</span><span class="p">${</span><span class="nx">cron</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span> <span class="na">code</span><span class="p">:</span> <span class="nx">lambda</span><span class="p">.</span><span class="nx">Code</span><span class="p">.</span><span class="nf">fromAsset</span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">../dist</span><span class="dl">'</span><span class="p">),</span> <span class="p">{</span> <span class="na">exclude</span><span class="p">:</span> <span class="p">[</span> <span class="p">...</span><span class="nx">otherLambdas</span><span class="p">.</span><span class="nf">map</span><span class="p">((</span><span class="nx">cron</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="s2">`</span><span class="p">${</span><span class="nx">cron</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2">.js`</span><span class="p">),</span> <span class="p">...</span><span class="nx">otherLambdas</span><span class="p">.</span><span class="nf">map</span><span class="p">((</span><span class="nx">cron</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="s2">`</span><span class="p">${</span><span class="nx">cron</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2">.js.map`</span><span class="p">)</span> <span class="p">]</span> <span class="p">}),</span> <span class="na">environment</span><span class="p">:</span> <span class="nx">envVars</span><span class="p">,</span> <span class="na">securityGroups</span><span class="p">:</span> <span class="p">[</span><span class="nx">lambdaSecurityGroup</span><span class="p">],</span> <span class="nx">vpc</span><span class="p">,</span> <span class="na">vpcSubnets</span><span class="p">:</span> <span class="p">{</span> <span class="na">subnetType</span><span class="p">:</span> <span class="nx">SubnetType</span><span class="p">.</span><span class="nx">PRIVATE_WITH_EGRESS</span> <span class="p">},</span> <span class="na">role</span><span class="p">:</span> <span class="nx">lambdaVPCExecutionRole</span> <span class="p">})</span> </code></pre> </div> <p>It will be failing. At first I though I can just switch a subnet into Private by going into AWS console <a href="proxy.php?url=https://eu-west-1.console.aws.amazon.com/vpc/home?region=eu-west-1#subnets:" rel="noopener noreferrer">https://eu-west-1.console.aws.amazon.com/vpc/home?region=eu-west-1#subnets:</a> <br> but it's not as simple.<br> This is what you need to do:</p> <ol> <li>create a new subnet. You don't choose whether it is public or private in this step. </li> <li>create a new network ACL with everything blocked- AWS will recognize the subnet as private if it sees network ACL blocking remote access</li> <li>now click <img src="proxy.php?url=https://media.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%2Fyxtp1nslb403ixax0ieh.png" alt="Image description"> </li> <li>go into you <code>cdk.context.json</code> and delete your VPC there. This will force AWS CDK to refresh the subnets in your VPC.</li> </ol> <p>The last remaining bit for the subnet to be PRIVATE_WITH_EGRESS is to create a NAT and add it to your routing table.</p> <ol> <li><p><a href="proxy.php?url=https://media.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%2F5xq0f1kz6hcwypx68c9g.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.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%2F5xq0f1kz6hcwypx68c9g.png" alt="Image description"></a></p></li> <li><p>name it, ⚠️ select a PUBLIC subnet. This is where a lot of people make a mistake. If you choose a private subnet when creating NAT, it will not work. NAT needs to be in a public subnet with internet gateway(IGW is available by default).</p></li> <li><p>go create a new route table <br> <a href="proxy.php?url=https://media.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%2Fmo1jvgpbbcfoj5cq926e.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.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%2Fmo1jvgpbbcfoj5cq926e.png" alt="Image description"></a></p></li> <li><p>add your nat as target for 0.0.0.0/0 route <a href="proxy.php?url=https://media.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%2Fkc3qrlx9terzrm2xau9x.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.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%2Fkc3qrlx9terzrm2xau9x.png" alt="Image description"></a></p></li> <li><p>associate the new route table with your subnet: <a href="proxy.php?url=https://media.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%2Fqxjhpwwjsaubp3q2692f.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.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%2Fqxjhpwwjsaubp3q2692f.png" alt="Image description"></a></p></li> <li><p>go into you network ACL add the local ip address of the NAT gateway to both Inbound and Outbound rules. This is needed because ACLs are stateless, whereas with security groups it is enough to have just one sided rule. For ACL ALWAYS make sure to have rules for both sides.</p></li> <li><p><code>cdk deploy</code></p></li> </ol> <p>🚀 Congratulations, now you have deployed your lambda into a PRIVATE_WITH_EGRESS subnet and they will be able to do network call outside of their VPC.</p> <p>Lambdas are a powerful tool, but this should ideally be a single switch somewhere. A senior developer with no prior AWS experience shouldn't need to spend more than 15 minutes to do this. Some reportedly spent several hours, me included.<br> ATM it's a very error prone process with a lot of steps to achieve. Thankfully for going the other way, there are function URLs, which greatly simplify deployment and provisioning of lambdas when you need to call them from outside of the VPC. If anyone from AWS team reads this, please consider doing the similar simplification for the other way too.</p> How to send a file in form of a Buffer as a FormData in node.js Jiri Spac Tue, 10 Jan 2023 20:30:22 +0000 https://dev.to/capaj/how-to-send-a-file-in-form-of-a-buffer-as-a-formdata-in-nodejs-1pe4 https://dev.to/capaj/how-to-send-a-file-in-form-of-a-buffer-as-a-formdata-in-nodejs-1pe4 <p>This is slightly modified excerpt from <a href="proxy.php?url=https://github.com/octet-stream/form-data#readme">https://github.com/octet-stream/form-data#readme</a><br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="k">import</span> <span class="p">{</span><span class="nx">Readable</span><span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">stream</span><span class="dl">"</span> <span class="k">import</span> <span class="p">{</span><span class="nx">FormData</span><span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">formdata-node</span><span class="dl">"</span> <span class="kd">class</span> <span class="nc">BlobFromStream</span> <span class="p">{</span> <span class="err">#</span><span class="nx">stream</span><span class="p">:</span> <span class="nx">Readable</span> <span class="nx">size</span><span class="p">:</span> <span class="kr">number</span> <span class="nf">constructor</span><span class="p">(</span><span class="nx">stream</span><span class="p">:</span> <span class="nx">Readable</span><span class="p">,</span> <span class="nx">size</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">stream</span> <span class="o">=</span> <span class="nx">stream</span> <span class="k">this</span><span class="p">.</span><span class="nx">size</span> <span class="o">=</span> <span class="nx">size</span> <span class="p">}</span> <span class="nf">stream</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">stream</span> <span class="p">}</span> <span class="kd">get</span> <span class="p">[</span><span class="nb">Symbol</span><span class="p">.</span><span class="nx">toStringTag</span><span class="p">]()</span> <span class="p">{</span> <span class="k">return</span> <span class="dl">"</span><span class="s2">Blob</span><span class="dl">"</span> <span class="p">}</span> <span class="p">}</span> <span class="kd">const</span> <span class="nx">content</span> <span class="o">=</span> <span class="nx">Buffer</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="dl">"</span><span class="s2">Stream content</span><span class="dl">"</span><span class="p">)</span> <span class="kd">const</span> <span class="nx">stream</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Readable</span><span class="p">({</span> <span class="nf">read</span><span class="p">()</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">content</span><span class="p">)</span> <span class="k">this</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="kc">null</span><span class="p">)</span> <span class="p">}</span> <span class="p">})</span> <span class="kd">const</span> <span class="nx">form</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FormData</span><span class="p">()</span> <span class="nx">form</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="dl">"</span><span class="s2">stream</span><span class="dl">"</span><span class="p">,</span> <span class="k">new</span> <span class="nc">BlobFromStream</span><span class="p">(</span><span class="nx">stream</span><span class="p">,</span> <span class="nx">content</span><span class="p">.</span><span class="nx">length</span><span class="p">),</span> <span class="dl">"</span><span class="s2">file.txt</span><span class="dl">"</span><span class="p">)</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://httpbin.org/post</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span><span class="na">method</span><span class="p">:</span> <span class="dl">"</span><span class="s2">post</span><span class="dl">"</span><span class="p">,</span> <span class="na">body</span><span class="p">:</span> <span class="nx">form</span><span class="p">})</span> </code></pre> </div> <p>just in case anyone would be struggling with this same as me.</p> dailystruggle node sending a file with curl POST needs a magic at sign Jiri Spac Sat, 31 Dec 2022 00:37:58 +0000 https://dev.to/capaj/sending-a-file-with-curl-post-needs-a-magic-at-sign-2f3p https://dev.to/capaj/sending-a-file-with-curl-post-needs-a-magic-at-sign-2f3p <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>curl -X POST -H "Content-Type: multipart/form-data" -F "file=myAwesomeFile.pdf" </code></pre> </div> <p>does not work. The magic part os prepending at sign in the beginning of file name.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>curl -X POST -H "Content-Type: multipart/form-data" -F "[email protected]" </code></pre> </div> <p>Just in case someone struggles with this same as me.</p> dailystruggle curl cli http Overhead of using REST instead of Graphql with node.js Jiri Spac Fri, 04 Feb 2022 18:51:05 +0000 https://dev.to/capaj/overhead-of-using-rest-instead-of-graphql-with-nodejs-16d4 https://dev.to/capaj/overhead-of-using-rest-instead-of-graphql-with-nodejs-16d4 <p>There are quite a few misconceptions when it comes to graphql. It cannot be cached on http protocol level, it has to return 200 code even when there are errors, it kills fluffy kitten every time you make a mutation.<br> In this short post I would like to address one of them in particular. No not the one about the kittens.</p> <p><iframe class="tweet-embed" id="tweet-1410228246487126017-4" src="proxy.php?url=https://platform.twitter.com/embed/Tweet.html?id=1410228246487126017"> </iframe> // Detect dark theme var iframe = document.getElementById('tweet-1410228246487126017-4'); if (document.body.className.includes('dark-theme')) { iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1410228246487126017&amp;theme=dark" } <br> <iframe class="tweet-embed" id="tweet-1433825642227781632-945" src="proxy.php?url=https://platform.twitter.com/embed/Tweet.html?id=1433825642227781632"> </iframe> // Detect dark theme var iframe = document.getElementById('tweet-1433825642227781632-945'); if (document.body.className.includes('dark-theme')) { iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1433825642227781632&amp;theme=dark" } <br> <iframe class="tweet-embed" id="tweet-1307017642478247937-908" src="proxy.php?url=https://platform.twitter.com/embed/Tweet.html?id=1307017642478247937"> </iframe> // Detect dark theme var iframe = document.getElementById('tweet-1307017642478247937-908'); if (document.body.className.includes('dark-theme')) { iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1307017642478247937&amp;theme=dark" } <br> <iframe class="tweet-embed" id="tweet-1278941977413902336-279" src="proxy.php?url=https://platform.twitter.com/embed/Tweet.html?id=1278941977413902336"> </iframe> // Detect dark theme var iframe = document.getElementById('tweet-1278941977413902336-279'); if (document.body.className.includes('dark-theme')) { iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1278941977413902336&amp;theme=dark" } </p> <p>Let's analyse and dissect what these developers might be talking about.</p> <h2> Overhead on the Client side </h2> <p>As a consumer of graphql APIs you need to send an exact list of fields you want back. If your model is complex and it has lots of fields, this does indeed introduce extra DX complexity writing the API request.<br> This "overhead" only exists without tooling.<br> Every graphql API should come with graphiql/playground bundled. These tools complete the query for you, so there is little to no developer time lost. On the contrary developers can explore the API at the same time as they query.</p> <p>Even when it doesn't come bundled with the API, developer can run a variety of graphql clients on their end. <br> For example graphiql does it <a href="proxy.php?url=https://github.com/graphql/graphiql/blob/b538f6e6f39b954cdf0c264f7adfc3e98666f802/packages/graphiql/src/utility/fillLeafs.ts#L41" rel="noopener noreferrer">here</a>.<br> Hence I came to a conclusion that it's not this overhead that these people are talking about.This is too trivial and solvable with tooling everyone uses by default. This cannot be it. </p> <h2> Overhead on the Server side </h2> <p>This one is harder to crack. Is this actually a myth? There are myriad ways to write a backend in both of these. In order to prove I knew I had to get my hands dirty.</p> <p>I was always a fan of DRY, not DRY nazi, just a fan. So I've been actually always searching for less verbose ways to write APIs, ever since I started years ago.</p> <h3> REST </h3> <p>In the last few years I've gravitated toward stacks centered around <a href="proxy.php?url=https://www.fastify.io/" rel="noopener noreferrer">fastify</a>. So let that be the http server base for our comparison.<br> I don't think there are more succint ways to write a documented REST api other than fastify and fastify-swagger. The way fastify-swagger leverages validation schema to produce the docs is quite awesome. It reminds me of a a poor man's thing which starts with a capital "G" and it's killing kittens.</p> <h3> Graphql </h3> <p>Similarly, for the Graphql camp, I don't know anything less verbose than Typegraphql and mercurius.<br> Mercurius-fastify plugin has the best defaults of any GQL server and performance on whole different level compared to apollo-server.</p> <p>So I've taken a bit of time today and written a single CRUD endpoint for a very simple blog. There is no authentication, no rate limiting, no caching, nothing extra-just a pure bussines logic for CRUD over one DB table.<br> What I do implement in both is validation and documentation as we need to have feature parity for a fair comparison.<br> For ORM I chose prisma, as it offers awesome generators to spare my lazy ass from writing API schemas manually.<br> For the DB we just use sqlite.</p> <p>What was the result? <br> 🥁 <br> See this repo: <a href="proxy.php?url=https://github.com/capaj/node-rest-vs-graphql" rel="noopener noreferrer">https://github.com/capaj/node-rest-vs-graphql</a></p> <p>These endpoints are implemented:</p> <ul> <li>query for blogpost with filtering by from and to dates</li> <li>query to fetch a single blogpost</li> <li>mutation to create a new post, update existing one and delete a blogpost</li> </ul> <p><a href="proxy.php?url=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%2Fti52mcz4fh93hq2mmr28.png" class="article-body-image-wrapper"><img src="proxy.php?url=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%2Fti52mcz4fh93hq2mmr28.png" alt="PR stats" width="347" height="81"></a></p> <p>Just for this very simple API, REST adds 21 extra lines.</p> <p>I would welcome any PR regarding simplification of the REST api, because I may have missed something. Maybe there is some magical way how to have validation and documentation without all that work. Maybe I just haven't learned it yet. I am just a junior dev 4life.</p> <p>I really wish there was an easier way to properly do REST apis in node.js. Personally I'll be sticking to typegraphql and mercurius.<br> Keep in mind that for a more complex api-where you have deeply nested relations of entities, REST becomes even more unwieldy. Graphql is natural for representing data with lots of relations allowing the consumer to nest their queries deep. In leaf resolvers you always have access to a parent when resolving a child-you can often use this to simplify your BE code.<br> These practices of structuring endpoints exist in REST as well, but it's all manual. You actually need to write bunch of middleware to get it to work.</p> <p>One last reason for me to avoid REST is that I am quite bad at remembering to do something when I am not forced to do so by an automated system. If I was writing REST apis, I would most likely need a colleague to remind me to add that optional validation in every odd code review. This is potentially solvable in fastify type definitions. I think it's not likely it will ever get implemented. Fastify is primarily a javascript framework and the type defs are manually added just for consumers.</p> <p>Final note: please if you see anyone on twitter talk about how Graphql has implementation overhead compared to REST, do send them this link: <a href="proxy.php?url=https://github.com/capaj/node-rest-vs-graphql/pull/1" rel="noopener noreferrer">https://github.com/capaj/node-rest-vs-graphql/pull/1</a></p>