<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Jolo's Blog]]></title><description><![CDATA[Jolo's Blog]]></description><link>https://blog.jolo.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 23:41:51 GMT</lastBuildDate><atom:link href="https://blog.jolo.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Oh CommonJS! Why are you mESMing with me?!]]></title><description><![CDATA[It was a normal patching day. I patched and upgraded my npm dependencies without making code changes, and suddenly, some of my unit tests failed.

Wtf!

My tests failed because Jest encountered an unexpected token; they failed because Jest cannot han...]]></description><link>https://blog.jolo.dev/oh-commonjs-why-are-you-mesming-with-me</link><guid isPermaLink="true">https://blog.jolo.dev/oh-commonjs-why-are-you-mesming-with-me</guid><category><![CDATA[esm]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[commonjs]]></category><dc:creator><![CDATA[JoLo]]></dc:creator><pubDate>Fri, 12 Jul 2024 09:30:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1720776146190/22dcf34d-dcf3-4ed5-8c0d-7d580005c36d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It was a normal patching day. I patched and upgraded my npm dependencies without making code changes, and suddenly, some of my unit tests failed.</p>
<p><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qhTdhj-u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://jolo-dev-blog-images.s3.amazonaws.com/test-fail.gif" alt="Test Failed.gif\" /></p>
<p>Wtf!</p>
<p><img src="https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExdndqOGhxM21wZGhpam14ejR3YTZiMTA2NzltNmdpMXFmdGJ4eXZwciZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/l3q2K5jinAlChoCLS/200w.webp" alt="Huh" /></p>
<p>My tests failed because <code>Jest encountered an unexpected token</code>; they failed because Jest cannot handle ESM-only packages out of the box. In fact, Jest is written in CommonJS. But what does that mean? To do so, we need to understand why CommonJS and ESM exist.</p>
<h2 id="heading-why-do-we-need-module-systems">Why Do We Need Module Systems?</h2>
<p>In the early days of web development, JavaScript was mainly used to manipulate the Document Object Model (DOM) with libraries like jQuery. However, the introduction of Node.js also led to JavaScript being used for server-side programming. This shift increased the complexity and size of JavaScript codebases. As a result, there arose a need for a structured method to organize and manage JavaScript code. Module systems were introduced to meet this need, enabling developers to divide their code into manageable, reusable units<a target="_blank" href="https://www.freecodecamp.org/news/javascript-es-modules-and-module-bundlers/#why-use-modules">^1</a>.</p>
<h3 id="heading-the-emergence-of-commonjs">The Emergence of CommonJS</h3>
<p>CommonJS was established in 2009, originally named ServerJS<a target="_blank" href="https://deno.com/blog/commonjs-is-hurting-javascript">^2</a>. It was designed for server-side JavaScript, providing conventions for defining modules. Node.js adopted CommonJS as its default module system, making it prevalent among backend JavaScript developers. CommonJS uses <code>require</code> to import and <code>module.exports</code> to export modules. All operations in CommonJS are synchronous, meaning each module is loaded individually.</p>
<h3 id="heading-the-rise-of-esm-ecmascript-modules">The Rise of ESM (ECMAScript Modules)</h3>
<p>In 2015, ECMAScript introduced a new module system called ECMAScript Modules (ESM), primarily targeting client-side development. ESM uses <code>import</code> and <code>export</code> statements, and its operations are asynchronous, allowing modules to be loaded in parallel<a target="_blank" href="https://tc39.es/ecma262/#sec-overview">^3</a>. Initially, ESM was intended for browsers, whereas CommonJS was designed for servers. It became more and more a standard for the JS ecosystem. Nowadays, modern JavaScript runtimes support both module systems. Browsers began supporting ESM natively in 2017. Even Typescript adapted the ESM syntax, and whenever you learn it, you also learn ESM subconsciously.</p>
<p><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K9CAic7d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://jolo-dev-blog-images.s3.amazonaws.com/How%2520Are%2520you%2520not%2520dead.jpg" alt="How Are you not dead.jpg" /></p>
<h2 id="heading-commonjs-is-here-to-stay">CommonJS is here to stay</h2>
<p>The truth is that there are many more CommonJS (CJS)- only packages than ESM-only packages<a target="_blank" href="https://twitter.com/wooorm/status/1759918205928194443">^4</a>. ![CJS vs ESM.jpeg](https://jolo-dev-blog-images.s3.amazonaws.com/CJS vs ESM.jpeg) However, there is a clear trend. The number of ESM-only or dual module packages is on the rise, while fewer CJS-only packages are being created. This trend underscores the growing preference for ESM and raises the question of how many of the CJS-only packages are actively maintained.</p>
<h3 id="heading-comparison">Comparison</h3>
<p>An interesting comparison between CommonJS and ESM involves performance benchmarks. Due to its synchronous nature, CommonJS is faster when directly using require and import statements. Let's consider the following example:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// CommonJS -&gt; s3-get-files.cjs</span>
<span class="hljs-keyword">const</span> s3 = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@aws-sdk/client-s3'</span>);
<span class="hljs-keyword">new</span> s3.S3Client({ <span class="hljs-attr">region</span>: <span class="hljs-string">'eu-central-1'</span> });

<span class="hljs-comment">// ESM -&gt; s3-get-files.mjs</span>
<span class="hljs-keyword">import</span> { S3Client } <span class="hljs-keyword">from</span> <span class="hljs-string">'@aws-sdk/client-s3'</span>;

<span class="hljs-keyword">new</span> S3Client({ <span class="hljs-attr">region</span>: <span class="hljs-string">'eu-central-1'</span> });
</code></pre>
<p>I used the <a target="_blank" href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3/">aws-sdk S3-Client</a> because it has dual module support. Here we instantiate the client and then execute it with <code>node</code>:</p>
<pre><code class="lang-sh">hyperfine --warmup 10 --style color <span class="hljs-string">'node s3-get-files.cjs'</span> <span class="hljs-string">'node s3-get-files.mjs'</span>

Benchmark 1: node s3-get-files.cjs
Time (mean ± σ): 82.6 ms ± 3.7 ms [User: 78.5 ms, System: 16.7 ms]
Range (min … max): 78.0 ms … 93.6 ms 37 runs
Benchmark 2: node s3-get-files.mjs
Time (mean ± σ): 93.9 ms ± 4.0 ms [User: 98.3 ms, System: 18.1 ms]
Range (min … max): 88.1 ms … 104.8 ms 32 runs

Summary
node s3-get-files.cjs ran
  1.14 ± 0.07 <span class="hljs-built_in">times</span> faster than node s3-get-files.mjs
</code></pre>
<p>As you can see, the <code>s3-get-files.cjs</code> and, thus, CommonJS run faster. I got inspired by <a target="_blank" href="https://bun.sh/blog/commonjs-is-not-going-away">Buns Blogpost</a>.</p>
<p>However, when you want to productionize your JS library, you need to bundle it. Otherwise, you will ship all the <code>node_modules</code>. Is used <a target="_blank" href="https://esbuild.github.io/"><code>esbuild</code></a> because it is able to bundle to CJS and ESM. Now, let's run the same benchmark with the bundled version.</p>
<pre><code class="lang-sh">hyperfine --warmup 10 --style color <span class="hljs-string">'node s3-bundle.cjs'</span> <span class="hljs-string">'node s3-bundle.mjs'</span>

Benchmark 1: node s3-bundle.cjs
Time (mean ± σ): 62.1 ms ± 2.5 ms [User: 53.8 ms, System: 6.7 ms]
Range (min … max): 59.5 ms … 74.5 ms 45 runs

Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might <span class="hljs-built_in">help</span> to use the <span class="hljs-string">'--warmup'</span> or <span class="hljs-string">'--prepare'</span> options.

Benchmark 2: node s3-bundle.mjs
Time (mean ± σ): 45.3 ms ± 2.2 ms [User: 38.1 ms, System: 5.6 ms]
Range (min … max): 43.0 ms … 59.2 ms 62 runs

Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might <span class="hljs-built_in">help</span> to use the <span class="hljs-string">'--warmup'</span> or <span class="hljs-string">'--prepare'</span> options.

Summary

  node s3-bundle.mjs ran
    1.37 ± 0.09 <span class="hljs-built_in">times</span> faster than node s3-bundle.cjs
</code></pre>
<p>As you can see, the <code>s3-bundle.mjs</code> is now faster than the <code>s3-bundle.cjs</code>. The ESM file is now even faster than the unbundled CommonJS file because it results in smaller file sizes and faster load times due to efficient tree-shaking—a process that removes unused code.</p>
<h2 id="heading-embrace-esm">Embrace ESM!</h2>
<p>The future of JavaScript modules is undoubtedly leaning towards ESM. This starts when creating a new NodeJS project or even a React project. Every tutorial and article uses the <code>import</code>—statement, which is thus ESM. Despite many existing CommonJS packages, the trend is shifting as more developers and maintainers adopt ESM for its performance benefits and modern syntax. Another question is also how many of these CJS-only projects are still maintained.</p>
<p>ESM is a standard that works in any runtime, such as NodeJS, Bun, or Deno, and in the browser without running on a server. It is unnecessary to convert via Babel to CommonJS because the browser understands ESM. You can still use Babel to convert to a different ECMAScript version, but you shouldn't convert to CJS.</p>
<p>You should develop in ESM-only because every runtime now and browser newer than 2017 understands ESM.</p>
<p>If your code breaks, you may have legacy issues. Consider using different tooling or packages. For example, you can migrate from Jest to <a target="_blank" href="https://vitest.dev/"><code>vitest</code></a> or from ExpressJS to <a target="_blank" href="https://h3.unjs.io/"><code>h3</code></a>. The syntax remains the same; the only difference is the import statement.</p>
<p><strong>Key Takeaways</strong>:</p>
<ul>
<li><p><strong>Smaller Bundles</strong>: ESM produces smaller bundles through tree-shaking, leading to faster load times.</p>
</li>
<li><p><strong>Universal Support</strong>: ESM is supported natively by browsers and JavaScript runtimes (Node.js, Bun, Deno).</p>
</li>
<li><p><strong>Future-Proof</strong>: With ongoing adoption, ESM is positioned as the standard for modern JavaScript modules.</p>
</li>
</ul>
<p>To get started, you can follow this <a target="_blank" href="https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c">Gist</a> or get an inspirational learning <a target="_blank" href="https://blog.isquaredsoftware.com/2023/08/esm-modernization-lessons/">here</a>.</p>
<p>For a better JavaScript Future, Embrace ESM!</p>
<p><a target="_blank" href="https://youtu.be/hp4g23Zsmaw">The presentation</a></p>
<h2 id="heading-more-resources">More Resources</h2>
<ul>
<li><p><a target="_blank" href="https://dev.to/logto/migrate-a-60k-loc-typescript-nodejs-repo-to-esm-and-testing-become-4x-faster-22-4a4k">https://dev.to/logto/migrate-a-60k-loc-typescript-nodejs-repo-to-esm-and-testing-become-4x-faster-22-4a4k</a></p>
</li>
<li><p><a target="_blank" href="https://jakearchibald.com/2017/es-modules-in-browsers/">https://jakearchibald.com/2017/es-modules-in-browsers/</a></p>
</li>
<li><p><a target="_blank" href="https://gist.github.com/joepie91/bca2fda868c1e8b2c2caf76af7dfcad3">https://gist.github.com/joepie91/bca2fda868c1e8b2c2caf76af7dfcad3</a></p>
</li>
<li><p><a target="_blank" href="https://gist.github.com/joepie91/bca2fda868c1e8b2c2caf76af7dfcad3">https://gist.github.com/joepie91/bca2fda868c1e8b2c2caf76af7dfcad3</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[GHosttp]]></title><description><![CDATA[https://youtu.be/kinz7OA4zPQ
 
GHosttp is a tiny development server for developing GCP functions. With GHosttp, you can choose the folder where your GCP functions reside, allowing you to start developing from there. Your functions can be developed in...]]></description><link>https://blog.jolo.dev/ghosttp</link><guid isPermaLink="true">https://blog.jolo.dev/ghosttp</guid><category><![CDATA[GCP]]></category><category><![CDATA[Express.js]]></category><category><![CDATA[google cloud functions]]></category><dc:creator><![CDATA[JoLo]]></dc:creator><pubDate>Fri, 24 May 2024 15:44:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1716565332264/65ccda2b-b17f-4247-8c11-26fe216a758d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/kinz7OA4zPQ">https://youtu.be/kinz7OA4zPQ</a></div>
<p> </p>
<p>GHosttp is a tiny development server for developing GCP functions. With GHosttp, you can choose the folder where your GCP functions reside, allowing you to start developing from there. Your functions can be developed in Typescript and use the latest ESM features.</p>
<h2 id="heading-why">Why?</h2>
<p>The <a target="_blank" href="https://github.com/GoogleCloudPlatform/functions-framework-nodejs?tab=readme-ov-file">functions framework</a> by Google does not offer a great developer experience when developing an entire backend using GCP. Whenever you develop or test a new function, you must run the <code>npx @google-cloud/functions-framework --target=myFunction</code> command. Not only is that super long, but it also does not offer hot module reloading. What if the name changes? You need to rerun the command. Do you really want to create a bash alias for this library? Furthermore, their repository shows CommonJS syntax. What if you want to use ESM or Typescript? Since Gen2 is running on Node v20, you could and should leverage ESM. GHosttp is familiar with both ESM and Typescript.</p>
<h3 id="heading-cloud-run">Cloud Run</h3>
<p>The Gen2 GCP cloud functions use <a target="_blank" href="https://expressjs.com/">ExpressJS</a> on top of Cloud Run. ExpressJS comes with a development server. So, why not directly develop it locally in Express and then put it in a Dockerfile? Well, that's a lot of work, and you must be familiar with Docker and Cloud Run. GCP Cloud Functions gen2 abstracts many things for you.</p>
<h2 id="heading-opinionated">Opinionated</h2>
<p>This package is a bit opinionated about developing your GCP function, but I made it as close to the ExpressJS function as possible. You must name your function <code>handler</code>, and it shall be exported. Below you find a Typescript version.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Request, Response } <span class="hljs-keyword">from</span> <span class="hljs-string">'@google-cloud/functions-framework'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> handler = <span class="hljs-keyword">async</span> (req: Request, res: Response) =&gt; {
  res.set(<span class="hljs-string">'Access-Control-Allow-Origin'</span>, <span class="hljs-string">'*'</span>);
  res.set(<span class="hljs-string">'Access-Control-Allow-Methods'</span>, <span class="hljs-string">'POST'</span>);

  <span class="hljs-comment">// Because of CORS</span>
  <span class="hljs-keyword">if</span> (req.method === <span class="hljs-string">'OPTIONS'</span>) {
    res.set(<span class="hljs-string">'Access-Control-Allow-Methods'</span>, <span class="hljs-string">'GET'</span>);
    res.set(<span class="hljs-string">'Access-Control-Allow-Headers'</span>, <span class="hljs-string">'Content-Type'</span>);
    res.set(<span class="hljs-string">'Access-Control-Max-Age'</span>, <span class="hljs-string">'3600'</span>);
    res.status(<span class="hljs-number">204</span>).send(<span class="hljs-string">''</span>);
    <span class="hljs-keyword">return</span>;
  }

  <span class="hljs-comment">// Destructering your request body</span>
  <span class="hljs-keyword">const</span> { message } = req.body;
  res.set(<span class="hljs-string">'Content-Type'</span>, <span class="hljs-string">'application/json'</span>);
  res.send(
    <span class="hljs-built_in">JSON</span>.stringify({
      message,
    }),
  );
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(<span class="hljs-built_in">JSON</span>.stringify(message));
};
</code></pre>
<h2 id="heading-requirements">Requirements</h2>
<ul>
<li><p>GCP account</p>
</li>
<li><p>GCP functions gen2</p>
</li>
<li><p>Node &gt;= 20 (I haven't tried with 18, but it's a good point to upgrade)</p>
</li>
</ul>
<h2 id="heading-getting-started">Getting Started</h2>
<p>In your repository, just run <code>npx ghosttp --dir path/to/my/functions</code>. That's all. Per default, your backend will run on <code>http://localhost:3000</code></p>
<pre><code class="lang-sh">  ➜ Local:    http://localhost:3000/
  ➜ Network:  use --host to expose

🚀 Loading server entry ./src/run-dev-server.ts GHosttp 12:21:58 AM
✅ Server initialized <span class="hljs-keyword">in</span> 90ms                   GHosttp 12:21:58 AM
👀 Watching ./path/to/<span class="hljs-built_in">functions</span> <span class="hljs-keyword">for</span> changes     GHosttp 12:21:58 AM
ℹ Following endpoints are available             GHosttp 12:21:58 AM
ℹ /logger                                       GHosttp 12:21:58 AM
ℹ /run-dev-server                               GHosttp 12:21:58 AM
</code></pre>
<p>It watches whenever you make changes to your function or add a new function. This is great if you want to do Test-Driven-Development and get instant feedback.</p>
<p>Watch the video above to see it in action.</p>
<h3 id="heading-arguments">Arguments</h3>
<ul>
<li><p><code>--dir</code> - Path to your functions (default <code>"."</code>)</p>
</li>
<li><p><code>--port</code> - Port where your localhost will run (default <code>3000</code>)</p>
</li>
</ul>
<h3 id="heading-pr-and-contributions-are-welcome">PR and contributions are welcome</h3>
<p>Find the repository here: <a target="_blank" href="https://github.com/jolo-dev/ghosttp">https://github.com/jolo-dev/ghosttp</a></p>
<h2 id="heading-whats-next">What's next?</h2>
<p>I will add a development server for Flask and Go. Furthermore, I want to add a new command, <code>npx ghost add-handler my-new-handler --typescript</code>, which scaffolds a new file. I will also make a blog/video about deploying your ESM/Typescript GCP function ;)</p>
<p>Let me know what you think, and I will be happy to make any contribution 🤩</p>
]]></content:encoded></item><item><title><![CDATA[Don't (.git)ignore `.vscode`]]></title><description><![CDATA[https://www.youtube.com/watch?v=7TGx2ZaBDdA
 
Have you ever reviewed a PR that has formatting changes?

That could happen because developer tend to have their configurations and settings or don't use the linting and formatting command and simply skip...]]></description><link>https://blog.jolo.dev/dont-gitignore-vscode</link><guid isPermaLink="true">https://blog.jolo.dev/dont-gitignore-vscode</guid><category><![CDATA[VSCode Tips]]></category><category><![CDATA[VS Code]]></category><category><![CDATA[debugging]]></category><dc:creator><![CDATA[JoLo]]></dc:creator><pubDate>Mon, 06 May 2024 19:38:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1715024010414/32adb679-a0de-4ad3-9b12-713785ab1681.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=7TGx2ZaBDdA">https://www.youtube.com/watch?v=7TGx2ZaBDdA</a></div>
<p> </p>
<p>Have you ever reviewed a PR that has formatting changes?</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/tabs-vs-spaces-quotes.png" alt="tabs-vs-spaces-quotes.png" /></p>
<p>That could happen because developer tend to have their configurations and settings or don't use the linting and formatting command and simply skip the pre-commit 🫠</p>
<p>Let's say your team uses VS Code as their editor. What if you could use the <code>.vscode</code> folder to share repository settings? You can put settings, configuration, and recommended extensions inside <code>.vscode</code> and share them with your colleagues. This ensures that everyone is on the same page, reducing the chances of errors and inconsistencies. However, I have noticed that some people delete the <code>.vscode</code> folder or put it into the <code>.gitignore</code>.</p>
<p>But with that, you are missing out on its capabilities to streamline settings and even increase productivity by enabling formatting and linting. So, please Don't (.git)ignore the <code>vscode</code> 🙅‍♂️ Let's explore together how to utilize its features to take your development to the next level.</p>
<h2 id="heading-understanding-the-importance-of-configuration">Understanding the Importance of Configuration</h2>
<p>To create a consistent configuration, you can create a <code>settings.json</code> file in your VS Code folder. This file allows you to define settings for your project, such as the formatting style, compiler version, and default formatter. By sharing this file with your team, you can ensure everyone is working with the same configuration.</p>
<pre><code class="lang-json">{
    'editor.formatOnSave': <span class="hljs-literal">true</span>,
    'typescript.tsdk': './node_modules/typescript/lib',
    '[typescript]': {
        'editor.defaultFormatter': 'biomejs.biome'
    },
    'eslint.format.enable': <span class="hljs-literal">false</span>,
    'typescript.format.enable': <span class="hljs-literal">false</span>,
    'prettier.enable': <span class="hljs-literal">false</span>
}
</code></pre>
<ul>
<li><p><code>editor.formatOnSave: true</code> - allows formatting whenever you hit the save button</p>
</li>
<li><p><code>typescript.tsdk</code> - ensures using the correct compiler version</p>
</li>
<li><p><code>editor.defaultFormatter</code> - allows changing the formatter. You can have individual formatter by programming languages. Above we define a formatter for Typescript- files.</p>
</li>
<li><p><code>eslint.format.enable: false</code> - ensures that ESLint is disabled because, in my case, BiomeJS is able to do it. Otherwise, you may have conflicting linting errors</p>
</li>
<li><p><code>typescript.format.enable: false</code> - disable the VS Code in-built formatter. It could conflict with the default formatter</p>
</li>
<li><p><code>prettier.enable: false</code> - same as above</p>
</li>
</ul>
<h2 id="heading-recommending-extensions">Recommending Extensions</h2>
<p>VS Code allows you to recommend extensions to new users or team members. By adding an <code>extensions.json</code> file to your VS Code folder, you can specify the extensions that are required for your project. This ensures that new users have the necessary tools to work on the project efficiently.</p>
<pre><code class="lang-json">{
  'recommendations': [
    'biomejs.biome'
  ]
}
</code></pre>
<h2 id="heading-code-snippets">Code Snippets</h2>
<p>Code snippets are a powerful feature in VS Code that allows you to scaffold code quickly. By creating custom snippets, you can generate boilerplate code for your project, reducing the time and effort required to write code. However, the syntax for defining is quite complicated. That is why I would recommend to use the <a target="_blank" href="https://snippet-generator.app/?description=&amp;tabtrigger=&amp;snippet=&amp;mode=vscode">snippet generator tool</a> which helps you create snippets quickly and efficiently.</p>
<pre><code class="lang-json">{
  'Creates a simple Snippet': {
    'prefix': 'snippet',
    'description': 'Creates a simple Snippet',
    'body': [
      'import { http, type Request, type Response } from '@google-cloud/functions-framework';',
      '',
      'export const $<span class="hljs-number">0</span> = async ( req: Request, res: Response ) =&gt; {',
      '  const result = { result: 'This is gonna be the result for Handler: $<span class="hljs-number">0</span>'}'
      '  res.send(',
      '    JSON.stringify( {',
      '      result,',
      '    } ),',
      '  );',
      '};'
    ]
  }
}
</code></pre>
<ul>
<li><p><code>prefix</code> - the shortcut that appears in the autocompletion box</p>
</li>
<li><p><code>description</code> - the description of the snippet</p>
</li>
<li><p><code>body</code>- the code which will be generated or scaffolded</p>
</li>
<li><p><code>$0</code>- where the cursor will land. You can add multiple variables by <code>$1</code>, <code>$2</code> etc. you change the variables by tabbing.</p>
</li>
</ul>
<h2 id="heading-launching-a-fully-fledged-ide">Launching a Fully Fledged IDE</h2>
<p>VS Code can be turned into a fully-fledged IDE by using the <code>launch.json</code> file. This file allows you to define configurations for your project, including the executable, working directory, and runtime arguments. By using the <code>launch.json</code> file, you can create a customized development environment that suits your needs.</p>
<p>For example, when you have a Monorepo you can turn on a debugger for the backend and frontend.</p>
<pre><code class="lang-json">{
    'configurations': [
        {
            'type': 'node',
            'name': 'Run Backend',
            'cwd': '${workspaceFolder}/packages/backend',
            'request': 'launch',
            'runtimeExecutable': 'bun',
            'runtimeArgs': [
                'dev'
            ],
            'console': 'integratedTerminal'
        },
        {
          'type': 'expo',
          'request': 'attach',
          'name': 'Debug Expo app',
          'projectRoot': '${workspaceFolder}/packages/app',
          'bundlerPort': '<span class="hljs-number">8081</span>',
          'bundlerHost': '<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>'
        }
    ]
}
</code></pre>
<ul>
<li><p><code>type</code> - Where the debugger should be running</p>
</li>
<li><p><code>request</code> - either <code>attach</code> for attaching it to a running instance e.g. you turn on the development server and want to attach the debugger to it, or <code>launch</code> where you create a new instance</p>
</li>
<li><p><code>name</code> - the name will be reflected in the <code>Run and Debug</code> tab</p>
</li>
<li><p><code>cwd</code> or <code>projectRoot</code>- change working directory if your script is located in a different folder</p>
</li>
<li><p><code>runtimeExecutable</code> - for running a binary</p>
</li>
<li><p><code>runtimeArgs</code> - the arguments that the binary is using</p>
</li>
<li><p><code>console</code> - where you want to log the logs. VS Code is giving you suggestions</p>
</li>
<li><p>if <code>attach</code>, <code>bundlerPort</code> - the port where the current instance is running</p>
</li>
<li><p>if <code>attach</code>, <code>bundlerHost</code> - the url where the current instance is running (usually, <code>localhost</code>)</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In conclusion, VS Code is a powerful tool that can help you streamline your development process, improve code quality, and increase productivity. You can take your development to the next level by configuring your editor like a pro. Don't ignore the power of VS Code; instead, harness its features to create a customized development environment that suits your needs ⭐</p>
]]></content:encoded></item><item><title><![CDATA[Cloud Tools You Probably Haven't Heard Of]]></title><description><![CDATA[Keeping an eye on your cloud resources becomes increasingly difficult as their number grows. However, the tools below offer effective solutions for monitoring resources and controlling costs.
Komiser
Komiser is an open-source platform loved by cloud ...]]></description><link>https://blog.jolo.dev/cloud-tools-you-probably-havent-heard-of</link><guid isPermaLink="true">https://blog.jolo.dev/cloud-tools-you-probably-havent-heard-of</guid><category><![CDATA[AWS]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[devtools]]></category><dc:creator><![CDATA[JoLo]]></dc:creator><pubDate>Sun, 31 Mar 2024 20:11:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1711915595219/aa50277f-2f46-4cf9-8d23-a0ff07d6d156.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Keeping an eye on your cloud resources becomes increasingly difficult as their number grows. However, the tools below offer effective solutions for monitoring resources and controlling costs.</p>
<h2 id="heading-komiser">Komiser</h2>
<p><a target="_blank" href="https://www.komiser.io/">Komiser</a> is an open-source platform loved by cloud experts and developers. Connect your cloud accounts to get detailed insights into your cloud infrastructure.</p>
<p>It provides a nice dashboard to see your <strong>cloud assets inventory</strong>, understand their <strong>resource dependencies</strong>, and uncover <strong>idle, underutilized, and untagged resources</strong>. Komiser aims to bring <strong>transparency</strong> to cloud management. I see it as a useful tool for creating an inventory of your cloud resources. It can be connected to AWS, GCP, Azure, Digital Ocean, and more. It is interesting to see what resources are abstracted from when using a library like AWS CDK or AWS SAM.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711915841206/d7cc820a-8c59-4937-ac0b-0ef4a41b5a34.gif" alt /></p>
<h2 id="heading-steampipe">Steampipe</h2>
<p><a target="_blank" href="https://steampipe.io/">Steampipe</a> is a tool for querying cloud APIs and other data sources using SQL in a zero-ETL manner.</p>
<p>Some key capabilities include:</p>
<ul>
<li><p>Dynamically query APIs, code repositories, logs, and other data sources using SQL for unified access.</p>
</li>
<li><p>Combining data from different sources creates new insights through relationships and attributes across entities.</p>
</li>
<li><p>It operates without needing to set up traditional databases through its "Zero-ETL" approach of querying data directly at the source.</p>
</li>
<li><p>Providing metadata about available data tables and fields to help with the discovery and understanding of cloud resources and attributes.</p>
</li>
</ul>
<p>I think Steampipe's <a target="_blank" href="https://hub.powerpipe.io/">dashboards</a> (Mods) make Steampipe more powerful. They offer a variety of them, such as <a target="_blank" href="https://hub.powerpipe.io/mods/turbot/aws_well_architected">AWS Well-Architected</a>, <a target="_blank" href="https://hub.powerpipe.io/mods/turbot/aws_compliance">AWS Compliance</a>, and many more.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711915836092/49440315-8775-4586-b2cd-7a29e712b96e.gif" alt /></p>
<p>If you look for an inventory, you can check <a target="_blank" href="https://powerpipe.io/docs/build/writing-dashboards">how to build your dashboard</a>.</p>
<h2 id="heading-stackql">StackQL</h2>
<p>Like <a class="post-section-overview" href="#steampipe">Steampipe</a>'s revolutionary approach, <a target="_blank" href="https://stackql.io/">StackQL</a> harnesses the power of SQL to query your resources seamlessly. Moreover, it empowers you to utilize SQL syntax for querying and creating resources.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> aws.s3.buckets (<span class="hljs-keyword">bucket</span>, region)  
<span class="hljs-keyword">SELECT</span> <span class="hljs-string">'stackql-demo'</span>,  
<span class="hljs-string">'eu-central-1'</span>
</code></pre>
<p>It is versatile and usable. Even though it is written in Go, they provide a Python package, which means you can use it with <a target="_blank" href="https://stackql.io/blog/cloud-security-and-inventory-analysis-with-stackql-and-jupyter">Jupyter Notebooks</a>. I also like their guide on using it with <a target="_blank" href="https://stackql.io/blog/stackql-dashboards-with-superset">Superset</a>. In comparison to Steampipe, you can query all AWS services, whereas Steampipe is missing some. However, Steampipe offers autocompletion, which StackQL not have.</p>
<p>I used this <a target="_blank" href="https://stackql.io/docs/cookbooks/aws/aws-global-inventory">instruction</a> to create the inventory of my AWS account.</p>
<h2 id="heading-chatwithcloud">ChatWithCloud</h2>
<p>Thanks to a generative AI model, <a target="_blank" href="https://chatwithcloud.ai/">ChatWithCloud</a> allows users to interact with and manage their AWS cloud infrastructure using natural language commands in their terminal. Some key capabilities mentioned include:</p>
<ul>
<li><p>Performing cost analysis to find out spending and optimize costs</p>
</li>
<li><p>Conducting security analysis by reviewing IAM policies and resources</p>
</li>
<li><p>Troubleshooting infrastructure issues to understand problems and how to fix them</p>
</li>
<li><p>Having the ability to fix infrastructure issues directly through commands</p>
</li>
<li><p>There is no need for AWS knowledge or API keys to use most basic functions</p>
</li>
</ul>
<p>So, in summary, ChatWithCloud provides a natural language interface for viewing, analyzing, and troubleshooting AWS resources with the help of an AI assistant. You must buy a license and use your OpenAI key or subscribe to it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711915849266/d923a995-36a1-4f33-85d3-7e59f5d875e2.gif" alt /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>All these tools provide a comprehensive overview of your AWS resources. <em>Komiser</em> also displays costs, while <em>ChatWithCloud</em> provides answers by entering prompts. <em>Steampipe</em> and <em>StackQL</em> use SQL to interact with the cloud. Additionally, you can connect these to dashboards to visualize them, similar to any data analytics process.</p>
<h2 id="heading-bonus-create-inventory-with-aws-console">Bonus: Create Inventory with AWS Console</h2>
<p>Instructions are from <a target="_blank" href="https://bobbyhadz.com/blog/aws-list-all-resources">here</a>.</p>
<p>The tools above offer dynamic monitoring of your AWS resources. But what if you need inventory just once or less frequently? The following steps allow you to create a CSV of all the resources within your account:</p>
<ul>
<li><p>Use the <code>Resource Group &amp; Tag Editor</code>.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711915149957/27a1875a-36df-43b9-a542-a80298674603.png" alt /></p>
</li>
<li><p>Choose the regions from which you want to fetch. The more regions you select, the longer it will take.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711915155266/a82d2c5f-1e25-4cec-a976-3bfb1dd69cb7.png" alt /></p>
</li>
<li><p>Then download them.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711915160943/43ed433a-1f75-4124-8540-30ec0235a430.png" alt /></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Building a sophisticated CodePipeline on AWS with AWS CDK]]></title><description><![CDATA[You got hired as a DevOps engineer and have experience in building CI/CD- pipelines with Gitlab CI and GitHub actions. But the new company requires you to make CI/CD entirely on AWS and put the infrastructure to code.
CodePipeline, CodeBuild, CodeCom...]]></description><link>https://blog.jolo.dev/building-a-sophisticated-codepipeline-on-aws-with-aws-cdk</link><guid isPermaLink="true">https://blog.jolo.dev/building-a-sophisticated-codepipeline-on-aws-with-aws-cdk</guid><category><![CDATA[AWS]]></category><category><![CDATA[aws-cdk]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[CodePipeline]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[JoLo]]></dc:creator><pubDate>Sun, 14 Jan 2024 18:14:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1705255851653/17c8555e-9791-417d-9b9e-cde347769402.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You got hired as a DevOps engineer and have experience in building CI/CD- pipelines with Gitlab CI and GitHub actions. But the new company requires you to make CI/CD entirely on AWS and put the infrastructure to code.</p>
<p>CodePipeline, CodeBuild, CodeCommit! These are the DevOps services of AWS.</p>
<p>Your first reaction is, <em>why</em>? <em>This looks so ugly! Where should I start?</em> But the code is already on CodeCommit. It’s a monorepo.</p>
<p>Your research finds an interesting article on creating sophisticated pipelines entirely on AWS with AWS CDK. That blog post demonstrates each pipeline for different scenarios and how to organize your codebase. Let's have a look together.</p>
<h2 id="heading-simple-pipeline">Simple Pipeline</h2>
<p>The simple pipeline includes a CodePipeline for orchestrating your CI/CD workflow.</p>
<p>A CodePipeline consists of stages, and each stage contains actions. An action is a command which is executed. Each stage produces an artifact resulting from an action that can be passed to the next stage. A stage always needs an artifact as an input, and it can define multiple actions that can run in parallel or sequentially.</p>
<p><img src="https://assets.vrite.io/646ce5fc07ef9ba4530fd23b/oA3rQgfpirc-VYPMqUhSq.png" alt="Simple Pipeline Diagram" /></p>
<p>Below is the AWS CDK code for the simple pipeline. Out of brevity, it left out the import statements.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Create the pipeline</span>
<span class="hljs-keyword">const</span> pipeline = <span class="hljs-keyword">new</span> Pipeline(stack, <span class="hljs-string">"Pipeline"</span>, {
  pipelineName: <span class="hljs-string">"aws-cdk-pipeline-demo"</span>,
});
<span class="hljs-comment">// Create the artifact to store outputs of the pipeline</span>
<span class="hljs-keyword">const</span> artifact = <span class="hljs-keyword">new</span> Artifact();
<span class="hljs-comment">// 1.1. Best Practice to Import Repository</span>
<span class="hljs-keyword">const</span> repository = Repository.fromRepositoryName(
  stack,
  <span class="hljs-string">"Repository"</span>,
  <span class="hljs-string">"aws-cdk-pipeline-demo"</span>,
);
<span class="hljs-comment">// 1.2. Add the source action to the pipeline</span>
pipeline.addStage({
  stageName: <span class="hljs-string">"Source"</span>,
  actions: [
    <span class="hljs-keyword">new</span> CodeCommitSourceAction({
      actionName: <span class="hljs-string">"Source"</span>,
      output: artifact,
      repository,
      branch: <span class="hljs-string">"main"</span>,
    }),
  ],
});
<span class="hljs-comment">// 2. Stage : CodeBuild</span>
pipeline.addStage({
  stageName: <span class="hljs-string">"Build"</span>,
  <span class="hljs-comment">// We can actually paste many actions in here</span>
  <span class="hljs-comment">// But since we can dump anything in one CodeBuild</span>
  actions: [
    <span class="hljs-keyword">new</span> CodeBuildAction({
      actionName: <span class="hljs-string">"Build"</span>,
      input: artifact,
      project: <span class="hljs-keyword">new</span> PipelineProject(stack, <span class="hljs-string">"BuildProject"</span>, {
        projectName: <span class="hljs-string">"aws-cdk-build-project"</span>,
        <span class="hljs-comment">// You can also create your own buildspec.yml file and reference it here instead of using the inline buildspec</span>
        <span class="hljs-comment">// https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html#build-spec-ref-syntax</span>
        buildSpec: BuildSpec.fromObject({
          version: <span class="hljs-string">"0.2"</span>,
          phases: {
            install: {
              commands: [<span class="hljs-string">"npm install"</span>],
            },
            build: {
              commands: [
                <span class="hljs-string">"npm run lint"</span>,
                <span class="hljs-string">"npm run test:unit"</span>,
                <span class="hljs-string">"npm run deploy"</span>,
                <span class="hljs-string">"npm run test:integration"</span>,
              ],
            },
          },
          artifacts: {
            <span class="hljs-string">"base-directory"</span>: <span class="hljs-string">"."</span>,
            files: [<span class="hljs-string">"**/*"</span>],
          },
        }),
      }),
    }),
  ],
});
</code></pre>
<p>However, the Simple Pipeline would not meet your requirements. Because you also want to deploy to different AWS accounts.</p>
<h2 id="heading-cross-account-pipeline">Cross-Account Pipeline</h2>
<p>You continue reading, and indeed, there is a section about deploying to different AWS accounts. You learned that CodePipeline is made with that in mind. Below, you see the diagram for cross-deploying to <code>DEV</code>, <code>STAGING</code>, and <code>PROD</code>- accounts.</p>
<p><img src="https://assets.vrite.io/646ce5fc07ef9ba4530fd23b/Bu0vqQXTDnw755RHDVb1D.png" alt="Cross Account Pipeline Diagram" /></p>
<p>As depicted in the diagram, the <code>Build</code> - the stage is now shorter. You learned that the following stages can reuse the artifact resulting from it. <em>Yes, that makes sense</em>.</p>
<p>You go further, and your thought is <em>CDK as Infrastructure as Code shines because it is written in Typescript, allowing object-oriented programming paradigms to be applied</em>. It is time to get your hands dirty, and you put the <a class="post-section-overview" href="#simple-pipeline">“Simple Pipeline”</a> idea into an abstract class.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">/**
 * Abstract Class for building multiple Pipelines
 * Make it abstract to give the Pipeline a base
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> PipelineStack <span class="hljs-keyword">extends</span> Stack {
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">readonly</span> pipeline: Pipeline;
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">readonly</span> artifact: Artifact;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">scope: Construct, id: <span class="hljs-built_in">string</span>, props: StackProps</span>) {
    <span class="hljs-built_in">super</span>(scope, id, props);

    <span class="hljs-comment">// 0. Stage: CodePipeline (Pre-Requisite)</span>
    <span class="hljs-comment">// 0.1 Create the pipeline</span>
    <span class="hljs-built_in">this</span>.pipeline = <span class="hljs-keyword">new</span> Pipeline(<span class="hljs-built_in">this</span>, <span class="hljs-string">"Pipeline"</span>, {
      pipelineName: id,
    });

    <span class="hljs-comment">// 0.2 Create the artifact to store outputs of the pipeline</span>
    <span class="hljs-built_in">this</span>.artifact = <span class="hljs-keyword">new</span> Artifact(<span class="hljs-string">"SourceArtifact"</span>);

    <span class="hljs-comment">// 1. Stage : CodeCommit</span>
    <span class="hljs-comment">// We are importing the repository because it's a stateful resource</span>
    <span class="hljs-comment">// and should be deployed in a stateful stack</span>
    <span class="hljs-keyword">const</span> repository = Repository.fromRepositoryName(
      <span class="hljs-built_in">this</span>,
      <span class="hljs-string">"Repository"</span>,
      <span class="hljs-string">"aws-cdk-pipeline-demo"</span>,
    );

    <span class="hljs-comment">// 1.2. Add the source action to the pipeline</span>
    <span class="hljs-built_in">this</span>.pipeline.addStage({
      stageName: <span class="hljs-string">"Source"</span>,
      actions: [
        <span class="hljs-keyword">new</span> CodeCommitSourceAction({
          actionName: <span class="hljs-string">"Source"</span>,
          output: <span class="hljs-built_in">this</span>.artifact,
          repository,
          branch: props.branch ?? <span class="hljs-string">"main"</span>,
        }),
      ],
    });
    <span class="hljs-comment">// 2. Stage: CodeBuild</span>
    <span class="hljs-comment">// 2.1. Build Stage</span>
    <span class="hljs-built_in">this</span>.pipeline.addStage({
      stageName: <span class="hljs-string">"Build"</span>,
      <span class="hljs-comment">// We can actually paste many actions in here</span>
      <span class="hljs-comment">// But since we can dump anything in one CodeBuild</span>
      actions: [
        <span class="hljs-keyword">new</span> CodeBuildAction({
          actionName: <span class="hljs-string">"Build"</span>,
          input: <span class="hljs-built_in">this</span>.artifact,
          project: <span class="hljs-keyword">new</span> PipelineProject(<span class="hljs-built_in">this</span>, <span class="hljs-string">"BuildProject"</span>, {
            projectName: <span class="hljs-string">"aws-cdk-build-project-demo"</span>,
            <span class="hljs-comment">// You can also create your own buildspec.yml file and reference it here instead of using the inline buildspec</span>
            <span class="hljs-comment">// https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html#build-spec-ref-syntax</span>
            buildSpec: BuildSpec.fromObject({
              version: <span class="hljs-string">"0.2"</span>,
              phases: {
                install: {
                  commands: [<span class="hljs-string">"npm install"</span>],
                },
                build: {
                  commands: [
                    <span class="hljs-string">"npm run lint"</span>,
                    <span class="hljs-string">"npm run test:unit"</span>,
                    <span class="hljs-string">"npm run build"</span>,
                  ],
                },
              },
              artifacts: {
                <span class="hljs-string">"base-directory"</span>: <span class="hljs-string">"dist"</span>,
                files: [<span class="hljs-string">"**/*"</span>],
              },
            }),
          }),
        }),
      ],
    });

    <span class="hljs-comment">// 2.2. Deploy Stage</span>
    <span class="hljs-comment">// This part is now handled in the Concrete class which inherits this</span>
    <span class="hljs-comment">// However, we can simplify the inheritance and provide helper functions below</span>
    <span class="hljs-comment">// createPipelineProject(), addPoliciesToProject(), addStageToPipeline(), createPipelineProject(), addStageToPipeline()</span>
  }

  <span class="hljs-comment">/**
   * The function creates a pipeline project with specified commands and build specifications.
   * @param {Construct} scope - The scope parameter is the parent construct that the pipeline project
   * will be created under. It defines the scope or context in which the project will exist.
   * @param {string} name - The name of the pipeline project. It is used as the project name and also as
   * the name of the construct in the AWS CDK.
   * @param {Command} commands - The `commands` parameter is an object that contains the commands to be
   * executed in different phases of the build process. It has the following structure:
   * @returns a new instance of the `PipelineProject` class.
   */</span>
  <span class="hljs-keyword">public</span> createPipelineProject(
    scope: Construct,
    name: <span class="hljs-built_in">string</span>,
    commands: Command,
  ): PipelineProject {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> PipelineProject(scope, name, {
      projectName: name,
      environment: {
        buildImage: LinuxBuildImage.STANDARD_7_0,
        computeType: ComputeType.LARGE,
      },
      buildSpec: BuildSpec.fromObject({
        version: <span class="hljs-string">"0.2"</span>,
        phases: {
          pre_build: {
            commands: commands.preBuild,
          },
          install: {
            commands: commands.install,
          },
          build: {
            commands: commands.build,
          },
          post_build: {
            commands: commands.postBuild,
          },
        },
        artifacts: {
          <span class="hljs-string">"base-directory"</span>: <span class="hljs-string">"."</span>,
          files: [<span class="hljs-string">"**/*"</span>],
        },
      }),
    });
  }

  <span class="hljs-comment">/**
   * The function adds a stage to a pipeline with a given stage name and a list of actions.
   * @param {string} stageName - A string representing the name of the stage to be added to the
   * pipeline.
   * @param {Action[]} actions - The `actions` parameter is an array of `Action` objects. Each `Action`
   * object represents a specific action or task that needs to be performed in the pipeline stage.
   */</span>
  <span class="hljs-keyword">public</span> addStageToPipeline(stageName: <span class="hljs-built_in">string</span>, actions: Action[]): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.pipeline.addStage({
      stageName,
      actions,
    });
  }
}
</code></pre>
<p>The beauty of the code is that you can simplify methods for the concrete class. You create a method called <code>createPipelineProject</code> which takes a third parameter <code>commands</code> from the type <code>Command</code> :</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> Command {
  preBuild?: <span class="hljs-built_in">string</span>[];
  install?: <span class="hljs-built_in">string</span>[];
  build?: <span class="hljs-built_in">string</span>[];
  postBuild?: <span class="hljs-built_in">string</span>[];
}
</code></pre>
<p>The concrete class would use this method whenever it creates a <code>CodeBuild</code>-Action like</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">this</span>.createPipelineProject(<span class="hljs-built_in">this</span>, <span class="hljs-string">`Deploy-<span class="hljs-subst">${account.stage}</span>`</span>, {
  install: [<span class="hljs-string">"./scripts/assume-role.sh"</span>],
  build: [<span class="hljs-string">`npx cdk deploy --stage <span class="hljs-subst">${account.stage}</span>`</span>],
  postBuild: [<span class="hljs-string">"npm run test:integration"</span>],
});
</code></pre>
<p><em>Wow, I simplified the method because it’s now abstracted from the parent,</em> you say. Now, you implement <code>CrossAccountPipelineStack</code></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> Account {
  stage: <span class="hljs-built_in">string</span>;
  <span class="hljs-built_in">number</span>: <span class="hljs-built_in">string</span>;
  region: <span class="hljs-built_in">string</span>; <span class="hljs-comment">// or to narrow it:  "eu-central-1" | "us-east-1"</span>
}

<span class="hljs-keyword">const</span> accounts: Account[] = [
  {
    <span class="hljs-built_in">number</span>: <span class="hljs-string">"111111111111"</span>,
    region: <span class="hljs-string">"eu-central-1"</span>,
    stage: <span class="hljs-string">"dev"</span>,
  },
  {
    <span class="hljs-built_in">number</span>: <span class="hljs-string">"222222222222"</span>,
    region: <span class="hljs-string">"eu-central-1"</span>,
    stage: <span class="hljs-string">"staging"</span>,
  },
  {
    <span class="hljs-built_in">number</span>: <span class="hljs-string">"333333333333"</span>,
    region: <span class="hljs-string">"eu-central-1"</span>,
    stage: <span class="hljs-string">"prod"</span>,
  },
];

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CrossAccountPipelineStack <span class="hljs-keyword">extends</span> PipelineStack {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">scope: Construct, id: <span class="hljs-built_in">string</span>, props: StackProps</span>) {
    <span class="hljs-built_in">super</span>(scope, id, props);

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> account <span class="hljs-keyword">of</span> accounts) {
      <span class="hljs-comment">// Create CodeBuild Project</span>
      <span class="hljs-keyword">const</span> deploy = <span class="hljs-built_in">this</span>.createPipelineProject(
        <span class="hljs-built_in">this</span>,
        <span class="hljs-string">`DeployTo<span class="hljs-subst">${account.stage}</span>`</span>,
        {
          install: [<span class="hljs-string">"./scripts/assume-role.sh"</span>],
          build: [
            <span class="hljs-string">`npx sst deploy --stage <span class="hljs-subst">${account.stage}</span>`</span>,
          ],
          postBuild: [<span class="hljs-string">"npm run test:integration"</span>],
        },
      );

      <span class="hljs-comment">// Create Action for Codepipeline</span>
      <span class="hljs-keyword">const</span> actions = [
        <span class="hljs-keyword">new</span> CodeBuildAction({
          actionName: <span class="hljs-string">`Deploy-<span class="hljs-subst">${account.stage.toUpperCase()}</span>`</span>,
          input: <span class="hljs-built_in">this</span>.artifact,
          project: deploy,
          runOrder: <span class="hljs-number">2</span>,
        }),
      ];

      <span class="hljs-comment">// Add Action to Pipeline</span>
      <span class="hljs-built_in">this</span>.addStageToPipeline(
        <span class="hljs-string">`Deploy-<span class="hljs-subst">${account.stage.toUpperCase()}</span>`</span>,
        account.stage !== <span class="hljs-string">"dev"</span>
          ? [
              <span class="hljs-keyword">new</span> ManualApprovalAction({
                actionName: <span class="hljs-string">"ManualApproval"</span>,
                additionalInformation: <span class="hljs-string">`Review Before Deploy <span class="hljs-subst">${account.stage}</span>`</span>,
                runOrder: <span class="hljs-number">1</span>,
              }),
              ...actions,
            ]
          : actions,
      );
    }
  }
}
</code></pre>
<p>The <code>CrossAccountPipelineStack</code> will get the <code>Source</code> and the <code>Build</code>- stage out-of-the-box, and the following is a loop through <code>accounts</code>. You can even add an action as you did in line 56, whether we want to add a <code>ManualApprovalAction</code> to our stage.</p>
<p>You wonder now how you can add that to your <strong>Monorepo project.</strong></p>
<h2 id="heading-pipeline-in-a-monorepo-setup">Pipeline in a Monorepo setup</h2>
<p>Your project has a <code>backend</code>, and a <code>frontend</code> inside the <code>packages</code>.</p>
<p>Here is where it can get complex.</p>
<p><em>When to trigger the pipeline?</em> <em>Will all packages be deployed?</em> <em>How can I only deploy the packages related to the files which have been changed?</em></p>
<p>You think you can only solve this with <strong>“Multiple Pipelines”.</strong></p>
<h3 id="heading-multiple-pipelines">Multiple Pipelines</h3>
<p>Okay, you think that each package needs to create its own CodePipeline. Since the setup is the same, you copy the CodePipeline from the diagram above.</p>
<p>But when should the pipeline be triggered? After researching, you find out that almost all services emit events. Thus, you can use EventBridge with Lambda. You are going to note that down.</p>
<p><img src="https://assets.vrite.io/646ce5fc07ef9ba4530fd23b/v9KxRQIcsqeZ4m5RT0vGe.png" alt /></p>
<p>AWS emits an event every time something changes in our CodeCommit. We can catch that with EventBridge. The event pattern for the event looks like</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"detail-type"</span>: [<span class="hljs-string">"CodeCommit Repository State Change"</span>],
  <span class="hljs-attr">"resources"</span>: [<span class="hljs-string">"repository.repositoryArn"</span>],
  <span class="hljs-attr">"source"</span>: [<span class="hljs-string">"aws.codecommit"</span>],
  <span class="hljs-attr">"detail"</span>: {
    <span class="hljs-attr">"referenceType"</span>: [<span class="hljs-string">"branch"</span>],
    <span class="hljs-attr">"event"</span>: [<span class="hljs-string">"referenceCreated"</span>,<span class="hljs-string">"referenceUpdated"</span>],
    <span class="hljs-attr">"referenceName"</span>: [<span class="hljs-string">"main"</span>]
  }
}
</code></pre>
<p>You add that in your abstract class <code>PipelineStack</code> and create the respective Lambda and a policy for triggering CodePipeline to check for differences in CodeCommit.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// MultiPipelineStack: Lambda to trigger the correct Pipeline</span>
<span class="hljs-comment">// Because it's event-based, we need to understand the event pattern.</span>
<span class="hljs-keyword">const</span> eventPattern = {
  <span class="hljs-string">"detail-type"</span>: [<span class="hljs-string">"CodeCommit Repository State Change"</span>],
  resources: [repository.repositoryArn],
  source: [<span class="hljs-string">"aws.codecommit"</span>],
  detail: {
    referenceType: [<span class="hljs-string">"branch"</span>],
    event: [<span class="hljs-string">"referenceCreated"</span>, <span class="hljs-string">"referenceUpdated"</span>],
    referenceName: [props.branch ?? <span class="hljs-string">"main"</span>],
  },
};

<span class="hljs-comment">// Policy for the Lambda</span>
<span class="hljs-keyword">const</span> initialPolicy = [
  <span class="hljs-keyword">new</span> PolicyStatement({
    actions: [<span class="hljs-string">"codecommit:GetDifferences"</span>],
    resources: [repository.repositoryArn],
  }),
  <span class="hljs-keyword">new</span> PolicyStatement({
    actions: [<span class="hljs-string">"codepipeline:StartPipelineExecution"</span>],
    resources: [<span class="hljs-string">`arn:aws:codepipeline:<span class="hljs-subst">${<span class="hljs-built_in">this</span>.region}</span>:<span class="hljs-subst">${<span class="hljs-built_in">this</span>.account}</span>:*`</span>],
  }),
];

<span class="hljs-keyword">const</span> customLambdaPipelineTrigger =
  <span class="hljs-comment">// We need to check if the function already exists</span>
  <span class="hljs-comment">// Otherwise whenever we create a new pipeline it would create a new Lambda but we only need one</span>
  <span class="hljs-built_in">Function</span>.fromFunctionName(
    <span class="hljs-built_in">this</span>,
    <span class="hljs-string">"CustomLambdaPipelineTrigger"</span>,
    <span class="hljs-string">"customLambdaPipelineTrigger"</span>,
  ) ??
  <span class="hljs-keyword">new</span> <span class="hljs-built_in">Function</span>(<span class="hljs-built_in">this</span>, <span class="hljs-string">"CustomLambdaPipelineTrigger"</span>, {
    handler: <span class="hljs-string">"packages/core/src/customLambdaPipelineTrigger.handler"</span>,
    description:
      <span class="hljs-string">"Trigger the pipeline when a commit is pushed to the master branch"</span>,
    functionName: <span class="hljs-string">"customLambdaPipelineTrigger"</span>,
    initialPolicy,
  });

<span class="hljs-comment">// This emits an event and will halt before the pipeline is triggered</span>
<span class="hljs-built_in">this</span>.pipeline.onEvent(<span class="hljs-string">"EventTrigger"</span>, {
  eventPattern,
  description: <span class="hljs-string">"Trigger the pipeline when a commit is pushed to the branch"</span>,
  target: <span class="hljs-keyword">new</span> LambdaFunction(customLambdaPipelineTrigger),
});
</code></pre>
<p>You realize that you need to check if the Lambda Function already exists; otherwise, whenever a new pipeline is created, it will create a new Lambda.</p>
<p>Now, the pipeline will not be triggered immediately. Instead, the event is sent to EventBridge, which forwards it to Lambda. Lambda decides when to start which pipeline based on the logic.</p>
<h3 id="heading-the-customlambdapipelinetrigger-lambda">The <code>CustomLambdaPipelineTrigger</code>- Lambda</h3>
<p>Okay, now you need to write a Lambda and decide to go with Typescript. AWS CDK comes with an interface <code>EventPattern</code> which it can extend from.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { EventPattern } <span class="hljs-keyword">from</span> <span class="hljs-string">'aws-cdk-lib/aws-events'</span>;

<span class="hljs-keyword">interface</span> CodeCommitStateChangeEvent <span class="hljs-keyword">extends</span> EventPattern {
  detail: {
    callerUserArn: <span class="hljs-built_in">string</span>;
    commitId: <span class="hljs-built_in">string</span>;
    oldCommitId: <span class="hljs-built_in">string</span>;
    event: <span class="hljs-built_in">string</span>;
    referenceFullName: <span class="hljs-built_in">string</span>;
    referenceName: <span class="hljs-built_in">string</span>;
    referenceType: <span class="hljs-built_in">string</span>;
    repositoryId: <span class="hljs-built_in">string</span>;
    repositoryName: <span class="hljs-built_in">string</span>;
  };
}
</code></pre>
<p>Because it will compare the file differences and execute the CodePipeline, you add the <a target="_blank" href="https://www.npmjs.com/package/@aws-sdk/client-codecommit">@aws-sdk/client-codecommit</a> and <a target="_blank" href="https://www.npmjs.com/package/@aws-sdk/client-codepipeline">@aws-sdk/client-codepipeline</a> npm packages.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { CodeCommitClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@aws-sdk/client-codecommit'</span>;
<span class="hljs-keyword">import</span> { CodePipelineClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@aws-sdk/client-codepipeline'</span>;

<span class="hljs-keyword">const</span> codecommitClient = <span class="hljs-keyword">new</span> CodeCommitClient({});
<span class="hljs-keyword">const</span> codepipelineClient = <span class="hljs-keyword">new</span> CodePipelineClient({});
</code></pre>
<p>This is the structure of your Monorepo</p>
<pre><code class="lang-sh">├── packages
│   ├── backend
│   ├── core
│   └── frontend
├── stacks
│   ├── BackendStack.ts
│   ├── FrontendStack.ts
│   └── PipelineStack
</code></pre>
<p>The <code>core</code> is a shared package between <code>backend</code> and <code>frontend</code>. In total, you need two pipelines. Furthermore, you define the paths for the respective pipeline.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> stacksPath = <span class="hljs-string">"stacks"</span>;
<span class="hljs-keyword">const</span> packages = <span class="hljs-string">"packages"</span>;
<span class="hljs-keyword">const</span> commonFolder = [<span class="hljs-string">"stacks/PipelineStack"</span>, <span class="hljs-string">`<span class="hljs-subst">${packages}</span>/core`</span>];

<span class="hljs-keyword">const</span> PipelinePath = {
  backend: [
    <span class="hljs-string">`<span class="hljs-subst">${packages}</span>/backend`</span>,
    <span class="hljs-string">`<span class="hljs-subst">${stacksPath}</span>/BackendStack.ts`</span>,
    ...commonFolder,
  ],
  frontend: [
    <span class="hljs-string">`<span class="hljs-subst">${packages}</span>/frontend`</span>,
    <span class="hljs-string">`<span class="hljs-subst">${stacksPath}</span>/FrontendStack.ts`</span>,
    ...commonFolder,
  ],
};

<span class="hljs-keyword">const</span> Pipeline = {
  frontend: <span class="hljs-string">"FrontendStackPipeline"</span>,
  backend: <span class="hljs-string">"BackendStackPipeline"</span>,
};
</code></pre>
<p>If something is changed in the <code>commonFolder</code>, each pipeline is triggered. Otherwise, it depends on the <code>packages</code> - path. It is essential to put the correct name in the <code>Pipeline</code>- object.</p>
<p>After you set up the pre-requisite, you write the Lambda handler</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handler</span>(<span class="hljs-params">event: Prettify&lt;CodeCommitStateChangeEvent&gt;</span>) </span>{
  <span class="hljs-comment">// Use the SDK to get the difference from the new commit and previous commit</span>
  <span class="hljs-keyword">const</span> getDifferences = <span class="hljs-keyword">new</span> GetDifferencesCommand({
    repositoryName: event.detail.repositoryName,
    afterCommitSpecifier: event.detail.commitId,
    beforeCommitSpecifier: event.detail.oldCommitId,
  });
  <span class="hljs-keyword">const</span> codecommit = <span class="hljs-keyword">await</span> codecommitClient.send(getDifferences);

  <span class="hljs-comment">// iterate over the paths in PipelinePath to check which pipeline should be triggered</span>
  <span class="hljs-comment">// e.g. if the path includes functions/src/Application, then trigger the AppPipeline</span>
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> path <span class="hljs-keyword">in</span> PipelinePath) {
    <span class="hljs-keyword">const</span> typePath = path <span class="hljs-keyword">as</span> keyof <span class="hljs-keyword">typeof</span> Pipeline;
    <span class="hljs-keyword">const</span> pipeline = Pipeline[typePath];

    <span class="hljs-keyword">if</span> (codecommit.differences) {
      <span class="hljs-keyword">const</span> check = codecommit.differences.some(<span class="hljs-function">(<span class="hljs-params">difference</span>) =&gt;</span> {
        <span class="hljs-keyword">return</span> PipelinePath[typePath].some(<span class="hljs-function">(<span class="hljs-params">substring</span>) =&gt;</span> {
          <span class="hljs-comment">// Check if the path includes the substring</span>
          <span class="hljs-keyword">return</span> difference.afterBlob?.path?.includes(substring);
        });
      });

      <span class="hljs-keyword">if</span> (check) {
        <span class="hljs-keyword">const</span> triggerPipelineCommand = <span class="hljs-keyword">new</span> StartPipelineExecutionCommand({
          name: pipeline,
        });
        <span class="hljs-keyword">await</span> codepipelineClient.send(triggerPipelineCommand);
      }
    }
  }
}
</code></pre>
<ol>
<li><p>It gets the differences between two commits with <code>GetDifferencesCommand</code> (Line 3-8).</p>
</li>
<li><p>The <code>for-in</code>- The loop goes through each file path of the packages (Lines 12-31).</p>
</li>
<li><p>It extracts the pipeline type path and gets the path according to the pipeline name (Lines 13-14).</p>
</li>
<li><p>If there are differences resulting from the <code>GetDifferencesCommand</code> , it uses the <code>some</code> method tests whether at least one element in the array passes the test implemented by the provided function. In this case, it's checking if any of the <code>differences</code> have a <code>afterBlob.path</code> that includes any of the substrings in <code>PipelinePath[typePath]</code> (Line 16-22)</p>
</li>
<li><p>If Step 4 is true, it triggers the pipeline according to the <code>pipeline</code> by using the SDK (Lines 24-29)</p>
</li>
</ol>
<h3 id="heading-multipipelinestack">MultiPipelineStack</h3>
<p>Along the way, you find an excellent framework, <a target="_blank" href="https://sst.dev/">SST</a>. Which is much faster than CDK and provides a better DX<a target="_blank" href="%5Bhttps://medium.com/@jolodev/sst-24fd4e2f4d7c%5D(https://medium.com/@jolodev/sst-24fd4e2f4d7c)">^1</a>. Here is how you then define your <code>MultiPipelineStack</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { StackContext } <span class="hljs-keyword">from</span> <span class="hljs-string">"sst/constructs"</span>;
<span class="hljs-keyword">import</span> { CrossAccountPipelineStack } <span class="hljs-keyword">from</span> <span class="hljs-string">"./CrossAccountPipelineStack"</span>;
<span class="hljs-keyword">import</span> { accounts } <span class="hljs-keyword">from</span> <span class="hljs-string">"./accounts"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MultiPipelineStack</span>(<span class="hljs-params">{ stack }: StackContext</span>) </span>{
  <span class="hljs-keyword">new</span> CrossAccountPipelineStack(stack, <span class="hljs-string">"FrontendPipelineStack"</span>, {
    accounts,
    purpose: <span class="hljs-string">"frontend"</span>,
  });

  <span class="hljs-keyword">new</span> CrossAccountPipelineStack(stack, <span class="hljs-string">"BackendPipelineStack"</span>, {
    accounts,
    purpose: <span class="hljs-string">"backend"</span>,
  });
}
</code></pre>
<p>Since you have defined a <code>CrossAccountPipelineStack</code> you put multiple of them in that stack. You distinguish them by adding a <code>purpose</code> into the <code>StackProps</code>, which you then renamed to <code>CrossAccountPipelineStackProps</code>. This is needed to add it to the environment variable. <code>PURPOSE</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// in CrossAccountPipelineStack.ts</span>
<span class="hljs-keyword">type</span> CrossAccountPipelineStackProps = PipelineStackProps &amp; {
  purpose: <span class="hljs-built_in">string</span>;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CrossAccountPipelineStack <span class="hljs-keyword">extends</span> PipelineStack {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">scope: Construct, id: <span class="hljs-built_in">string</span>, props: CrossAccountPipelineStackProps</span>) {
                                                    <span class="hljs-comment">// ^ This is changed</span>
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> account <span class="hljs-keyword">of</span> accounts) {
      <span class="hljs-comment">// Create CodeBuild Project</span>
      <span class="hljs-keyword">const</span> deploy = <span class="hljs-built_in">this</span>.createPipelineProject(<span class="hljs-built_in">this</span>, <span class="hljs-string">`DeployTo<span class="hljs-subst">${account.stage}</span>`</span>, {
        install: [<span class="hljs-string">'./scripts/assume-role.sh'</span>],
        <span class="hljs-comment">// This is how you create a new environment variable</span>
        build: [<span class="hljs-string">`PURPOSE=<span class="hljs-subst">${props.purpose}</span> npx sst deploy --stage <span class="hljs-subst">${account.stage}</span>`</span>],
        postBuild: [<span class="hljs-string">'npm run test:integration'</span>],
      });
      <span class="hljs-comment">// ...</span>
    }
  }
}
</code></pre>
<p>The deployment is controlled by the <code>PURPOSE</code> environmental variable inside the <code>sst.config.ts</code> .</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { SSTConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"sst"</span>;
<span class="hljs-keyword">import</span> { BackendStack } <span class="hljs-keyword">from</span> <span class="hljs-string">"./stacks/BackendStack"</span>;
<span class="hljs-keyword">import</span> { FrontendStack } <span class="hljs-keyword">from</span> <span class="hljs-string">"./stacks/FrontendStack"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  config(_input) {
    <span class="hljs-keyword">return</span> {
      name: <span class="hljs-string">"aws-ug-codepipeline-demo"</span>,
      region: <span class="hljs-string">"eu-central-1"</span>,
    };
  },
  stacks(app) {
    <span class="hljs-keyword">switch</span> (process.env.PURPOSE) {
      <span class="hljs-keyword">case</span> <span class="hljs-string">"backend"</span>:
        app.stack(BackendStack);
        <span class="hljs-keyword">break</span>;
      <span class="hljs-keyword">case</span> <span class="hljs-string">"frontend"</span>:
        app.stack(FrontendStack);
        <span class="hljs-keyword">break</span>;
      <span class="hljs-keyword">default</span>:
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"PURPOSE environment variable is not set"</span>);
    }
  },
} satisfies SSTConfig;
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Puh, you have written a lot of code, but you’re satisfied with the work and could even impress the other Engineers, especially your CTO.</p>
<p>You are proud that you have leveraged AWS CDK to make your CodePipeline less suck. You enabled the OOP paradigm to reuse code and abstract classes for the concrete class to have a pipeline foundation. It can fill the need for cross-account deployment and trigger the correct pipeline when having multiple pipelines in a Monorepo project.</p>
<p>For now, the backend team can push to the <code>main</code>- branch, and it triggers the <code>BackendPipeline</code> and the <code>FrontendPipeline</code> will deploy the NextJS application whenever a new file changes in the <code>frontend</code>- package.</p>
<p>However, you know there are ways to improve the pipeline by adding notifications and caches for CodeBuild and triggering a pipeline whenever a Pull Request has been created.</p>
]]></content:encoded></item><item><title><![CDATA[Demystifying AWS CDK's ECS Pattern]]></title><description><![CDATA[AWS CDK is my first choice when choosing an IaC for AWS (actually, it's a lie; it's SST). Using the same programming language for IaC and application development is convenient, allowing me to use the same tools without switching contexts.
Another pow...]]></description><link>https://blog.jolo.dev/demystifying-aws-cdks-ecs-pattern</link><guid isPermaLink="true">https://blog.jolo.dev/demystifying-aws-cdks-ecs-pattern</guid><category><![CDATA[AWS]]></category><category><![CDATA[aws-cdk]]></category><category><![CDATA[Docker]]></category><category><![CDATA[aws-fargate]]></category><dc:creator><![CDATA[JoLo]]></dc:creator><pubDate>Tue, 12 Dec 2023 20:44:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1702413657697/97ed85d5-bd0a-4679-85c7-4fb04ce444b6.gif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>AWS CDK is my first choice when choosing an IaC for AWS (actually, it's a lie; it's <a target="_blank" href="https://medium.com/@jolodev/sst-24fd4e2f4d7c">SST</a>). Using the same programming language for IaC and application development is convenient, allowing me to use the same tools without switching contexts.</p>
<p>Another powerful asset is L3-Constructs or <code>patterns</code>. L3- constructs simplify multiple resources for common tasks. One of them is the <code>ECS-Pattern</code>.</p>
<p>However, they abstract away many resources, and in this blog post, I am going to demystify the <code>ApplicationLoadBalancedFargateService</code> construct.</p>
<h2 id="heading-applicationloadbalancedfargateservice"><code>ApplicationLoadBalancedFargateService</code></h2>
<p>Let’s break down the verbose construct <code>ApplicationLoadBalancedFargateService</code> into their resources and what code you may need.</p>
<h3 id="heading-minimal">Minimal</h3>
<p>Below is the minimal code that deploys an Application Load Balancer, a Fargate ECS Cluster and task, Target Groups, and a Listener on port 80. Depending on the deployment region, the code deploys a VPC and at least two private and public subnets.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> service = <span class="hljs-keyword">new</span> ApplicationLoadBalancedFargateService(stack, <span class="hljs-string">"AlbFargateService"</span>, {
    <span class="hljs-comment">// You have to define a task image option</span>
    taskImageOptions: {
      image: ContainerImage.fromAsset(<span class="hljs-string">"path/to/your/Dockerfile"</span>),
    },
  },
);
</code></pre>
<p>Or if you want to use your own <code>taskDefinition</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> service = <span class="hljs-keyword">new</span> ApplicationLoadBalancedFargateService(stack, <span class="hljs-string">'AlbFargateService'</span>, {
  <span class="hljs-comment">// The task definition will mostlikely be bigger as you need to pass props</span>
  taskDefinition: <span class="hljs-keyword">new</span> FargateTaskDefinition(stack, <span class="hljs-string">'TaskDefinition'</span>, {
    <span class="hljs-comment">// here you will need add props which looks similar to above `taskImageOptions`</span>
  })
})
</code></pre>
<p>That is what you will get. Out of simplicity, I left out the IAM roles and policies.</p>
<p><img src="https://assets.vrite.io/646ce5fc07ef9ba4530fd23b/dsD_5ZshvhWzRm4W6wbhX.png" alt="Minimal Code for ALB ECS pattern" /></p>
<p>When using the <code>taskDefinition</code>you often need to configure the container, which usually ends up in more code.</p>
<h3 id="heading-https">HTTPS</h3>
<p>However, the minimal code approach only listens on port 80 (HTTP). If you want to create a serious application using AWS ECS Fargate, you want HTTPS (port 443).</p>
<p>As a pre-requisite, you must have a <a target="_blank" href="https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zones-working-with.html">Hosted Zone</a> and a domain name in your Route53.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">new</span> ApplicationLoadBalancedFargateService(stack, <span class="hljs-string">"AlbFargateServiceHttps"</span>, {
  taskImageOptions: {
    image: ContainerImage.fromAsset(<span class="hljs-string">"path/to/your/Dockerfile"</span>),
  },
  publicLoadBalancer: <span class="hljs-literal">true</span>,
  redirectHTTP: <span class="hljs-literal">true</span>, <span class="hljs-comment">// Best practice to redirect if calling URL with `http`</span>
  cpu: <span class="hljs-number">512</span>, <span class="hljs-comment">// &lt;-- Default: 0.25 Otherwise container dies to quickly</span>
  memoryLimitMiB: <span class="hljs-number">1024</span>, <span class="hljs-comment">// &lt;-- Otherwise container dies to quickly</span>
  domainName: <span class="hljs-string">"my.example.com"</span>,
  domainZone: HostedZone.fromHostedZoneAttributes(<span class="hljs-built_in">this</span>, <span class="hljs-string">"HostedZone"</span>, {
    hostedZoneId: props.hostedZoneId,
    zoneName: props.zoneName,
  }),
});
</code></pre>
<p>And this is what it looks like</p>
<p><img src="https://assets.vrite.io/646ce5fc07ef9ba4530fd23b/5Fn6cXrKt4EKA7oID33ow.png" alt="ALB ECS Pattern with HTTPs" /></p>
<p>7 extra lines create a second listener on port 443, and the Application Load Balancer (ALB) terminates HTTPS with an AWS Certificate Manager (ACM) certificate. This certificate will be automatically created when adding <code>domainName</code> and <code>domainZone</code>.</p>
<p>Now, you will be able to access your container with <a target="_blank" href="https://my.example.com"><code>https://my.example.com</code></a>.</p>
<h3 id="heading-authentication-with-cognito">Authentication with Cognito</h3>
<p>Cognito is a great option if we want to add an authentication layer. This means we must authenticate with Cognito before loading data into the Docker container. To achieve this, we add an action to our listener that tells the user to authenticate before being directed to the container.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Define the Service first</span>
<span class="hljs-keyword">const</span> service = <span class="hljs-keyword">new</span> ApplicationLoadBalancedFargateService(
  stack,
  <span class="hljs-string">"AlbFargateService"</span>,
  {
    taskImageOptions: {
      image: ContainerImage.fromAsset(<span class="hljs-string">"path/to/your/Dockerfile"</span>),
    },
    publicLoadBalancer: <span class="hljs-literal">true</span>,
    redirectHTTP: <span class="hljs-literal">true</span>, <span class="hljs-comment">// Best practice to redirect if calling URL with `http`</span>
    cpu: <span class="hljs-number">512</span>, <span class="hljs-comment">// &lt;-- Default: 0.25 Otherwise container dies to quickly</span>
    memoryLimitMiB: <span class="hljs-number">1024</span>, <span class="hljs-comment">// &lt;-- Otherwise container dies to quickly</span>
    domainName: <span class="hljs-string">"my.example.com"</span>,
    domainZone: HostedZone.fromHostedZoneAttributes(<span class="hljs-built_in">this</span>, <span class="hljs-string">"HostedZone"</span>, {
      hostedZoneId: props.hostedZoneId,
      zoneName: props.zoneName,
    }),
  },
);

<span class="hljs-comment">// Set Cognito</span>
<span class="hljs-keyword">const</span> userPool = <span class="hljs-keyword">new</span> UserPool(<span class="hljs-built_in">this</span>, <span class="hljs-string">"UserPool"</span>);
<span class="hljs-keyword">const</span> userPoolClient = <span class="hljs-keyword">new</span> UserPoolClient(<span class="hljs-built_in">this</span>, <span class="hljs-string">"UserPoolClient"</span>, {
  userPool,
  generateSecret: <span class="hljs-literal">true</span>,
  authFlows: {
    userPassword: <span class="hljs-literal">true</span>,
  },
  oAuth: {
    flows: {
      authorizationCodeGrant: <span class="hljs-literal">true</span>,
    },
    callbackUrls: [
      <span class="hljs-string">`https://<span class="hljs-subst">${service.loadBalancer.loadBalancerDnsName}</span>/oauth2/idpresponse`</span>,
    ],
  },
});
<span class="hljs-keyword">const</span> userPoolDomain = <span class="hljs-keyword">new</span> UserPoolDomain(<span class="hljs-built_in">this</span>, <span class="hljs-string">"Domain"</span>, {
  userPool,
  cognitoDomain: {
    domainPrefix: <span class="hljs-string">"test-cdk-prefix"</span>,
  },
});

<span class="hljs-comment">// Add actions to Target Groups</span>
service.listener.addAction(<span class="hljs-string">"CognitoListener"</span>, {
  action: <span class="hljs-keyword">new</span> AuthenticateCognitoAction({
    userPool,
    userPoolClient,
    userPoolDomain,
    next: ListenerAction.forward([service.targetGroup]),
  }),
});
</code></pre>
<p>Line 34 is crucial for redirecting back to the load balancer domain. Lines 46 - 53 describe the new action for the listener, where we add the Cognito Suite from lines 22-43.</p>
<p>The “simplified” diagram</p>
<p><img src="https://assets.vrite.io/646ce5fc07ef9ba4530fd23b/p-RZtd9Ii__5nhozMVURM.png" alt="Using ALB ECS pattern with Cognito" /></p>
<h3 id="heading-cognito-with-custom-domain-and-identity-federation">Cognito with Custom Domain and Identity Federation</h3>
<p>If we want to use an Identity Federation like GitHub, we can utilize an L2-Construct <code>UserPoolIdentityProviderOidc</code>. Moreover, if we aim to establish a personalized domain for our login page, we need to generate a certificate initially and then add it to Route 53's Hosted Zone.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">declare</span> <span class="hljs-keyword">const</span> service: ApplicationLoadBalancedFargateService <span class="hljs-comment">// like in the previous code</span>
<span class="hljs-keyword">declare</span> <span class="hljs-keyword">const</span> userPool: UserPool(<span class="hljs-built_in">this</span>, <span class="hljs-string">'UserPool'</span>) <span class="hljs-comment">// like in the previous code</span>
<span class="hljs-keyword">declare</span> <span class="hljs-keyword">const</span> userPoolClient: UserPoolClient(<span class="hljs-built_in">this</span>, <span class="hljs-string">'UserPoolClient'</span>) <span class="hljs-comment">// like in the previous code</span>
<span class="hljs-keyword">const</span> userPoolIdentityProviderOidc = <span class="hljs-keyword">new</span> UserPoolIdentityProviderOidc(<span class="hljs-built_in">this</span>, <span class="hljs-string">'GitHubOidc'</span>, {
    clientId: <span class="hljs-string">'register-the-app-on-github'</span>,
    clientSecret: <span class="hljs-string">'get-it-on-github'</span>,
    issuerUrl: <span class="hljs-string">'https://github.com'</span>,
    userPool,
    name: <span class="hljs-string">'GitHub'</span>,
});

<span class="hljs-keyword">const</span> hostedZone = HostedZone.fromHostedZoneAttributes(<span class="hljs-built_in">this</span>, <span class="hljs-string">'HostedZone'</span>, {
    hostedZoneId: <span class="hljs-string">'my-id'</span>,
    zoneName: <span class="hljs-string">'example.com'</span>,
})
<span class="hljs-keyword">const</span> certificate = <span class="hljs-keyword">new</span> CdkCertificate(<span class="hljs-built_in">this</span>, <span class="hljs-string">'Certificate'</span>, {
    domainName: <span class="hljs-string">'github.example.com'</span>,
    validation: CertificateValidation.fromDns(hostedZone),
}); <span class="hljs-comment">// ❗ You need a new one</span>
<span class="hljs-keyword">const</span> userPoolDomain = <span class="hljs-keyword">new</span> UserPoolDomain(<span class="hljs-built_in">this</span>, <span class="hljs-string">'UserPoolDomain'</span>, {
    userPool,
    customDomain: {
        certificate,
        domainName: <span class="hljs-string">'github.example.com'</span>,
    }
});

service.listener.addAction(<span class="hljs-string">'CognitoListener'</span>, {
    action: <span class="hljs-keyword">new</span> AuthenticateCognitoAction({
        userPool,
        userPoolClient,
        userPoolDomain,
        next: ListenerAction.forward([service.targetGroup]),
    }),
});
</code></pre>
<p><strong>Note:</strong> Lines 1-3 have the same implementation as in the previous section.</p>
<p><img src="https://assets.vrite.io/646ce5fc07ef9ba4530fd23b/xPj7v9VU8PHtGe61mLJh8.png" alt /></p>
<p>It is possible to use Cognito with Identity Federation and a custom domain. However, you may not require a Cognito Custom Domain if you already use ID Federation with services like GitHub, Google, or Azure AD. A custom domain is available for completeness but may not be necessary in certain cases.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The ECS pattern is very powerful and provides many services with less code. They still provide properties for customization. However, you most likely end up with more code (at least because of having HTTPs). What we haven’t seen are roles and policies. Do not worry the community won’t let you down and will try to reach the least privileged. And even if you have doubts, you could easily add your roles or policies.</p>
<p>In this blog post, we have demystified the <code>ApplicationLoadBalancedFargateService</code> pattern in their services, and I hope you understand this pattern a bit better. I recommend using them. However, don’t blindly trust them; check their underlying resources.</p>
]]></content:encoded></item><item><title><![CDATA[Eslint Dropping their Formatting Rules]]></title><description><![CDATA[ESLint has been my go-to tool for almost all of my Node.js projects. It is a mature and widely used tool that I rely on for its effectiveness. Moreover, I use ESLint as my default formatter, as it helps me maintain a consistent code style throughout ...]]></description><link>https://blog.jolo.dev/eslint-dropping-their-formatting-rules</link><guid isPermaLink="true">https://blog.jolo.dev/eslint-dropping-their-formatting-rules</guid><category><![CDATA[development]]></category><category><![CDATA[eslint]]></category><category><![CDATA[vscode]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[JoLo]]></dc:creator><pubDate>Mon, 20 Nov 2023 21:28:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1700515523215/a9170eb4-8fda-4570-95f4-f1b3b49841fd.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>ESLint has been my go-to tool for almost all of my Node.js projects. It is a mature and widely used tool that I rely on for its effectiveness. Moreover, I use ESLint as my default formatter, as it helps me maintain a consistent code style throughout my projects.</p>
<p>When <a target="_blank" href="https://eslint.org/">ESLint</a> announced that they <a target="_blank" href="https://eslint.org/blog/2023/10/deprecating-formatting-rules/">deprecated their formatting rules</a>, my reaction was “WTF”… I am aware that the ESLint team has always been <a target="_blank" href="https://github.com/eslint/eslint.org/issues/435">advised against using them as a formatter</a>. However, I disagree.</p>
<h2 id="heading-why-i-think-you-should-use-it-as-a-formatter">Why I think you should use it as a Formatter</h2>
<p>Antfu made good points about <a target="_blank" href="https://antfu.me/posts/why-not-prettier">why he does not use Prettier.</a></p>
<blockquote>
<p>“It’s a Mess with ESLint” [^<a target="_blank" href="%5Bhttps://antfu.me/posts/why-not-prettier%5D(https://antfu.me/posts/why-not-prettier)">1</a>]</p>
</blockquote>
<p>And I agree. I spent so much time configuring both together…</p>
<p><a target="_blank" href="https://x.com/antfu7/status/1279149212974776320?s=20">https://x.com/antfu7/status/1279149212974776320?s=20</a></p>
<p>For instance, my auto-fix settings in VS Code for ESLint and Prettier would constantly overwrite each other, causing frustration and wasting time.</p>
<p>When working with ESLint and Prettier, it's common to end up with two configuration files (and possibly two ignore files, making it a total of four files). One configuration file is for ESLint, and the other is for Prettier. I would advise against putting the configuration in the <code>package.json</code> file as it would make it unnecessarily large, and you would be limited to using JSON format. It would require additional effort to maintain.</p>
<blockquote>
<p><strong>Linters</strong> are tools that verify and correct logical and non-whitespace style issues in code, such as naming consistency and bug detection. Linters often take seconds or more to run because they apply many logical rules to code. [²]</p>
</blockquote>
<p>I think if you configure your VS Code correctly, it does lint on the fly. Why shouldn’t it detect stylistic rules? Yes, it may take a few seconds to run <code>eslint . —fix</code> , but I don’t think it is slow. Of course, it depends on the project size. But would it be much faster when we put the stylistic rules into Prettier and then run <code>prettier . —write</code>. Which one should be executed first, ESLint or Prettier? 😵‍💫</p>
<p>Take advantage of the powerful capabilities of your IDE and let it handle linting and formatting on the fly. Here is my VS Code setting:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"editor.formatOnSave"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"typescript.tsdk"</span>: <span class="hljs-string">"./node_modules/typescript/lib"</span>,
  <span class="hljs-attr">"eslint.format.enable"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"editor.codeActionsOnSave"</span>: {
    <span class="hljs-attr">"source.fixAll.eslint"</span>: <span class="hljs-literal">true</span>
  },
  <span class="hljs-attr">"typescript.format.enable"</span>: <span class="hljs-literal">false</span>,
  <span class="hljs-attr">"prettier.enable"</span>: <span class="hljs-literal">false</span>
}
</code></pre>
<p>I disabled all formatter except for ESLint to make sure only ESLint is used here.</p>
<blockquote>
<p>It’s very opinionated [³]</p>
</blockquote>
<p>Prettier forces you to use <code>printwidth</code>. Of course, you can adjust it. However, most likely, you have already run the formatter… Grr</p>
<p>See below and build your own opinion if Prettier makes this really “prettier” 🫠</p>
<p><img src="https://antfu.me/images/prettier-print-width.png" alt="Prettier forces unnecessary wrapping/unwrapping" /></p>
<h2 id="heading-okay-they-deprecated-it-what-should-we-do-now">Okay, they deprecated it. What should we do now?</h2>
<p>Using an older version is possible, but it is not recommended.</p>
<p>Anthony Fu started the project <a target="_blank" href="https://eslint.style/">ESLint Stylistic</a>, an ESLint plugin with all the deprecated formatting rules. The maintainer is one of the best, and I am pretty sure he will come up with something great.</p>
<p>But if you're looking for a reliable and efficient tool to manage your code, <a target="_blank" href="https://biomejs.dev/">Biome</a> is the way to go. In my experience, it's far more effective than ESLint. I highly recommend trying it and seeing the difference it can make in your workflow, and you get a linter and a formatter in one tool.</p>
<p>And their benchmark is very promising<a target="_blank" href="%5Bhttps://github.com/biomejs/biome/tree/main/benchmark%5D(https://github.com/biomejs/biome/tree/main/benchmark)">^4</a>:</p>
<blockquote>
<ul>
<li><p>Biome's ~25 times faster than Prettier</p>
</li>
<li><p>Biome's ~20 times faster than parallel-prettier</p>
</li>
<li><p>Biome's ~20 times faster than <code>xargs -P</code></p>
</li>
<li><p>Biome's 1.5-2 times faster than <code>dprint</code></p>
</li>
<li><p>Biome single-threaded is ~7 times faster than Prettier.</p>
</li>
</ul>
</blockquote>
<h2 id="heading-outlook">Outlook</h2>
<p>There is an interesting competition on going to <a target="_blank" href="https://console.algora.io/challenges/prettier">rewrite the prettier in Rust</a>. I can only guess that Prettier will get faster.</p>
<p>Interestingly, the Biome team is also joining that competition. <a target="_blank" href="https://twitter.com/biomejs/status/1726365565348991346">https://twitter.com/biomejs/status/1726365565348991346</a> I can imagine they will be merged together.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>ESLint has deprecated its formatting rules. The ESLint team advises using a Formatter just formatting. However, if your Linter is already reading your codebase, why should you run another tool to reread it? Luckily, there are several alternatives available that you can consider.</p>
<p>I advise using one tool for lining and formatting your code, whether using Biome or ESLint with the Stylistic Rules plugin, but that would avoid some maintenance overhead. Furthermore, if you have configured your IDE, it can lint and format your code on the fly.</p>
<p>And if you don’t work on Google’s Monorepo, I think you wait for the time to finish formatting 😉</p>
<p>[²]: <a target="_blank" href="https://typescript-eslint.io/linting/troubleshooting/formatting/">https://typescript-eslint.io/linting/troubleshooting/formatting/</a><br />[³]: <a target="_blank" href="https://eslint.style/guide/why">https://eslint.style/guide/why</a></p>
]]></content:encoded></item><item><title><![CDATA[My Hacktoberfest]]></title><description><![CDATA[#hacktoberfest #ai #development #aws 

“October, crisp, misty, golden October, when the light is sweet and heavy.”
― Angela Carter, The Magic Toyshop

When the leaves turn their color, and the sun is enjoyable, ahh, October, you beautiful month. When...]]></description><link>https://blog.jolo.dev/my-hacktoberfest</link><guid isPermaLink="true">https://blog.jolo.dev/my-hacktoberfest</guid><category><![CDATA[Hacktoberfest2023]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Open Source]]></category><dc:creator><![CDATA[JoLo]]></dc:creator><pubDate>Mon, 30 Oct 2023 16:21:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1698682892292/ad0dc973-a4c1-42df-ad7d-f4857cabd73e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>#hacktoberfest #ai #development #aws </p>
<blockquote>
<p>“October, crisp, misty, golden October, when the light is sweet and heavy.”
― Angela Carter, <a target="_blank" href="https://www.goodreads.com/work/quotes/78229">The Magic Toyshop</a></p>
</blockquote>
<p>When the leaves turn their color, and the sun is enjoyable, ahh, October, you beautiful month. When the days are shorter, and you don't feel bad when it's raining, oh yes, it's Hacktober.</p>
<h2 id="heading-how-has-my-hacktoberfest">How has my #Hacktoberfest?</h2>
<p>Even though I haven't got all my PRs approved, I can say it was very good.</p>
<p>I am a fan of open source, but honestly, I don't contribute much. Hacktoberfest motivates me to change that.</p>
<p>I use @OpenSauced to search for repositories that have the #hacktoberfest label. It's a fantastic tool for finding excellent repositories to contribute to. I understand that it may not capture all available repositories, but the goal of Hacktoberfest is to discover something exciting and make a meaningful contribution to it.</p>
<p>And I found so many great projects.</p>
<ul>
<li><a target="_blank" href="https://github.com/triggerdotdev/trigger.dev">trigger.dev</a><a target="_blank" href="https://github.com/triggerdotdev/trigger.dev">-</a>-) for running background jobs framework with Typescript</li>
<li><a target="_blank" href="https://github.com/turbot/steampipe">Steampipe</a><a target="_blank" href="https://github.com/turbot/steampipe">-</a>-) Using SQL to query your cloud services without a database</li>
<li>SAML <a target="_blank" href="https://github.com/boxyhq/jackson">jackson </a><a target="_blank" href="https://github.com/boxyhq/jackson">(Best</a>(Best) name!!)- for a seamless integration of a single authentication</li>
<li><a target="_blank" href="https://github.com/novuhq/novu">Novu</a><a target="_blank" href="https://github.com/novuhq/novu">-</a>-) The notification center infrastructure for your app</li>
</ul>
<p>and many more. Go check it out yourself <a target="_blank" href="https://app.opensauced.pizza/hacktoberfest/repositories/filter/recent">here</a>.</p>
<h2 id="heading-my-contributions">My contributions</h2>
<h3 id="heading-komiser">Komiser</h3>
<p>I often work with AWS and recently came across a tool called <a target="_blank" href="https://github.com/tailwarden/komiser">Komiser</a>, which can create an inventory of your cloud assets and display the costs of each cloud service. I found it quite interesting, but I noticed a bug where <a target="_blank" href="https://github.com/tailwarden/komiser/issues/1122">services were labeled incorrectly</a>. Since the tool is written in Golang, I tried to fix the bug <a target="_blank" href="https://github.com/tailwarden/komiser/pull/1128">myself</a>, but someone else came up with a better solution. Although it was my first time working with Go, <a target="_blank" href="https://www.phind.com/">Phind</a> taught me about <a target="_blank" href="https://golangbot.com/goroutines/">goroutines</a>, which are used in Go's concurrent model. I was eager to contribute to the repository, so I added a new <a target="_blank" href="https://github.com/tailwarden/komiser/pull/1127">pricing support for CloudFront Functions</a>. Additionally, I could apply my Javascript knowledge to <a target="_blank" href="https://github.com/tailwarden/komiser/pull/1142">animate the edges of their explorer</a>.</p>
<p>My learnings:</p>
<ul>
<li>Usage of Go and their concurrent execution.</li>
<li>How the pricing of <a target="_blank" href="https://aws.amazon.com/cloudfront/pricing/?nc=sn&amp;loc=3">CloudFront Functions are calculated,</a> and that they <a target="_blank" href="https://aws.amazon.com/cloudfront/pricing/?nc=sn&amp;loc=3">differ</a> from <a target="_blank" href="https://aws.amazon.com/cloudfront/pricing/?nc=sn&amp;loc=3">Lambda@Edge</a></li>
<li>Refreshing my NextJS skills and how to use <a target="_blank" href="https://github.com/sgratzl/cytoscape.js-layers/tree/main">Cytoscape Layer</a></li>
</ul>
<h3 id="heading-docsgpt">DocsGPT</h3>
<p>I became interested in <a target="_blank" href="https://github.com/arc53/DocsGPT">DocsGPT</a> because I had a similar idea of training a model based on my own documents. I started reading through their <a target="_blank" href="https://github.com/orgs/arc53/projects/2">roadmap</a> and engaging in their Discord channel. One of the ideas was to put <a target="_blank" href="https://python.langchain.com/docs/modules/data_connection/vectorstores/">Langchain's Vectorstore</a> on AWS S3 or connect the application with an AWS S3 storage. DocsGPT is written in Python, and I found it to be a good project to brush up on my Python skills. The pull request is currently being worked on.</p>
<p>My Learnings here:</p>
<ul>
<li>Use <a target="_blank" href="https://www.langchain.com/">LangChain</a> to develop a language model</li>
<li>Get to improve my rusty Python skills</li>
</ul>
<h3 id="heading-s3-simpler">S3-Simpler</h3>
<p>I realized that I had already started working on my own project called <a target="_blank" href="https://github.com/jolo-dev/s3-simpler">S3-Simpler (S4)</a> for last year’s #hacktoberfest. The main aim of this project is to create a small wrapper that can be used for uploading, downloading, and sharing any type of file, regardless of its size. Additionally, this repository also includes a CLI that can be run to upload, download, and share your files of an S3-Bucket (I was thinking of running something like <code>npx s4 upload --bucket my-bucket --filepath /path/to/my/file</code>). Typically, multipart uploads are required for files larger than 1 GB, but this CLI simplifies the process.</p>
<p>This project is built using NodeJS and has a Monorepo setup. I attempted to use Bun for bundling, but it had issues with bundling CommonJS packages to ESM. The library uses AWS SDK JS v3, and the CLI uses <a target="_blank" href="https://github.com/vadimdemedes/ink">ink</a>. It's still under development and needs to be published to npm. Any feedback and contributions would be greatly appreciated.</p>
<p>However, this project was forgotten, and #Hacktoberfest is over. Despite having a label, there was no marketing done, and as a result, it wasn't as visible as it could have been.</p>
<p>My Learnings:</p>
<ul>
<li>Without any marketing, you won't get any visibility</li>
<li>Project Management is harder than I thought</li>
<li>Trying out <a target="_blank" href="https://bun.sh/">Bun</a> and realized its bundler was not correctly bundling to ESM</li>
<li>Creating a library for ESM and CJS (Dual-Mode)</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Throughout Hacktoberfest, I attempted to complete four pull requests. Nonetheless, the experience was a great opportunity for me to learn and grow. I participated in various projects, including those related to AI, my own personal projects, and tools that I use for my daily work. This allowed me to gain exposure to a wide range of different technologies and to learn new skills that I can apply to future projects.</p>
<p>Moreover, the event provided me with the chance to explore many great projects, which helped me to expand my knowledge and develop my coding abilities. Beyond the hacking and coding aspects, I also enjoyed the social aspect of the event, interacting with others on Discord channels and getting to know new people who share my passion for open source.</p>
<p>Hacktoberfest motivated me to commit to doing something every day. Although I wasn't able to complete all four PRs, I am eager to continue contributing to open-source projects, as I believe that this is an important way to give back to the community and to help others. I am grateful for the opportunity to have participated in Hacktoberfest and look forward to continuing to learn and grow as a developer.</p>
]]></content:encoded></item><item><title><![CDATA[Run Bun Run!]]></title><description><![CDATA[On a Sunday morning, as you walk towards the bakery, the aroma of freshly baked buns fills the air. You can't resist taking a bite of the hot bun fresh from the oven as soon as you get it.
Now, it’s most likely that you burn yourself or you are smart...]]></description><link>https://blog.jolo.dev/run-bun-run</link><guid isPermaLink="true">https://blog.jolo.dev/run-bun-run</guid><category><![CDATA[aws lambda]]></category><category><![CDATA[Bun]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[aws-cdk]]></category><dc:creator><![CDATA[JoLo]]></dc:creator><pubDate>Thu, 28 Sep 2023 08:53:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1695891257718/d2368f33-139f-4f59-ade3-1e8a51dce344.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>On a Sunday morning, as you walk towards the bakery, the aroma of freshly baked buns fills the air. You can't resist taking a bite of the hot bun fresh from the oven as soon as you get it.</p>
<p>Now, it’s most likely that you burn yourself or you are smart and let it cool down a bit.</p>
<p>Bun is so cool that I also got hyped. However, I questioned myself:</p>
<ul>
<li><p>How can I use it in my current or future projects?</p>
</li>
<li><p>How can I deploy it?</p>
</li>
<li><p>Do I have to rewrite a lot or even everything?</p>
</li>
</ul>
<p>In this blog post, we will discover the usage of Bun by creating an AWS CDK template using Bun tooling.</p>
<h2 id="heading-what-is-bun">What is Bun?</h2>
<p>The NodeJS was established long ago but had no unified tooling and an ever-changing ecosystem. Bun wants to become the all-in-one JavaScript toolkit, including runtime, package manager, test runner, and bundler. It is built from scratch with three primary design goals: fast startup, fast running performance, and cohesive DX.</p>
<h3 id="heading-what-does-it-mean-runtime">What does it mean “Runtime”?</h3>
<p>It means how your computer interprets your script. Each programming language has its Syntax and grammar; you can run a program using their language<a target="_blank" href="%5Bhttps://www.techtarget.com/searchsoftwarequality/definition/runtime%5D(https://www.techtarget.com/searchsoftwarequality/definition/runtime)">^1</a>. For example, if you write a Python script, you run it with <code>python myscript.py</code> . The machine uses Python as runtime to interpret it. The file extension allows humans to understand that this is a Python file or editors to do color highlighting, but it’s unnecessary for the machine. The same applies to NodeJS and Bun. You can execute the script by running <code>node myscript.js</code> and <code>bun myscript.js</code>. Notice that Bun can run Javascript.</p>
<p>More interesting is what Syntax Bun supports. - A bun of (pun intended ✊) including Typescript, JSX, and JSON<a target="_blank" href="%5Bhttps://bun.sh/docs/runtime/loaders%5D(https://bun.sh/docs/runtime/loaders)">²</a>.</p>
<p>What if we want to deploy a function to any Cloud provider such as Cloudflare or AWS? None of them support these for now (28. September 2023).</p>
<p>Remember, we need to run <code>bun myscript.ts</code> . Luckily, on AWS Lambda, we could create a custom runtime.</p>
<p>That means we don’t need to transpile the Typescript code to ESM or CJS. Currently, only <a target="_blank" href="https://deno.com/deploy">Deno Deploy</a> can run your Typescript function out of the box. However, in order to keep the code small, we still need some sort of bundling. Luckily, Bun is also a bundler 😉</p>
<h3 id="heading-what-is-a-bundler">What is a Bundler?</h3>
<p>A bundler is a tool that puts your code into a single file. When you develop a function, you may split your code into multiple files and use third-party packages. So, instead of shipping the whole <code>node_modules</code>- folder or all the libraries, a bundler only uses the correct code.</p>
<p>We do this to optimize JavaScript files by reducing their size and number, which can significantly improve website performance, resulting in better user experience and increased engagement.</p>
<p>Furthermore, Node-Server cannot understand Typescript, so we use a bundler to transpile and bundle.</p>
<p>The famous bundler is <a target="_blank" href="https://webpack.js.org/">Webpack,</a> but <a target="_blank" href="https://esbuild.github.io/">esbuild</a> is the fastest one… Well, until Bun 🍔</p>
<h2 id="heading-bunnyfied-aws-cdk-project">Bunnyfied AWS CDK project 🐰</h2>
<p>As a cloud engineer, I am working a lot with <a target="_blank" href="https://docs.aws.amazon.com/cdk/">AWS CDK</a>, a tool for infrastructure on AWS. Usually, I develop my AWS Resources in SST ([here are my thoughts on SST](<a target="_blank" href="https://medium.com/@jolodev/sst-24fd4e2f4d7c">here my thoughts on SST</a>) and not in Projen ([here are my thoughts on Projen](<a target="_blank" href="https://medium.com/@jolodev/sst-24fd4e2f4d7c">here my thoughts on SST</a>).</p>
<h3 id="heading-aws-cdk">AWS CDK</h3>
<p>AWS CDK is an open-source software development framework developed by Amazon Web Services (AWS) that allows developers to define and provision cloud infrastructure resources using familiar programming languages.</p>
<p>Their CLI bootstraps an npm project. I used that to Bunyfied it.</p>
<h3 id="heading-bunx-cdk-init-app-language-typescript"><code>bunx cdk init app --language typescript</code></h3>
<p>For fun, I decided to test which package manager is the fastest for creating an AWS CDK project. It appears that <code>bunx</code> is faster than <code>npx</code> and <code>pnpx</code>. However, this was not a proper benchmarking test.</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/bunx-benchmark.gif" alt="bunx-benchmark.gif" /></p>
<p>On this attempt <code>bunx</code> runs the fastest but is not really impressive.</p>
<h3 id="heading-bun-test-and-bun-install"><code>bun test</code> and <code>bun install</code></h3>
<p>When I first did it, I removed <code>jest</code> and <code>package-lock.json</code> because I wanted to try out the native <a target="_blank" href="https://bun.sh/docs/cli/test">Bun Test</a>, and I used <a target="_blank" href="https://bun.sh/docs/cli/install">Bun’s package manager</a> to install packages.</p>
<h4 id="heading-testing">Testing</h4>
<p>Bun comes with its own testing suite, and it gives you nice hints in order to make this test pass 😇</p>
<p><img src="https://assets.vrite.io/646ce5fc07ef9ba4530fd23b/J8Iq7x4CL49UgjB3aDqTb.png" alt /></p>
<p>And I have to say the <code>--watch</code> mode is really fast. <code>vitest</code> is also fast and reruns only failed tests, which makes it, in the end, faster (here when using Monorepo).</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/bun-test-bench.gif" alt="bun-test-bench.gif" /></p>
<p>However, Mocking didn't work as expected. I attempted to spy on a function to manipulate a result but was unsuccessful. Their <a target="_blank" href="https://bun.sh/docs/test/mocks">mocks</a> provided were also unusable when trying to mock out the functions of packages. The documentation was not helpful either. As a result, I had to abandon <code>bun test</code> and used <code>vitest</code> instead.</p>
<h3 id="heading-package-manager">Package Manager</h3>
<p>Bun is clearly the fastest, and this time, you can really feel it 🤩</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/bun-package-bench.gif" alt="bun-package-bench.gif" /></p>
<p>When I installed <a target="_blank" href="https://docs.powertools.aws.dev/lambda/typescript/latest/">Lambda Powertools</a>, Bun became significantly faster. It turns out that I had already installed some packages, but still, Bun was faster than pnpm. ![bun-sinlge-package-bench 1.gif](https://jolo-dev-blog-images.s3.amazonaws.com/bun-sinlge-package-bench 1.gif)</p>
<h3 id="heading-workspaces">Workspaces</h3>
<p>I created a Monorepo because Bun supports <a target="_blank" href="https://bun.sh/docs/install/workspaces">Workspaces</a>).</p>
<h3 id="heading-deploying-a-bun-runtime">Deploying a Bun Runtime</h3>
<p>Bun has its own package for creating a <a target="_blank" href="https://github.com/oven-sh/bun/tree/main/packages/bun-lambda">Lambda Layer</a>.</p>
<p>I created a <code>BunFunLayerStack</code> to deploy a Lambda Layer. That layer is customizable and could be consumed by another account, or you could even make it public.</p>
<h3 id="heading-bunfun-construct">BunFun Construct</h3>
<p>Then, I created a Construct <code>BunFun</code> which creates a <code>Fun</code>ction using the Lambda Layer.</p>
<p>It tries to read Arn, resulting from the <code>BunFunLayerStack</code> but can be overwritten when passing <code>props.bunLayer</code> in case you bring your own Layer.</p>
<p>By default, the respective target will be used. If it's a Typescript or Javascript file, then <code>node</code> it will be the target. But then you could use the <code>NodeJsFunction</code>- Construct of CDK. However, when you need the Bun there is a <code>bunConfig</code>- property that also opens up all the Bun configurations from their bundler <a target="_blank" href="https://bun.sh/docs/bundler#api">API</a>.</p>
<p>I configured that it is optional, but when you use it, make sure you add a target. Sometimes I love Typescript 🤩</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/bun-fun-autocomplete.gif" alt="bun-fun-autocomplete.gif" /></p>
<p>Generally, I put three examples in the <code>BunFunStack</code>.</p>
<h3 id="heading-run-it">Run it</h3>
<p>I put a couple of Lambdas in the <code>packages/functions</code> to get started and I realized that the Lambdas using that runtime must always return a <code>new Response</code>. Otherwise, the Lambda will not execute successfully. But that doesn't mean you cannot use it for handling SQS.</p>
<p><code>BunFunStack</code> and <code>BunFunLayerStack</code> can be deployed separately. In this repo, I put them into an <code>BunOvenStack</code>.</p>
<p>Simply run <code>bun deploy:all</code> to deploy the <code>BunOvenStack</code> and run <code>bun deploy:only</code> to deploy only the <code>BunFunStack</code>. The <code>bun deploy:only</code> is also parameterized and could run another stack e.g. <code>STACK=BunFunLayerStack bun deploy:only</code>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this blog post, I used Bun's tooling to create an AWS CDK project. It includes a Layer for deploying a Bun Runtime and a Construct for creating a BunFunction.</p>
<p>Why should we care? Well, Bun would eliminate the fact of either using CommonJS or EcmaScript Modules (ESM) because most of the developers (me included) simply don't care. With the Bun runtime, you would run your Typescript files without transpiling them. Furthermore, Bun is fast. This blog does not cover benchmarking the Lambdas with a Bun runtime as there are already Benchmarks for Bun’s performance[^4](%5Bhttps://medium.com/@mitchellkossoris/serverless-bun-vs-node-benchmarking-on-aws-lambda-ecd4fe7c2fc2%5D(%3Chttps://medium.com/@mitchellkossoris/serverless-bun-vs-node-benchmarking-on-aws-lambda-ecd4fe7c2fc2%5D(https://medium.com/@mitchellkossoris/serverless-bun-vs-node-benchmarking-on-aws-lambda-ecd4fe7c2fc2)%3E)).</p>
<p>Unfortunately, <code>bun test</code> was not usable, and I use <code>vitest</code> instead. Moreover, the Lambda Runtime requires you always return <code>new Response</code> when using the <code>bun</code> target. This could be confusing when using Lambda for a different purpose then using an API Gateway.</p>
<p>With my Bunnyfied AWS CDK template, you could already start deploying Lambda functions using Bun Runtime, so give it a shoot 😉 To try it out, use the following command <code>bun create github.com/jolo-dev/bunnyfied-aws-cdk</code></p>
<p>P.S.: It's faster than the <code>npx cdk init app</code>. P.S.S: Feedback and Contributions are welcome!</p>
]]></content:encoded></item><item><title><![CDATA[Thoughts on SST]]></title><description><![CDATA[#aws #cdk #serverless
Developers are increasingly adopting serverless computing since it removes the requirement to manage infrastructure. When it comes to creating Infrastructure as Code, AWS CDK has become a favored option. Nonetheless, the Develop...]]></description><link>https://blog.jolo.dev/thoughts-on-sst</link><guid isPermaLink="true">https://blog.jolo.dev/thoughts-on-sst</guid><category><![CDATA[AWS]]></category><category><![CDATA[CDK]]></category><category><![CDATA[sst]]></category><category><![CDATA[serverless]]></category><dc:creator><![CDATA[JoLo]]></dc:creator><pubDate>Tue, 27 Jun 2023 09:48:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/cckf4TsHAuw/upload/c32985b6f86a34844abd1068825eabf2.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>#aws #cdk #serverless</p>
<p>Developers are increasingly adopting serverless computing since it removes the requirement to manage infrastructure. When it comes to creating Infrastructure as Code, AWS CDK has become a favored option. Nonetheless, the Developer Experience (DX) for creating serverless applications with CDK isn't optimal. For instance, the deployment time can be excessively long when building REST APIs and debugging Lambdas can become tedious.</p>
<p>The Serverless Stack (SST) framework simplifies the process of building serverless applications on AWS. This article explores SST's architecture, features, and benefits for building and deploying serverless applications.</p>
<h2 id="heading-whats-wrong-with-vanilla-cdk">What's wrong with Vanilla CDK?</h2>
<p>AWS CDK (I call it vanilla CDK) generates Cloudformation, AWS native Infrastructure as Code, by using a high-level programming language such as Typescript, Python, or Go.</p>
<p>As someone who has worked on numerous CDK codebases, the deployment process and developer setup have often caused me delays. Though <a target="_blank" href="https://aws.amazon.com/blogs/developer/increasing-development-speed-with-cdk-watch/">CDK hotswap</a> has been a significant improvement, it still tends to be sluggish.</p>
<p>Developing your REST API with API Gateway and Lambdas in CDK is quite painful. AWS SAM and Serverless Framework offer local testing but often I believe you get better feedback when directly deploying on AWS. Debugging with CDK is not streamlined as you need to find the correlated logs. And we all know, the AWS Console sucks.</p>
<p>In my article, Strategies for a faster Lambda Development, I was mentioning <a target="_blank" href="https://sst.dev/">SST</a>.</p>
<h2 id="heading-why-sst">Why SST?</h2>
<p>SST offers powerful monitoring and debugging tools while allowing developers to focus on application logic. It comes with its own UI console based on your CDK stack.</p>
<h4 id="heading-why-should-i-care-about-its-console">Why should I care about its Console?</h4>
<p>Picture this scenario: You have created a stack using several Lambdas and now you need to debug them. To do so, you head over to AWS Cloudwatch and try to locate their logs. Unfortunately, you realize that you have redeployed the stack and now you have to sift through numerous logs to locate the correct log file.</p>
<p>SST’s console shows their log files which makes debugging so much faster.</p>
<p>However, you won’t find all AWS resources but only those which are related to Serverless (Lambdas, S3, DynamoDB etc.). When utilizing a serverless approach, then it should be fine though 😉</p>
<h4 id="heading-should-i-still-consider-developing-it-locally-first">Should I still consider developing it locally first?</h4>
<p>With SST you don’t have to do it anymore as it (re)deploys super fast and it feels like local development. Really, try it yourself 🤯</p>
<p><img src="https://assets.vrite.io/646ce5fc07ef9ba4530fd23b/B53bAIG_NlV7TJRC63h9C.gif" alt /></p>
<p>It feels like that I do not wait for anything. Whereas in CDK’s hotswap, I have to wait until it’s deployed (you don’t see it but I was hitting refresh the whole time).</p>
<p>P.S.: I was a bit slow when changing the Lambda above, I am pretty sure SST is much faster.</p>
<p><img src="https://assets.vrite.io/646ce5fc07ef9ba4530fd23b/3RvHiwnpDdpbzQV3mQ-4c.gif" alt /></p>
<p>Furthermore, SST allows live Lambda live developing which can be integrated into your IDE. That means you could set breakpoints and debug your application while it’s deployed on AWS.</p>
<h4 id="heading-developer-experience-dx">Developer Experience (DX)</h4>
<p>In general, the SST team put a lot of effort into the Developer Experience (DX). Their constructs are very intuitive. Here is an example with their <code>API</code><a target="_blank" href="https://docs.sst.dev/constructs/Api">- construct</a>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> api = <span class="hljs-keyword">new</span> Api(<span class="hljs-built_in">this</span>, <span class="hljs-string">'KeyVaultApi'</span>, {
  defaults: {
    <span class="hljs-function"><span class="hljs-keyword">function</span>: </span>{
      runtime: <span class="hljs-string">'nodejs18.x'</span>,
    },
  },
  routes: {
    <span class="hljs-string">"GET /ping"</span>: <span class="hljs-string">"packages/functions/src/Foo/ping.handler"</span>,
    <span class="hljs-string">"GET /pong"</span>: <span class="hljs-string">"packages/functions/src/Foo/pong.handler"</span>,
  },
});
</code></pre>
<p>instead of</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> pingLambda = <span class="hljs-keyword">new</span> lambda.NodejsFunction(<span class="hljs-built_in">this</span>, <span class="hljs-string">'Ping'</span>, {
  functionName: <span class="hljs-string">'ping'</span>,
  entry: <span class="hljs-string">'src/Foo/ping.ts'</span>,
  handler: <span class="hljs-string">'handler'</span>,
  runtime: Runtime.NODEJS_18_X,
  bundling: {
    minify: <span class="hljs-literal">true</span>,
  },
});

<span class="hljs-keyword">const</span> pongLambda = <span class="hljs-keyword">new</span> lambda.NodejsFunction(<span class="hljs-built_in">this</span>, <span class="hljs-string">'Pong'</span>, {
  functionName: <span class="hljs-string">'pong'</span>,
  entry: <span class="hljs-string">'src/Foo/pong.ts'</span>,
  handler: <span class="hljs-string">'handler'</span>,
  runtime: Runtime.NODEJS_18_X,
  bundling: {
    minify: <span class="hljs-literal">true</span>,
  },
});

<span class="hljs-keyword">const</span> pingPongApi = <span class="hljs-keyword">new</span> apigateway.RestApi(<span class="hljs-built_in">this</span>, <span class="hljs-string">'ping-pong-api'</span>);
<span class="hljs-keyword">const</span> ping = pingPongApi.root.addResource(<span class="hljs-string">'ping'</span>);
ping.addMethod(<span class="hljs-string">'GET'</span>, <span class="hljs-keyword">new</span> apigateway.LambdaIntegration(pingLambda));
<span class="hljs-keyword">const</span> pong = keyGenRestApi.root.addResource(<span class="hljs-string">'pong'</span>);
pong.addMethod(<span class="hljs-string">'GET'</span>, <span class="hljs-keyword">new</span> apigateway.LambdaIntegration(pongLambda));
</code></pre>
<p>Cool, we reduced the lines of code by 14 and make the code more readable 😎.</p>
<h4 id="heading-use-vanilla-cdk">Use vanilla CDK</h4>
<p>SST, being built on top of CDK, allows for the integration of CDK constructs. This means that despite using SST, you still have the option to utilize the powerful features of CDK.</p>
<h4 id="heading-documentation">Documentation</h4>
<p>I am always annoyed about <a target="_blank" href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-construct-library.html">CDK’s API reference documentation</a> because there is no search and <code>CMD + F</code> does just half of its job. However, SST's implementation of Algolia's search feature makes it incredibly easy to locate not just their constructs, but anything relevant to your search term.</p>
<h2 id="heading-wow-sst-is-amazing-but-does-it-have-a-downside">Wow, SST is amazing but does it have a downside?</h2>
<p>Yep, unfortunately, there are some limitations.</p>
<h4 id="heading-not-possible-to-do-cross-account-deployments-with-cdk-pipelines">Not possible to do cross-account deployments with CDK pipelines</h4>
<p>We had already some CDK pipelines established and in fact, I have written an <a target="_blank" href="https://medium.com/@jolodev/effectively-using-cdk-pipelines-to-deploy-cdk-stacks-into-multiple-accounts-35f501a58d87">article</a> about it. However, in the end, you have a CDK app that should be deployable via CDK pipelines… that’s what I thought.</p>
<p>I spent some time figuring out why it does not deploy to a different account. Assuming roles from a different account is not straightforward with CDK pipelines and it usually works with Vanilla CDK.</p>
<p>I found out that if you <code>node_modules/sst/constructs/Stack.js</code> manually remove Line 69 and Line 74 - 78, it</p>
<pre><code class="lang-typescript">Stack.checkForPropsIsConstruct(id, props);
<span class="hljs-comment">// Stack.checkForEnvInProps(id, props);</span>

<span class="hljs-built_in">super</span>(scope, stackId, {
  ...props,
<span class="hljs-comment">//  env: {</span>
<span class="hljs-comment">//    account: app.account,</span>
<span class="hljs-comment">//    region: app.region,</span>
<span class="hljs-comment">//  },</span>
  synthesizer: props?.synthesizer || Stack.buildSynthesizer(),
});
</code></pre>
<h4 id="heading-sst-cannot-be-integrated-into-an-existing-cdk-stack-but-the-other-way-round-works">SST cannot be integrated into an existing CDK stack (but the other way round works)</h4>
<p>I had problems integrating SST in my current CDK stack as if you bootstrapped your project with <code>npx cdk init —lang typescript</code> , you may get incompatibility between ESM and CommonJS as CDK is compiled to CommonJS (which is older) and SST uses ESM (which is a modern and modular approach).</p>
<h4 id="heading-ui-is-limited-to-serverless-only-services">UI is limited to Serverless Only services</h4>
<p>SST console shows only Serverless service but no EC2 or ECS containers.</p>
<h4 id="heading-sst-is-not-good-for-developing-a-library-or-constructs">SST is not good for developing a library or constructs</h4>
<p>If you want to create CDK library consisting of custom constructs, SST won’t be able to satisfy you. Which is fine as that’s not their intention.</p>
<p>However, if you intend to do so, you may read my thought about using <a target="_blank" href="https://medium.com/@jolodev/projen-75027e83479e">Projen</a> 🫣</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>If you're a developer, you'll appreciate how well SST caters to your needs. It provides a smooth and integrated solution for building full-stack applications and guarantees an exceptional developer experience.</p>
<p>The console provides a detailed overview of your serverless stack's associated resources and presents comprehensive outputs. No need to find anything on the AWS console 😜</p>
<p>Hopefully, this blog was helpful in showing you how to enhance your stack and take it to the next level. Consider using SST for your future AWS projects.</p>
<p>Still in doubt? Watch Fireship’s <a target="_blank" href="https://youtu.be/JY_d0vf-rfw">100 seconds of SST</a>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=JY_d0vf-rfw">https://www.youtube.com/watch?v=JY_d0vf-rfw</a></div>
]]></content:encoded></item><item><title><![CDATA[Strategies for a Faster Lambda Development]]></title><description><![CDATA[#aws #typescript #lambda #development
Starting with AWS Lambda can be challenging, especially when optimizing and streamlining the development process. While running functions directly in the AWS Console is the simplest and quickest approach, it beco...]]></description><link>https://blog.jolo.dev/strategies-for-a-faster-lambda-development</link><guid isPermaLink="true">https://blog.jolo.dev/strategies-for-a-faster-lambda-development</guid><category><![CDATA[AWS]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[aws lambda]]></category><category><![CDATA[development]]></category><dc:creator><![CDATA[JoLo]]></dc:creator><pubDate>Sat, 06 May 2023 18:16:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/7tXA8xwe4W4/upload/3bb5257d4b8746aa074a9a9b60381690.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>#aws #typescript #lambda #development</p>
<p>Starting with AWS Lambda can be challenging, especially when optimizing and streamlining the development process. While running functions directly in the AWS Console is the simplest and quickest approach, it becomes tedious when adding packages, as it requires zipping and uploading them to the console – a method that is rarely used in practice.</p>
<p>An alternative workflow involves setting up Infrastructure as Code (IaC) tooling, such as <a target="_blank" href="https://aws.amazon.com/cdk/">AWS CDK</a> and running deployment commands like <code>sam deploy</code> with <a target="_blank" href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html">AWS SAM</a>. These tools handle code uploading and simplify the process.</p>
<p>Although using IaC tooling is a step forward, it can still be slow, as it often requires updating CloudFormation, which can take time. In this blog post, we will explore various strategies and tools to make Lambda development faster and more efficient.</p>
<h2 id="heading-strategy-bypassing-cloudformation">Strategy: Bypassing Cloudformation</h2>
<p>Many tools which are on top of Cloudformation (CFN) like CDK or AWS SAM, synthesize their code to a CFN- template before deploying to the cloud. That means they create a CFN template and make use of AWS native IaC-service. It is nothing wrong with that, it is just freaking slow. Let's have a look at the <code>cdk deploy</code> command</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/classic-cdk-deploy.png" alt="classic-cdk-deploy.png" /></p>
<p>To begin, the command creates a CFN template and uploads it, along with a ZIP file, to the assets-bucket (which is created when <code>cdk bootstrap</code> is initiated, more information can be found <a target="_blank" href="https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html">here</a>). The Cloudformation stack is then either created or updated, with drift detection being conducted to ensure any manual changes are accounted for. Finally, an AWS Lambda function is created alongside the stack to retrieve the ZIP-file.</p>
<p>We can automate many steps with this command, but updating Cloudformation for small changes takes a lot of time and can disrupt the development process. Let's explore <code>cdk deploy --hotswap --watch</code>, also known as <code>cdk watch</code>. This command uses <code>--watch</code> to detect any file changes and trigger updates automatically.</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/cdk-hotswap.png" alt="cdk-hotswap.png" /></p>
<p>By using this command, we skip the CloudFormation step and directly call the Lambda-API. Additionally, the development experience is improved by running a watcher that detects file changes, giving the feeling of a hot module reload. However, it's important to note that this process may cause discrepancies between your CloudFormation template and the deployed application, and not all resource changes can be 'hot-swapped' (Ref. <a target="_blank" href="https://aws.amazon.com/blogs/developer/increasing-development-speed-with-cdk-watch/">here</a>). Therefore, it's recommended to only use this in development and use the <code>cdk deploy</code> command in production.</p>
<p><a target="_blank" href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html">AWS SAM</a> has also introduced a similar concept called <a target="_blank" href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/using-sam-cli-sync.html">SAM accelerate</a>.</p>
<h3 id="heading-sst">SST</h3>
<p>The first time when I saw a 'hot swap' was at <a target="_blank" href="https://sst.dev/">SST</a>.</p>
<blockquote>
<p><em>SST is a framework that makes it easy to build modern full-stack applications on AWS.</em></p>
</blockquote>
<p>SST sits on top of CDK, and they had first the idea of bypassing Cloudformation. They call it <a target="_blank" href="https://docs.sst.dev/live-lambda-development">Live Lambda Development</a>.</p>
<blockquote>
<p><em>Live Lambda Development or Live Lambda is feature of SST that allows you to</em> <strong><em>debug and test your Lambda functions locally*</em></strong>, while being<em> **</em>invoked remotely by resources in AWS<em>**</em>. It works by proxying requests from your AWS account to your local machine.*</p>
</blockquote>
<p>I am a big fan of SST because they do a much better job than plain CDK ❤️</p>
<ul>
<li><p>Much better documentation, (why the heck I cannot search in the CDK API doc?)</p>
</li>
<li><p>Much better development experience (DX)</p>
</li>
<li><p>Faster Development and Deployment</p>
</li>
<li><p>Local UI locally for triggering the Lambda</p>
</li>
</ul>
<p>Generally, if you consider developing Serverless Apps, you should consider SST. You won't be disappointed ;)</p>
<h2 id="heading-strategy-run-lambda-locally">Strategy: Run Lambda locally</h2>
<p>Another strategy could be to run an emulator locally which simulates your cloud provider. An emulator is a service with a bunch of APIs running locally in your container or as a web service.</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/local-development.png" alt="local-development.png" /></p>
<h3 id="heading-serverless-framework-offline">Serverless Framework Offline</h3>
<p>The <a target="_blank" href="https://github.com/dherault/serverless-offline">Serverless Offline-plugin</a> is the only local emulator which does not require Docker but creates a local HTTP Server in NodeJS (see the diagram above). It simulates an API gateway and simply invokes the lambda locally.</p>
<p>This only works with Lambda which is invoked via an API Gateway. This great but limited to API Gateway.</p>
<p>Nevertheless, there are a few plugins for <a target="_blank" href="https://github.com/rubenkaiser/serverless-offline-eventBridge">Eventbridge</a>, <a target="_blank" href="https://github.com/CoorpAcademy/serverless-plugins">S3 Events</a>, and <a target="_blank" href="https://github.com/99x/serverless-dynamodb-local">DynamoDB local</a> for local development which are sitting on top of Serverless Offline.</p>
<p>This is easy to set up and really fast and highly recommended when using Serverless Framework.</p>
<h3 id="heading-localstack">Localstack</h3>
<p><a target="_blank" href="https://github.com/localstack/localstack">Localstack</a> is a cloud service emulator that runs in a single container on your laptop or in your CI environment.</p>
<p>It can be <a target="_blank" href="https://docs.localstack.cloud/getting-started/installation/#localstack-cli">installed and run</a> and make sure Docker is installed and also running</p>
<pre><code class="lang-sh">pip install localstack

<span class="hljs-comment"># or in with docker</span>
<span class="hljs-comment"># docker run \</span>
<span class="hljs-comment">#   --rm -it \</span>
<span class="hljs-comment">#   -p 4566:4566 \</span>
<span class="hljs-comment">#   -p 4510-4559:4510-4559 \</span>
<span class="hljs-comment">#   localstack/localstack</span>

localstack start -d <span class="hljs-comment"># in Daemon mode (running in the background)</span>
</code></pre>
<p>It offers a bunch of ports to run against it. But most of the core services are running on port <code>4566</code>. You could now do all the AWS-CLI- commands against this endpoint by appending the flag <code>--endpoint-url=http://localhost:4566</code>. Or you can use a tool called <a target="_blank" href="https://github.com/localstack/awscli-local"><code>awslocal</code></a>. Localstack also comes with a lot of <a target="_blank" href="https://docs.localstack.cloud/user-guide/integrations/">integrations for your development</a> tools such as CDK, Terraform, AWS SAM and many more.</p>
<p>Let's have a look at <a target="_blank" href="https://www.npmjs.com/package/aws-cdk-local">cdklocal</a>. I would recommend installing it <strong>not</strong> globally but in your project. <code>pnpm install aws-cdk-local</code>. Once you have created your CDK-stack, you could run <code>npx cdklocal deploy</code>. But whoops...</p>
<pre><code class="lang-sh">✨  Synthesis time: 4.89s

LambdaStack: building assets...


 ❌ Building assets failed: Error: Building Assets Failed: Error: LambdaStack: SSM parameter /cdk-bootstrap/hnb659fds/version not found. Has the environment been bootstrapped? Please run <span class="hljs-string">'cdk bootstrap'</span> (see https://docs.aws.amazon.com/cdk/latest/guide/bootstrapping.html)
    at buildAllStackAssets (/Users/jolo/Development/FTI/GeniusCloud/node_modules/aws-cdk/lib/index.js:383:115268)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async CdkToolkit.deploy (/Users/jolo/Development/FTI/GeniusCloud/node_modules/aws-cdk/lib/index.js:383:143485)
    at async exec4 (/Users/jolo/Development/FTI/GeniusCloud/node_modules/aws-cdk/lib/index.js:438:51799)

Building Assets Failed: Error: LambdaStack: SSM parameter /cdk-bootstrap/hnb659fds/version not found. Has the environment been bootstrapped? Please run <span class="hljs-string">'cdk bootstrap'</span> (see https://docs.aws.amazon.com/cdk/latest/guide/bootstrapping.html)
</code></pre>
<p>Okay, you need to run <code>npx cdklocal bootstrap</code> first and then <code>npx cdklocal deploy</code>. The CLI shows you exactly the same as the CDK-CLI which is pretty cool. After deployment, you see in the Terminal something like</p>
<pre><code class="lang-sh">LambdaStack: creating CloudFormation changeset...

 ✅  LambdaStack

✨  Deployment time: 25.17s

Outputs:
LambdaStack.DemoRestApiEndpointF0DDDDE8 = https://i6d181wlsa.execute-api.localhost.localstack.cloud:4566/dev/
Stack ARN:
arn:aws:cloudformation:eu-central-1:000000000000:stack/LambdaStack/90063dd4

✨  Total time: 27.94s
</code></pre>
<p>If you have implemented an API Gateway like me, you will even see an endpoint as an <code>Outputs</code>. <code>cdklocal</code>can even execute <code>npx cdklocal watch</code> and do hot-swap.</p>
<p>However, you may notice that it involved too many steps, and it's faster to use hot-swap directly with CDK.</p>
<h3 id="heading-aws-sam-local">AWS SAM local</h3>
<p>The all-rounder AWS SAM can also run an emulator for its SAM template. It offers <a target="_blank" href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/using-sam-cli-local.html"><code>sam local</code></a> with many subcommands:</p>
<ul>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/using-sam-cli-local-generate-event.html">sam local generate-event</a> – Generate AWS service events for local testing.</p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/using-sam-cli-local-invoke.html">sam local invoke</a> – Initiate a one-time invocation of an AWS Lambda function locally.</p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/using-sam-cli-local-start-api.html">sam local start-api</a> – Run your Lambda functions using a local HTTP server.</p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/using-sam-cli-local-start-lambda.html">sam local start-lambda</a> – Run your Lambda functions using a local HTTP server for use with the AWS CLI or SDKs.</p>
</li>
</ul>
<h2 id="heading-strategy-test-driven-development-tdd">Strategy: Test-Driven Development (TDD)</h2>
<p>Test-Driven Development (TDD) is an effective approach for breaking down Lambda code into smaller, single functions. This helps to structure the code better and gives the developer more confidence in the implementation. By breaking down your code into smaller, individual functions, you can easily write unit tests. Your Lambda handler should be the culmination of all your functions.</p>
<p>When using TDD, you can use a test runner such as <a target="_blank" href="https://jestjs.io/">Jest</a> or <a target="_blank" href="https://vitest.dev/">Vitest</a> for NodeJS, or <a target="_blank" href="https://pytest.org/">pytest</a> with <a target="_blank" href="https://pypi.org/project/pytest-watch/">pytest-watch</a> for Python. This will help to ensure that the code is being tested for correctness and that any errors or bugs are quickly identified and addressed. Additionally, the test runner can be used to automate the testing process, saving time and effort.</p>
<p>When using TDD and Lambda, mocking AWS services can be challenging. Your Lambda's behavior may not match expectations, making it difficult to identify all edge cases. Despite this, the advantages of TDD are worth it, as it ensures the code works correctly and detects and resolves errors or bugs before they become a problem in production. This increases confidence after deployment.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>We have seen some strategies for developing Lambda faster by leveraging tools that are bypassing Cloudformation such as SAM Accelerate or CDK's hotswap. It is also okay to gain confidence by using local emulators as they can be integrated into your development workflow.</p>
<p>Let's summarize</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td>Pros</td><td>Cons</td><td>Confidence</td></tr>
</thead>
<tbody>
<tr>
<td>Hotswap</td><td>Hot reloading</td><td>Costs Incurrs</td><td>High</td></tr>
<tr>
<td>Faster Deployment</td><td>No Drift Detection</td></tr>
<tr>
<td>Faster Feedback</td></tr>
<tr>
<td>Test Against a real environment</td></tr>
<tr>
<td>Emulators</td><td>No costs</td><td>Too long to set Up</td><td>Medium</td></tr>
<tr>
<td>No need to deploy your code</td><td>Sometimes more time in Debugging Emulator</td></tr>
<tr>
<td>More confident before deployment</td><td>Working against a mocked environment</td></tr>
<tr>
<td>Reduce the latency of your deploys - develop your apps offline</td></tr>
<tr>
<td>Hot reloading but faster than hot-swap</td></tr>
<tr>
<td>TDD</td><td>No Costs</td><td>Not testing against cloud environment</td><td>Medium</td></tr>
<tr>
<td>Having Tests</td></tr>
</tbody>
</table>
</div><p>According to Yan Cui's blog post titled <a target="_blank" href="https://theburningmonk.com/2022/05/my-testing-strategy-for-serverless-applications/">'My testing strategy for serverless applications'</a>, using an emulator like localstack <em>is a waste of time</em> and <em>breaks in a mysterious way</em>. However, using a combination of different testing strategies can increase confidence before deployment. I have personally found that using TDD and hot-swap is effective for faster integration testing.</p>
<p>Let me know, what is your development setup and what tips and tricks you have.</p>
]]></content:encoded></item><item><title><![CDATA[Thoughts about Projen]]></title><description><![CDATA[A new generation of project generators

Introduction
Projen, an open-source project generator, was created by Elad Ben-Israel, who also built AWS CDK. It offers modern software engineering practices like linting, prettier, test runners, Github action...]]></description><link>https://blog.jolo.dev/thoughts-about-projen</link><guid isPermaLink="true">https://blog.jolo.dev/thoughts-about-projen</guid><category><![CDATA[AWS]]></category><category><![CDATA[aws-cdk]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Python]]></category><category><![CDATA[React]]></category><dc:creator><![CDATA[JoLo]]></dc:creator><pubDate>Thu, 27 Apr 2023 21:18:15 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p><em>A new generation of project generators</em></p>
</blockquote>
<h1 id="heading-introduction">Introduction</h1>
<p><a target="_blank" href="https://github.com/projen/projen">Projen</a>, an open-source project generator, was created by Elad Ben-Israel, who also built <a target="_blank" href="https://aws.amazon.com/cdk/">AWS CDK</a>. It offers modern software engineering practices like linting, prettier, test runners, Github actions, and more when starting a new project.</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/chatgpt-question-1.png" alt="chatgpt-question-1.png" /></p>
<p>You might assume that Projen is only compatible with CDK, just like ChatGPT. However, after reviewing their documentation, it became apparent that Projen also handles generating project files, managing dependencies, and configuring build tools. This means that developers can concentrate on writing code instead of worrying about these tasks. Projen can be used to bootstrap various projects, including <code>React</code> and <code>mvn</code> apps, as listed on <a target="_blank" href="https://github.com/projen/projen#project-types">their website</a>. While Projen is compatible with any project, it particularly shines when used with AWS CDK. When creating a CDK project using <code>cdk init app</code> you won't have all the necessary tooling at first. Projen provides these tools, letting you focus on developing infrastructure rather than setting up the project.</p>
<p>In this blog post, we will explore the 'battery includes' of Projen and evaluate its outcome.</p>
<h1 id="heading-whats-inside">What's inside?</h1>
<p>We will have a look at bootstrapping a CDK with <a target="_blank" href="https://projen.io/api/API.html#projen-awscdk-awscdktypescriptapp">Typescript</a> and <a target="_blank" href="%5Bawscdk-app-py%5D(https://projen.io/api/API.html#projen-awscdk-awscdkpythonapp)">Python</a>, <a target="_blank" href="https://projen.io/api/API.html#projen-web-reacttypescriptproject">React with Typescript</a>, and a blank <a target="_blank" href="https://projen.io/api/API.html#projen-python-pythonproject">Python</a> project.</p>
<h2 id="heading-aws-cdk-typescript">AWS CDK Typescript</h2>
<p>To start with AWS CDK in Typescript, you simply run <code>npx projen new awscdk-app-ts</code></p>
<pre><code class="lang-sh">👾 Project definition file was created at /Users/jolo/Development/projen-demo/.projenrc.ts
👾 Installing dependencies...
👾 install | yarn install --check-files
yarn install v1.22.19
info No lockfile found.
[1/4] 🔍  Resolving packages...
warning npm-check-updates &gt; pacote &gt; @npmcli/run-script &gt; node-gyp &gt; make-fetch-happen &gt; cacache &gt; @npmcli/move-file@2.0.1: This functionality has been moved to @npmcli/fs
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
✨  Done <span class="hljs-keyword">in</span> 37.05s.
👾 Installing dependencies...
👾 install | yarn install --check-files
yarn install v1.22.19
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
✨  Done <span class="hljs-keyword">in</span> 4.80s.

&gt; projen-demo@0.0.0 eslint
&gt; npx projen eslint

Initialized empty Git repository <span class="hljs-keyword">in</span> /Users/jolo/Development/projen-demo/.git/
[main (root-commit) 53e6677] chore: project created with projen
 22 files changed, 6925 insertions(+)
 create mode 100644 .eslintrc.json
 create mode 100644 .gitattributes
 create mode 100644 .github/pull_request_template.md
 create mode 100644 .github/workflows/build.yml
 create mode 100644 .github/workflows/pull-request-lint.yml
 create mode 100644 .github/workflows/upgrade.yml
 create mode 100644 .gitignore
 create mode 100644 .mergify.yml
 create mode 100644 .npmignore
 create mode 100644 .projen/deps.json
 create mode 100644 .projen/files.json
 create mode 100644 .projen/tasks.json
 create mode 100644 .projenrc.ts
 create mode 100644 LICENSE
 create mode 100644 README.md
 create mode 100644 cdk.json
 create mode 100644 package.json
 create mode 100644 src/main.ts
 create mode 100644 <span class="hljs-built_in">test</span>/main.test.ts
 create mode 100644 tsconfig.dev.json
 create mode 100644 tsconfig.json
 create mode 100644 yarn.lock
</code></pre>
<p>When working with Node.js projects, you'll find that it generates certain files for linting (using <a target="_blank" href="https://eslint.org/">ESLint</a>), Typescript (using <code>tsconfig.json</code>), testing (using <a target="_blank" href="https://jestjs.io/">Jest</a>), and even GitHub actions for CI/CD. To configure your project, you'll need to use the <code>.projenrc.ts</code> file. However, the documentation for this can be difficult to understand. Thankfully, using Typescript can help with auto-suggestions for available configuration options. If you prefer to use <a target="_blank" href="https://pnpm.io/"><code>pnpm</code></a> over <a target="_blank" href="https://yarnpkg.com/"><code>yarn</code></a>, you can run <code>npx projen new awscdk-app-ts --package-manager PNPM</code> or add it to the <code>.projenrc.ts</code> file, although this is not clearly explained in the documentation.</p>
<h3 id="heading-evaluation">Evaluation</h3>
<p>The CDK Typescript app that is bootstrapped contains all the necessary tools for development, including linting rules, a testing framework, Typescript configuration, and even GitHub actions for CI/CD. Although <code>cdk init app --language typescript</code> only comes equipped with <code>jest</code>, in my opinion, these tools are sufficient to get started.</p>
<p>If you prefer a different tool, such as <a target="_blank" href="https://vitest.dev/">vitest</a>, you can disable jest by configuring it in <code>.projenrc.ts</code>. However, it is important to note that packages installed via <code>npm</code> must also be added to this file to avoid being overwritten. I have made the mistake of forgetting to add a package and then having it overwritten when I ran <code>pnpm install my-npm-package</code>. Additionally, the documentation is very bad.</p>
<h2 id="heading-aws-cdk-python">AWS CDK Python</h2>
<p>To bootstrap your AWS CDK python, run <code>npx projen new awscdk-app-py</code>.</p>
<pre><code class="lang-sh">[main (root-commit) 68e75df] chore: project created with projen
 16 files changed, 434 insertions(+)
 create mode 100644 .gitattributes
 create mode 100644 .github/workflows/pull-request-lint.yml
 create mode 100644 .gitignore
 create mode 100644 .projen/deps.json
 create mode 100644 .projen/files.json
 create mode 100644 .projen/tasks.json
 create mode 100644 .projenrc.py
 create mode 100644 README.md
 create mode 100644 app.py
 create mode 100644 cdk.json
 create mode 100644 projen_demo/__init__.py
 create mode 100644 projen_demo/main.py
 create mode 100644 requirements-dev.txt
 create mode 100644 requirements.txt
 create mode 100644 tests/__init__.py
 create mode 100644 tests/test_example.py
</code></pre>
<p>The <code>.projenrc.py</code> file is equivalent to the <code>.projenrc.ts</code> file used in the Typescript app discussed earlier. The virtual environment is established through the use of <code>.env</code>, although I prefer to name it <code>.venv</code>. Unfortunately, I have not been able to locate an option to change the name. While <a target="_blank" href="https://docs.pytest.org/en/7.3.x/"><code>pytest</code></a> is set to <code>True</code>, it is not included in <code>requirements-dev.txt</code>, so you will have to add it manually to the <code>.projenrc.py</code> file. However, the file does include the necessary GitHub actions for CI/CD.</p>
<h3 id="heading-evaluation-1">Evaluation</h3>
<p>I have encountered some issues with missing linting and formatting tools like <a target="_blank" href="https://github.com/pycqa/flake8">flake8</a> or <a target="_blank" href="https://github.com/psf/black">black</a>. Additionally, I faced difficulties while using <a target="_blank" href="https://python-poetry.org/">poetry</a>. When I added <code>pytest</code> in <code>.projenrc.py</code> as <code>dev_deps</code>, the <code>npx projen</code> did not include the dependencies in the <code>poetry.toml</code>. As a result, I had to manually add <code>pytest</code>, which was unexpected. It is also important to note that installing Node.js is necessary to execute <code>npx projen</code>. Considering all these issues, I would not recommend using this for your Python application. If you prefer using CDK with Python, it is better to use the standard <a target="_blank" href="https://docs.aws.amazon.com/cdk/v2/guide/work-with-cdk-python.html">CDK-CLI</a> and add your developer tools there. Finally, I also do not recommend using the blank Python-projen.</p>
<h2 id="heading-react-typescript">React Typescript</h2>
<p>Let's see what's included in the React app, <code>npx projen new react-ts --package-manager PNPM</code>.</p>
<pre><code class="lang-sh">👾 Project definition file was created at /Users/jolo/Development/projen-demo/.projenrc.ts
👾 Installing dependencies...
👾 install | pnpm i --no-frozen-lockfile
Downloading registry.npmjs.org/typescript/4.9.5: 11.6 MB/11.6 MB, <span class="hljs-keyword">done</span>
 WARN  deprecated svgo@1.3.2: This SVGO version is no longer supported. Upgrade to v2.x.x.
 WARN  deprecated rollup-plugin-terser@7.0.2: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser
 WARN  deprecated stable@0.1.8: Modern JS already guarantees Array<span class="hljs-comment">#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility</span>
 WARN  deprecated sourcemap-codec@1.4.8: Please use @jridgewell/sourcemap-codec instead
 WARN  deprecated w3c-hr-time@1.0.2: Use your platform<span class="hljs-string">'s native performance.now() and performance.timeOrigin.
 WARN  deprecated @npmcli/move-file@2.0.1: This functionality has been moved to @npmcli/fs
Packages: +1423
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Packages are hard linked from the content-addressable store to the virtual store.
  Content-addressable store is at: /Users/foo/Library/pnpm/store/v3
  Virtual store is at:             node_modules/.pnpm
Progress: resolved 1423, reused 959, downloaded 464, added 1423, done
node_modules/.pnpm/core-js-pure@3.30.1/node_modules/core-js-pure: Running postinstall script, done in 39ms
node_modules/.pnpm/core-js@3.30.1/node_modules/core-js: Running postinstall script, done in 38ms

dependencies:
+ react 18.2.0
+ react-dom 18.2.0
+ react-scripts 5.0.1
+ web-vitals 3.3.1

devDependencies:
+ @testing-library/jest-dom 5.16.5
+ @testing-library/react 14.0.0
+ @testing-library/user-event 14.4.3
+ @types/jest 29.5.1
+ @types/node 16.18.25 (18.16.1 is available)
+ @types/react 18.2.0
+ @types/react-dom 18.2.1
+ @typescript-eslint/eslint-plugin 5.59.1
+ @typescript-eslint/parser 5.59.1
+ eslint 8.39.0
+ eslint-import-resolver-node 0.3.7
+ eslint-import-resolver-typescript 3.5.5
+ eslint-plugin-import 2.27.5
+ npm-check-updates 16.10.9
+ projen 0.71.27
+ ts-node 10.9.1
+ typescript 4.9.5 (5.0.4 is available)

The integrity of 6434 files was checked. This might have caused installation to take longer.
Done in 15.9s
👾 Installing dependencies...
👾 install | pnpm i --no-frozen-lockfile
 WARN  deprecated svgo@1.3.2: This SVGO version is no longer supported. Upgrade to v2.x.x.
 WARN  deprecated rollup-plugin-terser@7.0.2: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser
 WARN  deprecated stable@0.1.8: Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility
 WARN  deprecated @npmcli/move-file@2.0.1: This functionality has been moved to @npmcli/fs
 WARN  deprecated sourcemap-codec@1.4.8: Please use @jridgewell/sourcemap-codec instead
 WARN  deprecated w3c-hr-time@1.0.2: Use your platform'</span>s native performance.now() and performance.timeOrigin.
Already up to date
Progress: resolved 1423, reused 1423, downloaded 0, added 0, <span class="hljs-keyword">done</span>
Done <span class="hljs-keyword">in</span> 3.7s

&gt; projen-demo@0.0.0 eslint
&gt; npx projen eslint

Initialized empty Git repository <span class="hljs-keyword">in</span> /Users/jolo/Development/projen-demo/.git/
[main (root-commit) ca3f2aa] chore: project created with projen
 35 files changed, 13095 insertions(+)
 create mode 100644 .eslintrc.json
 create mode 100644 .gitattributes
 create mode 100644 .github/pull_request_template.md
 create mode 100644 .github/workflows/build.yml
 create mode 100644 .github/workflows/pull-request-lint.yml
 create mode 100644 .github/workflows/upgrade.yml
 create mode 100644 .gitignore
 create mode 100644 .mergify.yml
 create mode 100644 .npmignore
 create mode 100644 .npmrc
 create mode 100644 .projen/deps.json
 create mode 100644 .projen/files.json
 create mode 100644 .projen/tasks.json
 create mode 100644 .projenrc.ts
 create mode 100644 LICENSE
 create mode 100644 README.md
 create mode 100644 package.json
 create mode 100644 pnpm-lock.yaml
 create mode 100644 public/favicon.ico
 create mode 100644 public/index.html
 create mode 100644 public/logo192.png
 create mode 100644 public/logo512.png
 create mode 100644 public/manifest.json
 create mode 100644 public/robots.txt
 create mode 100644 src/App.css
 create mode 100644 src/App.test.tsx
 create mode 100644 src/App.tsx
 create mode 100644 src/index.css
 create mode 100644 src/index.tsx
 create mode 100644 src/logo.svg
 create mode 100644 src/react-app-env.d.ts
 create mode 100644 src/reportWebVitals.ts
 create mode 100644 src/setupTests.ts
 create mode 100644 tsconfig.dev.json
 create mode 100644 tsconfig.json
</code></pre>
<p>The app includes linting with ESLint, testing with Jest and <a target="_blank" href="https://testing-library.com/docs/react-testing-library/intro/">testing library</a>, Typescript configs, and GitHub Actions for CI/CD. The setup is like the AWS CDK Typescript, which means that all the dependencies need to be placed in the <code>.projenrc.ts</code>.</p>
<h3 id="heading-evaluation-2">Evaluation</h3>
<p>Upon inspection, I noticed that the React app uses <code>create-react-app</code> and was last updated on April 12th, 2022. However, it is not mentioned in the official documentation on <a target="_blank" href="https://react.dev/learn/start-a-new-react-project">react.dev</a>. Additionally, I find it inconvenient to store my dependencies within the <code>.projenrc.ts</code> file. Overall, I believe that most people would choose a better alternative, such as <a target="_blank" href="https://vitejs.dev/">ViteJS</a>, instead of using projen for their React app.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Projen is a valuable open-source tool designed to simplify the creation and management of software projects for developers. It works exceptionally well with AWS CDK and Typescript, automating repetitive and error-prone tasks and providing an organized project structure, a testing framework, and built-in CI/CD support.</p>
<p>In our blog, we have showcased several project types that Projen can bootstrap, including AWS CDK with Typescript and Python, and React app. However, the entry point file <code>.projenrc.[ts|py|js|json]</code> can be frustrating to work with since it requires the manual addition of all dependencies. This is not the standard way of adding dependencies for most developers, who usually copy the <code>npm install third-party</code> command from a website and paste it into the terminal. Additionally, the documentation for CDK with Python or creating a React App is not very helpful.</p>
<p>As a tip, you could bootstrap your AWS CDK Typescript app and then delete the <code>.projenrc.ts</code> - file. But then you will need to change the permissions of all the configuration files such as <code>tsconfig.json</code>, basically any file which contains <code>~~ Generated by projen. To modify..</code> as they make them read-only. For the rest, use the basic CDK- CLI and customize it to your need.</p>
]]></content:encoded></item><item><title><![CDATA[Declutter your inbox to achieve Zero Inbox with n8n]]></title><description><![CDATA[Are you feeling overwhelmed by a cluttered email inbox? Perhaps you're approaching the limits of your Google storage subscription, or simply wish your inbox could be decluttered without spending hours sorting through emails. In any case, it's importa...]]></description><link>https://blog.jolo.dev/declutter-your-inbox-to-achieve-zero-inbox-with-n8n</link><guid isPermaLink="true">https://blog.jolo.dev/declutter-your-inbox-to-achieve-zero-inbox-with-n8n</guid><category><![CDATA[n8n]]></category><category><![CDATA[automation]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[gmail]]></category><dc:creator><![CDATA[JoLo]]></dc:creator><pubDate>Tue, 11 Apr 2023 11:04:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/D2TZ-ashGzc/upload/7dc397dce424b54a6c61856604461cc5.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Are you feeling overwhelmed by a cluttered email inbox? Perhaps you're approaching the limits of your Google storage subscription, or simply wish your inbox could be decluttered without spending hours sorting through emails. In any case, it's important to practice good 'digital hygiene' and take control of your emails. With n8n, you can do just that.</p>
<p><a target="_blank" href="https://n8n.io/">n8n</a> (pronounced: nodemation) is a low-code automation platform that empowers users to build powerful workflows and automate their daily tasks. It is an open-source tool and a very good alternative to Zapier or Make. However, these tools require you to know a bit of coding.</p>
<p>You can use it to automate (boring) tasks, you can save yourself time and energy while keeping your inbox organized and under control. In this blog post, I will show how you can declutter your inbox by labeling your mails by the respective sender and achieving a <a target="_blank" href="https://www.techtarget.com/whatis/definition/inbox-zero">Zero Inbox</a>.</p>
<h2 id="heading-problem">Problem</h2>
<p>The problem is simple, you might have a Gmail account since forever, and you have old emails which you probably will never read again. But they are in your inbox.</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/problem-too-many-emails.png" alt="problem-too-many-emails.png" /></p>
<p>Another problem is that you might have too many unread E-Mails, and it would be nice to identify important emails and send the unimportant ones directly to the trash. By labeling them, you can then decide for example to unsubscribe from that newsletter.</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/problem-cluttered-inbox.png" alt="problem-cluttered-inbox.png" /></p>
<p>What also can happen, is that you might reach the end of your storage. At that time, you will be forced to delete older emails. That leads to a struggle by searching which one to keep and which not... sigh... wish you would have categorized or tagged, right? Or maybe you don't trust Gmail and want to keep as fewer data as possible.</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/problem-storage.png" alt="problem-storage.png" /></p>
<p>It is like your daily washing routine, keep good hygiene. In the following sections, we will set up an n8n workflow that will read messages, get their senders, use that value to create a label, and attach these to the messages.</p>
<h2 id="heading-pre-requisite">Pre-requisite</h2>
<p>If you follow along, you need to have n8n installed (I use <a target="_blank" href="https://docs.n8n.io/hosting/installation/docker/">Docker</a>, but there is also a <a target="_blank" href="https://docs.n8n.io/choose-n8n/desktop-app/">Desktop version</a>) and a <a target="_blank" href="https://cloud.google.com/">Google Developer account</a> (use the <a target="_blank" href="https://docs.n8n.io/integrations/builtin/credentials/google/oauth-generic/">tutorial of n8n</a> to create your credentials).</p>
<p>You should also get familiar with n8n and also have some JavaScript knowledge. Luckily, n8n provides <a target="_blank" href="https://docs.n8n.io/courses/">courses</a> to get started.</p>
<h2 id="heading-workflow">Workflow</h2>
<p>Below you can find my result workflow.</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/whole-workflow.png" alt="whole-workflow.png" /></p>
<p>In order to use the Gmail- node, you need to create Gmail Credentials (Below is Google Calendar but it's pretty much the same).</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/n8n-add-google-calendar.gif" alt="n8n-add-google-calendar.gif" /></p>
<p>The next subsections will explain each node.</p>
<h3 id="heading-trigger">Trigger</h3>
<p>This is how every n8n- workflow starts. You can trigger a workflow on a scheduled basis, via webhooks, or in my case manually.</p>
<p><strong>Pro-tip</strong>: n8n also offers a trigger whenever an E-Mail has been received. That would label your email immediately 😉</p>
<h3 id="heading-get-messages">Get Messages</h3>
<p>Once you are authenticated, you should be able to use all the functions from the <a target="_blank" href="https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.gmail/">Gmail Node</a>. I recommend using a <code>Limit</code> and execute in batches. I also put a filter but you could leave that out and receive the latest 500 mails. Here it labels the messages which are in your '<strong>INBOX</strong>'. Later you <strong>should archive</strong> your mails otherwise they would be fetched again.</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/GMail-Node_Get_Messages.png" alt="GMail-Node_Get_Messages.png" /></p>
<h3 id="heading-get-the-senderdomain-name-of-the-from-value">Get the sender/domain name of the 'From'- value</h3>
<p>This node contains a code snippet that extracts the sender domain from the <code>From</code>- value (E-Mail). I save that information into a new column and to avoid a naming clash later, I also renamed the <code>id</code> to <code>mailId</code> and removed the <code>id</code> - column.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> item <span class="hljs-keyword">of</span> $input.all()) {
  <span class="hljs-comment">// This is how 'From' looks like: Foo Bar &lt;foo@bar.xyz&gt;</span>
  <span class="hljs-comment">// and this method returns bar.xyz</span>
  <span class="hljs-keyword">const</span> sender = item.json.From.split(<span class="hljs-string">'@'</span>)[<span class="hljs-number">1</span>].slice(<span class="hljs-number">0</span>,<span class="hljs-number">-1</span>);
  item.json.sender = sender;
  item.json.mailId = item.json.id;
  <span class="hljs-keyword">delete</span> item.json.id;
}

<span class="hljs-keyword">return</span> $input.all();
</code></pre>
<h3 id="heading-creating-labels">Creating Labels</h3>
<p>From the previous node, the value <code>sender</code> here will be set as a new Label. <strong>Important</strong>: the node should continue running regardless if the label already exists. If you don't enable the settings, the node might fail. For setting the label's name, we use the <a target="_blank" href="https://docs.n8n.io/code-examples/expressions/"><code>Expression</code></a> function of n8n. That allows us to use a dynamic value depending on the sender's name.</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/n8n-creating-labels.gif" alt="n8n-creating-labels.gif" /></p>
<h3 id="heading-get-all-the-labels">Get all the labels</h3>
<p>The next node is for retrieving all the labels.</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/Gmail-node-get-all-labels.png" alt="Gmail-node-get-all-labels.png" /></p>
<p><strong>But make sure that this is executed just once!</strong> n8n is implemented in a way that it runs for each row the <code>Get All Labels</code>- node. In my case, it would execute that node 500x.</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/n8n-get-all-labels.gif" alt="n8n-get-all-labels.gif" /></p>
<h3 id="heading-merge">Merge</h3>
<p>The Merge- Node combines the mails with the labels accordingly. So the upper path creates the labels according to the sender's information. Each label then gets a new <code>LabelID</code> which needs to be combined with the E-mail. Below, you can find my option to achieve that.</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/merge-options.png" alt="merge-options.png" /></p>
<h2 id="heading-attach-the-labels-to-the-mail">Attach the Labels to the Mail</h2>
<p>Now, we attach the label with the message by its <code>Message ID</code> and <code>Label Id</code>. Again here, we use the n8n's expression function. Remember that [above](# Get the sender/domain name of the 'From'- value), we have renamed the ID of the message to <code>mailId</code>and the <code>id</code> below is the ID of the label.</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/attach-label-to-mail-options.png" alt="attach-label-to-mail-options.png" /></p>
<h2 id="heading-review-each-label-and-then-delete-the-corresponding-labels">Review each label and then delete the corresponding labels</h2>
<p>After each message got attached, it's now human intervention to identify which E-Mails you should safely delete and which you should archive. Below, you can see a preview of the attached labels and in the next section, I will list which kind of E-Mails you can safely delete.</p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/labeled-mails.gif" alt="labeled-mails.gif" /></p>
<p><img src="https://jolo-dev-blog-images.s3.amazonaws.com/labeled-mails2.gif" alt="labeled-mails2.gif" /></p>
<h2 id="heading-ideas-of-which-kind-of-e-mails-to-delete">Ideas of which kind of E-Mails to delete</h2>
<p>Great, now you labeled many emails, and it's now up to you what kind of emails to delete. I think it's good to label them first so that you as a human can now decide which emails to delete. Are you a bit overwhelmed, what to delete? Here are some emails you can delete for sure:</p>
<ul>
<li><p>Confirmation mails as they are outdated</p>
</li>
<li><p>Notification mails by Google, Post courier, or any other providers which say e.g. that you have logged into a new browser as they just notify you</p>
</li>
<li><p>RSVP of an invitation, well they are kinda Notification mails</p>
</li>
<li><p>Messages which you also can find on their website or app (e.g. PayPal)</p>
</li>
<li><p>Newsletter, by that time they are outdated</p>
</li>
<li><p>Travel ternaries, if you haven't gone yet, why should you keep them? Hope you have taken great pictures</p>
</li>
<li><p>Auto replies</p>
</li>
</ul>
<h2 id="heading-things-to-keep-in-mind">Things to keep in mind</h2>
<p>These are things you should keep in mind when building such a workflow.</p>
<h3 id="heading-you-cannot-run-this-workflow-on-all-your-e-mails-at-once">You cannot run this workflow on all your E-Mails at once</h3>
<p>Google might warn because of suspicious activities. They might also lock your account.</p>
<blockquote>
<p><strong>Recommendation</strong>: Run your workflow on 500 mails and do it twice a day. Also put a time filter here</p>
</blockquote>
<h3 id="heading-evaluate-each-label-and-try-not-to-delete-important-mails">Evaluate each label and try not to delete important mails</h3>
<p>It is important not blindly to delete the emails but also to do a good review and not just a quick scan through them. Otherwise, you might delete important emails.</p>
<h3 id="heading-put-the-mails-into-the-archive">Put the mails into the archive</h3>
<p>If you don't put the emails in the archive after reviewing them, they might be picked up again, and you will never finish decluttering your inbox.</p>
<blockquote>
<p>The easiest way is to put everything to archive. However, that would still lead to some mails to archive which you might will never need again or which are simply outdated.</p>
</blockquote>
<h2 id="heading-full-workflow-as-code">Full Workflow as Code</h2>
<p>Below, you find the whole workflow as JSON which you can paste into your n8n.</p>
<pre><code class="lang-json">{
  'meta': {
    'instanceId': 'e842ae497bd2320305776c95ab48327e7541feb0365d1ecd05e0e0ca871ef6df'
  },
  'nodes': [
    {
      'parameters': {},
      'id': '<span class="hljs-number">94603</span>d9d<span class="hljs-number">-1</span>f1a<span class="hljs-number">-4</span>d77<span class="hljs-number">-8</span>d0a-f1cfa9c0a7ab',
      'name': 'When clicking \'Execute Workflow\'',
      'type': 'n8n-nodes-base.manualTrigger',
      'typeVersion': <span class="hljs-number">1</span>,
      'position': [
        <span class="hljs-number">940</span>,
        <span class="hljs-number">620</span>
      ]
    },
    {
      'parameters': {
        'mode': 'combine',
        'mergeByFields': {
          'values': [
            {
              'field1': 'name',
              'field2': 'sender'
            }
          ]
        },
        'options': {
          'clashHandling': {
            'values': {
              'resolveClash': 'preferInput1'
            }
          }
        }
      },
      'id': '<span class="hljs-number">81</span>cb45ca-bccb<span class="hljs-number">-4</span>c21<span class="hljs-number">-95</span>fb<span class="hljs-number">-6</span>db89969acfc',
      'name': 'Merge',
      'type': 'n8n-nodes-base.merge',
      'typeVersion': <span class="hljs-number">2</span>,
      'position': [
        <span class="hljs-number">1980</span>,
        <span class="hljs-number">600</span>
      ]
    },
    {
      'parameters': {
        'operation': 'getAll',
        'limit': <span class="hljs-number">500</span>,
        'filters': {
          'labelIds': [
            'INBOX'
          ],
          'receivedBefore': '<span class="hljs-number">2022</span><span class="hljs-number">-02</span><span class="hljs-number">-28</span>T23:<span class="hljs-number">00</span>:<span class="hljs-number">00.000</span>Z'
        }
      },
      'id': '<span class="hljs-number">14</span>b681ff<span class="hljs-number">-4</span>ce8<span class="hljs-number">-4</span>b5e-bc71<span class="hljs-number">-1</span>c7cac34d13e',
      'name': 'Get Messages',
      'type': 'n8n-nodes-base.gmail',
      'typeVersion': <span class="hljs-number">2</span>,
      'position': [
        <span class="hljs-number">1160</span>,
        <span class="hljs-number">620</span>
      ],
      'notesInFlow': <span class="hljs-literal">true</span>,
      'credentials': {
        'gmailOAuth2': {
          'id': '<span class="hljs-number">8</span>',
          'name': 'Gmail account'
        }
      },
      'notes': 'Messages which have the Label \'INBOX\''
    },
    {
      'parameters': {
        'jsCode': 'for (const item of $input.all()) {\n  const sender = item.json.From.split('@')[1].slice(0,-1);\n  item.json.sender = sender;\n  item.json.mailId = item.json.id;\n  delete item.json.id;\n}\n\nreturn $input.all();'
      },
      'id': '<span class="hljs-number">209</span>f6aeb-ca56<span class="hljs-number">-4</span>b27<span class="hljs-number">-8</span>cf8-f5d3a029f523',
      'name': 'Get the Sender and Cleaning',
      'type': 'n8n-nodes-base.code',
      'typeVersion': <span class="hljs-number">1</span>,
      'position': [
        <span class="hljs-number">1380</span>,
        <span class="hljs-number">620</span>
      ],
      'notesInFlow': <span class="hljs-literal">true</span>,
      'notes': 'The function which slices the value from the \'From\'- value to get the sender/its domain'
    },
    {
      'parameters': {
        'resource': 'label',
        'returnAll': <span class="hljs-literal">true</span>
      },
      'id': 'daab0c5b<span class="hljs-number">-31</span>bd<span class="hljs-number">-47</span>c1<span class="hljs-number">-9054</span>-fa40709c545b',
      'name': 'Get All Labels',
      'type': 'n8n-nodes-base.gmail',
      'typeVersion': <span class="hljs-number">2</span>,
      'position': [
        <span class="hljs-number">1760</span>,
        <span class="hljs-number">400</span>
      ],
      'executeOnce': <span class="hljs-literal">true</span>,
      'credentials': {
        'gmailOAuth2': {
          'id': '<span class="hljs-number">8</span>',
          'name': 'Gmail account'
        }
      }
    },
    {
      'parameters': {
        'operation': 'addLabels',
        'messageId': '={{ $json.mailId }}',
        'labelIds': '={{ $json.id }}'
      },
      'id': '<span class="hljs-number">1</span>d3c4cca-bb1f<span class="hljs-number">-46</span>ee<span class="hljs-number">-9</span>d13-d8fc9065b02e',
      'name': 'Attach Label to Mail',
      'type': 'n8n-nodes-base.gmail',
      'typeVersion': <span class="hljs-number">2</span>,
      'position': [
        <span class="hljs-number">2200</span>,
        <span class="hljs-number">600</span>
      ],
      'credentials': {
        'gmailOAuth2': {
          'id': '<span class="hljs-number">8</span>',
          'name': 'Gmail account'
        }
      }
    },
    {
      'parameters': {
        'resource': 'label',
        'operation': 'create',
        'name': '={{ $json.sender }}',
        'options': {}
      },
      'id': '<span class="hljs-number">44</span>a02846<span class="hljs-number">-31</span>a5<span class="hljs-number">-42e3</span>-af1e<span class="hljs-number">-7378</span>ff4f87f0',
      'name': 'Creating Labels',
      'type': 'n8n-nodes-base.gmail',
      'typeVersion': <span class="hljs-number">2</span>,
      'position': [
        <span class="hljs-number">1560</span>,
        <span class="hljs-number">400</span>
      ],
      'alwaysOutputData': <span class="hljs-literal">true</span>,
      'retryOnFail': <span class="hljs-literal">false</span>,
      'credentials': {
        'gmailOAuth2': {
          'id': '<span class="hljs-number">8</span>',
          'name': 'Gmail account'
        }
      },
      'continueOnFail': <span class="hljs-literal">true</span>
    }
  ],
  'connections': {
    'When clicking \'Execute Workflow\'': {
      'main': [
        [
          {
            'node': 'Get Messages',
            'type': 'main',
            'index': <span class="hljs-number">0</span>
          }
        ]
      ]
    },
    'Merge': {
      'main': [
        [
          {
            'node': 'Attach Label to Mail',
            'type': 'main',
            'index': <span class="hljs-number">0</span>
          }
        ]
      ]
    },
    'Get Messages': {
      'main': [
        [
          {
            'node': 'Get the Sender and Cleaning',
            'type': 'main',
            'index': <span class="hljs-number">0</span>
          }
        ]
      ]
    },
    'Get the Sender and Cleaning': {
      'main': [
        [
          {
            'node': 'Creating Labels',
            'type': 'main',
            'index': <span class="hljs-number">0</span>
          },
          {
            'node': 'Merge',
            'type': 'main',
            'index': <span class="hljs-number">1</span>
          }
        ]
      ]
    },
    'Get All Labels': {
      'main': [
        [
          {
            'node': 'Merge',
            'type': 'main',
            'index': <span class="hljs-number">0</span>
          }
        ]
      ]
    },
    'Creating Labels': {
      'main': [
        [
          {
            'node': 'Get All Labels',
            'type': 'main',
            'index': <span class="hljs-number">0</span>
          }
        ]
      ]
    }
  }
}
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This n8n- workflow declutters your inbox by creating labels for the senders. The owner can then identify quicker which emails to be deleted and which one to keep/archive. This approach usually takes a lot of time, and building a workflow like this saves you a lot of time. But keep in mind that this workflow does not delete, but just labels them. However, it took me weeks to declutter my inbox, as you cannot label it all at once. Take the section 'Things to keep in mind' seriously.</p>
<p>The workflow is also adaptable to any other workflow automation tool, such as Zapier and Make.</p>
<p>It's like your body's hygiene. The cleaner and tidier your inbox is, the more relaxed and mind will be. Start your healthy digital hygiene now! If you want to integrate that yourself but struggle with the setup, I am happy to help. Follow for more!</p>
]]></content:encoded></item><item><title><![CDATA[Effectively using CDK-pipelines to deploy CDK stacks into multiple accounts]]></title><description><![CDATA[Excellent! You have developed your product using AWS and now you are looking to implement effective CI/CD strategies. You are already aware of AWS Codepipeline and you have a fair comprehension of AWS Codebuild. Nevertheless, if your infrastructure i...]]></description><link>https://blog.jolo.dev/effectively-using-cdk-pipelines-to-deploy-cdk-stacks-into-multiple-accounts</link><guid isPermaLink="true">https://blog.jolo.dev/effectively-using-cdk-pipelines-to-deploy-cdk-stacks-into-multiple-accounts</guid><category><![CDATA[AWS]]></category><category><![CDATA[CI/CD]]></category><category><![CDATA[CI/CD pipelines]]></category><category><![CDATA[aws-cdk]]></category><dc:creator><![CDATA[JoLo]]></dc:creator><pubDate>Mon, 27 Mar 2023 19:52:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/4XvAZN8_WHo/upload/5d992b2b23efb4b83d469fb9df6e3c7a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Excellent! You have developed your product using AWS and now you are looking to implement effective CI/CD strategies. You are already aware of <a target="_blank" href="https://aws.amazon.com/codepipeline/">AWS Codepipeline</a> and you have a fair comprehension of <a target="_blank" href="https://aws.amazon.com/codebuild/pricing/?nc=sn&amp;loc=3">AWS Codebuild</a>. Nevertheless, if your infrastructure is constructed using CDK, you might want to explore <a target="_blank" href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.pipelines-readme.html">CDK pipelines</a> as an alternative option.</p>
<p>CDK pipelines integrate with other AWS services, such as AWS CodePipeline and AWS CodeBuild, to enable you to use their functionality within your CDK pipelines. For example, you can use AWS CodePipeline to manage the release process for your application, and AWS CodeBuild to build and test your application.</p>
<p>Overall, CDK pipelines provide an easy way to automate the deployment of your CDK-based applications, helping you to save time and effort and ensure that your applications are deployed consistently and reliably.</p>
<p>In this blog, I will show you how to use CDK pipelines to create a Codepipeline for deploying CDK-Stack into multiple accounts.</p>
<h2 id="heading-why-have-multiple-aws-accounts">Why Have Multiple AWS Accounts?</h2>
<p>Using multiple AWS accounts can help you to effectively manage your resources, isolate your environments and business units, and control access to your resources. In addition, it can enhance your security and help you comply with regulatory requirements. It is recommended to use separate accounts for different purposes from the start or gradually migrate your resources from a single account to multiple accounts.</p>
<p>Here are some keys to using multiple accounts</p>
<ul>
<li><p>Improved resource management</p>
</li>
<li><p>Increased security</p>
</li>
<li><p>Compliance with regulations</p>
</li>
</ul>
<h2 id="heading-the-setup">The Setup</h2>
<p>If you're setting up Landing Zones, consider using <a target="_blank" href="https://superwerker.cloud/">superwerker</a>. This tool helps set up AWS Control Tower and AWS Organization for account management, following best practices. It's also best practice to have separate accounts for <em>Dev</em>, <em>Staging</em>, and <em>Prod</em> for your application, as well as an account for your CI/CD, which may contain your Git repository and AWS ECR for your Docker images.</p>
<p>Assuming you've set up separate accounts, you can develop a CDK stack and push the code to your CI/CD account. From there, any CI/CD pipeline should be triggered. To enable this, use AWS CodePipeline and the CDK library: CDK <a target="_blank" href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.pipelines-readme.html">pipelines</a>.</p>
<p>Below you will find the setup for this blog.</p>
<h2 id="heading-the-cdk-pipeline-stack">The CDK- pipeline Stack</h2>
<p>It's important to note the difference between the <code>Codepipeline</code> module and CDK Pipelines. While CDK Pipelines uses CodePipeline for deploying stacks created with CDK, CodePipeline is a more general-purpose tool that allows you to customize your own pipeline as you would when setting up a new CI/CD pipeline.</p>
<p>With CodePipeline, you can define the entire release process for your application, including building, testing, and deploying your code. This flexibility allows you to tailor your pipeline to your specific needs, which can be especially useful if you have specific compliance or security requirements.</p>
<p>On the other hand, CDK Pipelines provides a more streamlined approach to deploying CDK-based applications. It automates the deployment process and integrates with other AWS services, such as CodeBuild, to help you build, test, and deploy your applications.</p>
<p>Overall, both CodePipeline and CDK Pipelines provide valuable tools for automating the deployment of your applications. Understanding the differences between them can help you choose the best tool for your specific use case. You can find more information about <code>Codepipeline</code> in the <a target="_blank" href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_codepipeline-readme.html">official AWS CDK documentation</a>.</p>
<h3 id="heading-defining-a-cdk-pipeline">Defining a CDK- Pipeline</h3>
<p>To define a CDK pipeline in our <code>PipelineStack</code>, we will use the <a target="_blank" href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.pipelines.CodePipeline.html">CodePipeline</a> construct from <code>aws-cdk-lib/pipelines</code>. While there are more properties available (which can be found in the documentation), this blog post will focus on deploying a simple CDK stack into multiple accounts. The only required property is <code>synth</code>.</p>
<blockquote>
<p>The build step that produces the CDK Cloud Assembly.</p>
</blockquote>
<p>I must admit that I'm not sure what that means. Plus, simply reading the name <a target="_blank" href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.pipelines.IFileSetProducer.html">IFileSetProducer</a> doesn't provide much context. However, based on the example provided, it appears that the <code>ShellStep</code> is leveraging the functionality of the <code>IFileSetProducer</code> interface. Another option could be to use a <a target="_blank" href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.pipelines.CodeBuildStep.html">CodeBuildStep</a>, since the pipeline is using CodeBuild for both installation and building. For the sake of this blog, we'll stick with the <code>ShellStep</code>.</p>
<p>The <code>ShellStep</code> requires an <code>input</code> of type <a target="_blank" href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.pipelines.CodePipelineSource.html">CodePipelineSource</a>, which represents the source of our CodePipeline. The source could be Github, CodeCommit, or S3. In this case, we will use an existing CodeCommit repository.</p>
<p>For the <code>commands</code>, there are default commands <code>yarn install</code> and <code>yarn build</code>. You can add testing or linting commands, or change the package manager as needed. The pipeline uses CodeBuild with all the necessary tools for deploying CDK stacks such as <code>yarn</code>.</p>
<p>One important property for cross-account deployment is <code>crossAccountKeys</code>. It is a Boolean and should be set to <code>true</code>, even if you are not doing cross-account deployment eventually you might plan to do cross-account deployment.</p>
<blockquote>
<p>Create KMS keys for the artifact buckets, allowing cross-account deployments.</p>
</blockquote>
<p>The stack has a property <code>pipeline</code> that you can then access later.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Stack, Fn, StackProps } <span class="hljs-keyword">from</span> <span class="hljs-string">'aws-cdk-lib'</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> codecommit <span class="hljs-keyword">from</span> <span class="hljs-string">'aws-cdk-lib/aws-codecommit'</span>;
<span class="hljs-keyword">import</span> { CodePipeline, CodePipelineSource, ShellStep } <span class="hljs-keyword">from</span> <span class="hljs-string">'aws-cdk-lib/pipelines'</span>;
<span class="hljs-keyword">import</span> { Construct } <span class="hljs-keyword">from</span> <span class="hljs-string">'constructs'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> PipelineStack <span class="hljs-keyword">extends</span> Stack {
  <span class="hljs-keyword">readonly</span> pipeline: CodePipeline;
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">scope: Construct, id: <span class="hljs-built_in">string</span>, props: StackProps</span>) {
    <span class="hljs-built_in">super</span>(scope, id, props);
    <span class="hljs-comment">// Existing CodeCommit</span>
    <span class="hljs-keyword">const</span> repository = codecommit.Repository.fromRepositoryName(<span class="hljs-built_in">this</span>, <span class="hljs-string">'CodeCommit'</span>, <span class="hljs-string">'my-cool-codecommit'</span>);
    <span class="hljs-built_in">this</span>.pipeline = <span class="hljs-keyword">new</span> CodePipeline(<span class="hljs-built_in">this</span>, <span class="hljs-string">'CdkPipeline'</span>, {
      synth: <span class="hljs-keyword">new</span> ShellStep(<span class="hljs-string">`<span class="hljs-subst">${props.pipelineName}</span>Synth`</span>, {
        input: CodePipelineSource.codeCommit(repository, props.branch),
        commands: props.commands ?? [
          <span class="hljs-string">'yarn install'</span>,
          <span class="hljs-string">'yarn build'</span>,
        ],
      }),
            crossAccountKeys: <span class="hljs-literal">true</span>,
    });
  }
}
</code></pre>
<h3 id="heading-adding-a-stage-for-deploying-stack">Adding a Stage for Deploying Stack</h3>
<p>Now, we can use our <code>PipelineStack</code> into <code>main.ts</code> . Since we added a <code>readonly pipeline</code> , we can now add stages to the pipeline.</p>
<p>The method <code>addStage()</code> requires a class that extends from <code>Stage</code> . Here, we named it <code>MyApplication</code> and it only has <code>StackProps</code>. Now inside <code>MyApplication</code>, we can add a new Stack, <code>MyStack</code> where define your resources which you then deploy to a different account. The <code>MyApplication</code> requires an <code>env</code>- object to deploy to different stages by giving it an account number and a region.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// In main.ts</span>

<span class="hljs-keyword">import</span> { App, Stage, Stack } <span class="hljs-keyword">from</span> <span class="hljs-string">'@aws-cdk/core'</span>;
<span class="hljs-keyword">import</span> { PipelineStack } <span class="hljs-keyword">from</span> <span class="hljs-string">'./pipeline-stack'</span>;
<span class="hljs-keyword">import</span> { ManualApprovalStep } <span class="hljs-keyword">from</span> <span class="hljs-string">'aws-cdk-lib/pipelines'</span>;

<span class="hljs-keyword">const</span> app = <span class="hljs-keyword">new</span> App();

<span class="hljs-keyword">const</span> cicd = <span class="hljs-keyword">new</span> PipelineStack(app, <span class="hljs-string">'PipelineStack'</span>, {
  env: { account: <span class="hljs-string">'MySharedAccount'</span>, region: <span class="hljs-string">'us-east-2'</span> },
});

<span class="hljs-keyword">class</span> MyApplication <span class="hljs-keyword">extends</span> Stage {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">scope, id, props</span>) {
    <span class="hljs-built_in">super</span>(scope, id, props);

    <span class="hljs-keyword">new</span> MyStack(<span class="hljs-built_in">this</span>, <span class="hljs-string">'MyStack'</span>);
  }
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> MyStack <span class="hljs-keyword">extends</span> Stack {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">scope, id</span>) {
    <span class="hljs-built_in">super</span>(scope, id);

        <span class="hljs-comment">/* My resources */</span>
  }
}

cicd.pipeline.addStage(<span class="hljs-keyword">new</span> MyApplication(app, <span class="hljs-string">'DevDeployment'</span>, {
  env: {
    account: <span class="hljs-string">'DEV_ACCOUNT'</span>,
    region: <span class="hljs-string">'eu-west-1'</span>,
  },
}));


cicd.pipeline.addStage(<span class="hljs-keyword">new</span> MyApplication(app, <span class="hljs-string">'TestDeployment'</span>, {
  env: {
    account: <span class="hljs-string">'TEST_ACCOUNT'</span>,
    region: <span class="hljs-string">'eu-west-1'</span>,
  },
}));

cicd.pipeline.addStage(<span class="hljs-keyword">new</span> MyApplication(app, <span class="hljs-string">'TestDeployment'</span>, {
  env: {
    account: <span class="hljs-string">'PROD_ACCOUNT'</span>,
    region: <span class="hljs-string">'eu-west-1'</span>,
  },
}, {
  pre: [
    <span class="hljs-keyword">new</span> ManualApprovalStep(<span class="hljs-string">'PromoteToProd'</span>),
  ],
}));

app.synth();
</code></pre>
<h3 id="heading-bootstrapping">Bootstrapping</h3>
<p>Let's take a look at what we have achieved up to this point. We have multiple accounts set up and we will deploy the CDK from above in our <code>CI/CD</code>- account. This account will then deploy to our <code>Dev</code>, <code>Test</code>, and <code>Prod</code> - environment.</p>
<p>In order to do that, each Development environment needs to trust the <code>CI/CD</code>- account. The following script should be executed once to allow the <code>CI/CD</code>- account to deploy to each development account. Below you find a command on how to trust a different account. In this case, you trust the <code>CICD_ACCOUNT</code> from a <code>DEV_ACCOUNT</code> , <code>TEST_ACCOUNT</code> , and <code>PROD_ACCOUNT</code>. <code>MY_SERVICE_ACCESS_POLICY</code> can be for example <code>AWSCodeDeployFullAccess</code> , <code>AWSEcsFullAccess</code> or a custom policy. You can chain them accordingly with a comma (see below).</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Replace *_ACCOUNT accordingly to your account number</span>
CICD_ACCOUNT=<span class="hljs-string">"replace-this"</span>
DEV_ACCOUNT=<span class="hljs-string">"replace-this"</span>
TEST_ACCOUNT=<span class="hljs-string">"replace-this"</span>
PROD_ACCOUNT=<span class="hljs-string">"replace-this"</span>
MY_SERVICE_ACCESS_POLICY1=<span class="hljs-string">"replace-this"</span>
MY_SERVICE_ACCESS_POLICY2=<span class="hljs-string">"replace-this"</span>
AWS_REGION=<span class="hljs-string">"eu-west-1"</span>

npx cdk bootstrap \
  --profile dev-account-profile \
  --trust <span class="hljs-variable">$CICD_ACCOUNT</span> \
    --cloudformation-execution-policies arn:aws:iam::aws:policy/<span class="hljs-variable">$MY_SERVICE_ACCESS_POLICY1</span>,arn:aws:iam::aws:policy/<span class="hljs-variable">$MY_SERVICE_ACCESS_POLICY2</span> \
  aws://<span class="hljs-variable">$DEV_ACCOUNT</span>/<span class="hljs-variable">$AWS_REGION</span>

npx cdk bootstrap \
  --profile test-account-profile \
  --trust <span class="hljs-variable">$CICD_ACCOUNT</span> \
    --cloudformation-execution-policies arn:aws:iam::aws:policy/<span class="hljs-variable">$MY_SERVICE_ACCESS_POLICY1</span>,arn:aws:iam::aws:policy/<span class="hljs-variable">$MY_SERVICE_ACCESS_POLICY2</span> \
  aws://<span class="hljs-variable">$TEST_ACCOUNT</span>/<span class="hljs-variable">$AWS_REGION</span>

npx cdk bootstrap \
  --profile prod-account-profile \
  --trust <span class="hljs-variable">$CICD_ACCOUNT</span> \
    --cloudformation-execution-policies arn:aws:iam::aws:policy/<span class="hljs-variable">$MY_SERVICE_ACCESS_POLICY1</span>,arn:aws:iam::aws:policy/<span class="hljs-variable">$MY_SERVICE_ACCESS_POLICY2</span> \
  aws://<span class="hljs-variable">$PROD_ACCOUNT</span>/<span class="hljs-variable">$AWS_REGION</span>
</code></pre>
<h3 id="heading-deploy-stack">Deploy Stack</h3>
<p>Let's take a look at what we have achieved up to this point. We have multiple accounts set up and we will deploy the CDK from above in our <code>CI/CD</code>- account. This account will then deploy to our <code>Dev</code>, <code>Test</code>, and <code>Prod</code> - environment.</p>
<p>In order to do that, each Development environment needs to trust the <code>CI/CD</code>- account. The following script should be executed once to allow the <code>CI/CD</code>- account to deploy to each development account. Below you find a command on how to trust a different account. In this case, you trust the <code>CICD_ACCOUNT</code> from a <code>DEV_ACCOUNT</code> , <code>TEST_ACCOUNT</code> , and <code>PROD_ACCOUNT</code>. <code>MY_SERVICE_ACCESS_POLICY</code> can be for example <code>AWSCodeDeployFullAccess</code> , <code>AWSEcsFullAccess</code> or a custom policy. You can chain them accordingly with a comma (see below).</p>
<p><img src="https://s3.us-west-2.amazonaws.com/secure.notion-static.com/b2c3e5e9-629c-4f6f-b3f6-4ba5ca50507a/codepipeline.gif?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&amp;X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20230327%2Fus-west-2%2Fs3%2Faws4_request&amp;X-Amz-Date=20230327T195002Z&amp;X-Amz-Expires=86400&amp;X-Amz-Signature=7a49b4f1704cad4bd851f405689bf9c737a7f2b5012ed545f9b1d130ce1af1a9&amp;X-Amz-SignedHeaders=host&amp;response-content-disposition=filename%3D%22codepipeline.gif%22&amp;x-id=GetObject" alt /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this blog post, you have seen how to deploy to several accounts with CDK- pipelines in a multi-account setup. The CDK- pipelines provide an easy and efficient way to automate the deployment of CDK-based applications.</p>
<p>As always, the setup is always a bit tedious but once you've done all that, you have a good process when deploying CDK stacks to multiple accounts. This will save a lot of time and effort and can help your organization scale its cloud operations. Plus, thanks to CDK you can build a CDK library and share the construct among your developers.</p>
<p>This was part one of how you can achieve cross-account deployment using CDK pipelines. The next part is how you can leverage the power of Typescript to deploy a generic CDK stack.</p>
<p>Thanks for reading and happy coding!</p>
]]></content:encoded></item></channel></rss>