<?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[AgileCoding.io - connecting agile project management with coding and all it takes around it]]></title><description><![CDATA[CTO at greeny+, passionate agile project lead and software architect. Blogging about my professional life.]]></description><link>https://agilecoding.io</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1643118795669/CsnVDoVkPV.jpeg</url><title>AgileCoding.io - connecting agile project management with coding and all it takes around it</title><link>https://agilecoding.io</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 15 Oct 2026 20:22:03 GMT</lastBuildDate><atom:link href="https://agilecoding.io/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Advanced project setup with Nest.js and Typescript]]></title><description><![CDATA[When you're coming from the Java world to the Node.js ecosystem like I did, you will struggle a bit in the beginning. It's a constant back and forth and not everything I tried was a good one.
As I already explained in my previous article, I finally e...]]></description><link>https://agilecoding.io/advanced-project-setup-with-nestjs-and-typescript</link><guid isPermaLink="true">https://agilecoding.io/advanced-project-setup-with-nestjs-and-typescript</guid><category><![CDATA[Node.js]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Linting]]></category><category><![CDATA[eslint]]></category><category><![CDATA[nestjs]]></category><dc:creator><![CDATA[Klaus Lehner]]></dc:creator><pubDate>Mon, 15 Apr 2024 12:46:44 GMT</pubDate><content:encoded><![CDATA[<p>When you're coming from the Java world to the Node.js ecosystem <a target="_blank" href="https://agilecoding.io/why-i-transitioned-from-spring-kotlin-to-nestjs-typescript">like I did</a>, you will struggle a bit in the beginning. It's a constant back and forth and not everything I tried was a good one.</p>
<p>As I already explained in <a target="_blank" href="https://agilecoding.io/why-i-transitioned-from-spring-kotlin-to-nestjs-typescript">my previous article</a>, I finally ended up with a project setup of NPM, TypeScript, and Nest.js.</p>
<p>The <a target="_blank" href="https://docs.nestjs.com/cli/overview">Nest CLI</a> provides you with great possibilities to quickly scaffold your new project and create boilerplate code. After selecting a package manager (one of <a target="_blank" href="https://www.npmjs.com/">npm</a>, <a target="_blank" href="https://yarnpkg.com/">yarn</a>, <a target="_blank" href="https://pnpm.io/">pnpm</a>), the CLI generates a full-contained web application:</p>
<pre><code class="lang-bash">nest new nestjs-coreui-starter
</code></pre>
<p>will generate that structure for you:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713174771102/7b671d3b-145f-45f0-931a-8535dbd727d8.png" alt class="image--center mx-auto" /></p>
<p>That code contains one simple <code>AppController</code>, which gets an <code>AppService</code> injected - when you run <code>npm run start:debug</code>, you will see <code>Hello World!</code> printed as a response to <code>http://localhost:3000</code>.</p>
<p>Good to go for the start, but if you want to create a real web application, there is some stuff missing and in that blog post I'd like to guide you through my approach.</p>
<h2 id="heading-editor-config">Editor Config</h2>
<p>No matter if you are in a team or not. Before you start a project, agree on a configuration for your IDE Editor how to format your files. The open-source project <a target="_blank" href="https://editorconfig.org/">EditorConfig</a> provides a syntax that works fine across IDEs and programming languages.</p>
<p>I don't wanna get into the "2 vs 4 indent" or "tabs vs space"-flamewar. All is good as long as everyone on the project does the same.</p>
<p>In my case, the <code>.editorconfig</code> looks like this:</p>
<pre><code class="lang-plaintext">[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
</code></pre>
<p>That file will later be picked up by our infrastructure to ensure that all files are formatted in the same way. You can already right away execute <code>npm run format</code>, and all your source files will be formatted accordingly.</p>
<p>I'd also recommend updating your <code>package.json</code> in the <code>scripts</code> tag to also format <code>.json</code><em>and</em><code>.js</code> files in the root directory:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
   <span class="hljs-attr">"format"</span>: <span class="hljs-string">"prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"*.json\" \"*.js\""</span>,
}
</code></pre>
<h2 id="heading-control-dependencies">Control dependencies</h2>
<p>The Nest.js CLI generates a <code>package.json</code> which denotes dependencies like this:</p>
<pre><code class="lang-json">  <span class="hljs-string">"dependencies"</span>: {
    <span class="hljs-attr">"@nestjs/common"</span>: <span class="hljs-string">"^10.0.0"</span>,
    <span class="hljs-attr">"@nestjs/core"</span>: <span class="hljs-string">"^10.0.0"</span>,
    <span class="hljs-attr">"@nestjs/platform-express"</span>: <span class="hljs-string">"^10.0.0"</span>,
    <span class="hljs-attr">"reflect-metadata"</span>: <span class="hljs-string">"^0.1.13"</span>,
    <span class="hljs-attr">"rxjs"</span>: <span class="hljs-string">"^7.8.1"</span>
  },
</code></pre>
<p>As I'm a Java guy, I asked myself what the <code>^</code> means, and thankfully we do have ChatGPT now that can help immediately.</p>
<blockquote>
<p>In a <code>package.json</code> file, the <code>^</code> symbol preceding a version number indicates that npm (Node Package Manager) should install the specified package with a version that is compatible with the specified version, up to the next major version.</p>
<p>For example, if you specify <code>"some-package": "^1.2.3"</code>, npm will install version 1.2.3 of "some-package" or the latest minor or patch version that is backward-compatible with 1.2.3. So, it might install versions like 1.2.4, 1.3.0, 1.4.0, etc., but it won't install version 2.0.0 or higher, as those might contain breaking changes.</p>
<p>This caret symbol is part of npm's semantic versioning (semver) scheme, which aims to provide a standardized way of declaring and managing dependencies in Node.js projects.</p>
</blockquote>
<p>So I had a look in the <code>package-lock.json</code> and I found an entry like this:</p>
<pre><code class="lang-json"><span class="hljs-string">"node_modules/@nestjs/common"</span>: {
   <span class="hljs-attr">"version"</span>: <span class="hljs-string">"10.3.7"</span>,
   <span class="hljs-attr">"resolved"</span>: <span class="hljs-string">"https://registry.npmjs.org/@nestjs/common/-/common-10.3.7.tgz"</span>,
   <span class="hljs-attr">"integrity"</span>: <span class="hljs-string">"sha512-gKFtFzcJznrwsRYjtNZoPAvSOPYdNgxbTYoAyLTpoy393cIKgLmJTHu6ReH8/qIB9AaZLdGaFLkx98W/tFWFUw=="</span>,
   <span class="hljs-attr">"dependencies"</span>: {
      <span class="hljs-attr">"iterare"</span>: <span class="hljs-string">"1.2.1"</span>,
      <span class="hljs-attr">"tslib"</span>: <span class="hljs-string">"2.6.2"</span>,
      <span class="hljs-attr">"uid"</span>: <span class="hljs-string">"2.0.2"</span>
   },
}
</code></pre>
<p>So the version <code>^10.0.0</code> in my <code>package.json</code> installed <code>^10.3.7</code>. That also means that a subsequent call of <code>npm install</code> could install a new version of a dependency without any code change on my side.</p>
<p>To get reproducible builds on your CI server, you should therefore run <code>npm ci</code> instead of <code>npm install</code>, which will not install new dependencies but always take the ones from <code>package-lock.json</code>, but I still do have some bad feelings there.</p>
<p>Call me old-fashioned, but I want to have control over the exact version that is being installed, therefore I stripped all occurrences of <code>^</code> in my <code>package.json</code>. That requires a bit more manual work, but now dependency upgrades are a manual thing that gets into my daily routine when calling <code>npm outdated</code>.</p>
<p>If you are using Visual Studio Code, you get a nice popup over each dependency showing you the latest version:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713176071727/8343eabf-551c-4dca-a7e1-b6c00a27aba4.png" alt class="image--center mx-auto" /></p>
<p>So when you remove the <code>^</code>, ensure that you replace the version number with the previously installed version.</p>
<h2 id="heading-keep-dependencies-up-to-date">Keep dependencies up-to-date</h2>
<p>So let's ensure we have the latest version of all dependencies. I have seen so many projects in the past with really outdated and unsupported libraries that made it really hard to do any upgrades. If you work on that regularly, it's not a pain at all.</p>
<p>NPM provides <a target="_blank" href="https://docs.npmjs.com/cli/v7/commands/npm-outdated">a great command</a> that tells you for which library there exists a newer version</p>
<pre><code class="lang-bash">npm outdated
</code></pre>
<p>Depending on the version of your Nest CLI you might get an output like this:</p>
<pre><code class="lang-bash">Package                           Current  Wanted  Latest  Location                                       Depended by
@typescript-eslint/eslint-plugin   6.21.0  6.21.0   7.6.0  node_modules/@typescript-eslint/eslint-plugin  nestjs-coreui-starter
@typescript-eslint/parser          6.21.0  6.21.0   7.6.0  node_modules/@typescript-eslint/parser         nestjs-coreui-starter
eslint                             8.57.0  8.57.0   9.0.0  node_modules/eslint                            nestjs-coreui-starter
reflect-metadata                   0.1.14  0.1.14   0.2.2  node_modules/reflect-metadata                  nestjs-coreui-starter
</code></pre>
<p>Update the versions in your <code>package.json</code> accordingly and run <code>npm install</code>, until the result of <code>npm outdated</code> is empty or there are any version upgrades that you don't want to or can't go.</p>
<p>So after doing all that, the <code>dependencies</code> and <code>devDependencies</code> section of my <code>package.json</code> looks like that (you can also explore the full <a target="_blank" href="https://github.com/klu2/nestjs-coreui-starter/commit/227465c329c9a4d8c6198252eac608361cf25b82">git diff</a>):</p>
<pre><code class="lang-json">  <span class="hljs-string">"dependencies"</span>: {
    <span class="hljs-attr">"@nestjs/common"</span>: <span class="hljs-string">"10.3.7"</span>,
    <span class="hljs-attr">"@nestjs/core"</span>: <span class="hljs-string">"10.3.7"</span>,
    <span class="hljs-attr">"@nestjs/platform-express"</span>: <span class="hljs-string">"10.3.7"</span>,
    <span class="hljs-attr">"reflect-metadata"</span>: <span class="hljs-string">"0.2.2"</span>,
    <span class="hljs-attr">"rxjs"</span>: <span class="hljs-string">"7.8.1"</span>
  },
  <span class="hljs-string">"devDependencies"</span>: {
    <span class="hljs-attr">"@nestjs/cli"</span>: <span class="hljs-string">"10.3.2"</span>,
    <span class="hljs-attr">"@nestjs/schematics"</span>: <span class="hljs-string">"10.1.1"</span>,
    <span class="hljs-attr">"@nestjs/testing"</span>: <span class="hljs-string">"10.3.7"</span>,
    <span class="hljs-attr">"@types/express"</span>: <span class="hljs-string">"4.17.21"</span>,
    <span class="hljs-attr">"@types/jest"</span>: <span class="hljs-string">"29.5.12"</span>,
    <span class="hljs-attr">"@types/node"</span>: <span class="hljs-string">"20.12.7"</span>,
    <span class="hljs-attr">"@types/supertest"</span>: <span class="hljs-string">"6.0.2"</span>,
    <span class="hljs-attr">"@typescript-eslint/eslint-plugin"</span>: <span class="hljs-string">"7.6.0"</span>,
    <span class="hljs-attr">"@typescript-eslint/parser"</span>: <span class="hljs-string">"7.6.0"</span>,
    <span class="hljs-attr">"eslint"</span>: <span class="hljs-string">"8.57.0"</span>,
    <span class="hljs-attr">"eslint-config-prettier"</span>: <span class="hljs-string">"9.1.0"</span>,
    <span class="hljs-attr">"eslint-plugin-prettier"</span>: <span class="hljs-string">"5.1.3"</span>,
    <span class="hljs-attr">"jest"</span>: <span class="hljs-string">"29.7.0"</span>,
    <span class="hljs-attr">"prettier"</span>: <span class="hljs-string">"3.2.5"</span>,
    <span class="hljs-attr">"source-map-support"</span>: <span class="hljs-string">"0.5.21"</span>,
    <span class="hljs-attr">"supertest"</span>: <span class="hljs-string">"6.3.4"</span>,
    <span class="hljs-attr">"ts-jest"</span>: <span class="hljs-string">"29.1.2"</span>,
    <span class="hljs-attr">"ts-loader"</span>: <span class="hljs-string">"9.5.1"</span>,
    <span class="hljs-attr">"ts-node"</span>: <span class="hljs-string">"10.9.2"</span>,
    <span class="hljs-attr">"tsconfig-paths"</span>: <span class="hljs-string">"4.2.0"</span>,
    <span class="hljs-attr">"typescript"</span>: <span class="hljs-string">"5.4.5"</span>
  },
</code></pre>
<p>That way I immediately see my peers, and I don't need to crawl inside <code>package-lock.json</code>.</p>
<h2 id="heading-linting">Linting</h2>
<p>The Nest.js CLI provides you with initial support for linting your code with ESLint. That is a good starter, but the ruleset did not fulfill my needs, so I added some <code>plugins</code> and <code>extends</code>.</p>
<p>My old colleagues from <a target="_blank" href="https://cloudflight.io">Cloudflight</a> are providing <a target="_blank" href="https://github.com/cloudflightio/cloudflight-eslint-plugin">a great starting point with sample rules</a>, and I also added another plugin to sort imports.</p>
<pre><code class="lang-json">npm install --save-dev @cloudflight/eslint-plugin-node
npm install --save-dev eslint-plugin-simple-import-sort
</code></pre>
<p>And then adapt the plugins and extends section of your .eslintrc.js as follows:</p>
<pre><code class="lang-javascript">  plugins: [<span class="hljs-string">'@typescript-eslint/eslint-plugin'</span>, <span class="hljs-string">'simple-import-sort'</span>],
  <span class="hljs-attr">extends</span>: [
    <span class="hljs-string">'plugin:@typescript-eslint/recommended'</span>,
    <span class="hljs-string">'plugin:prettier/recommended'</span>,
    <span class="hljs-string">'plugin:@cloudflight/node/recommended'</span>,
    <span class="hljs-string">'plugin:import/typescript'</span>
  ],
</code></pre>
<p>If you now run <code>npm run lint</code> again, you will get a lot of errors. I have decided to disable some of those rules, let's go through them step by step:</p>
<pre><code class="lang-bash">C:\github\klu2\src\nestjs-coreui-starter\src\app.controller.spec.ts
  1:37  error  <span class="hljs-string">"@nestjs/testing"</span> is not published      node/no-unpublished-import
</code></pre>
<p>This rule is complaining that we are importing <code>@nestjs/testing</code> here but don't publish it. True, because this is a <code>devDependency</code> and we only need it in Spec-Files. The best way I found is to disable this rule in all <code>*.spec.ts</code> files inside <code>.eslintrc.js</code>:</p>
<pre><code class="lang-json"> overrides: [{
     files: ['**<span class="hljs-comment">/*.spec.ts'],
     rules: {
         "node/no-unpublished-import": 'off'
     },
 }],</span>
</code></pre>
<p>Then there are a couple of rules that don't make sense in Nest.js projects like <code>@typescript-eslint/no-extraneous-class</code> (it fails for each class without members, but this is how Nest.js modules are designed) and also some that I believe do make code unnecessarily bloated (like <code>no-magic-numbers</code> or <code>@typescript-eslint/explicit-member-accessibility</code>), so I deactivated those.</p>
<p>Run <code>npm run lint</code> again and you will find some more errors:</p>
<pre><code class="lang-bash">C:\github\klu2\src\nestjs-coreui-starter\src\app.controller.spec.ts
  0:1  error  This rule requires the `strictNullChecks` compiler option to be turned on to <span class="hljs-keyword">function</span> correctly  @typescript-eslint/strict-boolean-expressions        
  0:1  error  This rule requires the `strictNullChecks` compiler option to be turned on to <span class="hljs-keyword">function</span> correctly  @typescript-eslint/prefer-nullish-coalescing
</code></pre>
<p>We will temporarily turn off those rules as well now so that we can <a target="_blank" href="https://github.com/klu2/nestjs-coreui-starter/commit/6b82601dccfce31fca958b0eb0c5515ba6649bd4">commit our changes</a>, but we will fix that in a minute.</p>
<h2 id="heading-adapting-typescript-configuration">Adapting TypeScript configuration</h2>
<p>The default <code>tsconfig.json</code> that comes with the Nest.js CLI is quite progressive in some areas, meaning it allows some code flaws that could make your code end up in serious crap. I used the default configuration for some weeks and had to tidy up the code and found a lot of bugs that could have been detected with a more strict configuration.</p>
<p>Specifically, the default configuration switches off the following checks:</p>
<pre><code class="lang-json"> <span class="hljs-string">"strictNullChecks"</span>: <span class="hljs-literal">false</span>,
 <span class="hljs-string">"noImplicitAny"</span>: <span class="hljs-literal">false</span>,
 <span class="hljs-string">"strictBindCallApply"</span>: <span class="hljs-literal">false</span>,
 <span class="hljs-string">"forceConsistentCasingInFileNames"</span>: <span class="hljs-literal">false</span>,
 <span class="hljs-string">"noFallthroughCasesInSwitch"</span>: <span class="hljs-literal">false</span>
</code></pre>
<p>I'd recommend switching them all on from the very beginning. We may argue about <code>noImplicitAny</code>, because that could come in handy sometimes, but I don't see any reasons, why to switch off the other checks. They really help you a lot during development time, avoiding bugs from slipping into production. You can <a target="_blank" href="https://github.com/klu2/nestjs-coreui-starter/commit/5006e7a042d4f5df090470e5dc9825d36bd4698c">review the according commit here</a>.</p>
<h2 id="heading-specify-node-version">Specify Node version</h2>
<p>The last thing you should do right from the start is to configure the minimum Node.js version that is required to run your application. This is important if your codebase is relying on native functionality that entered Node.js just recently, like the <a target="_blank" href="https://blog.logrocket.com/fetch-api-node-js/">native fetch API</a> which became stable in Node 21.</p>
<p>You can easily specify that in your <code>package.json</code> by adding the following block:</p>
<pre><code class="lang-json"><span class="hljs-string">"engines"</span>: {
    <span class="hljs-attr">"node"</span>: <span class="hljs-string">"&gt;=21.0.0"</span>
},
</code></pre>
<h2 id="heading-code">Code</h2>
<p>If you are interested in the code, you can clone the whole repository from <a target="_blank" href="https://github.com/klu2/nestjs-coreui-starter">https://github.com/klu2/nestjs-coreui-starter</a>. Please note that this repository will change over time as I will be using it for subsequent blog postings.</p>
<h2 id="heading-summary">Summary</h2>
<p>We are good to go now, we do have a clean project that goes beyond the default scaffold from the Nest.js CLI, including:</p>
<ul>
<li><p>EditorConfig</p>
</li>
<li><p>Advanced Linting</p>
</li>
<li><p>Clean dependency management</p>
</li>
</ul>
<p>The next blog posts will then focus on a clean module setup including the view layer.</p>
<h2 id="heading-disclaimer">Disclaimer</h2>
<p>As mentioned some times already, I'm an absolute beginner in the Node.js ecosystem. I gave my best to apply my 2 decades of Java knowledge to this new world, but for sure there are also more elegant ways.</p>
<p>If you have ideas on how to improve that setup, please send me a comment here or do a pull request on GitHub.</p>
<p>I am happy for every constructive input!</p>
]]></content:encoded></item><item><title><![CDATA[Why I transitioned from Spring & Kotlin to Nest.js & Typescript]]></title><description><![CDATA[After more than two decades of dedicated work in the tech industry, I recently made a significant shift in my career, stepping back as COO from Cloudflight, an incredible company building custom software with more than 1.000 of the best engineers you...]]></description><link>https://agilecoding.io/why-i-transitioned-from-spring-kotlin-to-nestjs-typescript</link><guid isPermaLink="true">https://agilecoding.io/why-i-transitioned-from-spring-kotlin-to-nestjs-typescript</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[nestjs]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[npm]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[Springboot]]></category><dc:creator><![CDATA[Klaus Lehner]]></dc:creator><pubDate>Sun, 24 Mar 2024 17:01:45 GMT</pubDate><content:encoded><![CDATA[<p>After more than two decades of dedicated work in the tech industry, I recently made a significant shift in my career, <a target="_blank" href="https://www.linkedin.com/feed/update/urn:li:activity:7127919529438920704/">stepping back as COO</a> from <a target="_blank" href="https://www.cloudflight.io/en/">Cloudflight</a>, an incredible company building custom software with more than 1.000 of the best engineers you can imagine, and a bit later joining <a target="_blank" href="https://greenyplus.com/gesunde-ernaehrung">greeny+</a>, a startup which builds <a target="_blank" href="https://be.greenyplus.com/v/5293959/ggh">aeroponic towers</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711291425158/53394198-a807-4c53-9366-7c6427369b6f.png" alt class="image--center mx-auto" /></p>
<p>Almost everything is different here for me and that personal change also came along with a paradigm shift with regards to technology and software development. In this blog series, I want to take you a bit on my journey so far, sharing my technical experiences:</p>
<h2 id="heading-where-im-coming-from">Where I'm coming from...</h2>
<p>For more than 15 years, I led awesome teams at Cloudflight, where we leveraged the robustness of <a target="_blank" href="https://spring.io/projects/spring-boot">Spring</a>, the elegance of <a target="_blank" href="https://kotlinlang.org/">Kotlin</a>, and the reliability of the <a target="_blank" href="https://www.java.com/en/">JVM</a> to develop complex, scalable applications. Our processes were well-established, we had the <a target="_blank" href="https://register.codingcontest.org/">best developers</a> both on frontend and backend, and our portfolio was supported by a skilled ensemble of Requirements Engineers, UI/UX Designers, and Site Reliability Engineers. We operated on <a target="_blank" href="https://kubernetes.io/">Kubernetes</a>, ensuring high availability and seamless scalability for our products both on hyperscalers (<a target="_blank" href="https://azure.microsoft.com/">Azure</a>, <a target="_blank" href="https://aws.amazon.com/">AWS</a>, <a target="_blank" href="https://cloud.google.com/">GCP</a>) and on-premise solutions like <a target="_blank" href="https://www.redhat.com/en/technologies/cloud-computing/openshift/container-platform">OpenShift</a>.</p>
<p>While that is all still true for Cloudflight, I started again from scratch.</p>
<p>Arriving at greeny+, I was suddenly alone in that regard. We do have fantastic people for lots of other topics around product design, plant growth, marketing and sales, but I am (at least at the time of writing) the only IT guy. No one setting up server infrastructure, no one taking care of my deployment pipeline, no requirement engineers that build up and develop a clear backlog and talk to customers, no UX designers with all those so important skills to create the best possible user interfaces, no frontend developers with deep knowledge in HTML/SCSS/JS.</p>
<p>Just me.</p>
<p>And, as in most other startups as well: time and money are somehow limited.</p>
<p>Additionally, not to forget, there has not been too much practice time in software development in the last few years for me...</p>
<h2 id="heading-from-custom-to-standard-software">From custom to standard software</h2>
<p>Still, when I joined greeny+ beginning of that year, I didn't start on empty ground. The team - also with some external help - had made a lot of valuable contributions already: <a target="_blank" href="https://hubspot.com/">HubSpot</a> was being used as CRM and our <a target="_blank" href="https://greenyplus.com/gesunde-ernaehrung">new web page</a> based on <a target="_blank" href="https://www.shopify.com/">Shopify</a> was about to go live. In between, we were rolling out <a target="_blank" href="https://uppromote.com/">UpPromote</a> as affiliate solution.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711291681147/f90e63bf-0bec-4c10-8bd2-3b06ae174580.png" alt class="image--center mx-auto" /></p>
<p>I was trained for years to design tailor-made software systems that outperform standard solutions (but also cost a bunch of money upfront), so I had to change perspective and evaluate those existing tools, trying to get the maximum out of them.</p>
<p>So I looked through Shopify's backend and found out that it had adapted to the headless e-commerce hype which advocates to separate the presentation layer from its core logic. In Shopify, you don't have the flexibility like with <a target="_blank" href="https://kontent.ai/">Kontent.ai</a>, <a target="_blank" href="https://commercetools.com/">Commercetools</a>, <a target="_blank" href="https://strapi.io/">Strapi</a> or <a target="_blank" href="https://medusajs.com/">Medusa.js</a>, but it's good enough to go for the start. We could customize the shop to our needs very quickly, integrating payment gateways thanks to Shopify Payments as well as interactive forms (like newsletter registration) with HubSpot, no blockers here.</p>
<p>HubSpot really positively surprised me when it comes to adaptability, it gives you an incredible amount of customization options, including a very mature and <a target="_blank" href="https://developers.hubspot.com/docs/api/overview">well-documented API</a> and a built-in <a target="_blank" href="https://knowledge.hubspot.com/workflows/create-workflows">workflow engine</a>.</p>
<p>Especially the latter raised my attention, it gives you a great mix of a Low/No-Code graphical editor along with the possibility to create custom code blocks. In the following screenshot you can see our workflow which connects new customers with the respective affiliate based on UTM parameters:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711275429356/438ab053-0659-44d1-ba86-77c1231a1f2c.png" alt class="image--center mx-auto" /></p>
<p>That works great already, that way we could connect HubSpot with UpPromote. Custom code inside HubSpot can be written in either Python or Node.js. I chose the latter, as JavaScript is much more familiar to a Java guy like myself.</p>
<p>And those few lines of JavaScript were actually the entrance to my new experience with <a target="_blank" href="https://nodejs.org/">Node.js</a>.</p>
<h2 id="heading-our-challenges">Our challenges</h2>
<p>Quite quickly it was clear, that our tooling landscape could not fulfill all of our needs. Just to give you a few examples:</p>
<ul>
<li><p>The <a target="_blank" href="https://apps.shopify.com/hubspot-2">official HubSpot integration with Shopify</a> has some quite serious limitations in terms of privacy, i.e. we would have not been able to reduce the visibility of incoming orders in Shopify to only the affected salespeople in HubSpot.</p>
</li>
<li><p>UpPromote cannot calculate commission fees fully as per our ruleset and also cannot generate invoices that fulfill our needs tax-wise.</p>
</li>
<li><p>UpPromote only gives limited possibilities to provide affiliates with personally branded sales kits. We used <a target="_blank" href="https://spreadly.app/">Spreadly</a> for that in the past, but the process of creating such digital business cards was manual, costly, and error-prone. We needed a solution for scale here.</p>
</li>
</ul>
<p>So we knew we needed something dedicated for us. Time for custom software, again.</p>
<h2 id="heading-first-choice-jvm-again">First choice: JVM again</h2>
<p>As mentioned above, I did not have much time to bridge the gaps between Shopify, UpPromote and HubSpot, so my intuitive first choice was again my well-known JVM-stack with Spring Boot, Kotlin and Thymeleaf (I'm a lousy frontend developer, so I tried to avoid things like <a target="_blank" href="https://angular.io/">Angular</a>, <a target="_blank" href="https://vuejs.org/">Vue.js</a> or <a target="_blank" href="https://react.dev/">React</a>).</p>
<p>But then some questions came up:</p>
<ul>
<li><p>How to develop code? After working with <a target="_blank" href="https://www.jetbrains.com/idea">Intellij IDEA</a> for 10 years, I gave <a target="_blank" href="https://code.visualstudio.com/">VS.code</a> a chance end of December. Very lightweight, good support for Python and Javascript, but JVM/Gradle integration has some serious flaws. So again purchase an Intellij license?</p>
</li>
<li><p>How to connect to our three systems? There are REST APIs and partly OpenAPI specifications for HubSpot, Shopify and UpPromote, but no ready-to-use client libraries for the JVM. I could of course generate my own Kotlin clients just like we did in the past with other systems, but would again go my own way there.</p>
</li>
<li><p>Where to finally run the code? Kubernetes and all kinds of Hyperscalers would have been an overkill for what I needed (actually one web server and a relational database). Of course, there are a couple of hosters out there that offer lightweight JVM hosting without the complexity of K8S (like <a target="_blank" href="https://www.cloudfoundry.org/">Cloudfoundry</a> or <a target="_blank" href="https://platform.sh/">Platform.sh</a>), although they are more costly than platforms like Vercel.</p>
</li>
</ul>
<p>I could have done that all and for sure it would have worked well enough, but as there was so much change already, I thought:</p>
<blockquote>
<p>Isn't that a good occasion to give Node.js a try?</p>
</blockquote>
<p>I could reuse Javascript code from my HubSpot workflows as well then, and also would finally learn something new after so many years of Spring. If I would fail, I'd still have a plan B with good old Spring Boot.</p>
<p>So I gave myself 2 days...</p>
<h2 id="heading-my-first-nodejs-application">My first Node.js application</h2>
<p>If you google "web application node.js", you very quickly end up at <a target="_blank" href="https://expressjs.com/">Express.js</a>. I went through some tutorials, and the <a target="_blank" href="https://expressjs.com/en/starter/hello-world.html">Hello World</a> is really straightforward (just as in any framework, btw...):</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>)
<span class="hljs-keyword">const</span> app = express()
<span class="hljs-keyword">const</span> port = <span class="hljs-number">3000</span>

app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.send(<span class="hljs-string">'Hello World!'</span>)
})

app.listen(port, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Example app listening on port <span class="hljs-subst">${port}</span>`</span>)
})
</code></pre>
<p>I went on with more advanced examples, started to integrate the APIs of <a target="_blank" href="https://www.npmjs.com/package/shopify-api-node">Shopify</a> and <a target="_blank" href="https://www.npmjs.com/package/@hubspot/api-client">HubSpot</a> and showed the combined data in some really ugly HTML.</p>
<p>That was running within a few hours, so the next step was to get familiar with hosting.</p>
<h2 id="heading-deployment-to-rendercom">Deployment to render.com</h2>
<p>I knew <a target="_blank" href="https://render.com/">render.com</a> already from my OpenSource activities on <a target="_blank" href="https://github.com/klu2/">GitHub</a>, and as they offer a <a target="_blank" href="https://render.com/pricing">free plan</a> for individuals, that was my first choice.</p>
<p>Just 15 minutes later, after pushing my prototype to GitHub, my application was running on render.com. Not only that, render.com offered me a dashboard including a deployment pipeline and automatic deployment after code changes on GitHub, log console view, the possibility to maintain my own production environment and so much more. I have huge customization possibilities there (i.e. which git branch to watch on, which npm scripts to run and so much more), the admin dashboard is clean and easy to understand.</p>
<p>I literally had to do nothing. It was working within some minutes. Really awesome, especially as I still had the much more complex Microsoft Azure Portal in mind.</p>
<p>Meanwhile, I am also running a Postgres database (including automatic backup) and a Redis cache as session store on render.com, also setting that up and integrating it was just a matter of a few clicks.</p>
<h2 id="heading-from-javascript-to-typescript">From JavaScript to TypeScript</h2>
<p>After writing some more JavaScript files and letting my codebase grow, my headache grew and I remembered again why I like statically typed languages so much.</p>
<p>The logical solution was Typescript, also something I've never set up before, but thanks to Co-Pilot that worked quickly as well. I migrated all of my code, adjusted my NPM scripts accordingly, struggled a bit with the correct <code>tsconfig.json</code> (more on that later), and finally deployed my app again.</p>
<p>Good, typesafe now.</p>
<p>My big recommendation: If you're doing Node.js and take it seriously, start with TypeScript from day 1. Also lint your code strongly from day 1. I will give some hints on that later as well.</p>
<h2 id="heading-from-expressjs-to-nestjs">From Express.js to Nest.js</h2>
<p>If you're coming from Spring and write your first Express.js application, you will miss one fundamental concept quite early: <a target="_blank" href="https://en.wikipedia.org/wiki/Dependency_injection">Dependency Injection</a>.</p>
<p>If Express.js is the equivalent of Apache Tomcat, then <a target="_blank" href="https://nestjs.com/">Nest.js</a> gives you what you know from Spring.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711289401738/44b0a961-7838-4843-9610-d8f1b8794549.png" alt class="image--center mx-auto" /></p>
<p>So, let's take the Hello World from above, with Nest.js you would write:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Controller, Get } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;

@Controller()
<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloController</span> </span>{
  @Get()
  getHello(): string {
    <span class="hljs-keyword">return</span> <span class="hljs-string">'Hello World!'</span>;
  }
}
</code></pre>
<p>A bit more of code initially, but especially if you're coming from Spring, you'll be familiar with that syntax immediately. You can inject your services and make your components much more testable.</p>
<p>Additionally, Nest.js offers <a target="_blank" href="https://docs.nestjs.com/">really great documentation</a>, check it out directly at the source, there is no need to copy code samples over here.</p>
<p>That was enough for me to proceed that path with Node.js. There were some more challenges like database connection or the view layer, but I could find decent solutions for all of that, and so I decided to stick with that stack.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I didn't regret my technological choice. Just two months later, we now have a web application that serves as a one-stop shop for more than 300 sales partners of greeny+, offering:</p>
<ul>
<li><p>A responsive WebUI based on CoreUI, showing customers, orders and commission fees of our sales partners.</p>
</li>
<li><p>Full integration and synchronization of data between Shopify, UpPromote and HubSpot, 100% as per our needs, including an admin user interface to validate data synchronization.</p>
</li>
<li><p>Custom-built PDF invoice generation for all affiliates including mail delivery.</p>
</li>
<li><p>Automatically generated digital business cards for all affiliates including personal branded marketing PDFs. If you're curious, <a target="_blank" href="https://be.greenyplus.com/v/5293959">look at mine</a> (German only).</p>
</li>
</ul>
<p>And there are many more ideas to be implemented...</p>
<h2 id="heading-disclaimer">Disclaimer</h2>
<p>Important to say: I'm sure I could not have done all that without generative AI in such a short amount of time. <a target="_blank" href="https://github.com/features/copilot">GitHub Co-Pilot</a> and <a target="_blank" href="https://chat.openai.com/">ChatGPT</a> really helped me a lot. You cannot take everything for granted what those tools tell you, but if you use them wisely, they give you real super-power.</p>
<p>That mix of AI, lightweight frameworks, easy-to-use SaaS solutions, some years of experience how to build software, and last but not least: the urge to learn new things and never settle, made all that possible.</p>
<p>Still, you can't compare that solution with something you would get from a whole team with experts in all areas (especially on the UI side...). Right now, I also do have my doubts how solid that architecture is on the long run, especially if the code base grows further and multiple people would work on it in parallel. I also don't have any results yet how the system behaves under high load - I didn't optimize it in that direction yet. To be fair, I also bypassed some quailty gates (i.e. there could be much more unit tests), and probably I will need to do some refactorings in near future.</p>
<p>It's still good enough for what we require right now.</p>
<h2 id="heading-next-steps">Next Steps</h2>
<p>This was only the first part of a series of blog entries, digging more into each topic from the technical side, also offering code samples for the project setup, my <code>package.json</code> with support for unit testing with Jest including code coverage, SCSS, webpack, and the integration with all 3rd party systems.</p>
<p>By the way: Shopify, UpPromote, and HubSpot were not the only external services that we integrated. I will give you more insights on that as well.</p>
<p>If you're interested, follow me here on HashNode and stay tuned!</p>
]]></content:encoded></item><item><title><![CDATA[Typesafe API testing with Spring Boot, Kotlin and OpenFeign]]></title><description><![CDATA[In my previous blog entry I've shown you how to do end-to-end testing using Cypress. That means:

We take our web application including frontend and deploy it to our target platform with all infrastructure components (database, message broker, cache,...]]></description><link>https://agilecoding.io/typesafe-api-testing-with-spring-boot-kotlin-and-openfeign</link><guid isPermaLink="true">https://agilecoding.io/typesafe-api-testing-with-spring-boot-kotlin-and-openfeign</guid><category><![CDATA[Spring]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[Testing]]></category><category><![CDATA[TDD (Test-driven development)]]></category><dc:creator><![CDATA[Klaus Lehner]]></dc:creator><pubDate>Mon, 24 Jan 2022 07:52:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1643010166239/scpFkxwt2.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In my <a target="_blank" href="https://agilecoding.io/how-end-to-end-testing-finally-makes-fun">previous blog entry</a> I've shown you how to do end-to-end testing using Cypress. That means:</p>
<ul>
<li>We take our web application including frontend and deploy it to our target platform with all infrastructure components (database, message broker, cache,...)</li>
<li>We execute Cypress scripts towards that deployed application by interacting with the DOM just like the user does via the Browser.</li>
</ul>
<p>That has the big advantage that we are very close to realistic scenarios just like in production (that's why we call them end-to-end), but it has one major drawback: <strong>Usually those tests are being executed after your code changes have been merged to your master branch.</strong> I've seen projects where full environments are being provisioned on the target platform on every merge request, but in bigger projects with many team members you most likely can't afford that and it will also take ages for your build pipeline to complete.</p>
<p>That means, E2E-tests - compared to plain unit tests - have the big pain point that feedback comes very late. In many cases too late. That's where you should think about API testing of your backend services as part of the build process. This blog post will show you some approaches to achieve exactly that.</p>
<h2 id="heading-example-application">Example application</h2>
<p>As our very simple application under test, I chose these few lines of Kotlin code. Everything works with plain Java as well of course, but I love the precision and compactness of Kotlin's syntax, so here we go:</p>
<pre><code><span class="hljs-keyword">import</span> org.springframework.boot.autoconfigure.SpringBootApplication
<span class="hljs-keyword">import</span> org.springframework.boot.runApplication
<span class="hljs-keyword">import</span> org.springframework.web.bind.<span class="hljs-keyword">annotation</span>.GetMapping
<span class="hljs-keyword">import</span> org.springframework.web.bind.<span class="hljs-keyword">annotation</span>.RestController
<span class="hljs-keyword">import</span> java.time.OffsetDateTime

<span class="hljs-meta">@SpringBootApplication</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SpringApiTestingApplication</span></span>

<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">main</span><span class="hljs-params">(args: <span class="hljs-type">Array</span>&lt;<span class="hljs-type">String</span>&gt;)</span></span> {
    runApplication&lt;SpringApiTestingApplication&gt;(*args)
}

<span class="hljs-meta">@RestController</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloController</span> </span>{
    <span class="hljs-meta">@GetMapping(<span class="hljs-meta-string">"/hello-world"</span>)</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">helloWorld</span><span class="hljs-params">()</span></span>: Message {
        <span class="hljs-keyword">return</span> Message(message = <span class="hljs-string">"Hello World"</span>)
    }
}

<span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Message</span></span>(<span class="hljs-keyword">val</span> message: String)
</code></pre><p>This very simple application will return <code>{message: "Hello World"}</code> on <code>GET /hello-world</code> and that's what we're gonna test now in a couple of ways.</p>
<h2 id="heading-calling-your-web-controllers-in-a-springboottest">Calling your web controllers in a SpringBootTest</h2>
<p>The easiest approach is to just use <code>@SpringBootTest</code>, startup your full application within the JUnit-Test, autowire your controller and call the method on that controller:</p>
<pre><code><span class="hljs-selector-tag">import</span> <span class="hljs-selector-tag">org</span><span class="hljs-selector-class">.junit</span><span class="hljs-selector-class">.jupiter</span><span class="hljs-selector-class">.api</span><span class="hljs-selector-class">.Assertions</span><span class="hljs-selector-class">.assertEquals</span>
<span class="hljs-selector-tag">import</span> <span class="hljs-selector-tag">org</span><span class="hljs-selector-class">.junit</span><span class="hljs-selector-class">.jupiter</span><span class="hljs-selector-class">.api</span><span class="hljs-selector-class">.Test</span>
<span class="hljs-selector-tag">import</span> <span class="hljs-selector-tag">org</span><span class="hljs-selector-class">.springframework</span><span class="hljs-selector-class">.beans</span><span class="hljs-selector-class">.factory</span><span class="hljs-selector-class">.annotation</span><span class="hljs-selector-class">.Autowired</span>
<span class="hljs-selector-tag">import</span> <span class="hljs-selector-tag">org</span><span class="hljs-selector-class">.springframework</span><span class="hljs-selector-class">.boot</span><span class="hljs-selector-class">.test</span><span class="hljs-selector-class">.context</span><span class="hljs-selector-class">.SpringBootTest</span>

<span class="hljs-keyword">@SpringBootTest</span>
class SpringBootControllerTest(
    @Autowired private val <span class="hljs-attribute">controller:</span> HelloController
) {

    <span class="hljs-keyword">@Test</span>
    fun helloWorld() {
        <span class="hljs-selector-tag">assertEquals</span>("<span class="hljs-selector-tag">Hello</span> <span class="hljs-selector-tag">World</span>", <span class="hljs-selector-tag">controller</span><span class="hljs-selector-class">.helloWorld</span>()<span class="hljs-selector-class">.message</span>)
    }
}
</code></pre><p>This setup is fast and short, it is also fully typesafe, but you are missing a lot here in your web layer:</p>
<ul>
<li>Request Mapping (routing)</li>
<li>Object (de)serialization (when using i.e. JSON to transfer data)</li>
<li>Servlet filters</li>
<li>many more, basically everything from the WebMVC and Security layer</li>
</ul>
<p>Not the best approach possibly...</p>
<h2 id="heading-using-mockmvc-in-spring-boot-applications">Using MockMvc in Spring Boot applications</h2>
<p>To get closer to real-life scenarios, Spring provides <code>MockMvc</code>. Without firing up a full-blown web server, it gives you access to almost any HTTP API (GET, POST, HEAD,...) and you also get matchers to check if your controllers return the expected response:</p>
<pre><code><span class="hljs-keyword">import</span> org.junit.jupiter.api.Assertions.assertEquals
<span class="hljs-keyword">import</span> org.junit.jupiter.api.Test
<span class="hljs-keyword">import</span> org.springframework.beans.factory.<span class="hljs-keyword">annotation</span>.Autowired
<span class="hljs-keyword">import</span> org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
<span class="hljs-keyword">import</span> org.springframework.boot.test.context.SpringBootTest
<span class="hljs-keyword">import</span> org.springframework.http.MediaType
<span class="hljs-keyword">import</span> org.springframework.test.web.servlet.MockMvc
<span class="hljs-keyword">import</span> org.springframework.test.web.servlet.request.MockMvcRequestBuilders.<span class="hljs-keyword">get</span>
<span class="hljs-keyword">import</span> org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
<span class="hljs-keyword">import</span> org.springframework.test.web.servlet.result.MockMvcResultMatchers.status

<span class="hljs-meta">@SpringBootTest</span>
<span class="hljs-meta">@AutoConfigureMockMvc</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MockMvcTest</span></span>(
    <span class="hljs-meta">@Autowired</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> mockMvc: MockMvc
) {

    <span class="hljs-meta">@Test</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">helloWorld</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">val</span> result = mockMvc.perform(<span class="hljs-keyword">get</span>(<span class="hljs-string">"/hello-world"</span>))
            .andExpect(status().isOk)
            .andExpect(jsonPath(<span class="hljs-string">"$.message"</span>).value(<span class="hljs-string">"Hello World"</span>))
            .andReturn()

        assertEquals(
           MediaType.APPLICATION_JSON_VALUE, 
           result.response.contentType
        )
    }
}
`
</code></pre><p>But - and this is also explained in <a target="_blank" href="https://www.baeldung.com/integration-testing-in-spring#mockmvc-limitations">this blog article</a> - be aware of the limitations of that approach:</p>
<blockquote>
<p>A mock is a mock!</p>
</blockquote>
<p>The problem here is that <code>MockMvc</code> is not the same code that is being executed at runtime. It is quite close, but it is not the same, and there are situations where it behaves differently. Most importantly, you don't do real HTTP requests here over the wire, you don't have full error response handling (when using redirects), and much more.</p>
<p>There is another drawback with <code>MockMvc</code> as well. It requires a lot of redundant code as you have to manually enter all routes and field names (when deserializing JSON) in sync with your application code. When your codebase grows and your API evolves, this can be cumbersome.</p>
<h2 id="heading-using-testresttemplate">Using TestRestTemplate</h2>
<p>Also taken from <a target="_blank" href="https://spring.io/guides/gs/testing-web/">the official Spring docu</a> is that example where we start up the server in a real environment (using <code>WebEnvironment.RANDOM_PORT</code> instead of the default <code>WebEnvironment.MOCK</code>) and then using <code>TestRestTemplate</code> to perform real HTTP requests against our server:</p>
<pre><code><span class="hljs-keyword">import</span> org.assertj.core.api.Assertions.assertThat
<span class="hljs-keyword">import</span> org.junit.jupiter.api.Test
<span class="hljs-keyword">import</span> org.springframework.beans.factory.<span class="hljs-keyword">annotation</span>.Autowired
<span class="hljs-keyword">import</span> org.springframework.boot.test.context.SpringBootTest
<span class="hljs-keyword">import</span> org.springframework.boot.test.web.client.TestRestTemplate
<span class="hljs-keyword">import</span> org.springframework.boot.web.server.LocalServerPort

<span class="hljs-meta">@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestRestTemplateTest</span></span>(
    <span class="hljs-meta">@LocalServerPort</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> localServerPort: <span class="hljs-built_in">Int</span>,
    <span class="hljs-meta">@Autowired</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> restTemplate: TestRestTemplate
) {

    <span class="hljs-meta">@Test</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">helloWorld</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">val</span> url = <span class="hljs-string">"http://localhost:<span class="hljs-variable">$localServerPort</span>/hello-world"</span>
        assertThat(restTemplate
            .getForObject(url, Message::<span class="hljs-keyword">class</span>.java).message)
            .isEqualTo(<span class="hljs-string">"Hello World"</span>)
    }
}
</code></pre><p>Much better already, no mock environment anymore, we are close to a production-like scenario.</p>
<p>Still, we need to manually maintain all routes using <code>TestRestTemplate</code> which is also the case in the next example:</p>
<h2 id="heading-using-restassured">Using RestAssured</h2>
<p><a target="_blank" href="https://rest-assured.io/">RestAssured</a> is a great library to create automated tests against REST APIs. The setup is quite the same as with the <code>TestRestTemplate</code> approach:</p>
<pre><code><span class="hljs-keyword">import</span> io.restassured.RestAssured
<span class="hljs-keyword">import</span> io.restassured.RestAssured.given
<span class="hljs-keyword">import</span> org.assertj.core.api.Assertions.assertThat
<span class="hljs-keyword">import</span> org.hamcrest.CoreMatchers.equalTo
<span class="hljs-keyword">import</span> org.junit.jupiter.api.BeforeEach
<span class="hljs-keyword">import</span> org.junit.jupiter.api.Test
<span class="hljs-keyword">import</span> org.springframework.boot.test.context.SpringBootTest
<span class="hljs-keyword">import</span> org.springframework.boot.web.server.LocalServerPort
<span class="hljs-keyword">import</span> org.springframework.http.MediaType

<span class="hljs-meta">@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RestAssuredTest</span></span>(
    <span class="hljs-meta">@LocalServerPort</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> localServerPort: <span class="hljs-built_in">Int</span>
) {

    <span class="hljs-meta">@BeforeEach</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">setup</span><span class="hljs-params">()</span></span> {
        RestAssured.port = localServerPort
    }

    <span class="hljs-meta">@Test</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">helloWorld</span><span class="hljs-params">()</span></span> {
        given().<span class="hljs-keyword">get</span>(<span class="hljs-string">"/hello-world"</span>).then()
            .statusCode(<span class="hljs-number">200</span>)
            .assertThat()
            .contentType(MediaType.APPLICATION_JSON_VALUE)
            .body(<span class="hljs-string">"message"</span>, equalTo(<span class="hljs-string">"Hello World"</span>))
    }

    <span class="hljs-meta">@Test</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">helloWorldMapping</span><span class="hljs-params">()</span></span> {
        assertThat(given().<span class="hljs-keyword">get</span>(<span class="hljs-string">"/hello-world"</span>).`<span class="hljs-keyword">as</span>`(Message::<span class="hljs-keyword">class</span>.java).message)
            .isEqualTo(<span class="hljs-string">"Hello World"</span>)
    }
}
</code></pre><p>I've added two tests there, one without object mapping and one using Jackson to automatically map to our <code>Message</code> object - so you have similar possibilities as with <code>TestRestTemplate</code>. </p>
<p>Please note that large parts of RestAssured are written in Groovy which means a slightly slower runtime. Anyways, I do prefer the syntax over <code>TestRestTemplate</code>, it makes the code more readable.</p>
<p>Anyways, although we're communicating over HTTP with our server, we still need to manually maintain all routes in our test cases and keep them in sync with the server.</p>
<p>Fortunately, there is a way to get around that as well, and that leads us to the final solution:</p>
<h2 id="heading-using-declarative-feign-clients-for-typesafe-api-tests">Using declarative Feign clients for typesafe API tests</h2>
<p>OpenFeign`s <a target="_blank" href="https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html">declarative REST clients</a> allow us to keep routing and MVC mapping information in one place and reuse all of that in our test cases.</p>
<p>Before we can do that, we need to refactor our server code a bit, and extract an interface out of our <code>HelloController</code> which contains all SpringMVC annotations like <code>@GetMapping</code>, <code>@PostMapping</code> and so on:</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">HelloApi</span> </span>{
    <span class="hljs-meta">@GetMapping(<span class="hljs-meta-string">"/hello-world"</span>)</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">helloWorld</span><span class="hljs-params">()</span></span>: Message
}

<span class="hljs-meta">@RestController</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloController</span> : <span class="hljs-type">HelloApi {</span></span>
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">helloWorld</span><span class="hljs-params">()</span></span>: Message {
        <span class="hljs-keyword">return</span> Message(message = <span class="hljs-string">"Hello World"</span>)
    }
}
</code></pre><p>Now, after importing <code>org.springframework.cloud:spring-cloud-starter-openfeign</code>, we can write the following test:</p>
<pre><code><span class="hljs-keyword">import</span> org.assertj.core.api.Assertions.assertThat
<span class="hljs-keyword">import</span> org.junit.jupiter.api.Test
<span class="hljs-keyword">import</span> org.springframework.beans.factory.<span class="hljs-keyword">annotation</span>.Autowired
<span class="hljs-keyword">import</span> org.springframework.boot.test.context.SpringBootTest
<span class="hljs-keyword">import</span> org.springframework.boot.web.server.LocalServerPort
<span class="hljs-keyword">import</span> org.springframework.cloud.openfeign.FeignClientBuilder
<span class="hljs-keyword">import</span> org.springframework.context.ApplicationContext

<span class="hljs-meta">@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OpenFeignIntegrationTest</span></span>(
    <span class="hljs-meta">@LocalServerPort</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> localServerPort: <span class="hljs-built_in">Int</span>,
    <span class="hljs-meta">@Autowired</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> applicationContext: ApplicationContext
) {

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> helloApi =
        FeignTestClientFactory.createClientApi(HelloApi::<span class="hljs-keyword">class</span>.java, localServerPort, applicationContext)

    @Test
    <span class="hljs-keyword">fun</span> helloWorld() {
        assertThat(helloApi.helloWorld().message).isEqualTo(<span class="hljs-string">"Hello World"</span>)
    }
}

<span class="hljs-keyword">object</span> FeignTestClientFactory {
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;T&gt;</span> <span class="hljs-title">createClientApi</span><span class="hljs-params">(apiClass: <span class="hljs-type">Class</span>&lt;<span class="hljs-type">T</span>&gt;, port: <span class="hljs-type">Int</span>, clientContext: <span class="hljs-type">ApplicationContext</span>)</span></span>: T {
        <span class="hljs-keyword">return</span> FeignClientBuilder(clientContext)
            .forType(apiClass, apiClass.canonicalName)
            .url(<span class="hljs-string">"http://localhost:<span class="hljs-variable">$port</span>"</span>)
            .build()
    }
}
</code></pre><p>I've extracted a small helper class <code>FeignTestClientFactory</code> here for a more comfortable usage of <code>FeignClientBuilder</code> - you can reuse this utility across your test cases.</p>
<p>The test case itself remains short then:</p>
<ul>
<li>We are again using <code>WebEnvironment.RANDOM_PORT</code></li>
<li><code>@LocalServerPort</code> is being injected by Spring Boot</li>
<li>A declarative Feign clients is created based on our new interface <code>HelloApi</code>. OpenFeign reads our <code>@GetMapping</code> annotations including all routing info and creates an HTTP client for us behind a dynamic proxy of <code>HelloApi</code></li>
<li>That means we can now call all interface methods of <code>HelloApi</code>, but we're not calling our controller directly (like in the first example), instead we are doing real HTTP requests, accessing our server just like any other client would do.</li>
</ul>
<p>This now brings us a fully type-safe and refactoring-safe API test against our Spring Boot server:</p>
<ul>
<li>There is a single place of definition for routing: <code>HelloApi</code>.</li>
<li>If new methods are being added in the API, or existing methods are updated, you have those changes immediately available in your test cases.</li>
<li>You have full refactoring support in your IDE.</li>
<li>You can also use your IDE to find all tests accessing a certain API (by searching for all usages of <code>HelloApi</code>), and that is especially helpful in big codebases.</li>
</ul>
<h2 id="heading-summary-and-link-to-the-code">Summary and link to the code</h2>
<p>There are multiple ways to test your API of Spring Boot applications, and although startup time compared to <code>MockMvc</code> is slightly higher, I prefer the <code>OpenFeign</code> approach.</p>
<p>You can find the whole sample code of all 4 approaches on my <a target="_blank" href="https://github.com/klu2/spring-api-testing">Github page</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://github.com/klu2/spring-api-testing">https://github.com/klu2/spring-api-testing</a></div>
]]></content:encoded></item><item><title><![CDATA[Hello AgileCoding.io]]></title><description><![CDATA[A couple of days ago, a new year started. Every year somehow starts with new intentions, mostly about health - this year (besides more sports as well) I thought "why not create a private tech blog", so here we go.
My name is Klaus, married and father...]]></description><link>https://agilecoding.io/hello-agilecoding</link><guid isPermaLink="true">https://agilecoding.io/hello-agilecoding</guid><category><![CDATA[agile development]]></category><category><![CDATA[agile]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Open Source]]></category><dc:creator><![CDATA[Klaus Lehner]]></dc:creator><pubDate>Thu, 06 Jan 2022 10:40:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1641548217114/sx9-IinqAO.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A couple of days ago, a new year started. Every year somehow starts with new intentions, mostly about health - this year (besides more sports as well) I thought "why not create a private tech blog", so here we go.</p>
<p>My name is Klaus, married and father of 3 wonderful kids, working as CTO at <a target="_blank" href="https://cloudflight.io">Cloudflight</a>, and of course, we do have our <a target="_blank" href="https://www.cloudflight.io/insights/">company blog</a> there as well where I'm writing articles from time to time and will continue to do so, like many others of my awesome colleagues. </p>
<p>This one is my own private one, a bit more tech-related, with the following background:</p>
<ul>
<li>I've signed up with <a target="_blank" href="https://hashnode.com">Hashnode</a> a couple of months ago already and really like the platform. After reading a lot, I thought it's time to write and give something back.</li>
<li>I'm following a lot of tech experts on <a target="_blank" href="https://twitter.com/klu2">Twitter</a> already, really enjoy the great community, and also want to be challenged a bit here outside my comfort zone.</li>
<li>I wanna learn how to write and publish on the web and try out stuff.</li>
<li>And if I can help some people and build some kind of community, then I'd be more than glad.</li>
<li>If not - it should be at least fun :-)</li>
</ul>
<p>So, let's give it a try:</p>
<h2 id="heading-why-agilecodingio">Why AgileCoding.io?</h2>
<p>You might be curious about the domain name, well it combines two words that really meant a lot in my career so far: Agile &amp; Coding.</p>
<p>I'm a professional software engineer / architect for almost 20 years now. In the early 2000s, agile software development was that "new thing", meanwhile it's more or less common sense. Still, I notice that there exist multiple different interpretations for both "agile" and "coding" out there. </p>
<p>Examples?</p>
<p>I've done hundreds of job interviews and quite often I heard things like:</p>
<blockquote>
<p>I've coded for 2 years now, I'd like to be an architect now.</p>
</blockquote>
<p>or even I was quite often being asked:</p>
<blockquote>
<p>But Klaus, in your role, you don't code anymore right, ehm?</p>
</blockquote>
<p>Well in fact I don't have that amount of time for coding any more than 10 years ago, but still, I'm taking time, almost daily, to keep in touch with my IDE. </p>
<p>I've been working with customers together where software developers were really been treated like coding monkeys that should not make any own decisions and instead just translate PowerPoint architecture to code. I strongly believe that this is the wrong way, we need to empower each individual in a team to make his/her own thoughts about the big picture.</p>
<p>And when it comes to agile, for sure you've heard things like:</p>
<blockquote>
<p>We're agile because we're doing Scrum and running in sprints.</p>
</blockquote>
<p>Don't get me wrong, there's nothing bad with Scrum itself, but it's just a framework like many others, and it doesn't mean you're agile by just applying techniques like a daily standup or a grooming. It may help you though. In any way, one should be pragmatic here, and that's what I'm trying to do on a daily basis. Neither a perfectly applied scrum sprint nor TDD with 100% coverage is a guarantee to <strong>make your customers happy</strong>, and that's what it's all about in the end.</p>
<p>We're always trying to find simple answers for complex questions, so to keep it simple right now: my short definitions of Agile &amp; Coding would be:</p>
<ul>
<li><strong>Agile</strong> - the ability to react quickly to change </li>
<li><strong>Coding</strong> - obviously actually writing code including all architecture and design work as well as long-term maintenance</li>
</ul>
<p>That combined, designing (architecting) and writing code in an agile way, i.e. be able to react quickly to change (wherever this change comes from), is what I wanna write about. And probably my <a target="_blank" href="https://www.cloudflight.io/tech/thank-you-open-source-48446/">blog entry about the Log4Shell vulnerability</a>, which also forced us to react very quickly to a change (in that case an exposed bug of a Java logging framework) also motivated me to blog a bit more about things like that.</p>
<p>I'm trying to bring in my experience of over 100 custom software projects over the last 15 years, some stories from the past, but also tech stuff from the present and/or future.</p>
<p>My tech-stack is mainly the JVM universe (Java, Kotlin, Groovy) with all kinds of frameworks (Spring, Micronaut, Hibernate), but also cloud architectures with Kubernetes, AWS, Azure, or GCP. Architecture-wise I'm a big fan of <a target="_blank" href="https://twitter.com/simonbrown">Simon Brown</a> and his <a target="_blank" href="https://c4model.com/">C4-Model</a>, and I've been influenced a lot by people like Erich Gamma  ("build software to last"), Kent Beck (XP), and Uncle Bob (<a target="_blank" href="https://www.amazon.com/Clean-Agile-Basics-Robert-Martin/dp/0135781868">Clean Agile</a>), so there will be the one or other article about Design Patterns, TDD and agile project management as well.</p>
<p>So, if you're interested, just press the Follow-Button, and stay tuned.</p>
<p>Cheers,
Klaus</p>
]]></content:encoded></item></channel></rss>