<?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: Lucas Geovani Castro Brogni</title>
    <description>The latest articles on DEV Community by Lucas Geovani Castro Brogni (@lucasbrogni1).</description>
    <link>https://dev.to/lucasbrogni1</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F367307%2Faea71164-f5c8-406b-8a95-9f41e70d7182.JPG</url>
      <title>DEV Community: Lucas Geovani Castro Brogni</title>
      <link>https://dev.to/lucasbrogni1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lucasbrogni1"/>
    <language>en</language>
    <item>
      <title>I'm Halfway Through Udacity's AI-Powered Software Engineer Course — Here's What's Already Shifted</title>
      <dc:creator>Lucas Geovani Castro Brogni</dc:creator>
      <pubDate>Fri, 20 Feb 2026 10:25:38 +0000</pubDate>
      <link>https://dev.to/lucasbrogni1/im-halfway-through-udacitys-ai-powered-software-engineer-course-heres-whats-already-shifted-4njd</link>
      <guid>https://dev.to/lucasbrogni1/im-halfway-through-udacitys-ai-powered-software-engineer-course-heres-whats-already-shifted-4njd</guid>
      <description>&lt;p&gt;I've been a software engineer for over 10 years. I've done TDD, I know design patterns, and I've shipped production code in many setups. So when I enrolled in Udacity's AI-Powered Software Engineer Nanodegree, it wasn't because I felt like a beginner.&lt;/p&gt;

&lt;p&gt;I enrolled because I felt like something was changing faster than I could keep up with, and I wanted a structured way to think about it.&lt;/p&gt;

&lt;p&gt;I'm still in the middle of it. But two modules in, one thing has already stuck with me hard enough that I had to write it down.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Thought I Knew About TDD
&lt;/h2&gt;

&lt;p&gt;I've been practicing Test-Driven Development and I thought I understood it. In fact, I did — in a world where I was the one writing the code. But then I added AI to the Loop. Here's what changes when you pair TDD with AI code generation: the speed of code production becomes almost violent.&lt;/p&gt;

&lt;p&gt;You describe what you want. Claude writes a working implementation in seconds. It feels like productivity on steroids.&lt;/p&gt;

&lt;p&gt;And that's exactly where the danger is.&lt;/p&gt;

&lt;p&gt;When AI can generate code faster than you can review it, you lose something critical: the design conversation. Without tests in place first, you end up reviewing implementation details instead of guiding outcomes. You become a code reviewer of decisions you didn't make. The AI is driving. You're in the passenger seat, approving turns.&lt;/p&gt;

&lt;p&gt;I believe TDD fixes this. &lt;/p&gt;

&lt;h3&gt;
  
  
  TDD with AI Isn't About Correctness Anymore. It's About Control.
&lt;/h3&gt;

&lt;p&gt;The traditional argument for TDD is that tests catch bugs and force you to think about the API before you write implementation. That's still true. But with AI, there's a more fundamental reason to write tests first: &lt;strong&gt;Tests are the specification you hand to the AI. They define what you actually want.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you write the test before asking the AI to implement anything, you're not just checking future behavior. You're designing it. &lt;/p&gt;

&lt;p&gt;You're forcing yourself to answer: what should this do, in concrete, runnable terms? The AI then works within that contract. It can generate as fast as it wants, but it's generating toward your intention, not its own interpretation of your vague prompt.&lt;/p&gt;

&lt;p&gt;Without that contract, you get code that works, but may not be the code you needed. And at AI speed, you can accumulate a lot of that before you notice the design has gone sideways.&lt;/p&gt;

&lt;h3&gt;
  
  
  What This Looks Like in Practice
&lt;/h3&gt;

&lt;p&gt;Before the course reframed this for me, my AI workflow looked like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Describe the feature to Claude&lt;/li&gt;
&lt;li&gt;Review and tweak the generated code&lt;/li&gt;
&lt;li&gt;Write tests afterward to lock in the behavior&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It felt efficient. It was actually backwards.&lt;/p&gt;

&lt;p&gt;Now it looks like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write the test that describes the behavior I want&lt;/li&gt;
&lt;li&gt;Let the AI implement against that test&lt;/li&gt;
&lt;li&gt;Refactor together — me on the design, AI on the implementation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The difference isn't just the process. It's about who's driving. In the first flow, AI is making design decisions, and I'm approving them. In the second, I'm making design decisions, and AI is executing them. That distinction matters enormously at scale.&lt;/p&gt;

&lt;p&gt;For me, TDD isn't a safety net for bad code anymore. It's a steering wheel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I'm At
&lt;/h2&gt;

&lt;p&gt;I'm still working through the course. It will still have a module called "Vibe Engineering" that gives instructions on how to do better prompting and engineering oversight of AI output. &lt;/p&gt;

&lt;p&gt;When I finish, I'll write a proper retrospective. But I didn't want to wait until the end to share this shift, because it's already impacting my day-to-day work. &lt;/p&gt;

&lt;p&gt;If you're an engineer who's been "vibe coding" with AI and wondering why things feel slightly out of control, this might be why.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>tdd</category>
    </item>
    <item>
      <title>Testing in Serverless: TDD and Serverless at Scale</title>
      <dc:creator>Lucas Geovani Castro Brogni</dc:creator>
      <pubDate>Sat, 14 Feb 2026 09:35:30 +0000</pubDate>
      <link>https://dev.to/lucasbrogni1/testing-in-serverless-tdd-and-serverless-at-scale-i0</link>
      <guid>https://dev.to/lucasbrogni1/testing-in-serverless-tdd-and-serverless-at-scale-i0</guid>
      <description>&lt;p&gt;If you're building with serverless, you've probably already found yourself asking, "How am I supposed to properly test this thing?"&lt;/p&gt;

&lt;p&gt;You're not alone. &lt;/p&gt;

&lt;p&gt;Testing in serverless architectures is consistently one of the biggest pain points developers report, and for good reason. The event-driven nature of it all means your code doesn't run in a single, predictable place. Finding the right test infrastructure for that isn't just complex — it can feel like chasing a moving target.&lt;/p&gt;

&lt;p&gt;But here's the thing: the problem usually isn't serverless itself. It's that most teams approach testing in serverless the same way they approached it in monoliths or traditional microservices — and then wonder why it doesn't quite fit.&lt;/p&gt;

&lt;p&gt;Test-Driven Development changes that equation. Not because it's a silver bullet, but because it forces you to think about how your code behaves before you think about where it runs. And in serverless, that mindset shift makes all the difference.&lt;/p&gt;

&lt;p&gt;In this article, we'll walk through why testing serverless is genuinely hard, what a serverless-native testing strategy looks like, and how TDD can give you back the confidence to ship fast — even at scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Serverless Testing Paradox
&lt;/h3&gt;

&lt;p&gt;Think about why you chose serverless in the first place. No servers to manage, automatic scaling, pay-per-use pricing — and most importantly, the promise that you can stop worrying about infrastructure and focus entirely on your business logic. It's supposed to make things simpler.&lt;/p&gt;

&lt;p&gt;So it feels almost cruel that testing ends up being one of the hardest parts.&lt;/p&gt;

&lt;p&gt;Here is the paradox: What makes serverless powerful is exactly what makes it tricky to test. In serverless, your application is stateless and ephemeral - it spins up, does its job, and disappears. That's great for scaling. But when you think about what you want in your test infrastructure, you need the exact opposite. It needs to be persistent, reproducible, and fast. &lt;/p&gt;

&lt;p&gt;Sometimes, you will implement Lambdas that will communicate with many other AWS services. Perhaps your Lambda reads from S3, writes to DynamoDB, and triggers an SQS message. Replicating that chain locally means either mocking each dependency or spinning up cloud emulators with their own quirks and maintenance overhead.&lt;/p&gt;

&lt;p&gt;Then there's the integration testing trap. When an event triggers your function, reproducing that trigger in a test isn't straightforward. In these cases, it's tempting to just deploy and test in the real environment, and this is the trap many serverless teams fall into: they end up with slow, expensive, flaky tests that erode trust in the test suite—and, gradually, developers stop running them.&lt;/p&gt;

&lt;p&gt;Luckily, there's a way out. And it starts with rethinking where you test, not just how.&lt;/p&gt;

&lt;h3&gt;
  
  
  TDD: Your Serverless Development Foundation
&lt;/h3&gt;

&lt;p&gt;If the problem is that serverless testing feels like it's fighting against you, TDD is the practice that puts you back in control.&lt;br&gt;
Test-Driven Development isn't a new idea — but in the serverless world, it carries a different weight. &lt;/p&gt;

&lt;p&gt;When your infrastructure is ephemeral, and your functions are small, focused units of logic, writing the test first isn't just good practice. It's the most natural way to design your code.&lt;/p&gt;

&lt;p&gt;The core loop is simple: write a failing test, write the minimum code to make it pass, then clean it up. Red, green, refactor. You've probably heard it before. &lt;/p&gt;

&lt;p&gt;By following TDD, you're essentially describing what your function should do before you worry about how AWS will run it. &lt;/p&gt;

&lt;p&gt;Focusing on testing your business logic, you gain a superpower: your tests run locally in milliseconds, feedback is instant, cloud costs stay at zero, and flakiness from network calls simply disappears.&lt;/p&gt;

&lt;p&gt;Let's make that concrete. Say you're building a Lambda that processes an order. The temptation is to think about the full solution right away. What DB are you connecting to? What's the trigger of the lambda? TDD asks you to ignore all of that for now. Start with the only thing that actually matters: what should this function do? Have the answer? Great, so let's just write a quick test for it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test('should calculate total for a valid order', async () =&amp;gt; {
  const order = { id: '123', items: [{ price: 20 }, { price: 30 }] };
  const result = await processOrder(order);
  expect(result.success).toBe(true);
  expect(result.total).toBe(50);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After confirming your test is failing, write the minimum code to make that pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function processOrder(order) {
  const total = order.items.reduce((sum, item) =&amp;gt; sum + item.price, 0);
  return { success: true, total };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what just happened. Without ever thinking about AWS, we've built and validated the core of our order processing logic. And better yet, we are sure it works because we just tested it in milliseconds. Everything else that is not part of our core can come later, as a separate concern. And when they do, you'll have a solid, well-tested foundation to build on top of.&lt;/p&gt;

&lt;p&gt;But now you're probably thinking — "ok, that's great for the business logic. But what about the rest? What if the bug is in how I'm parsing the event? What if the item isn't actually being saved to DynamoDB? How do I test that without spinning up real AWS infrastructure?"&lt;/p&gt;

&lt;p&gt;That's exactly the right question. And it's one that trips up a lot of serverless developers — because they treat testing as a single problem, when it's actually a few, all stacked on top of each other. Each layer of your Serverless architecture has different testing needs, tools, and a different cost-to-confidence trade-off.&lt;/p&gt;

&lt;p&gt;That's what the Testing Pyramid is for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Testing Pyramid for Serverless
&lt;/h2&gt;

&lt;p&gt;The Testing Pyramid isn't a new concept, but in serverless, it takes on a very specific meaning. The idea is simple: different layers of your application need different kinds of tests, and the higher you go — the closer to real AWS infrastructure — the slower, more expensive, and more brittle your tests become. So you want most of your tests at the bottom, and only a few at the top.&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%2Fsadoz2qcpkk28noj0f7i.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%2Fsadoz2qcpkk28noj0f7i.png" alt="Test Pyramid in Serverless Environments" width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Unit Tests — the foundation
&lt;/h3&gt;

&lt;p&gt;This is where we already are with processOrder. Pure business logic, no AWS, runs in milliseconds. These tests should make up the vast majority of your suite — they're cheap to write, cheap to run, and give you immediate feedback. Every business rule, every edge case, every validation — if it's logic, it belongs here. The goal is to cover as much of your application's behavior as possible before you ever touch a cloud service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integration Tests — the middle layer
&lt;/h3&gt;

&lt;p&gt;This is where the handler comes in — and where most serverless developers feel lost. You need to verify that your Lambda correctly parses the event, calls your business logic, and interacts with AWS services in the right way. But you don't want to do that against real AWS infrastructure on every test run.&lt;/p&gt;

&lt;p&gt;The answer is fakes. Not mocks — fakes. A mock just verifies that a method was called. A fake is a lightweight, working implementation that behaves like the real thing. Instead of hitting a real DynamoDB table, you inject a fake repository that stores orders in memory and responds exactly like DynamoDB would. &lt;/p&gt;

&lt;p&gt;Your handler doesn't know the difference, and neither does your test. You get real confidence in the wiring without real infrastructure costs.&lt;/p&gt;

&lt;p&gt;This is also where contract testing becomes valuable. In serverless, your functions rarely live in isolation — one Lambda triggers another, events flow between services, and schemas need to stay in sync. &lt;/p&gt;

&lt;p&gt;Contract testing lets you verify that the event your order Lambda produces is exactly what the downstream fulfillment Lambda expects, without deploying either of them. If the contract breaks, you know before it reaches production.&lt;/p&gt;

&lt;h3&gt;
  
  
  End-to-End Tests — the tip
&lt;/h3&gt;

&lt;p&gt;These are your full-stack smoke tests — real AWS, real events, real database. They're slow, they cost money, and they can fail for reasons completely outside your code. That's why you want very few of them, covering only your most critical user journeys. Think of them not as a safety net, but as a final sanity check before you ship. For our order processing example, this might be a single test that fires a real API Gateway request and verifies the order lands in DynamoDB.&lt;/p&gt;

&lt;p&gt;The key insight is that by the time you reach this layer, you should already be confident in your logic and your wiring. E2E tests aren't there to find bugs — they're there to confirm that everything is connected correctly in a real environment. If you're relying on them to catch business logic errors, your pyramid is upside down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serverless at Scale: Where TDD Proves Its Worth
&lt;/h2&gt;

&lt;p&gt;Now that we already looked at how to test serverless applications, you might still wonder if this would make sense to apply.&lt;br&gt;
For that, you need to always check if the ROI is worth it. For doing it, I always think that it's worth looking at what some of the big names of our area have to say about it.&lt;/p&gt;

&lt;p&gt;Kent Beck, in one of its books have said the following sentence:&lt;br&gt;
"Most defects end up costing more than they would have cost to prevent them. Defects are expensive when they occur, both the direct costs of fixing the defects and the indirect costs because of damaged relationships, lost business, and lost development time." — Kent Beck, Extreme Programming Explained&lt;/p&gt;

&lt;p&gt;A study by the IBM System Sciences Institute found that a bug fixed during the design phase is 100 times cheaper to resolve than the same bug found in production.&lt;/p&gt;

&lt;p&gt;These numbers aren't abstract. In serverless, they hit harder than anywhere else.&lt;/p&gt;

&lt;p&gt;In a traditional application, a bug in production affects the instances running at that moment. In serverless, that same bug gets executed once per invocation. At a hundred requests a day, that's manageable. At a million, it's a catastrophe playing out in parallel across your entire infrastructure before anyone has had a chance to notice.&lt;/p&gt;

&lt;p&gt;This is where the compounding effect of TDD becomes undeniable. Every test you write early doesn't just prevent one bug — it prevents that bug from multiplying across every invocation, every downstream event it would have triggered, every user it would have affected. &lt;/p&gt;

&lt;p&gt;The ROI of a single well-written test isn't fixed. It grows proportionally with your scale.&lt;/p&gt;

&lt;p&gt;And it works the other way around too. As your application grows, so does its complexity. Every new function, means you're having more event sources, more service dependencies. Without a strong test foundation, every new feature becomes harder to add safely, because you never have full confidence that it won't break anything. With TDD, each new piece of logic arrives already understood, verified, and documented by its tests. The codebase stays navigable even as it scales.&lt;/p&gt;

&lt;p&gt;I've seen this play out firsthand. At a company I worked with, a serverless service with no strong testing foundation had reached a point where a single project would take an engineer 3 weeks to complete — and then 6 weeks in QA and bug fixing. Double the development time, just to stabilise what had already been built. &lt;br&gt;
As the team gradually increased test coverage and adopted a more disciplined approach, time to market improved, and something less obvious happened too: the codebase became safe to improve. Engineers could refactor, optimise, and think about quality as they finally had the confidence that they weren't going to break something in the process.&lt;/p&gt;

&lt;p&gt;That's the real competitive advantage. Not just fewer bugs — but the ability to keep moving fast when most teams at your scale have already started slowing down.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Competitive Advantage
&lt;/h2&gt;

&lt;p&gt;Serverless gives you the power to scale without thinking about infrastructure. TDD gives you the confidence to scale without thinking about what might break. Together, they're not just a technical choice — they're a competitive one.&lt;/p&gt;

&lt;p&gt;Teams that invest in testing early ship faster in the long run. They spend less time in QA, less time firefighting production incidents, and more time doing the work that actually moves the business forward. &lt;/p&gt;

&lt;p&gt;You don't need to overhaul your entire codebase to get there. You don't need a company-wide initiative or a three-month refactoring project. You just need one function. Pick the Lambda that has caused the most pain recently. Write a test for it. Make it pass. Then write another. Once you start feeling the difference, it's very hard to go back.&lt;/p&gt;

&lt;p&gt;The serverless ecosystem is only going to grow. The teams that will thrive in it aren't necessarily the ones with the most engineers or the biggest budgets. They're the ones that have built the discipline to move fast without breaking things — and TDD is how you build that discipline, one function at a time.&lt;/p&gt;

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

&lt;p&gt;Beck, K., &amp;amp; Andres, C. (2004). Extreme Programming Explained: Embrace Change (2nd ed.). Addison-Wesley Professional. ISBN: 978-0-321-27865-4&lt;/p&gt;

&lt;p&gt;IBM Systems Sciences Institute. Relative Cost of Fixing Defects by Phase. Referenced via ResearchGate: &lt;a href="https://www.researchgate.net/figure/BM-System-Science-Institute-" rel="noopener noreferrer"&gt;https://www.researchgate.net/figure/BM-System-Science-Institute-&lt;/a&gt;&lt;br&gt;
Relative-Cost-of-Fixing-Defects_fig1_255965523&lt;/p&gt;

&lt;p&gt;Beck, K. (2002). Test-Driven Development: By Example. Addison-Wesley Professional. — Kent Beck's dedicated book on TDD, a natural next read after this article.&lt;/p&gt;

&lt;p&gt;Fowler, M. Test Pyramid — martinfowler.com/bliki/TestPyramid.html — The canonical reference for the testing pyramid concept applied to modern software architecture.&lt;/p&gt;

&lt;p&gt;💬 Enjoyed the article? Let's keep the conversation going — find me on X at &lt;a href="https://x.com/brognilogs" rel="noopener noreferrer"&gt;@brognilogs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tdd</category>
      <category>serverless</category>
      <category>aws</category>
      <category>testing</category>
    </item>
    <item>
      <title>Building an MCP Server on AWS Lambda: Complete Serverless Architecture Guide</title>
      <dc:creator>Lucas Geovani Castro Brogni</dc:creator>
      <pubDate>Mon, 02 Feb 2026 16:53:36 +0000</pubDate>
      <link>https://dev.to/lucasbrogni1/building-an-mcp-server-on-aws-lambda-complete-serverless-architecture-guide-56jm</link>
      <guid>https://dev.to/lucasbrogni1/building-an-mcp-server-on-aws-lambda-complete-serverless-architecture-guide-56jm</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Over the past few months, I've been working on something that fundamentally changed how I think about building APIs: implementing the &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt; on AWS Lambda. This isn't just another serverless project—it's about rethinking how we expose services to AI assistants.&lt;/p&gt;

&lt;p&gt;In this post, I'll share the complete architecture, the challenges I faced, and the lessons learned while building a production-ready MCP server that enables AI assistants like Claude to interact with APIs in a better way—allowing humans to interact with the assistant without having to worry about what's behind it. Whether you're considering building your own MCP server or just curious about serverless architectures for AI-native interfaces, this guide is for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Build an MCP Server?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem with Traditional APIs
&lt;/h3&gt;

&lt;p&gt;Traditional REST APIs are designed for &lt;strong&gt;human developers&lt;/strong&gt;. They have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complex authentication flows&lt;/li&gt;
&lt;li&gt;Verbose request/response structures&lt;/li&gt;
&lt;li&gt;Documentation that humans read and interpret&lt;/li&gt;
&lt;li&gt;SDKs that wrap HTTP calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But AI assistants don't need SDKs. They don't read documentation the same way we do. They need &lt;strong&gt;structured, predictable interfaces&lt;/strong&gt; with clear schemas and consistent error handling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enter the Model Context Protocol
&lt;/h3&gt;

&lt;p&gt;MCP is an open protocol created by Anthropic that standardizes how AI assistants communicate with external tools. Think of it as &lt;strong&gt;a universal adapter&lt;/strong&gt; between AI models and your services.&lt;/p&gt;

&lt;p&gt;Instead of teaching an AI to make HTTP calls and parse responses, you define &lt;strong&gt;tools&lt;/strong&gt; with clear input schemas and let the AI invoke them directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Traditional API approach&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Make a POST to /tasks with this JSON body,
 set the Authorization header, handle pagination...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// MCP approach&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Call the create_task tool with title: &lt;/span&gt;&lt;span class="dl"&gt;"'&lt;/span&gt;&lt;span class="s1"&gt;Review PR #123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Motivation
&lt;/h3&gt;

&lt;p&gt;We had a product with a REST API, and users were increasingly working through AI assistants. The question wasn't &lt;em&gt;if&lt;/em&gt; we should support MCP, but &lt;em&gt;how&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The business case wrote itself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Meet users where they are&lt;/strong&gt;: AI assistants are becoming the default interface for many workflows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduce integration friction&lt;/strong&gt;: Instead of users learning our API, let the AI handle it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable new use cases&lt;/strong&gt;: Natural language interactions open doors that traditional APIs can't&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The technical benefits followed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Standardize the interface&lt;/strong&gt;: One protocol, multiple AI clients&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improve AI comprehension&lt;/strong&gt;: Structured tools instead of raw HTTP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable natural language workflows&lt;/strong&gt;: "Do X with Y" just works—the AI handles the complexity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Future-proof the integration&lt;/strong&gt;: As MCP evolves, so does the server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hide implementation details&lt;/strong&gt;: Users don't need to know about endpoints, authentication, or payloads&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  A Concrete Example: Task Management API
&lt;/h2&gt;

&lt;p&gt;To make this guide practical, I'll use a &lt;strong&gt;task management API&lt;/strong&gt; as our running example throughout this post. Think of it as a simplified Trello or Asana API with endpoints for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating, updating, and deleting tasks&lt;/li&gt;
&lt;li&gt;Organizing tasks into projects&lt;/li&gt;
&lt;li&gt;Assigning tasks to team members&lt;/li&gt;
&lt;li&gt;Tracking task status and due dates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;This is just one example.&lt;/strong&gt; The same architecture and patterns apply equally well to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;E-commerce APIs (products, orders, inventory)&lt;/li&gt;
&lt;li&gt;Content management systems (articles, media, categories)&lt;/li&gt;
&lt;li&gt;Customer relationship management (contacts, deals, activities)&lt;/li&gt;
&lt;li&gt;Analytics platforms (reports, dashboards, metrics)&lt;/li&gt;
&lt;li&gt;Any REST API you want to expose to AI assistants&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The beauty of MCP is that once you understand the pattern, you can apply it to any domain. The AI assistant doesn't care whether it's managing tasks, processing orders, or analyzing data—it just needs well-defined tools with clear schemas.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Serverless?
&lt;/h2&gt;

&lt;p&gt;When designing the architecture, I had several options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Traditional server (EC2, ECS)&lt;/li&gt;
&lt;li&gt;Containerized service (Fargate, Kubernetes)&lt;/li&gt;
&lt;li&gt;Serverless functions (Lambda)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I chose &lt;strong&gt;AWS Lambda&lt;/strong&gt; for several compelling reasons:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Per-Request Billing Matches Usage Patterns
&lt;/h3&gt;

&lt;p&gt;AI assistants don't make constant requests. Usage is bursty—sometimes hundreds of tool calls in a session, then nothing for hours. With Lambda:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pay only for actual invocations&lt;/li&gt;
&lt;li&gt;No idle compute costs&lt;/li&gt;
&lt;li&gt;Automatic scaling from 0 to thousands of concurrent requests&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Reduced Operational Complexity
&lt;/h3&gt;

&lt;p&gt;Running an MCP server isn't my core business. I don't want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manage server patches&lt;/li&gt;
&lt;li&gt;Configure auto-scaling groups&lt;/li&gt;
&lt;li&gt;Handle load balancer health checks&lt;/li&gt;
&lt;li&gt;Monitor container orchestration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lambda handles all of this automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Natural Fit for Request-Response Workloads
&lt;/h3&gt;

&lt;p&gt;MCP is fundamentally request-response:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AI sends a JSON-RPC request&lt;/li&gt;
&lt;li&gt;Server processes it&lt;/li&gt;
&lt;li&gt;Server returns a JSON-RPC response&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This maps perfectly to Lambda's execution model. No websockets, no long-running connections—just clean, stateless request handling.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Built-in Integration with API Gateway
&lt;/h3&gt;

&lt;p&gt;API Gateway provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom domain management&lt;/li&gt;
&lt;li&gt;Request validation&lt;/li&gt;
&lt;li&gt;Rate limiting&lt;/li&gt;
&lt;li&gt;Authentication via Lambda authorizers&lt;/li&gt;
&lt;li&gt;CloudWatch logging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This entire infrastructure is defined in a single &lt;code&gt;serverless.yml&lt;/code&gt; file.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;Here's the high-level architecture I implemented:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                                    ┌──────────────────┐
                                    │   AI Assistant   │
                                    │  (Claude, etc.)  │
                                    └────────┬─────────┘
                                             │
                                    HTTP POST /v1/mcp
                                             │
                                             ▼
┌────────────────────────────────────────────────────────────────────┐
│                         API Gateway                                 │
│  ┌─────────────────┐    ┌─────────────────┐    ┌────────────────┐ │
│  │ Custom Domain   │    │  Rate Limiting  │    │ Request Routing│ │
│  │ mcp.example.com │    │                 │    │                │ │
│  └─────────────────┘    └─────────────────┘    └────────────────┘ │
└────────────────────────────────┬───────────────────────────────────┘
                                 │
                        ┌────────▼────────┐
                        │ Lambda Authorizer│
                        │  (API Key Auth)  │
                        └────────┬─────────┘
                                 │
                                 ▼
┌────────────────────────────────────────────────────────────────────┐
│                      MCP Handler Lambda                             │
│  ┌──────────────┐   ┌──────────────┐   ┌────────────────────────┐ │
│  │ JSON-RPC     │   │ MCP Server   │   │ Tools (example)        │ │
│  │ Parser       │──▶│ Router       │──▶│ ├── create_task        │ │
│  │              │   │              │   │ ├── list_tasks         │ │
│  └──────────────┘   └──────────────┘   │ ├── update_task        │ │
│                                        │ ├── assign_task        │ │
│                                        │ └── get_project_stats  │ │
│                                        └────────────┬───────────┘ │
└─────────────────────────────────────────────────────┼──────────────┘
                                                      │
                                                      ▼
                                          ┌───────────────────┐
                                          │  Your Existing    │
                                          │    REST API       │
                                          └───────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Components
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Multiple Lambda Handlers
&lt;/h4&gt;

&lt;p&gt;Instead of one monolithic handler, I split responsibilities:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Handler&lt;/th&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Auth&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP Handler&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /v1/mcp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Main protocol handler&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Health Check&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /v1/health&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Monitoring/uptime&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP Config&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /.well-known/mcp-config&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Schema discovery&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Server Card&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /.well-known/mcp/server-card.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Server metadata&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This separation allows independent scaling and simpler debugging.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. External Lambda Authorizer
&lt;/h4&gt;

&lt;p&gt;Instead of validating API keys in every request, I use a shared Lambda authorizer:&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;functions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mcp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dist/index.handler&lt;/span&gt;
    &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/v1/mcp&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;post&lt;/span&gt;
          &lt;span class="na"&gt;authorizer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;request&lt;/span&gt;
            &lt;span class="na"&gt;arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${env:AUTHORIZER_LAMBDA_ARN}&lt;/span&gt;
            &lt;span class="na"&gt;resultTtlInSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
            &lt;span class="na"&gt;identitySource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;method.request.header.apikey&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This authorizer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validates API keys against my authentication service&lt;/li&gt;
&lt;li&gt;Returns IAM policies for API Gateway&lt;/li&gt;
&lt;li&gt;Can be shared across multiple services&lt;/li&gt;
&lt;li&gt;Caches results (I disabled caching for immediate key revocation)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3. Tool Architecture
&lt;/h4&gt;

&lt;p&gt;Each MCP tool follows a consistent pattern. Here's an example using our task management domain (remember, this same pattern works for any API):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. Define the input schema with Zod&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CreateTaskInputSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;project_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;assignee_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;due_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;low&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;medium&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;high&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Define the tool metadata&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createTaskToolDefinition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;McpTool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;create_task&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Create a new task in the task management system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;zodToJsonSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CreateTaskInputSchema&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Create the handler factory (dependency injection)&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createTaskHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Dependencies&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ToolHandler&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;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&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="nx"&gt;ToolResult&lt;/span&gt;&lt;span class="o"&gt;&amp;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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CreateTaskInputSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;formatToolError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;VALIDATION_ERROR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parsed&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="nx"&gt;message&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="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="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;formatToolError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&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="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&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="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;formatToolResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Task created: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (ID: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;This pattern provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Runtime validation&lt;/strong&gt; via Zod&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type safety&lt;/strong&gt; throughout&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testability&lt;/strong&gt; via dependency injection&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistent error handling&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Challenges
&lt;/h2&gt;

&lt;p&gt;Building this wasn't without its difficulties. Here are the main challenges I faced:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Cold Start Latency
&lt;/h3&gt;

&lt;p&gt;Lambda cold starts are the elephant in the room. For AI interactions, latency matters—users expect quick responses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js Lambda cold starts: 300-500ms typically&lt;/li&gt;
&lt;li&gt;With dependencies (Zod, axios, etc.): 500-800ms&lt;/li&gt;
&lt;li&gt;In a VPC: add another 200-500ms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;My Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Warmup Plugin&lt;/strong&gt;: Pre-warms Lambdas every 5 minutes in production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal Dependencies&lt;/strong&gt;: Carefully audited imports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VPC Optimization&lt;/strong&gt;: Used VPC endpoints where possible&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lazy Loading&lt;/strong&gt;: Tools only load schemas when called
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# serverless.yml&lt;/span&gt;
&lt;span class="na"&gt;warmup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;prod&lt;/span&gt;
    &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rate(5 minutes)&lt;/span&gt;
    &lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;prewarm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Error Handling Across Layers
&lt;/h3&gt;

&lt;p&gt;With MCP, errors can occur at multiple levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSON-RPC parsing errors&lt;/li&gt;
&lt;li&gt;Authentication failures&lt;/li&gt;
&lt;li&gt;Tool validation errors&lt;/li&gt;
&lt;li&gt;External API errors&lt;/li&gt;
&lt;li&gt;Rate limiting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Challenge:&lt;/strong&gt; Each level has different error formats and expectations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My Solution:&lt;/strong&gt; A two-tier error handling strategy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Protocol-level errors → JSON-RPC error format&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jsonrpc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;32600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid Request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Tool execution errors → Tool response format (better for AI)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jsonrpc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;result&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error: Rate limit exceeded. Try again in 60 seconds.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key insight: &lt;strong&gt;Tool errors should be returned as tool responses, not protocol errors&lt;/strong&gt;. This way, the AI can understand and act on them appropriately.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Authentication Context Propagation
&lt;/h3&gt;

&lt;p&gt;The Lambda authorizer validates API keys, but the MCP handler needs that key to make downstream API calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Challenge:&lt;/strong&gt; API Gateway doesn't automatically pass the API key to the Lambda function.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My Solution:&lt;/strong&gt; Configure the authorizer to return the API key in the authorization context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Authorizer returns&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;principalId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;validatedApiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Handler extracts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authorizer&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;
  &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nf"&gt;extractApiKeyFromHeaders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Testing Without Mocks
&lt;/h3&gt;

&lt;p&gt;I follow a strict "no mocks" policy—no &lt;code&gt;jest.fn()&lt;/code&gt; or &lt;code&gt;jest.mock()&lt;/code&gt;. This forced me to build proper &lt;strong&gt;fakes&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/__fakes__/api-client.fake.ts&lt;/span&gt;
&lt;span class="c1"&gt;// Example using task management domain - adapt to your domain&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createFakeApiClient&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;ApiClient&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;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&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="nx"&gt;Task&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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;tasks&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;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;generateId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&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="nx"&gt;input&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;task&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;success&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;task&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&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;task&lt;/span&gt;
          &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;success&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;task&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;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NOT_FOUND&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tests real behavior, not implementation details&lt;/li&gt;
&lt;li&gt;Catches bugs that mocks would hide&lt;/li&gt;
&lt;li&gt;Makes tests more readable&lt;/li&gt;
&lt;li&gt;Forces better interface design&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Choosing an Infrastructure as Code Tool
&lt;/h3&gt;

&lt;p&gt;For deploying Lambda functions, you have several IaC options:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Serverless Framework&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Simple YAML config, great plugins ecosystem, fast to get started&lt;/td&gt;
&lt;td&gt;Less control over fine-grained AWS resources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS SAM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Native AWS support, good for pure Lambda workloads&lt;/td&gt;
&lt;td&gt;Smaller plugin ecosystem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS CDK&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full programming language, type safety, maximum flexibility&lt;/td&gt;
&lt;td&gt;Steeper learning curve, more verbose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Terraform&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cloud-agnostic, mature ecosystem&lt;/td&gt;
&lt;td&gt;More complex for Lambda-specific patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We went with &lt;strong&gt;Serverless Framework&lt;/strong&gt; for its simplicity and plugin ecosystem. Tools like &lt;code&gt;serverless-offline&lt;/code&gt; for local development and &lt;code&gt;serverless-domain-manager&lt;/code&gt; for custom domains made the developer experience smooth. The entire infrastructure fits in a single &lt;code&gt;serverless.yml&lt;/code&gt; file that's easy to read and modify.&lt;/p&gt;

&lt;p&gt;That said, if you're already invested in CDK or Terraform, there's no strong reason to switch—the architecture patterns in this post apply regardless of your IaC choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Benefits
&lt;/h2&gt;

&lt;p&gt;After running this in production, here are the tangible benefits:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. True Pay-Per-Use Economics
&lt;/h3&gt;

&lt;p&gt;Lambda pricing is based on two factors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Requests&lt;/strong&gt;: Number of invocations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compute time&lt;/strong&gt;: GB-seconds (memory allocated × execution duration)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key insight: &lt;strong&gt;keep your functions fast and right-sized&lt;/strong&gt;. A 5ms function costs 100x less than a 500ms function doing the same work. MCP tools that just proxy to an existing API are naturally fast.&lt;/p&gt;

&lt;p&gt;The free tier (1M requests + 400K GB-seconds monthly) covers most development and low-traffic production scenarios. Compare that to a traditional server sitting idle at $50+/month waiting for traffic.&lt;/p&gt;

&lt;p&gt;Use the &lt;a href="https://aws.amazon.com/lambda/pricing/" rel="noopener noreferrer"&gt;AWS Lambda Pricing Calculator&lt;/a&gt; to estimate costs for your specific configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Operational Simplicity
&lt;/h3&gt;

&lt;p&gt;Things I don't manage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server provisioning&lt;/li&gt;
&lt;li&gt;OS patching&lt;/li&gt;
&lt;li&gt;Load balancer configuration&lt;/li&gt;
&lt;li&gt;Auto-scaling policies&lt;/li&gt;
&lt;li&gt;Health check infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Things I do manage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Business logic&lt;/li&gt;
&lt;li&gt;Tool definitions&lt;/li&gt;
&lt;li&gt;Error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Deployment Velocity
&lt;/h3&gt;

&lt;p&gt;Deploying a new tool:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write the tool handler&lt;/li&gt;
&lt;li&gt;Register it in the tool index&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm run deploy:dev&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Test&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm run deploy:prod&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Total time: ~5 minutes for the deployment itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Observability for Free (Almost)
&lt;/h3&gt;

&lt;p&gt;Lambda provides built-in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CloudWatch Logs (automatic)&lt;/li&gt;
&lt;li&gt;CloudWatch Metrics (invocations, errors, duration)&lt;/li&gt;
&lt;li&gt;X-Ray tracing (optional)&lt;/li&gt;
&lt;li&gt;API Gateway access logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I added OpenTelemetry on top for distributed tracing, but the baseline is already useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Natural Disaster Recovery
&lt;/h3&gt;

&lt;p&gt;Lambda functions are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deployed across multiple availability zones&lt;/li&gt;
&lt;li&gt;Automatically retried on infrastructure failures&lt;/li&gt;
&lt;li&gt;Stateless (easy to reason about)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No manual DR planning needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cons (Let's Be Honest)
&lt;/h2&gt;

&lt;p&gt;No architecture is perfect. Here's what I'd do differently or what remains challenging:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Cold Starts Are Real
&lt;/h3&gt;

&lt;p&gt;Despite warmup plugins, cold starts happen. When traffic spikes after idle periods, the first few requests are slow. For AI interactions where users expect sub-second responses, this can be jarring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mitigation:&lt;/strong&gt; Provisioned Concurrency (but adds cost and complexity).&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Debugging is Harder
&lt;/h3&gt;

&lt;p&gt;When something fails in Lambda:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can't SSH in&lt;/li&gt;
&lt;li&gt;Logs are the only visibility&lt;/li&gt;
&lt;li&gt;Reproducing issues requires understanding the exact event&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Mitigation:&lt;/strong&gt; Extensive structured logging and local testing with serverless-offline.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Vendor Lock-In
&lt;/h3&gt;

&lt;p&gt;This architecture is deeply tied to AWS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda function handlers&lt;/li&gt;
&lt;li&gt;API Gateway events&lt;/li&gt;
&lt;li&gt;CloudWatch logging&lt;/li&gt;
&lt;li&gt;IAM for authorization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Moving to GCP or Azure would require significant rewrites.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mitigation:&lt;/strong&gt; Keep business logic in pure functions, isolate AWS-specific code.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Costs Can Spike from Unexpected Places
&lt;/h3&gt;

&lt;p&gt;Lambda is often the cheapest part of your bill. Watch out for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway&lt;/strong&gt;: Charges per request, can exceed Lambda costs at scale&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch Logs&lt;/strong&gt;: Verbose logging adds up fast—log wisely&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry loops&lt;/strong&gt;: A bug causing infinite retries can burn budget in hours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long-running functions&lt;/strong&gt;: Cost scales linearly with duration—optimize slow tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Mitigation:&lt;/strong&gt; Set billing alerts from day one, implement rate limiting, and keep functions fast. Review your AWS Cost Explorer monthly to catch surprises early.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Complex Local Development
&lt;/h3&gt;

&lt;p&gt;Testing locally with serverless-offline isn't the same as production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authorizers don't actually invoke&lt;/li&gt;
&lt;li&gt;Cold starts don't happen&lt;/li&gt;
&lt;li&gt;VPC networking differs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Mitigation:&lt;/strong&gt; Deploy to a dev stage frequently, automate integration tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tips for Building Your Own MCP Server on Lambda
&lt;/h2&gt;

&lt;p&gt;Based on my experience, here are actionable tips. I'll continue using the task management example, but remember: &lt;strong&gt;replace "task" with your domain concept&lt;/strong&gt; (product, order, article, contact, etc.) and the patterns remain exactly the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Start with the Tool Schema
&lt;/h3&gt;

&lt;p&gt;Before writing any code, define your tool schemas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Define WHAT the tool does before HOW&lt;/span&gt;
&lt;span class="c1"&gt;// Example: task management (adapt to your domain)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toolDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;create_task&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Create a new task in the project.
    Returns the task ID and creation timestamp.
    Requires a title. Optionally accepts project_id, assignee, and due_date.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The title of the task&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;project_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The project to add this task to&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="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The description matters—it's what the AI uses to understand when to call your tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Use Zod for Runtime Validation
&lt;/h3&gt;

&lt;p&gt;TypeScript types vanish at runtime. Use Zod for actual validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example: validating task input (adapt schema to your domain)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CreateTaskSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Title is required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;due_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;low&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;medium&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;high&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;medium&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="c1"&gt;// In handler&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CreateTaskSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;formatToolError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;VALIDATION_ERROR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&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="nx"&gt;message&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;This catches bad input before it causes problems downstream.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Return AI-Friendly Error Messages
&lt;/h3&gt;

&lt;p&gt;Don't return technical errors to the AI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bad&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;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ECONNREFUSED 127.0.0.1:5432&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Good&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;formatToolError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SERVICE_UNAVAILABLE&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;Unable to create task. The service is temporarily unavailable. Please try again.&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;The AI will relay this to the user—make it understandable.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Use Factory Functions for Dependency Injection
&lt;/h3&gt;

&lt;p&gt;Avoid global state. Use factories:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Don't do this&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiClient&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;ApiClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleCreateTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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;apiClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Hard to test!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Do this&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createTaskHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;apiClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApiClient&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleCreateTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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;deps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Testable!&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;h3&gt;
  
  
  5. Implement Graceful Degradation
&lt;/h3&gt;

&lt;p&gt;When external services fail, handle it gracefully:&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleToolCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tool&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;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&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;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tool&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="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;formatToolError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UNKNOWN_TOOL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Tool "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" not found`&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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;result&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="c1"&gt;// Log the real error&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tool execution failed&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="nx"&gt;tool&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="c1"&gt;// Return a user-friendly message&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;formatToolError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INTERNAL_ERROR&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;Something went wrong. Please try again.&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Add Structured Logging from Day One
&lt;/h3&gt;

&lt;p&gt;You'll need it for debugging:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createLogger&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mcp-server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LOG_LEVEL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;info&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="c1"&gt;// In handler&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;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tool invoked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;toolName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sanitizeInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;awsRequestId&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;h3&gt;
  
  
  7. Separate Protocol Handling from Business Logic
&lt;/h3&gt;

&lt;p&gt;Keep the MCP protocol handling separate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── index.ts          # Lambda handler (thin)
├── mcp/
│   ├── protocol.ts   # JSON-RPC parsing
│   └── server.ts     # MCP routing
└── tools/
    ├── tasks/                      # Example: task management
    │   ├── create-task.tool.ts
    │   ├── list-tasks.tool.ts
    │   └── update-task.tool.ts
    └── projects/                   # Group tools by domain concept
        └── get-project-stats.tool.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes testing easier and keeps concerns separated.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Test with Real AI Assistants Early
&lt;/h3&gt;

&lt;p&gt;Don't wait until the end to test with actual AI:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deploy to dev&lt;/li&gt;
&lt;li&gt;Configure your AI assistant to use it&lt;/li&gt;
&lt;li&gt;Try natural language requests&lt;/li&gt;
&lt;li&gt;Iterate on tool descriptions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You'll discover that tool descriptions matter more than you think.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Implement Rate Limiting at Multiple Levels
&lt;/h3&gt;

&lt;p&gt;Protect yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API Gateway: Request throttling&lt;/li&gt;
&lt;li&gt;Authorizer: Per-key rate limits&lt;/li&gt;
&lt;li&gt;Tools: Per-operation limits
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# serverless.yml&lt;/span&gt;
&lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;apiGateway&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;throttle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;burstLimit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
      &lt;span class="na"&gt;rateLimit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  10. Monitor Cold Starts
&lt;/h3&gt;

&lt;p&gt;Track them explicitly:&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isColdStart&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;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isColdStart&lt;/span&gt;&lt;span class="p"&gt;)&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;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cold start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;awsRequestId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;isColdStart&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="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use this data to tune warmup settings.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Metrics After Running in Production
&lt;/h2&gt;

&lt;p&gt;Here's what I'm seeing after some time:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Average latency (warm)&lt;/td&gt;
&lt;td&gt;~100ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Average latency (cold)&lt;/td&gt;
&lt;td&gt;~600ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cold start rate&lt;/td&gt;
&lt;td&gt;&amp;lt;5% (with warmup)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error rate&lt;/td&gt;
&lt;td&gt;&amp;lt;0.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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

&lt;p&gt;Building an MCP server on AWS Lambda has been a rewarding journey. The combination of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP's standardized protocol&lt;/strong&gt; for AI interactions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda's serverless model&lt;/strong&gt; for operational simplicity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Functional programming principles&lt;/strong&gt; for maintainability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...creates an architecture that's both powerful and practical.&lt;/p&gt;

&lt;p&gt;Is it perfect? No. Cold starts are annoying, debugging requires discipline, and you're locked into AWS. But for the use case of exposing your existing APIs to AI assistants—letting users interact naturally without worrying about the technical details—the benefits far outweigh the drawbacks.&lt;/p&gt;

&lt;p&gt;The real magic happens when you see someone ask an AI assistant to "do something" and watch it seamlessly translate that into the right API calls. No documentation lookup, no curl commands, no debugging JSON payloads. Just natural conversation that gets things done.&lt;/p&gt;

&lt;p&gt;If you're considering building your own MCP server, I hope this guide helps you avoid some of my early mistakes and gives you a solid foundation to build on.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;AWS Lambda Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.serverless.com/" rel="noopener noreferrer"&gt;Serverless Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod - TypeScript-first Schema Validation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Have questions or want to share your own MCP journey? Find me on X &lt;a href="https://x.com/brognilogs" rel="noopener noreferrer"&gt;@brognilogs&lt;/a&gt; or drop a comment below.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Claude Code Will Be As Good As You Are</title>
      <dc:creator>Lucas Geovani Castro Brogni</dc:creator>
      <pubDate>Fri, 21 Nov 2025 15:04:46 +0000</pubDate>
      <link>https://dev.to/lucasbrogni1/claude-code-will-be-as-good-as-you-are-1f16</link>
      <guid>https://dev.to/lucasbrogni1/claude-code-will-be-as-good-as-you-are-1f16</guid>
      <description>&lt;p&gt;Over the past few months, I've been living in Claude Code. I'm talking about serious, production-level work—building new services, refactoring legacy systems, rebuilding our integration test infrastructure from scratch. If I'm being honest, over 80% of the code I've shipped in that time was generated by AI.&lt;/p&gt;

&lt;p&gt;But here's what nobody tells you: AI-generated code isn't replacing my engineering skills. It's amplifying them. And more importantly, it's exposing every gap in my own thinking.&lt;/p&gt;

&lt;p&gt;The code Claude produces will only be as good as the design you drive it toward. After months of experimentation—vibe coding, setting draconian rules, trying every configuration imaginable—I've learned that great AI-assisted development isn't about prompting harder. It's about thinking better.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Journey: From Chaos to Clarity
&lt;/h2&gt;

&lt;p&gt;When I first started with Claude Code, I did what most developers do: I described what I wanted and hoped for magic. Sometimes it worked. Often it didn't. &lt;/p&gt;

&lt;p&gt;Then I went in the opposite direction. Every instruction was spelled out. The results were getting better, but my performance wasn't. I was spending sometimes more time rewriting prompts than it would take me to write the code. &lt;/p&gt;

&lt;p&gt;At some point then, I realized that I was using Claude Code wrong and that the problem of the code not getting the results I expected soon was because Claude Code is actually just a tool that reflects what your design and architecture are.&lt;/p&gt;

&lt;p&gt;In summary, I realized that if your design is bad, the code it will generate will be bad. If your principles are clear, the code will be clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design First, Generate Second
&lt;/h2&gt;

&lt;p&gt;Here's what changed everything for me: I stopped thinking about "getting AI to write good code" and started thinking about describing my design in the most clear way possible so anyone can understand - AI or Human.&lt;/p&gt;

&lt;p&gt;In my CLAUDE.md, I now keep a section like this:&lt;br&gt;
markdown&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Code Quality Principles

**Function Design:**
- Pure functions with no side effects whenever possible
- Max 20 lines per function (guideline, not law)
- Max 3-4 parameters - use objects for more complex inputs
- Names should read like sentences: `calculateUserSubscriptionStatus()` not `checkUser()`

**Testing Philosophy:**
- Test-Driven Development - write the test first, always
- Each test should validate one behavior
- Use descriptive test names that explain the "why": `should_return_null_when_user_has_expired_trial`


**CRITICAL: Never use mocks (jest.fn(), jest.mock(), etc.) in tests.**

Instead:
- **Unit tests**: Create **fakes** (in-memory implementations) in `tests/__fakes__/`
- **Integration tests**: Use **Testcontainers** for real dependencies

**Fake example:**

// tests/__fakes__/database.fake.ts
export function createFakeDatabase(): Database {
  const links = new Map&amp;lt;LinkId, Link&amp;gt;();

  return {
    async getLink(id: LinkId) {
      const link = links.get(id);
      return link
        ? { success: true, data: link }
        : { success: false, error: { type: 'NOT_FOUND', linkId: id } };
    },
    async saveLink(link: Link) {
      links.set(link.id, link);
      return { success: true, data: link };
    },
  };
}

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

&lt;/div&gt;



&lt;p&gt;But here's the crucial part: these aren't arbitrary rules. &lt;br&gt;
They're my answer to 'what does good look like in practice. &lt;/p&gt;

&lt;p&gt;When I say "pure functions with no side effects," I'm communicating that I value testability and predictability over clever stateful solutions. &lt;/p&gt;

&lt;p&gt;When I enforce 20-line functions, I'm saying I value readability and single responsibility. &lt;/p&gt;

&lt;p&gt;Claude Code doesn't just follow these rules—it internalizes the philosophy behind them.&lt;/p&gt;
&lt;h2&gt;
  
  
  The TDD Cycle with AI: Green, Reflect, Refactor
&lt;/h2&gt;

&lt;p&gt;The most powerful pattern I've found combines Test-Driven Development with AI iteration. &lt;/p&gt;

&lt;p&gt;It goes like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write the test first (or have Claude write it based on your specification)
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;describe('MCP Server Authentication', () =&amp;gt; {
  it('should reject requests without valid API keys', async () =&amp;gt; {
    const request = createMockRequest({ apiKey: 'invalid' });
    const response = await handleMCPRequest(request);

    expect(response.status).toBe(401);
    expect(response.error).toContain('Invalid API key');
  });
});

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

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Let Claude implement&lt;br&gt;
Sometimes it nails it. Sometimes it doesn't. And here's the counterintuitive part: that's okay. If the test passes but the implementation feels wrong, you've learned something valuable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reflect before refactoring&lt;br&gt;
When the output isn't what you expected, pause. Don't immediately ask for a rewrite. Ask yourself:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Was my architectural vision clear in the prompt? Did I provide enough context about existing patterns in the codebase? Are my guidelines actually contradictory?  Did I specify what to build but forget why this feature exists?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Refactor with intention
Now go back to Claude with clarity:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;"The authentication logic works, but it's mixing concerns. Let's separate API key validation into its own pure function that returns a Result type. The handler should only orchestrate, not implement business logic."&lt;/p&gt;

&lt;p&gt;The second iteration is almost always better—not because Claude "learned," but because you clarified your thinking.&lt;/p&gt;
&lt;h2&gt;
  
  
  Real Example: Test Infrastructure Rewrite
&lt;/h2&gt;

&lt;p&gt;Let me give you a concrete example from my current work. I'm rewriting our integration tests—tests that have been running against actual production-like environments for years. These tests are slow (some take 5+ minutes), flaky (they fail randomly), and frankly, embarrassing. They're pure .js files with some functions and assertions thrown in. No real structure or patterns. &lt;/p&gt;

&lt;p&gt;The complexity is real: we need authentication strategies that work in test environments, we need to mock AWS services (S3, SQS, DynamoDB), we need database state management, and we need all of this to be fast and reliable.&lt;/p&gt;

&lt;p&gt;First attempt (vibe coding):&lt;/p&gt;

&lt;p&gt;"Rewrite these integration tests to use Testcontainers and proper test architecture."&lt;/p&gt;

&lt;p&gt;Claude generated... tests. They used Testcontainers. They had a setup and teardown. But they didn't even pass. &lt;br&gt;
Some have failed because they couldn't authenticate.&lt;br&gt;
Others failed because they didn't change the configuration and were trying to go on AWS instead of LocalStack, others were having cross dependencies with other tests and would be flaky - exactly the opposite of what we wanted to achieve.&lt;/p&gt;

&lt;p&gt;Second attempt (after reflection):&lt;/p&gt;

&lt;p&gt;I realized I was asking Claude to solve a design problem I hadn't solved myself. So I stepped back and documented the actual testing architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Integration Testing Principles

**Test Isolation:**
- Each test runs in its own Testcontainer instance
- Database state resets between tests using transactions
- No shared state between test cases
- Tests can run in parallel without conflicts

**Authentication Strategy:**
- Mock Authentication Service - We are focusing on the behaviour, not the authorization

**AWS Service Mocking:**
- LocalStack for S3, SQS, DynamoDB in containers
- One LocalStack instance per test file (isolated but fast)
- Test fixtures populate services in beforeAll
- Cleanup in afterAll, not in individual tests

**Test Structure:**
- Arrange: Set up test data using factory functions
- Act: Make actual HTTP requests to the service
- Assert: Verify both response and side effects (DB state, queue messages)
- Each test validates ONE behavior path
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I prompted:&lt;/p&gt;

&lt;p&gt;"Rewrite the links generation for tests following our integration testing principles. Use Testcontainers for MySQL, LocalStack for AWS Service. Show me the test structure for one complete test case."&lt;/p&gt;

&lt;p&gt;The result? Fast, reliable, and actually maintainable. &lt;/p&gt;

&lt;p&gt;Not because I prompted better, but because I designed better.&lt;/p&gt;

&lt;p&gt;The better part? When I asked Claude to rewrite the next test file, it already understood the patterns. It knew the setup to follow.&lt;/p&gt;

&lt;p&gt;The QA Multiplication Effect&lt;br&gt;
Here's where this gets really interesting for teams. I'm on a 12-developer team with only 2 QAs company-wide. That's a bottleneck. My mission has been pushing toward 99% automated QA coverage.&lt;br&gt;
Claude Code isn't just writing my application code—it's writing my test infrastructure. But only because I've been crystal clear about what quality means:&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for Your Engineering Career
&lt;/h2&gt;

&lt;p&gt;If you're worried that AI will make you obsolete, you're looking at it wrong. Claude Code isn't replacing engineers—it's raising the bar for what engineering means.&lt;/p&gt;

&lt;h2&gt;
  
  
  The skills that matter now:
&lt;/h2&gt;

&lt;p&gt;System design thinking - Can you articulate how components should interact?&lt;br&gt;
Architectural decision-making - Can you explain why this approach over that one?&lt;br&gt;
Code review expertise - Can you spot the subtle bugs, the performance issues, the maintainability problems?&lt;br&gt;
Quality standards definition - Can you define what "good" looks like for your domain?&lt;/p&gt;

&lt;p&gt;These are senior-level skills. If you've been coasting on syntax knowledge and Stack Overflow copy-paste, yeah, you're in trouble. But if you're actually engineering—thinking about trade-offs, designing systems, setting quality standards—you've just gained a superpower.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Uncomfortable Truth
&lt;/h2&gt;

&lt;p&gt;Here's what I wish someone had told me when I started: Claude Code will ruthlessly expose the gaps in your own thinking.&lt;br&gt;
If you can't explain your design clearly enough for an AI to implement it, you probably can't explain it to a human teammate either. &lt;/p&gt;

&lt;p&gt;If your code quality standards are vague, Claude will produce vague code. If you don't actually understand the architectural patterns you're trying to use, the generated code will be architecturally confused.&lt;/p&gt;

&lt;p&gt;This is uncomfortable. It's much easier to blame the tool. But it's also an incredible learning opportunity.&lt;/p&gt;

&lt;p&gt;Every time Claude produces code that's "not quite right," you have a choice: get frustrated, or get curious. Why didn't it work? What was unclear? What assumption did I make that I didn't communicate?&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Advice: Start Small, Think Big
&lt;/h2&gt;

&lt;p&gt;If you're just getting started with Claude Code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Document your non-negotiables first&lt;br&gt;
Before you generate a single line of code, write down your quality standards. What does "good code" mean in your context? Start with 5-10 core principles.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use TDD as your safety net&lt;br&gt;
Write tests first. Let Claude implement. If tests pass but code feels wrong, you've learned something about your specification.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Iterate in public&lt;br&gt;
Share your CLAUDE.md files with your team. Debate them. Refine them. The best engineering standards are the ones the whole team understands and buys into.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Measure what matters&lt;br&gt;
Track: test coverage, bug escape rate, time-to-production. Not: lines of code generated, prompts per day. Optimize for outcomes, not activity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Embrace the reflection cycle&lt;br&gt;
When output isn't what you wanted, resist the urge to immediately re-prompt. Take five minutes to understand why. Write it down. Update your guidelines. Then re-prompt.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Future Is Already Here
&lt;/h2&gt;

&lt;p&gt;We're in a weird transitional moment. Some developers are treating Claude Code like autocomplete++. Others are treating it like a junior developer to micromanage. Both are missing the point.&lt;/p&gt;

&lt;p&gt;The developers who will thrive are the ones who realize this: &lt;br&gt;
Claude Code isn't a replacement for engineering skills. It's a mirror that reflects the quality of your engineering thinking.&lt;/p&gt;

&lt;p&gt;If your design is bad, your code will be bad. If your architecture is elegant, your code will be elegant. If your quality standards are high, your output will be high-quality.&lt;br&gt;
Claude Code will be as good as you are.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>claude</category>
      <category>code</category>
    </item>
    <item>
      <title>Serverless Meets Zero Trust: Designing Secure Cloud-Native Apps from Day One</title>
      <dc:creator>Lucas Geovani Castro Brogni</dc:creator>
      <pubDate>Mon, 02 Jun 2025 11:01:44 +0000</pubDate>
      <link>https://dev.to/lucasbrogni1/serverless-meets-zero-trust-designing-secure-cloud-native-apps-from-day-one-1oho</link>
      <guid>https://dev.to/lucasbrogni1/serverless-meets-zero-trust-designing-secure-cloud-native-apps-from-day-one-1oho</guid>
      <description>&lt;p&gt;Serverless has grown exponentially in the last few years, and yet it is not done. In 2023, Datadog found that 70% of its customers who used AWS were adopting some serverless services. &lt;/p&gt;

&lt;p&gt;While serverless is on the rise, it was identified by CrowdStrike in 2024 that the biggest security threat in modern applications is Cloud misconfiguration. &lt;/p&gt;

&lt;p&gt;That is explained by the fact that as systems grow more complex and distributed, the risk of accidentally exposing resources or granting excessive permissions increases. In such a scenario, a more robust and proactive approach to security becomes critical.&lt;/p&gt;

&lt;p&gt;In this article, to help address this challenge, I'd like to present the concept of "Zero Trust" and how to apply its principles to serverless applications from day one.&lt;/p&gt;

&lt;p&gt;For the practical examples of this article, I am building a REST API for uploading and retrieving sensitive files. For achieving this, we are using the following tech stack: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS as cloud provider&lt;/li&gt;
&lt;li&gt;Serverless Framework for orchestration &lt;/li&gt;
&lt;li&gt;Resend for sending emails&lt;/li&gt;
&lt;li&gt;Core components: Lambda, API Gateway, S3, IAM, CloudWatch&lt;/li&gt;
&lt;li&gt;Serverless CI/CD &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can check the code on &lt;a href="https://github.com/brognilucas/zero-trust-serverless-sample" rel="noopener noreferrer"&gt;https://github.com/brognilucas/zero-trust-serverless-sample&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Zero Trust?
&lt;/h2&gt;

&lt;p&gt;Zero Trust is a security strategy based on the principle of "never trust, always verify." &lt;br&gt;
This security framework assumes that threats can exist everywhere, &lt;br&gt;
And thus nothing should be trusted by default, even if it is inside your own perimeter, in this case, inside the VPC of your cloud. &lt;/p&gt;

&lt;p&gt;Before looking into it specifically for the serverless context, and cloud configuration problems is important to have a better understanding of the key tenets of zero trust. &lt;/p&gt;

&lt;p&gt;Least privilege access:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One should never have more access than it needs to. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Continuous verification: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensure identity is checked before granting any type of access. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Assume breach: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assume that no system is perfect and that sooner or later a breach will happen, and therefore always try to do your best to minimize the impact. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Strong identity: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Leverage multi-factor authentication (MFA) and context-aware policies whenever possible.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Applying Zero Trust to Serverless Applications
&lt;/h2&gt;

&lt;p&gt;Applying ZERO trust architecture on the project requires as previously written, applying the following items:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Identity &amp;amp; Access Management&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always require authentication for actions to be performed. On the example of this project, for uploading a file, checking files that the user has, or for downloading a file, the authentication is required. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For login: &lt;br&gt;
Our implementation had the structure of authorization via user &amp;amp; password + 6 digit code sent via email. &lt;/p&gt;

&lt;p&gt;For protected routes: usage of JWT token &lt;/p&gt;

&lt;p&gt;You can achieve such protection on serverless by applying the following structure on the configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider: 
  httpApi:
    authorizers:
      authorizer:
        type: request
        identitySource: "$request.header.Authorization"
        functionName: authorizer
        enableSimpleResponses: true


functions:
  authorizer:
    handler: auth/jwt-authorizer.handler

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

&lt;/div&gt;



&lt;p&gt;And the Lambda for authorization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import jwt from 'jsonwebtoken';

export const handler = async (event) =&amp;gt; {
  try {
    const token = event.headers.authorization.split(' ')[1];
    if (!token) {
      return {
        isAuthorized: false,
      };
    }
    const decoded = jwt.verify(token, process.env.JWT_SECRET);    
    return {
      isAuthorized: true,
      context: {
        email: decoded.email
      }
    };
  } catch (error) { 
    return {
      isAuthorized: false,
    };
  }
};

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

&lt;/div&gt;



&lt;p&gt;For this example, we didn't have any type of roles, as every user could make upload of their files, and only manage it's own files, but it would be possible for a second iteration to implement a ROLE based system, where some users could only see files, others would be able to put files, etc. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implementing Least Privilege with IAM Roles&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For this project, we used S3 Buckets and DynamoDB, and each Lambda would need access to some specific resources, but with different levels of privileges. An example is that the Lambda that can list the files should not have access to get or put files in the bucket. &lt;/p&gt;

&lt;p&gt;Another example is that the Lambda that will authorize the user should not be able to insert or update the user table, but it obviously needs to get the user and create a new MFA code onto another table.  &lt;/p&gt;

&lt;p&gt;Such controls when building the IAM ROLE are created in the following spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;functions: 
 signin:
    handler: auth/signin.handler
    events:
      - httpApi:
          path: /signin
          method: post
    role: SigninLambdaRole

resources:
  Resources:SigninLambdaRole:
      Type: AWS::IAM::Role
      Properties:
        RoleName: zero-trust-confidential-files-api-signin-lambda-role-${self:provider.stage}
        AssumeRolePolicyDocument:
          Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Principal:
                Service:
                  - lambda.amazonaws.com
              Action:
                - sts:AssumeRole
        Policies:
          - PolicyName: SigninLambdaPolicy
            PolicyDocument:
              Version: "2012-10-17"
              Statement:
                - Effect: Allow
                  Action:
                    - dynamodb:GetItem
                  Resource:
                    - arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/Users-${self:provider.stage}
                - Effect: Allow
                  Action:
                    - dynamodb:PutItem
                  Resource:
                    - arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/MfaCodes-${self:provider.stage}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As it can be seen, no permissions that are not necessary are granted any of the resources. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Microsegmentation &amp;amp; Isolation&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Microsegmentation is fundamental to Zero trust, where the idea is to divide the environment in small and isolated blocks, allowing that each block has its own scope of security. By following such approach, in case of a breach, the impact is limited. &lt;/p&gt;

&lt;p&gt;In practical terms, for doing so in our project, we have achieved it by splitting the Lambdas by its own IAM role as shown in the previous   example. &lt;/p&gt;

&lt;p&gt;Other than that, such approach also helped us to achieve a few good software engineering practices, such as SRP (Single Responsibility Principle), while also facilitating logs auditing, faster and secure deployments, higher scalability, etc. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Observability &amp;amp; Auditing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Observability is another essential component of a Zero trust architecture, even further when thinking about serverless environments. &lt;br&gt;
For our project, we are using CloudWatch to collect logs, and the serverless dashboard for understanding what is happening at all times. With it, we can always have ideas about the status of our application, understand patterns, and check for possible errors or security breaches. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Securing the CI/CD Pipeline&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As you can imagine though, zero trust go beyond the security of the production application, but rather it also requires Zero trust for the deployments, and the code itself. &lt;/p&gt;

&lt;p&gt;Therefore, the usage of proper configuration on your CI/CD, the usage of parameters, environment variables and limited access of such components are a MUST when implementing a zero trust architecture. &lt;/p&gt;

&lt;p&gt;For this project, the parameters have been put on the serverless configuration, and injected onto the yml file by them. &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%2Fsixknq7rro660j0cm9yj.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%2Fsixknq7rro660j0cm9yj.png" alt="Serverless configuration of parameters" width="800" height="231"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider:
  name: aws
  runtime: nodejs20.x
  stage: ${opt:stage, 'dev'}
  environment:
    RESEND_API_KEY: ${param:resendApiKey}
    JWT_SECRET: ${param:jwtSecret}
    EMAIL_FROM: ${param:emailFrom}
    S3_UPLOAD_BUCKET_NAME: ${param:s3UploadBucketName}-${self:provider.stage}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Best Practices and Common Pitfalls
&lt;/h3&gt;

&lt;p&gt;When implementing Zero Trust architecture in serverless environments, it's fundamental to follow some best practices and be prepared to avoid some anti-patterns. Following such practices will help you to achieve a more secure, scalable, and maintainable system. &lt;/p&gt;

&lt;p&gt;Following, I have listed some of the patterns that are recommended to build a secure serverless application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Validate inputs and payloads&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use secret rotation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use environment variables and parameters stored in a safe environment&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Frequently monitor your application&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoid the usage of wildcard permissions (*)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoid long-running functions or critical external dependencies without fallback mechanisms&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;The Zero Trust architecture has become essential for modern applications. In a situation where cloud and serverless are highly used, adopting Zero Trust means assuming that no component, user, or service is automatically trusted, and can represent a significant reduction in the risks of accidental exposures, unauthorized access, and even insider attacks.&lt;/p&gt;

&lt;p&gt;Given that misconfiguration can and is the main reason for security breaches, following best practices, checking on your configuration, and using the right tools is the best way to help you unlock the benefits that serverless can provide. &lt;/p&gt;

&lt;p&gt;Also, it's always important to remember that security does not end at the moment of deployment: it is an ongoing process that involves constant monitoring, detailed auditing, and frequent adjustments to respond to new threats and vulnerabilities.&lt;/p&gt;

&lt;p&gt;For those who want to delve deeper into the subject, some recommended readings are the OWASP Serverless Top 10, which highlights the main risks specific to this model, and the official Serverless Framework documentation focused on security, in addition to other best practices recognized in the market.&lt;/p&gt;

&lt;p&gt;Sources and inspired content: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://nvlpubs.nist.gov/nistpubs/specialpublications/NIST.SP.800-207.pdf" rel="noopener noreferrer"&gt;https://nvlpubs.nist.gov/nistpubs/specialpublications/NIST.SP.800-207.pdf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.datadoghq.com/state-of-serverless/" rel="noopener noreferrer"&gt;https://www.datadoghq.com/state-of-serverless/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.crowdstrike.com/en-us/cybersecurity-101/cloud-security/cloud-vulnerabilities/" rel="noopener noreferrer"&gt;https://www.crowdstrike.com/en-us/cybersecurity-101/cloud-security/cloud-vulnerabilities/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=NBL-v984sc4" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=NBL-v984sc4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=WTJAjGKmAds" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=WTJAjGKmAds&lt;/a&gt;&lt;/p&gt;

</description>
      <category>design</category>
      <category>serverless</category>
      <category>security</category>
      <category>cloudcomputing</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Lucas Geovani Castro Brogni</dc:creator>
      <pubDate>Sat, 17 May 2025 08:10:03 +0000</pubDate>
      <link>https://dev.to/lucasbrogni1/-27g</link>
      <guid>https://dev.to/lucasbrogni1/-27g</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/lucasbrogni1/my-learnings-as-a-temporary-manager-4o01" class="crayons-story__hidden-navigation-link"&gt;My learnings as a temporary manager&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/lucasbrogni1" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F367307%2Faea71164-f5c8-406b-8a95-9f41e70d7182.JPG" alt="lucasbrogni1 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/lucasbrogni1" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Lucas Geovani Castro Brogni
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Lucas Geovani Castro Brogni
                
              
              &lt;div id="story-author-preview-content-1936883" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/lucasbrogni1" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F367307%2Faea71164-f5c8-406b-8a95-9f41e70d7182.JPG" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Lucas Geovani Castro Brogni&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/lucasbrogni1/my-learnings-as-a-temporary-manager-4o01" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 26 '24&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/lucasbrogni1/my-learnings-as-a-temporary-manager-4o01" id="article-link-1936883"&gt;
          My learnings as a temporary manager
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/management"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;management&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/leadership"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;leadership&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/lucasbrogni1/my-learnings-as-a-temporary-manager-4o01" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;5&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/lucasbrogni1/my-learnings-as-a-temporary-manager-4o01#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>management</category>
      <category>leadership</category>
    </item>
    <item>
      <title>What I've learned by building a fully serverless application</title>
      <dc:creator>Lucas Geovani Castro Brogni</dc:creator>
      <pubDate>Fri, 16 May 2025 07:24:03 +0000</pubDate>
      <link>https://dev.to/lucasbrogni1/what-ive-learned-by-building-a-fully-serverless-application-ojk</link>
      <guid>https://dev.to/lucasbrogni1/what-ive-learned-by-building-a-fully-serverless-application-ojk</guid>
      <description>&lt;p&gt;A few years back, while doing my masters in Software Architecture, I challenged myself to create a fully serverless application to be my graduation project. &lt;/p&gt;

&lt;p&gt;The idea of that project was to check how a fully serverless application would look, and where it would stand when choosing an architecture for other projects. &lt;/p&gt;

&lt;p&gt;For this project, I decided to go with a fairly simple but powerful set of tools: Serverless Framework, NodeJS, MongoDB running on Atlas, API Gateway for the endpoints, and AWS SNS for events. While the app itself centered around evaluating NFL draft prospects, the real learning came from designing and evolving the architecture. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Good
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1 - Faster time to market&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With no need to manage infrastructure, you can build, iterate, and ship features faster. In a matter of days, I had a fairly functional MVP. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2 - Cost Efficiency&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As this was an academic project, it was so good to see that in the end of the day, I was not spending any money on my infrastructure. But obviously, this is a huge advantage for companies as well, especially early-stage companies that are just building their product without having to worry about how much the bill will come at the end of the day. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3 - High availability&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Serverless platforms are designed with fault tolerance and high availability out of the box. Your functions run across multiple availability zones, minimizing the risk of downtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4 - Built-in scalability&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I didn’t have to think about scaling up or down at any point during the project. Whether there was one request or a thousand, AWS Lambda just handled it. That made the architecture feel robust and production-ready, even for an academic exercise. That's a massive advantage when you're building something with unpredictable or spiky workloads.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Trade-offs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1 - Cold starts are real&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In development, it’s easy to overlook cold starts, but in production, even for a small project, I could notice delays on initial invocations, especially for functions with more dependencies. It taught me to pay more attention to package sizes, lazy loading, and even runtime choices.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;2 - Testing and local development aren't straightforward *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Running everything locally with a full simulation of the cloud stack was difficult. I used tools like serverless-offline, but they only go so far. For testing, I had to think carefully about how I would approach it. At the end of the day, I was only able to have unit tests, as for integration tests, I would have to actually deploy the application, which would have slowed me down and decreased the feedback loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3 - Vendor lock-in&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Using AWS services so heavily meant I was tightly coupled to that ecosystem. You are always able to move it to another cloud provider if that's something you want to do, but that would obviously require a good amount of rework. &lt;/p&gt;

&lt;h3&gt;
  
  
  Results
&lt;/h3&gt;

&lt;p&gt;Overall, building this project was one of the most educational experiences I’ve had in terms of hands-on architecture. &lt;br&gt;
It helped me move beyond theory and directly experience the practical pros and cons of serverless in a real-world system.&lt;/p&gt;

&lt;p&gt;I could learn that Serverless is incredibly powerful, especially when you need to move fast, stay lean, and scale effortlessly. But as with everything else in software engineering, it has trade-offs. &lt;br&gt;
In many cases, the best results come from combining serverless with other architectural patterns, as in that case, you can get the best of both worlds. &lt;/p&gt;

</description>
      <category>serverless</category>
      <category>node</category>
      <category>aws</category>
      <category>mongodb</category>
    </item>
    <item>
      <title>Doing TDD improved my DevExp</title>
      <dc:creator>Lucas Geovani Castro Brogni</dc:creator>
      <pubDate>Fri, 02 May 2025 09:49:10 +0000</pubDate>
      <link>https://dev.to/lucasbrogni1/doing-tdd-improved-my-devexp-3lhi</link>
      <guid>https://dev.to/lucasbrogni1/doing-tdd-improved-my-devexp-3lhi</guid>
      <description>&lt;p&gt;A long time ago, I had worked on a project that had no tests whatsoever. In fact, I was working in a team dedicated to fixing bugs, which was called the Support LVL 3 team. It was awful, could you imagine spending your whole days just getting bugs to fix and never creating anything new, and eventually getting a bug that you had fixed before again? Well, that was my reality. &lt;/p&gt;

&lt;p&gt;After a few months there, I was already feeling, something was not right, and I had no idea how we could actually avoid such problems, and that was the moment I learned about automated testing. &lt;/p&gt;

&lt;p&gt;Well, unfortunately, it wasn't in that project that I started applying it, at that moment, for many people that I worked with, there was a belief that tests would slow us down. &lt;/p&gt;

&lt;p&gt;Shortly after leaving that company, I went to a place where I was incentivized to test my own code. It wasn't TDD yet, but it was some advance. I could finally stop seeing regressions, and would not have a team dedicated only to fixing bugs, which was awesome. I was still not producing my best code, though, and sometimes, I would write tests that were so broad that in case of a bug, I would still spend a few hours debugging to figure out where the error was. &lt;/p&gt;

&lt;p&gt;A few years down the road, at a conference, I came across this talk about TDD, and then I got hooked.&lt;/p&gt;

&lt;p&gt;The speaker wasn’t just talking about tests—he was talking about design, focus, and developer flow. He explained how writing tests first could lead to more modular, predictable, and clean code. It sounded almost too good to be true. But the examples were so simple, so intentional, that I left the room thinking: “Why haven’t I been doing this all along?”. &lt;/p&gt;

&lt;p&gt;Well, when I first started applying, it looked somewhat less obvious. &lt;/p&gt;

&lt;p&gt;I started playing with it on side projects, but I wasn't somehow getting the benefits. I would write this test, add the code, change the test, then be like, am I doing this wrong? &lt;/p&gt;

&lt;p&gt;I saw some people use TDD and handle it with an elegance and speed that I could only assume was from some years of experience, while I felt like I was constantly tripping over myself. Wasn't this meant to speed up my work? &lt;/p&gt;

&lt;p&gt;I even began to question everything, even as if maybe my first team was right - Tests maybe slow you down. What if this was all just theory that did not really work in the real-world development? &lt;/p&gt;

&lt;p&gt;I decided to keep researching, though, and keep trying it out. During this research, I came across another incredible talk, "TDD, Where Did It All Go Wrong" by Ian Cooper. &lt;/p&gt;

&lt;p&gt;That talk changed everything. &lt;/p&gt;

&lt;p&gt;I finally understood what I was doing wrong. I was testing the code, not the behaviour I wanted to achieve. I was creating a highly tight coupled test of my implementation details. That’s why it felt painful. That’s why it wasn’t working.&lt;/p&gt;

&lt;p&gt;Armed with that new perspective, I gave it another go. And this time, it was different.&lt;/p&gt;

&lt;p&gt;I stopped trying to test everything. I focused on testing what mattered. I stopped coupling my tests to how the code was written and focused on what it should do. My tests became simpler. My code became more expressive. &lt;/p&gt;

&lt;p&gt;And that is the reason I can say that applying TDD rightly improved my DevExp. After learning how to do it properly, I finally got the benefits everyone talked about. I started having fewer regressions, faster feedback, and having more time to do what I really like to do: build great software products. &lt;/p&gt;

&lt;p&gt;If you are still figuring out what TDD is and if you should use it, that's ok, just keep practicing, because one day it will click, and when it does, it will be like pulling back a curtain to reveal a scene you didn't know was there.&lt;/p&gt;

</description>
      <category>tdd</category>
      <category>softwaredevelopment</category>
      <category>testing</category>
      <category>devex</category>
    </item>
    <item>
      <title>MVVM on React: Why it might be the best solution for you?</title>
      <dc:creator>Lucas Geovani Castro Brogni</dc:creator>
      <pubDate>Wed, 08 Jan 2025 12:13:34 +0000</pubDate>
      <link>https://dev.to/lucasbrogni1/mvvm-on-react-why-it-might-be-the-best-solution-for-you-3j5f</link>
      <guid>https://dev.to/lucasbrogni1/mvvm-on-react-why-it-might-be-the-best-solution-for-you-3j5f</guid>
      <description>&lt;p&gt;In one of my recent experiences, you often would find components such as the example below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState, useEffect } from "react";
import { TodoApiImpl } from "../../TodoApi/TodoApi";
import './TodoOldStyle.css'
const api = new TodoApiImpl();
const TodoOldStyle = () =&amp;gt; {
  const [todos, setTodos] = useState&amp;lt;any[]&amp;gt;([]);
  const [newTodoText, setNewTodoText] = useState("");

  useEffect(() =&amp;gt; {
    const fetchTodos = async () =&amp;gt; {
      const fetchedTodos = await api.getTodos();
      setTodos(fetchedTodos);
    };
    fetchTodos();
  }, []);

  const addTodo = async () =&amp;gt; {
    if (!newTodoText.trim()) return;
    const newTodo = await api.addTodo(newTodoText);
    setTodos([...todos, newTodo]);
    setNewTodoText("");
  };

  const toggleTodo = async (id: number) =&amp;gt; {
    const toggledTodo = await api.toggleTodo(id);
    setTodos(todos.map(todo =&amp;gt; (todo.id === id ? toggledTodo : todo)));
  };

  const deleteTodo = async (id: number) =&amp;gt; {
    await api.deleteTodo(id);
    setTodos(todos.filter(todo =&amp;gt; todo.id !== id));
  };

  return (
    &amp;lt;div className="todo-container"&amp;gt;
      &amp;lt;h1&amp;gt;Todo List&amp;lt;/h1&amp;gt;
      &amp;lt;div className="todo-input"&amp;gt;
        &amp;lt;input
          type="text"
          placeholder="Add a new todo"
          value={newTodoText}
          onChange={(e) =&amp;gt; setNewTodoText(e.target.value)}
        /&amp;gt;
        &amp;lt;button onClick={addTodo}&amp;gt;Add&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;ul className="todo-list"&amp;gt;
        {todos.map((todo) =&amp;gt; (
          &amp;lt;li key={todo.id} className={`todo-item ${todo.completed ? "completed" : ""}`}&amp;gt;
            &amp;lt;label&amp;gt;
              &amp;lt;input
                type="checkbox"
                checked={todo.completed}
                onChange={() =&amp;gt; toggleTodo(todo.id)}
              /&amp;gt;
              &amp;lt;span&amp;gt;{todo.text}&amp;lt;/span&amp;gt;
            &amp;lt;/label&amp;gt;
            &amp;lt;button className="delete-button" onClick={() =&amp;gt; deleteTodo(todo.id)}&amp;gt;
              Delete
            &amp;lt;/button&amp;gt;
          &amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default TodoOldStyle;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was making me really frustrated, as our code was not easy to test, the dev experience was not great, and the maintenance was starting to become a nightmare. &lt;/p&gt;

&lt;p&gt;At that point, I started to look for alternatives, of how could we have a better code, improving the cohesion, coupling, and testability of our application, while also improving the Developer Experience. &lt;/p&gt;

&lt;p&gt;The solution that I found? To start using MVVM. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is MVVM?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're not familiar with MVVM (Model-View-ViewModel), it is an architectural pattern for frontend and mobile development that was created initially by John Gossman at Microsoft. &lt;br&gt;
Its main objective is to provide a better separation of concerns, which then provides an increase in the cohesion, coupling, and testability of the applications. &lt;/p&gt;

&lt;p&gt;This pattern breaks down the application into three key components:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model&lt;/strong&gt;: The data layer, that represents the application's core data and business logic. It is responsible for fetching, storing, and manipulating data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;View&lt;/strong&gt;: The user interface, is responsible for presenting data to the user. It listens for user input and forwards events to the ViewModel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ViewModel&lt;/strong&gt;: The intermediary between the View and Model. It retrieves data from the Model, processes it, and exposes it to the View. The ViewModel also handles user interactions by updating the Model and instructing the View on updating the UI.&lt;/p&gt;

&lt;p&gt;For a better understanding, using the same Todo Example, the application would then have, a model, a ViewModel, a View, and an Entrypoint for the application.  &lt;/p&gt;

&lt;p&gt;So how does that look like on the code:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Define the Model&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This Model, will eventually be injected over the view model, and  would be injected over the view model and serve as the business logic + data manager.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Todo/model/TodoModel.tsx. 

import { TodoApi } from "../../../TodoApi/TodoApi";

export interface ITodoModel {
  getTodos(): Promise&amp;lt;Todo[]&amp;gt;;
  addTodo(text: string): Promise&amp;lt;Todo&amp;gt;;
  toggleTodo(id: number): Promise&amp;lt;Todo&amp;gt;;
  deleteTodo(id: number): Promise&amp;lt;void&amp;gt;;
}

export default class TodoModel implements ITodoModel {
  constructor(private todoApi: TodoApi) {}

  async getTodos() {
    return await this.todoApi.getTodos();
  }

  async addTodo(text: string) {
    return await this.todoApi.addTodo(text);
  }

  async toggleTodo(id: number) {
    return await this.todoApi.toggleTodo(id);
  }

  async deleteTodo(id: number) {
    return await this.todoApi.deleteTodo(id);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Create the ViewModel&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The ViewModel acts as the moderator between the View and the Model. For better testability, use DI pattern, allowing the Model to be injected over the ViewModel, and keep the state management here, so the View would only be responsible for rendering the UI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Todo/view-model/TodoViewModel.tsx. 

import { useEffect, useState } from "react";
import { Todo } from "../../../TodoApi/TodoApi";
import TodoModel from "../model/TodoModel";

export const useTodoViewModel = (model: ITodoModel) =&amp;gt; {
  const [todos, setTodos] = useState&amp;lt;Todo[]&amp;gt;([]);
  const [newTodoText, setNewTodoText] = useState("");

  const fetchTodos = async () =&amp;gt; {
    const fetchedTodos = await model.getTodos();
    setTodos(fetchedTodos);
  };

  const addTodo = async () =&amp;gt; {
    if (!newTodoText.trim()) return;
    const newTodo = await model.addTodo(newTodoText);
    setTodos([...todos, newTodo]);
    setNewTodoText("");
  };

  const toggleTodo = async (id: number) =&amp;gt; {
    const toggledTodo = await model.toggleTodo(id);
    setTodos(todos.map((todo) =&amp;gt; (todo.id === id ? toggledTodo : todo)));
  };

  const deleteTodo = async (id: number) =&amp;gt; {
    await model.deleteTodo(id);
    setTodos(todos.filter((todo) =&amp;gt; todo.id !== id));
  };

  const onNewTodoTextChange = (text: string) =&amp;gt; setNewTodoText(text);

  useEffect(() =&amp;gt; {
    fetchTodos();
  }, [])


  return {
    todos,
    newTodoText,
    addTodo,
    toggleTodo,
    deleteTodo,
    fetchTodos,
    onNewTodoTextChange
  };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Create the View&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For the sake of simplicity, add todo, and list of todos are in the same component, but in an actual application, please divide it. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As stated before, the View should only receive the props and render it accordingly keeping the logic, and state management away of it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Todo/view/TodoView.tsx. 

import React from "react";
import "./TodoView.css";
import { Todo } from "../../../TodoApi/TodoApi";


interface TodoViewProps {
  todos: Todo[];
  toggleTodo: (id: number) =&amp;gt; void;
  deleteTodo: (id: number) =&amp;gt; void;
  addTodo: () =&amp;gt; void;
  newTodoText: string;
  onNewTodoTextChange: (text: string) =&amp;gt; void;
}

export const TodoView: React.FC&amp;lt;TodoViewProps&amp;gt; = ({
  todos,
  toggleTodo,
  deleteTodo,
  addTodo,
  onNewTodoTextChange,
  newTodoText,
}) =&amp;gt; {
  return (
    &amp;lt;div className="todo-ctainer"&amp;gt;
      &amp;lt;h1&amp;gt;Todo List&amp;lt;/h1&amp;gt;
      &amp;lt;div className="todo-input"&amp;gt;
        &amp;lt;input
          type="text"
          placeholder="Add a new todo"
          value={newTodoText}
          onChange={(e) =&amp;gt; onNewTodoTextChange(e.target.value)}
        /&amp;gt;
        &amp;lt;button onClick={addTodo}&amp;gt;Add&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;ul className="todo-list"&amp;gt;
        {todos.map((todo) =&amp;gt; (
          &amp;lt;li key={todo.id} className={`todo-item ${todo.completed ? "completed" : ""}`}&amp;gt;
            &amp;lt;label&amp;gt;
              &amp;lt;input
                type="checkbox"
                checked={todo.completed}
                onChange={() =&amp;gt; toggleTodo(todo.id)}
              /&amp;gt;
              &amp;lt;span&amp;gt;{todo.text}&amp;lt;/span&amp;gt;
            &amp;lt;/label&amp;gt;
            &amp;lt;button className="delete-button" data-testid={`delete-button-${todo.id}`} onClick={() =&amp;gt; deleteTodo(todo.id)}&amp;gt;
              Delete
            &amp;lt;/button&amp;gt;
          &amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Orchestrate it all&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here that would be the component rendered via router, or imported over others. &lt;br&gt;
This component, orchestrates the injection of the Model over the ViewModel, the API over the Model, and connects the model with the View.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Todo/App.tsx. 

import { TodoApiImpl } from "../../TodoApi/TodoApi";
import TodoModel from "./model/TodoModel";
import { useTodoViewModel } from "./view-model/TodoViewModel";
import { TodoView } from "./view/TodoView";

const api = new TodoApiImpl();
const model = new TodoModel(api);

export const Todo = () =&amp;gt; {
  const result = useTodoViewModel(model);

  return &amp;lt;TodoView {...result} /&amp;gt;;
};

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

&lt;/div&gt;



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

&lt;p&gt;While the MVVM pattern adds complexity upfront, due to setting up the additional layers, the long-term benefits have a huge payback over the initial investment, as with this architecture, you achieve low coupling with high cohesion, separation of concerns, better testability, and maintainability. &lt;/p&gt;

&lt;p&gt;It helps you to build scalable, testable, and maintainable applications that can evolve with ease. &lt;/p&gt;

&lt;p&gt;Whether you're working on a small app or a large enterprise project, adopting patterns like MVVM will help you maintain a clean architecture and improve the overall development experience.&lt;/p&gt;

</description>
      <category>react</category>
      <category>nvvm</category>
      <category>architecture</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Removing code smells: Using dependency injection through Props in React</title>
      <dc:creator>Lucas Geovani Castro Brogni</dc:creator>
      <pubDate>Tue, 26 Nov 2024 09:38:51 +0000</pubDate>
      <link>https://dev.to/lucasbrogni1/removing-code-smells-using-dependency-injection-through-props-in-react-501a</link>
      <guid>https://dev.to/lucasbrogni1/removing-code-smells-using-dependency-injection-through-props-in-react-501a</guid>
      <description>&lt;p&gt;Have you ever got to jumped into a React application and looked at some components and thought it was so hard to test? &lt;/p&gt;

&lt;p&gt;How could you do it, if it had the API client imported to the file, and the dependency was not managed by you? &lt;/p&gt;

&lt;p&gt;Well, one can say you can use mocks, and that would solve the problem. Yes, at some level this is true, but on the other hand, your test becomes much more complex, and as the application grows it gets harder and harder to test.&lt;/p&gt;

&lt;p&gt;Well, here comes a much better solution for it: Use dependency injection through the component properties.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you don't know what dependency injection is then a quick definition:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Dependency Injection is a technique used to achieve Inversion of Control by passing (injecting) an object’s dependencies rather than having the object create them itself. The goal of DI is to decouple the creation of objects from their usage, making the system more modular, easier to test, and easier to maintain. DI can be done in various ways, such as constructor injection, setter injection, or interface injection, each providing different levels of flexibility and control.&lt;br&gt;
-Robert C. Martin - Clean Code: A Handbook of Agile Software Craftsmanship&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In react there are a few ways for achieving it, but the easiest and probably the cleaner is using the props to pass it. &lt;/p&gt;

&lt;p&gt;&lt;del&gt;You might want to use the context to avoid props drilling, but that's the subject of another article.&lt;/del&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ok, how to implement it then? Well, here comes the code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's say you have a Todo application and want to fetch a list of Todos. &lt;/p&gt;

&lt;p&gt;For that, you will implement an interface of the API so we not only use Dependency Injection, but also follow the Dependency Inversion principle.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/api/ITodoAPI.ts
export interface ITodoAPI {
  getTodos(): Promise&amp;lt;Todo[]&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we are using typescript, we are going to also declare, our Todo type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/types.tsx
export type Todo = {
  id: number;
  title: string;
  completed: boolean;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With both interface and types defined, we can then implement our API Class. This is the place where your dependencies will land. If you want to use Axios, feel free to import it here, if you have another dependency, it does not have a problem to import, as this will be your Adapter for the outside world. Please note, that this class has no connection with the TodoList component whatsoever.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/api/TodoAPI.ts
import { ITodoAPI } from './ITodoAPI';
import { Todo } from '../types.tsx'; 
import * as axios from 'axios'; 

export class TodoAPI implements ITodoAPI {
  private URL = 'https://should-be-in-the-env.com';

  async getTodos(): Promise&amp;lt;Todo[]&amp;gt; {
    const result = await axios.get(`${this.URL}/todos`);
    return result.data;
  }
}

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

&lt;/div&gt;



&lt;p&gt;When you go the development of the component itself, the most important thing will be defining that your component will receive via Prop a &lt;code&gt;todoAPI&lt;/code&gt; property with type of &lt;code&gt;ITodoAPI&lt;/code&gt; so, it makes your component wait to receive the API and allows it to be used later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/components/TodoList.tsx
import React from 'react';
import { ITodoAPI } from '../api/ITodoAPI';

interface TodoListProps {
  todo API: ITodoAPI;
}

const TodoList: React.FC&amp;lt;TodoListProps&amp;gt; = ({ todoAPI }) =&amp;gt; {

  return (
    &amp;lt;&amp;gt;&amp;lt;/&amp;gt;
  );
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With all the setup ready, then it's time for the actual implementation of the component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/components/TodoList.tsx
import React, { useEffect, useState } from 'react';
import { ITodoAPI } from '../api/ITodoAPI';
import { Todo } from '../types'


interface TodoListProps {
  todoAPI: ITodoAPI;
}

const TodoList: React.FC&amp;lt;TodoListProps&amp;gt; = ({ todoAPI }) =&amp;gt; {
  const [todos, setTodos] = useState&amp;lt;Todo[]&amp;gt;([]);

  useEffect(() =&amp;gt; {
    const fetchTodos = async () =&amp;gt; {
      const todos = await todoAPI.getTodos();
      setTodos(todos);
    };

    fetchTodos();
  }, [todoAPI]);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Todo List&amp;lt;/h1&amp;gt;
      &amp;lt;ul&amp;gt;
        {todos.map((todo) =&amp;gt; (
          &amp;lt;li key={todo.id}&amp;gt;
            &amp;lt;span&amp;gt;
              {todo.title}
            &amp;lt;/span&amp;gt;
         &amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default TodoList;

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

&lt;/div&gt;



&lt;p&gt;With this, we can now test our component in isolation of the application, without any dependency on &lt;code&gt;jest.mocks&lt;/code&gt; or an actual API running. For that we can use a Fake implementation of the API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;describe('TodoList', () =&amp;gt; {
  // Fake implementation of the ITodoAPI
  const fakeTodoAPI: ITodoAPI = {
    getTodos: async (): Promise&amp;lt;Todo[]&amp;gt; =&amp;gt; [
      { id: 1, title: 'Test Todo 1', completed: false },
      { id: 2, title: 'Test Todo 2', completed: true },
    ]
  };

  it('should render a list of todos', async () =&amp;gt; {
    render(&amp;lt;TodoList todoAPI={fakeTodoAPI} /&amp;gt;);

    // Wait for todos to be fetched and displayed
    await waitFor(() =&amp;gt; screen.getByText('Test Todo 1'));

    expect(screen.getByText('Test Todo 1')).toBeInTheDocument();
    expect(screen.getByText('Test Todo 2')).toBeInTheDocument();
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For using it on our application we can then on the root of the application pass the actual class for our component and it will work equally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/App.tsx
import React from 'react';
import TodoList from './components/TodoList';
import { TodoAPI } from './api/TodoAPI';

const todoAPI = new TodoAPI();

function App() {
  return (
    &amp;lt;div className="App"&amp;gt;
      &amp;lt;TodoList todoAPI={todoAPI} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;By using dependency injection, we can easily swap between fake and real implementations of the API in our React components. This makes the component modular, testable, and easier to maintain. Whether you are testing your component with a fake API in isolation or using the real API in production, the code structure remains the same, making it adaptable and scalable for larger applications.&lt;/p&gt;

&lt;p&gt;Hopefully, it has helped! Happy coding.&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>beginners</category>
      <category>dependencyinjection</category>
    </item>
    <item>
      <title>You are an amazing engineer. You may just haven't noticed yet.</title>
      <dc:creator>Lucas Geovani Castro Brogni</dc:creator>
      <pubDate>Wed, 23 Oct 2024 15:20:30 +0000</pubDate>
      <link>https://dev.to/lucasbrogni1/you-are-amazing-you-may-just-havent-noticed-yet-49lb</link>
      <guid>https://dev.to/lucasbrogni1/you-are-amazing-you-may-just-havent-noticed-yet-49lb</guid>
      <description>&lt;p&gt;Recently, I've been going through impostor syndrome. Obviously, that has put my mood down and decreased my productivity, as I was feeling incapable of doing good work or delivering high-quality value.&lt;/p&gt;

&lt;p&gt;If you feel like this, here is an exercise that will help you get through it, just as it helped me.&lt;/p&gt;

&lt;p&gt;Think for a couple of minutes about all the technologies, design patterns, databases, processes, and everything else you need to know to be an engineer. How could an impostor know all of this? &lt;/p&gt;

&lt;p&gt;I'm pretty sure, your journey has been full of learning, adapting, and solving problems. No one stumbles upon this kind of skill set by accident. Each challenge you’ve faced has contributed to your growth. A true impostor wouldn’t be able to do as much as you did, nor keep the determination to continue learning more and more.&lt;/p&gt;

&lt;p&gt;Now that you know you're amazing, here are some tips on how to transform this realization into positive work:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Celebrate small wins&lt;/strong&gt;: Every task you complete, no matter how simple, is an achievement. Recognizing your daily successes helps you build confidence and stay motivated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Focus on growth, not perfection&lt;/strong&gt;: There is no such thing as perfection. You will commit mistakes, but those will help you to continue growing. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set realistic goals&lt;/strong&gt;: Break down your work into achievable steps. By having it, you can celebrate every time you achieve it, and your motivation will stay high throughout the process. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seek feedback&lt;/strong&gt;: Instead of guessing whether your work is good enough, ask for feedback from peers or mentors. Constructive criticism helps you grow and reinforces the fact that you're capable of delivering valuable work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reflect on past accomplishments&lt;/strong&gt;: Take time to look back at projects you’ve completed successfully. Look back on all that you know. That will help you to reinforce to yourself that you're capable and that each new challenge is just another opportunity to learn and grow even more.&lt;/p&gt;

&lt;p&gt;By keeping these practices in mind, you will be able to overcome the impostor syndrome and be a better engineer in the long run. &lt;/p&gt;

</description>
      <category>impostor</category>
      <category>syndrome</category>
    </item>
    <item>
      <title>My learnings as a temporary manager</title>
      <dc:creator>Lucas Geovani Castro Brogni</dc:creator>
      <pubDate>Fri, 26 Jul 2024 09:10:45 +0000</pubDate>
      <link>https://dev.to/lucasbrogni1/my-learnings-as-a-temporary-manager-4o01</link>
      <guid>https://dev.to/lucasbrogni1/my-learnings-as-a-temporary-manager-4o01</guid>
      <description>&lt;p&gt;A few months ago, my manager went on maternity leave and chose me to replace her for 10 months until she'd be back. &lt;/p&gt;

&lt;p&gt;I had no management experience or background, so it was surprising for both the other engineers and myself.&lt;/p&gt;

&lt;p&gt;But as someone that embraces challenges, and loves to experience new stuff, I couldn't get more excited with the opportunity. &lt;/p&gt;

&lt;p&gt;Bellow I list a few of my learnings.&lt;/p&gt;

&lt;h3&gt;
  
  
  It doesn't matter how good of an engineer you are, it doesn't mean you're going to be a good manager
&lt;/h3&gt;

&lt;p&gt;I've been proud of being a good engineer for a while, and most of the feedback I have received has been positive for a few years in a row. Turns out that being a manager is much different than being an engineer, and that being good as an engineer doesn't mean you're going to be a good manager.&lt;/p&gt;

&lt;p&gt;That was my case! &lt;/p&gt;

&lt;p&gt;When I first started in her position, I was completely lost. &lt;/p&gt;

&lt;p&gt;Previously, I used to know what my weekly challenges would be. However, when I transitioned into management, there was always something new popping up, and it usually revolved around people.&lt;/p&gt;

&lt;p&gt;One example was that at a certain moment, there was quite a big conflict between an engineer and the PM. I had no idea how to deal with it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build trust ASAP
&lt;/h3&gt;

&lt;p&gt;Nothing will help you more in the journey as a manager, than having the trust of your employees. &lt;/p&gt;

&lt;p&gt;When I started, I was probably terrible, as I said before. But having created trust with the people, and making sure that they understood that I was there for them was what helped me the most.&lt;/p&gt;

&lt;p&gt;By having their trust, and always being transparent with them, I could give feedback, learn their struggles, and fight for a better situation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't keep hands-on, or at least not very often
&lt;/h3&gt;

&lt;p&gt;I had to learn it in the worst way. When I initially transitioned to the EM position, I thought I would still have time to be hands-on. For a couple of months, I tried to do both, be an engineer, and be a manager. I learned that I was not doing well either. &lt;br&gt;
Nor I did have time to focus on solving technical problems, nor did I pay careful attention to the people problem. &lt;br&gt;
When transitioning to a people manager position, your focus needs to be the people.&lt;/p&gt;

&lt;h3&gt;
  
  
  You will become a better engineer by working a bit as a manager
&lt;/h3&gt;

&lt;p&gt;Yes, that's right. Even if you will not be coding as much, or dealing as much with technical problems, you can be sure that by working as a manager, you will become a better engineer. &lt;/p&gt;

&lt;p&gt;Being an engineer is much more than just focusing on the hard skills, and by becoming a manager, you will start taking deadlines more seriously, you will learn how to communicate better, have more empathy to deal with tough situations, and have more focus on what matters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Management is not for everyone
&lt;/h3&gt;

&lt;p&gt;If you are trying to be a manager or have read through it, it doesn't feel like the right fit for you, embrace the IC path. Both paths have their own unique challenges and rewards. &lt;/p&gt;

&lt;p&gt;Focus on where your strengths and passions lie, and you'll find fulfillment and achievement in your career.&lt;/p&gt;

</description>
      <category>management</category>
      <category>leadership</category>
    </item>
  </channel>
</rss>
