<?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" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Bonaventuragal on Medium]]></title>
        <description><![CDATA[Stories by Bonaventuragal on Medium]]></description>
        <link>https://medium.com/@bonaventuragal?source=rss-70a6887809ad------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*qEcLH4vyMlzEdknK</url>
            <title>Stories by Bonaventuragal on Medium</title>
            <link>https://medium.com/@bonaventuragal?source=rss-70a6887809ad------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 30 May 2026 17:17:14 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@bonaventuragal/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Advanced Collaboration Tools for Software Development Teams]]></title>
            <link>https://medium.com/@bonaventuragal/advanced-collaboration-tools-for-software-development-teams-45b151be4077?source=rss-70a6887809ad------2</link>
            <guid isPermaLink="false">https://medium.com/p/45b151be4077</guid>
            <category><![CDATA[jira]]></category>
            <category><![CDATA[git]]></category>
            <category><![CDATA[team-collaboration]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Bonaventuragal]]></dc:creator>
            <pubDate>Tue, 21 May 2024 09:23:03 GMT</pubDate>
            <atom:updated>2024-05-21T09:23:03.850Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/564/0*TubWX_ANF-u0Jv6-.jpg" /><figcaption>Collaboration illustration. <a href="https://i.pinimg.com/564x/7f/d4/09/7fd40983eb62a6dac14c20244fbbaea5.jpg">source</a></figcaption></figure><p>Effective team collaboration is crucial for the success of the software development process. When development teams work cohesively, they can enhance productivity, and produce higher-quality software. Strong collaboration fosters better communication, reduces misunderstandings, and allows for more efficient problem-solving, ultimately leading to a more successful and agile development process.</p><p>In this article, I will discuss the advanced usage of common tools for development team collaboration: Git and Jira. By leveraging these common tools to their full potential, development teams can significantly enhance their productivity and workflow.</p><h3>Advanced Git</h3><p>Git is a powerful version control system that facilitates efficient code management and collaboration among developers. Common commands such as add, commit,push, and mergeare widely used by developers to track and share changes. However, Git offers a wide range of advanced features and commands that can further enhance productivity and streamline workflows.</p><h4>Cherry-pick</h4><p>One of the advanced Git commands that can greatly enhance your workflow is the cherry-pick command. This command allows you to apply changes introduced by existing commits from one branch onto another branch. It is particularly useful in scenarios where you need to selectively integrate specific changes without merging entire branches.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tf9R0AfVYScidv4c_cSWzA.jpeg" /><figcaption>Git cherry-pick. <a href="https://ucarecdn.com/9a82bbf3-c595-437a-b78c-dd4debda1427/">source</a></figcaption></figure><p>This may look similar to git merge. However, git merge will apply all changes from another branch to the current branch. Meanwhile git cherry-pick will only apply all changes in a single commit from another branch.</p><p>To use cherry-pick, you can follow this syntax:</p><pre>git cherry-pick &lt;commmit SHA&gt; </pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/906/1*JfCuMoS-DBTQ11vZeiprrg.png" /><figcaption>git cherry-pick example</figcaption></figure><p>Commit SHA is the string hash to uniquely identify a commit. You can find the commit SHA by using git log. Alternatively, you can find the commit SHA from your repository website such as GitLab or GitHub.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iGA27HTkQ5rFIFK27qvpng.jpeg" /><figcaption>git log commit SHA</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hEvMZycPHa27p2nN5TjXGQ.png" /><figcaption>GitLab commit SHA</figcaption></figure><p>You can also apply the cherry-picked changes without automatically commit it. This works well if you want apply changes from another commit with additional modification. To do this, you can use the — no-commit flag.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/987/1*PwzdHlZr-vJpbX3B3y-obw.png" /><figcaption>git cherry-pick no commit</figcaption></figure><h4>Revert</h4><p>Another essential advanced Git command is git revert. git revert is used to undo changes introduced by previous commits. This command is particularly valuable when you need to safely undo changes in a collaborative environment without rewriting the commit history, as it creates a new commit that reverses the effects of a previous one.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/352/0*wBdNSlrCYr78RKyC.png" /><figcaption>Git revert. <a href="https://miro.medium.com/v2/resize:fit:352/1*zpmCaCM1fOT6xebUEx3BQA.png">source</a></figcaption></figure><p>To use revert, you can follow this syntax:</p><pre>git revert &lt;commit SHA&gt;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/854/1*SpEpOnVgmihm4M2RK8eVyw.png" /><figcaption>git revert example</figcaption></figure><p>Below are the comparison of the commit history before and after the revert. Note the new HEAD commit that reverts the prior commit.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/721/1*Y9o7BU8q_HHseQZVXTTo2Q.png" /><figcaption>Before git revert</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/735/1*YNbYwDD3-QwNtdbwLYUnBg.png" /><figcaption>After git revert</figcaption></figure><h4>Amend</h4><p>Git allows you to make small changes to the most recent commit using git commit --amend command. This command is particularly useful for making small changes or corrections to the last commit without creating a new commit. Whether you forgot to include a file, need to update the commit message, or made a small mistake, git commit --amend helps you refine your commit history for better clarity and accuracy.</p><p>To amend a commit, you need to stage the changes you want to apply using git add.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/589/1*YoP85Z8TmPc9kJtI_7N6xQ.png" /><figcaption>Staged changes</figcaption></figure><p>After that, use git commit --amend to apply the changes to the latest commit.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/909/1*zc8i6rPg5JY3dSFC8CvniQ.png" /><figcaption>git commit — amend</figcaption></figure><h4>Stash</h4><p>Git allows you to temporarily storing changes that are not ready to be committed using git stash. It allows developers to save their work-in-progress state, switch to another branch, or address urgent tasks without committing incomplete or experimental changes to the repository. This command is particularly useful when you need to switch context quickly without losing your current modifications.</p><p>After making some changes, you can stash the changes by simply using the command git stash.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nR49Rhf7IYjUop03V_2k2w.png" /><figcaption>Git stash example</figcaption></figure><p>As you can see in the image above, the changes has disappeared and stashed. In this state, you can safely switch branch or make additional changes without affecting the stashed changes.</p><p>To see the stashes available, you can use the command git stash list.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BhOJL64uwZPyq7TE-OZMTg.png" /><figcaption>git stash list</figcaption></figure><p>To apply the changes in a stash, use the command git stash apply.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/663/1*iLKf0MJIynXMlpJUzrg4-Q.png" /><figcaption>git stash apply</figcaption></figure><p>After the stash is not used anymore, you can delete the stash by using the command git stash drop.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/597/1*_NmitQH7oX2HnV6vuJ7y6w.png" /><figcaption>git stash drop</figcaption></figure><h3>Advanced Jira</h3><p>Jira is a robust project management tool designed to help teams plan, track, and release software effectively. Jira provides valuable insights into team performance through its reporting and analytics features, allowing teams to identify areas for improvement and optimize their workflows effectively.</p><h4>Sprint Insights</h4><p>Jira provides sprint insights, offering your team valuable information about the ongoing sprint’s progress. This feature allows teams to stay informed about their current sprint’s status, including completed tasks and remaining work, encouraging them to manage their workload effectively to ensure successful sprint completion.</p><p>This feature also gives a Sprint burndown chart that provides alternative visual representation of the sprint progress.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/561/1*eN-UjU3wOORa-ivH33e0dQ.png" /><figcaption>Sprint Insights</figcaption></figure><p>In this sprint insight example, we can see that the current sprint is still ongoing with 54% of the tasks are in progress and no tasks are done yet.</p><h4>Sprint Burndown Chart</h4><p>Continuing on the sprint burndown chart, Jira offers a detailed burndown chart for current sprint. The y-axis of the chart shows the amount of story points left. The x-axis shows the timeline of current sprint. The orange line represented the story points left over time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xhUkYNe-BcSCdK-Za3vTMg.png" /><figcaption>Sprint burndown chart</figcaption></figure><p>In this chart, it’s evident that no tasks have been completed since the start of the sprint. The burndown chart indicates that halfway through the sprint, no story points have been completed. Therefore, it’s crucial for the team to increase their pace to ensure that tasks are completed by the deadline.</p><h4>Sprint Velocity</h4><p>Jira also offers a sprint velocity report, which provides a comprehensive overview of the team’s performance over multiple sprints. This report calculates the average number of story points completed per sprint, allowing teams to assess their productivity and capacity for future sprints. By analyzing trends in sprint velocity, teams can make informed decisions about their workload allocation and sprint planning, ensuring a balanced and achievable pace of work. The sprint velocity report in Jira serves as a valuable tool for teams to continuously improve their performance and deliver projects on time and within scope.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4s4H7iWGThP6Vzjx5NwDBQ.png" /><figcaption>Velocity report</figcaption></figure><p>Looking at the chart, we can see how the team did in the last three sprints. The gray bar shows the story points commitment at the start of the sprint while the green bar shows the story points completed at the end of the sprint. With the velocity report, the team can estimate how much work they can handle in the next sprint. This helps the team to better plan and allocate resources, ensuring a balanced workload and increased productivity in future sprints.</p><h4>Cumulative Flow Diagram</h4><p>Jira provides a cumulative flow diagram, a visual representation of how work progresses through various stages of the workflow over time. By tracking the number of tasks in each stage, teams can identify bottlenecks and monitor the flow of work throughout the project. This helps teams understand where work is piling up and where it’s flowing smoothly, allowing them to take proactive measures to improve efficiency and streamline their processes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*p2JdWtRs7DeZhjbA8uIk2g.png" /><figcaption>Cumulative flow diagram</figcaption></figure><p>In this chart, we can see the issues in various stage of the development process over the course of the project. The stages includes “To Do”, “In Progress”, “In Review”, and “Done”. By comparing the growth rates of different colored sections, we can identify bottlenecks. For instance, in the chart provided, the purple section representing “To Do” tasks grows notably faster than others between May 15 and May 29. This indicates a bottleneck in the next stage, “In Progress.” To address this, the team needs to investigate the reasons behind the bottleneck, such as high workload or dependencies, and implement strategies to mitigate them.</p><p>In this article, we have discussed the advanced usage of Git and Jira as collaboration tools. With Git, developers can manage code changes with precision and agility such as using using cherry-pick, revert, amend, and stash. Meanwhile, Jira’s reporting features gives valuable insights for the team to plan, track, and optimize their workflow effectively. By embracing these advanced tools and practices, teams can streamline collaboration, boost productivity, and deliver high-quality software solutions in a dynamic and competitive landscape.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=45b151be4077" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Data Encryption and Hashing: A Scenario using NestJS + Prisma App]]></title>
            <link>https://medium.com/@bonaventuragal/data-encryption-and-hashing-a-scenario-using-nestjs-prisma-app-902b43530dbb?source=rss-70a6887809ad------2</link>
            <guid isPermaLink="false">https://medium.com/p/902b43530dbb</guid>
            <category><![CDATA[encryption]]></category>
            <category><![CDATA[nestjs]]></category>
            <category><![CDATA[cryptography]]></category>
            <category><![CDATA[data-security]]></category>
            <category><![CDATA[hashing]]></category>
            <dc:creator><![CDATA[Bonaventuragal]]></dc:creator>
            <pubDate>Sun, 21 Apr 2024 10:45:54 GMT</pubDate>
            <atom:updated>2024-04-21T10:55:20.003Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/564/1*PFyi-TFv1x9Gk18iLK1ubA.jpeg" /><figcaption>Illustration. <a href="https://i.pinimg.com/564x/f5/d4/09/f5d409c961e2201421a75d3625d66e7a.jpg">source</a></figcaption></figure><p>In my current project, I have to store users’ sensitive data in a database in a safe manner. After some exploration, I managed to create security functions tailor-made for my application needs. In this article, I want to share how I do it.</p><h3>Encryption vs Hashing</h3><p>First things first, let’s talk about encryption and hashing. Encryption and hashing are two fundamental techniques used to protect sensitive information. Encryption involves transforming data into a ciphertext format using an algorithm and a key, making it unreadable without the corresponding decryption key. This process allows for secure storage and transmission of data, as only authorized parties with the decryption key can access the original information.</p><p>On the other hand, hashing is a one-way process that converts data into a fixed-size string of characters, known as a hash value, using a hashing algorithm. Unlike encryption, hashing is irreversible, meaning that it cannot be decrypted to obtain the original data. Instead, it is commonly used for verifying data integrity and authentication, such as in password storage. Even small changes to the input data will result in vastly different hash values, making it an effective tool for detecting tampering or unauthorized modifications.</p><h3>Scenario</h3><p>My project is a NestJS project using Prisma ORM. I have to create user registration and login endpoints, which will work with sensitive data. The sensitive data is then stored in the User table, represented by this Prisma model:</p><pre>model User {<br>  id       String @id @default(uuid())<br>  email    String @unique<br>  password String<br>  name     String<br>  phoneNo  String<br>}</pre><p>In my case, I have to secure the email, password, and the phone number before storing it to the database. To achieve this, we can use encryption and/or hashing, depends on the nature of the data that needs to be secured.</p><p>The user’s password is highly confidential. Only the user is allowed to know the password. Furthermore, to maintain confidentiality, users are unable to retrieve their passwords after registration. Users are expected to memorize or store the password by themselves. In this case, we can hash the password before storing it. Hashing ensures that the resulting hash is irreversible, thus maintaining confidentiality. It also allows us to verify the correctness of the password when needed.</p><p>The email and phone number are also confidential, though not as sensitive as the password. In this scenario, both the email and phone number can be retrieved and displayed to the user. To achieve this, we can encrypt the data before storing it. When we retrieve the data, we decrypt it before sending it back to the user.</p><h4>Unsecured Register and Login</h4><p>Upon registration, user have to provide all data, and on login, user have to provide email and password. Therefore, we can create DTOs as follows:</p><pre>export class RegisterDTO {<br>  email: string;<br>  name: string;<br>  password: string;<br>  phoneNo: string;<br>}<br><br>export class LoginDTO {<br>  email: string;<br>  password: string;<br>}</pre><p>Next, we create controller and service functions to process the request. The API endpoint will be at auth/register and auth/login.</p><pre>// controller<br>import { Body, Controller, Post } from &#39;@nestjs/common&#39;;<br>import { AuthService } from &#39;./auth.service&#39;;<br>import { LoginDTO, RegisterDTO } from &#39;./dto&#39;;<br><br>@Controller(&#39;auth&#39;)<br>export class AuthController {<br>  constructor(private readonly authService: AuthService) {}<br><br>  @Post(&#39;register&#39;)<br>  async register(@Body() dto: RegisterDTO) {<br>    await this.authService.register(dto);<br><br>    return { message: &#39;Successfully register&#39; };<br>  }<br><br>  @Post(&#39;login&#39;)<br>  async login(@Body() dto: LoginDTO) {<br>    const user = await this.authService.login(dto);<br><br>    return { message: &#39;Successfully login&#39;, ...user };<br>  }<br>}</pre><pre>// service<br>import { BadRequestException, Injectable } from &#39;@nestjs/common&#39;;<br>import { LoginDTO, RegisterDTO } from &#39;./dto&#39;;<br>import { PrismaService } from &#39;src/prisma/prisma.service&#39;;<br><br>@Injectable()<br>export class AuthService {<br>  constructor(private readonly prisma: PrismaService) {}<br><br>  async register(registerDTO: RegisterDTO) {<br>    const user = await this.prisma.user.findUnique({<br>      where: {<br>        email: registerDTO.email,<br>      },<br>    });<br><br>    if (user) {<br>      throw new BadRequestException(&#39;User already exists&#39;);<br>    }<br><br>    await this.prisma.user.create({<br>      data: {<br>        ...registerDTO,<br>      },<br>    });<br>  }<br><br>  async login(loginDTO: LoginDTO) {<br>    const user = await this.prisma.user.findUnique({<br>      where: {<br>        email: loginDTO.email,<br>      },<br>    });<br><br>    if (!user) {<br>      throw new BadRequestException(&#39;Incorrect email or password&#39;);<br>    }<br><br>    if (user.password !== loginDTO.password) {<br>      throw new BadRequestException(&#39;Incorrect email or password&#39;);<br>    }<br>  }<br>}</pre><p>Up until this point, we have not created any security related features yet. All data is stored as-is in the database.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fTSh2Qflrh3GyiNdaTG01w.png" /><figcaption>Register result</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*L111My7SJuhDEA2NURxJWA.png" /><figcaption>Login result</figcaption></figure><h4>Secured Register and Login</h4><p>Next, let’s create security related function for hashing and encryption. I will use bcrypt library for hashing and the built-in crypto library for encryption.</p><p>First, let’s create the hash function. Hashing with bcrypt is pretty simple. All we need is the data and a salt. A salt is a random string of data that is added to the input of a hash function before it is hashed. Salting is used to add a noise to our data.</p><pre>// security.ts<br>import { hash as bcryptHash } from &#39;bcrypt&#39;;<br><br>export const hash = async (data: string) =&gt;<br>  bcryptHash(data, process.env.HASH_SALT as string);</pre><p>In my case, I will use a salt stored in the HASH_SALT environment variable to ensure a deterministic behavior. Why? More on this later.</p><p>bcrypt expected a salt with specific format to ensure that it can properly generate secure hash values and handle the comparison correctly. To achieve this, we can use the genSalt or genSaltSync functions provided by bcrypt:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/330/1*xX3v7yGvB2uDkVG51n_E-g.png" /><figcaption>Bcrypt genSaltSync</figcaption></figure><p>Trying to use a salt without adhering the format will resulting in an error.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/796/1*tnDMnrQC9IK2t3qLTPEXVg.png" /><figcaption>Random salt fail</figcaption></figure><p>For comparing data and a hash, bcrypt has provided a compare function that we will use later.</p><p>Moving on to encryption. For encryption, we need a password to encrypt our data. This allows us to decrypt our encrypted data and retrieve our original data. This is the custom encrypt-decrypt function I come up with.</p><pre>// security.ts<br>import {<br>  createCipheriv,<br>  createDecipheriv,<br>  randomBytes,<br>  randomFill,<br>  scrypt,<br>} from &#39;crypto&#39;;<br><br>export const _encrypt = async (data: string): Promise&lt;string&gt; =&gt; {<br>  const algorithm = &#39;aes-192-cbc&#39;;<br>  const salt = randomBytes(8).toString(&#39;hex&#39;);<br><br>  return new Promise((resolve, reject) =&gt; {<br>    scrypt(process.env.ENCRYPT_PASSWORD as string, salt, 24, (err, key) =&gt; {<br>      if (err) reject(err);<br><br>      randomFill(new Uint8Array(16), (err, iv) =&gt; {<br>        const ivHex = Buffer.from(iv).toString(&#39;hex&#39;);<br>        if (err) reject(err);<br><br>        const cipher = createCipheriv(algorithm, key, iv);<br><br>        let encrypted = cipher.update(data, &#39;utf8&#39;, &#39;hex&#39;);<br>        encrypted += cipher.final(&#39;hex&#39;);<br><br>        const result = `${salt}|${ivHex}|${encrypted}`;<br>        resolve(result);<br>      });<br>    });<br>  });<br>};<br><br>export const _decrypt = async (encryptedData: string): Promise&lt;string&gt; =&gt; {<br>  const algorithm = &#39;aes-192-cbc&#39;;<br><br>  return new Promise((resolve, reject) =&gt; {<br>    const [salt, ivHex, encrypted] = encryptedData.split(&#39;|&#39;);<br><br>    if (!salt || !ivHex || !encrypted) reject(new Error(&#39;Invalid data&#39;));<br><br>    const iv = Buffer.from(ivHex, &#39;hex&#39;);<br><br>    scrypt(process.env.ENCRYPT_PASSWORD as string, salt, 24, (err, key) =&gt; {<br>      if (err) reject(err);<br><br>      const decipher = createDecipheriv(algorithm, key, iv);<br><br>      let decrypted = decipher.update(encrypted, &#39;hex&#39;, &#39;utf8&#39;);<br>      decrypted += decipher.final(&#39;utf8&#39;);<br><br>      resolve(decrypted);<br>    });<br>  });<br>};<br><br>export const encrypt = async (data: string) =&gt; {<br>  const encrypted = await _encrypt(data);<br>  const hashed = await hash(data);<br><br>  return `${hashed}|${encrypted}`;<br>};<br><br>export const decrypt = async (data: string) =&gt; {<br>  const [_, ...rest] = data.split(&#39;|&#39;);<br>  const encrypted = rest.join(&#39;|&#39;);<br><br>  return await _decrypt(encrypted);<br>};</pre><ul><li>I’m using aes-192-cbc algorithm. No specific reason for this, just following the example on the documentation page. You can choose to use another encryption algorithm.</li><li>The _encrypt function contains the data encryption logic. Upon receiving a data, the function will create a random salt to be added to the input. After that, a random initialization vector (IV) is created to initialize the encryption process. The vector ensures that the same data encrypted with the same password produces different results. The data is encrypted using the password stored as ENCRYPT_PASSWORD environment variable. At the end, I merged the salt, IV, and the encrypted text into one with | as separator. This is needed for the decryption process.</li><li>The _decrypt function contains the data decryption logic. Knowing that the encrypted data is formatted as ${salt}|${ivHex}|${encrypted}, I splitted the string to obtain the salt, IV, and the encrypted text. Using the provided salt, IV, and the encryption password, I managed to decrypt the data and returned the original value.</li><li>The encrypt and decrypt function is not necessarily needed. These functions are used to combine hashing with encryption. In my case, I will encrypt the user’s email before storing to the database. However, I still need the capability of querying the database based on the user’s email. Using only encryption won’t work, because the encrypted data is always different thanks to the random salt. This is why I created a hash function with deterministic behavior. By appending the hash result of the plain data, I can query the encrypted data by it’s hash. The final format of my decrypted data is ${hash}|{salt}|${ivHex}|${encrypted}</li><li>The decrypt function is used to remove the hash from the encrypted data, and then forwarding the rest of the data to the _decrypt function for the real decryption.</li></ul><p>Using those functions, I can update my service functions to ensure the security of the data.</p><pre>// service<br>import { BadRequestException, Injectable } from &#39;@nestjs/common&#39;;<br>import { LoginDTO, RegisterDTO } from &#39;./dto&#39;;<br>import { PrismaService } from &#39;src/prisma/prisma.service&#39;;<br>import { encrypt, hash } from &#39;./security&#39;;<br>import { compare } from &#39;bcrypt&#39;;<br><br>@Injectable()<br>export class AuthService {<br>  constructor(private readonly prisma: PrismaService) {}<br><br>  async register(registerDTO: RegisterDTO) {<br>    const emailHash = await hash(registerDTO.email);<br><br>    const user = await this.prisma.user.findFirst({<br>      where: {<br>        email: {<br>          startsWith: `${emailHash}|`,<br>        },<br>      },<br>    });<br><br>    if (user) {<br>      throw new BadRequestException(&#39;User already exists&#39;);<br>    }<br><br>    const passwordHash = await hash(registerDTO.password);<br>    const emailEncrypt = await encrypt(registerDTO.email);<br>    const phoneNoEncrypt = await encrypt(registerDTO.phoneNo);<br><br>    await this.prisma.user.create({<br>      data: {<br>        name: registerDTO.name,<br>        password: passwordHash,<br>        email: emailEncrypt,<br>        phoneNo: phoneNoEncrypt,<br>      },<br>    });<br>  }<br><br>  async login(loginDTO: LoginDTO) {<br>    const emailHash = await hash(loginDTO.email);<br><br>    const user = await this.prisma.user.findFirst({<br>      where: {<br>        email: {<br>          startsWith: `${emailHash}|`,<br>        },<br>      },<br>    });<br><br>    if (!user) {<br>      throw new BadRequestException(&#39;Incorrect email or password&#39;);<br>    }<br><br>    if (await compare(loginDTO.password, user.password)) {<br>      const { email, name, phoneNo } = user;<br>      return {<br>        email,<br>        name,<br>        phoneNo,<br>      };<br>    }<br><br>    throw new BadRequestException(&#39;Incorrect email or password&#39;);<br>  }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6ONN3RToo9XRW2tqtncXUg.png" /><figcaption>Register result</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rE03d2Do-QBy7sLzT3GJdA.png" /><figcaption>Login result</figcaption></figure><p>For comparison, here are the unsecured and secured data inside the database.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Yx22HC4GK_ub_d0aC019Nw.png" /><figcaption>Data comparison</figcaption></figure><p>And that’s it! This is my first time delving into data hashing and security. I have so much to learn regarding these topics. While writing this article, I realized that the encryption format I used may not be the best practice. I should have stored the hash and encrypted data in separate columns to improve the database query performance. Nevertheless, I want to share how I originally did it. Learning is a journey, and without encountering bad practices, I won’t ever reach the best practices. If you found another way of doing it, feel free to share it with me.</p><p>References:</p><ul><li><a href="https://nodejs.org/api/crypto.html#class-decipher">https://nodejs.org/api/crypto.html#class-decipher</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=902b43530dbb" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[NestJS Monitoring with Sentry]]></title>
            <link>https://medium.com/@bonaventuragal/nestjs-monitoring-with-sentry-ed655cd0d375?source=rss-70a6887809ad------2</link>
            <guid isPermaLink="false">https://medium.com/p/ed655cd0d375</guid>
            <category><![CDATA[nestjs]]></category>
            <category><![CDATA[sentry]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[monitoring]]></category>
            <category><![CDATA[discord]]></category>
            <dc:creator><![CDATA[Bonaventuragal]]></dc:creator>
            <pubDate>Sat, 20 Apr 2024 15:45:06 GMT</pubDate>
            <atom:updated>2024-05-19T08:01:53.875Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*chwmSHki3cvBFTYtXgwGYg.png" /><figcaption>Sentry. <a href="https://intellyx.com/wp-content/uploads/2019/12/Sentry-intellyx-BC-logo-1200x628-1.png">source</a></figcaption></figure><p>In my current project, I had to set up Sentry for NestJS. Initially, I encountered some difficulties because Sentry doesn’t offer a specific guide for NestJS apps like it does for other frameworks such as Express or Django. However, after some exploration, I managed to set it up successfully for my project. In this article, I want to share how I set up Sentry for my NestJS app and integrating Sentry alerts with Discord.</p><h3>What is Sentry?</h3><p>Sentry is an error tracking and monitoring platform designed to help developers identify, diagnose, and fix issues in their applications. With Sentry, developers can gain deep insights into errors and exceptions occurring in their codebase across various platforms and frameworks. By providing real-time error reporting, stack traces, and detailed context about each issue, Sentry empowers teams to proactively manage and resolve bugs before they impact users. Its intuitive interface, customizable alerting system, and extensive integrations make it a popular choice for developers looking to maintain the stability and reliability of their software applications.</p><p>Sentry offers a range of pricing plan for using their product. In this article, I’m using the free Developer plan. This plan allows us to use basic Sentry services free of charge. Additionally, new users will get 14-day trial of the Business plan, which gives access to advanced services.</p><h3>Setting up Sentry</h3><p>First things first, <a href="https://sentry.io/signup/">create a Sentry account</a> if you don’t have one yet. You have to fill out an organization name for your account. Upon creation, you will gain access to org-slug.sentry.io. This page provides access to your Sentry projects. This article will use org-slug.sentry.io as a placeholder for your Sentry page URL.</p><p>Go to org-slug.sentry.io/project/new to create a new Sentry project. As you can see, there are no NestJS option for the Choose your platform section. Choose Node.js as your platform and then fill out your project name. Leave out other fields as default and then click Create Project. Choose skip when asked to select a specific framework.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IiI33HH40Hrw8SvO1QmmgA.png" /><figcaption>Sentry project creation</figcaption></figure><p>After your project is created, you will be taken to a page containing guide for setting up Sentry for your app. We won’t follow the guide as it is for setting up our NestJS app.</p><p>Assuming you already have a NestJS app, run yarn add @sentry/node @nestjs/config to install the required dependencies. The @nestjs/config library is needed to ensure environment variables is successfully read.</p><p>Take note of the dsn field value and store it as an environment variable SENTRY_DSN. This value will be used to connect your app to your Sentry project. Don’t store this value directly on your code as this is a sensitive value.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nEbByQeS_4TS_squoLy-qA.png" /><figcaption>Sentry DSN</figcaption></figure><h3>Setting up NestJS</h3><p>Basically, we will set up our NestJS app to send any unhandled error to Sentry, which is the 5xx. To achieve this, we will take advantage of the Exception Filters provided by NestJS. We will create a custom filter that acts as a middleware. The middleware will inform Sentry of any 5xx errors before the error response is returned to the user.</p><p>In your NestJS app, create a folder sentry inside src. Create a file called sentry.filter.ts inside it. Fill out the file with:</p><pre>import { Catch, ArgumentsHost, HttpException } from &#39;@nestjs/common&#39;;<br>import { BaseExceptionFilter } from &#39;@nestjs/core&#39;;<br>import * as Sentry from &#39;@sentry/node&#39;;<br><br>@Catch()<br>export class SentryFilter extends BaseExceptionFilter {<br>  catch(exception: HttpException, host: ArgumentsHost) {<br>    const status = exception.getStatus();<br><br>    if (status &gt;= 500) {<br>      Sentry.captureException(exception);<br>    }<br>    super.catch(exception, host);<br>  }<br>}</pre><p>The if statement is important because, without it, any 4xx errors will also be sent to Sentry. We don’t want this behavior because 4xx errors are handled errors and not our focus.</p><p>Inside the app.module.ts, imports ConfigModule to ensure environment variables are read on time.</p><pre>import { Module } from &#39;@nestjs/common&#39;;<br>import { AppController } from &#39;./app.controller&#39;;<br>import { AppService } from &#39;./app.service&#39;;<br>import { ConfigModule } from &#39;@nestjs/config&#39;;<br><br>@Module({<br>  imports: [ConfigModule.forRoot()],<br>  controllers: [AppController],<br>  providers: [AppService],<br>})<br>export class AppModule {}</pre><p>Inside your bootsrap function inmain.ts file, add the Sentry configuration:</p><pre>import { HttpAdapterHost, NestFactory } from &#39;@nestjs/core&#39;;<br>import { AppModule } from &#39;./app.module&#39;;<br>import * as Sentry from &#39;@sentry/node&#39;;<br>import { SentryFilter } from &#39;./sentry/sentry.filter&#39;;<br><br>async function bootstrap() {<br>  Sentry.init({<br>    dsn: process.env.SENTRY_DSN,<br>  });<br><br>  const app = await NestFactory.create(AppModule);<br><br>  const { httpAdapter } = app.get(HttpAdapterHost);<br>  app.useGlobalFilters(new SentryFilter(httpAdapter));<br><br>  await app.listen(3000);<br>}<br>bootstrap();</pre><p>Congrats! The NestJS app have been successfully set up with Sentry. To test it, we can create a controller that throws an error. For example:</p><pre>import { Controller, Get, InternalServerErrorException } from &#39;@nestjs/common&#39;;<br><br>@Controller(&#39;error&#39;)<br>export class ErrorController {<br>  constructor() {}<br><br>  @Get()<br>  async error() {<br>    throw new InternalServerErrorException({<br>      message: &#39;This is a test error&#39;,<br>    });<br>  }<br>}</pre><p>When the URL /error is called, an error will be thrown and Sentry will catch it and create an issue.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7Z4tf_UPywB21S61doIVPQ.png" /><figcaption>Sentry issue</figcaption></figure><p>By clicking the issue, you will see the detailed information about the issue. At the top, you can see the general information about the issue.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lG3KDXGt7T99xUOBZOeErA.png" /><figcaption>Issue general information</figcaption></figure><p>Scrolling for a bit, you can see the stack trace for the issue.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vS9W9DuzMp2eFHMEdcvBdQ.png" /><figcaption>Issue stack trace</figcaption></figure><p>At the very bottom, you’ll see the contexts for the issue.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5MyspQFb1gvzAPxYUsVPlg.png" /><figcaption>Issue contexts</figcaption></figure><p>If you‘re not aware yet, calling the URL /error multiple times won’t create additional issue. Instead, it will adds to the event count of the original issue. Below, I have created more test errors and invoked it multiple times.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MwJ48-U8lL6a42jgVJceQA.png" /><figcaption>Additional errors</figcaption></figure><h3>Configuring Profiling</h3><p>Sentry also provide profiling to monitor the performance of our application. If you haven’t set it up, click the Profiling button on the sidebar, then follow the guide for setting it up.</p><p>On the Profiling page, you can see various reports regarding your app. At the top, you can see the profiles by duration percentiles.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*F-UWCtGfrEuAEpuBueWQNg.png" /><figcaption>Profiles by percentiles</figcaption></figure><p>At the bottom, you can see the transactions that your app have completed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4260-LLVru0Mkf-gycO91w.png" /><figcaption>Transactions</figcaption></figure><p>You can click the transactions and see the details for each transaction.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aYsqeTimIZyZckLe4zK0Hw.png" /><figcaption>Transaction details</figcaption></figure><p>By clicking the View Transaction Summary button, you can see the detailed information regarding that transaction, such as the duration breakdown and the transaction spans.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4lXHspexC-M2_R_Uwm6tgQ.png" /><figcaption>Transaction summary — Duration Breakdown</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FT05yprhU8aN-VlO3jM49A.png" /><figcaption>Transaction summary — Suspect Spans</figcaption></figure><h3>Configuring Discord Alerts</h3><p>By default, Sentry will send an alert email to your registered email to inform you each time a new issue is created.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JGCtmSkoWi_ljNT0fwXyhQ.png" /><figcaption>Sentry email alert</figcaption></figure><p>Sentry allows you to configure this behavior or add additional alert channel, such as sending message to Discord. To achieve this, head to org-slug.sentry.io/settings/integrations/discord/ and click Add Installation. A modal will shows up to let you choose a Discord server. Sentry bot will then join your chosen server.</p><p>Go to org-slug.sentry.io/alerts/rules/ and edit your project’s rules. You will see some information about the alert conditions such as events, filters, and actions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6Nod0O1bYfuiCZYp3bbgrg.png" /><figcaption>Default alert</figcaption></figure><p>To send an alert to your Discord server, click Add action... and select Send a Discord notification. This option will only available if you have successfully add a Discord installation in the previous step. Choose your server and fill out the channel ID for your desired channel. You can obtain your channel ID by right clicking your Discord channel and click Copy Channel ID.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6FX4SZ_hlkEx_GhGNWbnaw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/373/1*u38IZ5UAWvQewG1qPLxtUg.png" /><figcaption>Discord channel ID</figcaption></figure><p>Click Send Test Notification to test out your Discord channel alert. If the test is success, you will receive a message in your channel like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xZsnMjX8zCNBwqNl-dehwA.png" /><figcaption>Test notification result.</figcaption></figure><p>You can add more configuration to your rule if you want. If you’re done, don’t forget to click Save to save your rule.</p><p>Another call to the /error URL will send a notification to the channel:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6irANa6w-dA3ojHoPl0jow.png" /><figcaption>Real error notification</figcaption></figure><p>Congrats! You have successfully set up a Discord alert channel for Sentry!</p><p>With Sentry’s powerful error tracking capabilities, we can easily identify, diagnose, and resolve issues in our codebase, ensuring a smoother and more reliable user experience. Additionally, we can easily receive alert notifications from various options provided by Sentry. Hopefully, this article will help you use Sentry effectively in your app.</p><p>References:</p><ul><li><a href="https://dev.to/marcelozapatta/how-to-integrate-sentry-in-nestjs-3ema">https://dev.to/marcelozapatta/how-to-integrate-sentry-in-nestjs-3ema</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ed655cd0d375" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[SonarQube for NestJS Project]]></title>
            <link>https://medium.com/@bonaventuragal/sonarqube-for-nestjs-project-bdc09d022033?source=rss-70a6887809ad------2</link>
            <guid isPermaLink="false">https://medium.com/p/bdc09d022033</guid>
            <category><![CDATA[nestjs]]></category>
            <category><![CDATA[static-code-analysis]]></category>
            <category><![CDATA[sonarqube]]></category>
            <category><![CDATA[code-smells]]></category>
            <category><![CDATA[nodejs]]></category>
            <dc:creator><![CDATA[Bonaventuragal]]></dc:creator>
            <pubDate>Sun, 31 Mar 2024 14:30:44 GMT</pubDate>
            <atom:updated>2024-03-31T14:30:44.925Z</atom:updated>
            <content:encoded><![CDATA[<h3>SonarQube Code Analysis for NestJS Project</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/564/1*ChZzrxfyO4Hkdnjr2emA1w.jpeg" /><figcaption>Software Development Illustration. <a href="https://i.pinimg.com/564x/0f/f8/00/0ff8005b0892f42ba5cec7325e6bc26d.jpg">source</a></figcaption></figure><p>With the advancements of tools that helps developers to generate code, it’s tempting to believe that coding is “easy”. While anyone can string together lines of code to create a functioning program, developing software that is not just functional but also maintainable and readable requires skill, experience, and discipline.</p><p>A good developer knows what will happen if they are neglecting code quality. Bad code not only introduces inefficiencies but also produces possible source of issues. These issues manifest in many forms, such as code smells. Code smells are subtle indications of deeper underlying problems.</p><p>To address these challenges, code analysis tools such as SonarQube emerged. SonarQube offers a comprehensive platform for code quality analysis, providing insights into various aspects of code health and offering actionable suggestions for improvement. By leveraging static code analysis techniques, SonarQube not only identifies potential issues but also helps in enforcing coding standards and promoting best practices to avoid future issues.</p><p>In this article, we will explore some features of SonarQube and how to set it up for a NestJS application.</p><h3>Setting Up Credentials</h3><p>Before configuring your app with SonarQube analysis, you have to create an account in a SonarQube platform. After obtaining an account, create or connect a project to your source code repository and obtain your <strong>project key</strong>.</p><p>Next, obtain an <strong>access token</strong> for your account. This token will be used to authenticate access to your SonarQube project. This token can be obtained from <strong>My Account &gt; Security</strong> tab.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OfaGyBe4inZf5PsRTSdUyQ.png" /><figcaption>Access Token Management Page</figcaption></figure><h3>Configuring SonarQube</h3><p>To set up SonarQube for NestJS, we can use sonarqube-scanner package to make things easier. Add the package to the development dependencies. For example using yarn:</p><pre>yarn add -D sonarqube-scanner</pre><p>The analysis itself is best to incorporated in a CI/CD pipeline. Therefore, the source code analysis will become automated. To achieve this in Gitlab CI/CD for example, we can use this job yml:</p><pre>sonar:<br>  stage: sonar<br>  image: node<br>  before_script:<br>    - yarn<br>  script:<br>    - yarn sonar-scanner -Dsonar.host.url=$SONAR_HOST -Dsonar.login=$SONAR_LOGIN -Dsonar.projectKey=$SONAR_PROJECT_KEY -Dsonar.projectName=$SONAR_PROJECT_NAME -Dsonar.sources=src -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info<br>  dependencies:<br>    - test</pre><p>There are some properties and variables used in the command</p><ul><li>sonar.host.url is used to define the location of the SonarQube host. For example, I’m using my university’s SonarQube platform, therefore I set the SONAR_HOST variable as <a href="https://sonarqube.cs.ui.ac.id.">https://sonarqube.cs.ui.ac.id.</a></li><li>sonar.login is used to authenticate the analysis request. Set your SONAR_LOGIN variable as the access token that have been <strong>generated earlier.</strong></li><li>sonar.projectKey is used to identify a project to connect to. Set your SONAR_PROJECT_KEY as the <strong>project key</strong> obtained when creating a SonarQube project.</li><li>sonar.projectName is used to set the name of your project in the SonarQube GUI. Set the SONAR_PROJECT_NAME variable as the name you desire. This variable is optional.</li><li>sonar.sources is used to tell SonarQube where the source code that needs analysis is located. For NestJS projects, this usually in the src folder.</li><li>sonar.javascript.lcov.reportPaths is used to pass the coverage report of your JS/TS project test result to SonarQube. For example, if you are using jest with default setting, the coverage report will be available in coverage/lcov.info.</li></ul><p>For the testing coverage report, you can use this job yml before the sonar job:</p><pre>test:<br>  stage: test<br>  image: $NODE_IMAGE<br>  before_script:<br>    - yarn<br>  script:<br>    - yarn jest --ci --coverage --verbose<br>  coverage: &#39;/All files[^|]*\|[^|]*\s+([\d\.]+)/&#39;<br>  artifacts:<br>    paths:<br>      - coverage/ </pre><p>The artifacts paths is needed to store and forward the coverage result to the next.</p><p>Here is the final yml:</p><pre>test:<br>  stage: test<br>  image: $NODE_IMAGE<br>  before_script:<br>    - yarn<br>  script:<br>    - yarn jest --ci --coverage --verbose<br>  coverage: &#39;/All files[^|]*\|[^|]*\s+([\d\.]+)/&#39;<br>  artifacts:<br>    paths:<br>      - coverage/<br><br>sonar:<br>  stage: sonar<br>  image: node<br>  before_script:<br>    - yarn<br>  script:<br>    - yarn sonar-scanner -Dsonar.host.url=$SONAR_HOST -Dsonar.login=$SONAR_LOGIN -Dsonar.projectKey=$SONAR_PROJECT_KEY -Dsonar.projectName=$SONAR_PROJECT_NAME -Dsonar.sources=src -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info<br>  dependencies:<br>    - test</pre><blockquote>Don’t forget to add the necessary variables as <strong>pipeline variables.</strong></blockquote><p>Push your code and job script and wait the pipeline to run. On success, open your SonarQube platform to see the analysis.</p><h3>SonarQube Analysis</h3><p>After successfully setting up SonarQube for our code, we can start analysing. There are a lot of metrics in SonarQube that can be used to analyse your code.</p><h4>Quality Gate</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*p4GvjAGBTJXs1vIdEtLDGg.png" /><figcaption>Project with Failed Quality Gate</figcaption></figure><p>A Quality Gate is a set of measure-based boolean conditions. It helps to know immediately whether your projects are production-ready. Each project’s Quality Gate status is displayed prominently on its homepage.</p><p>In the example above, the Quality Gate Status is Failed because the project has 4.76% of Duplicated Lines. This exceeds the acceptable condition for line duplications (3.0% by default).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EEZZPZYxHgV3FnZVzFS10g.png" /><figcaption>Duplicated Lines Details</figcaption></figure><p>SonarQube also tells us where the line duplications happens. In the example above, the code block highlighted by the vertical gray box (starting from line 66) is duplicated by lines 169–210.</p><h4>Code Smells</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eYj1nCH6liA1qiwHavAucA.png" /><figcaption>List of Code Smells</figcaption></figure><p>The code smells tab give you all code smells detected by SonarQube. By clicking one of the code smells, SonarQube will give us the detailed explanation, such as code smells description, location, the reason why the code is detected as code smells, non compliant code example, and the compliant code example.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QGB196Fg4KXAQ-Sm0h0jVg.png" /><figcaption>Code Smells Details.</figcaption></figure><p>The detailed explanations helps us to learn more about code smells and prevent aditional code smells in the future.</p><h4>Code Coverage</h4><p>As we have set up before, SonarQube also shows the coverage results of our tests.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Rl2ArZg5Euv9EOqE36jYyA.png" /><figcaption>Coverage Results</figcaption></figure><p>This page shows the total coverage of our code and which files is not fully covered yet.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZPwbiGrpVQydYCVr5uRuWQ.png" /><figcaption>Code Coverage Location</figcaption></figure><p>In the example above, the fillSurvey function has lines that are not fully covered by tests yet. The lines which highlighted by vertical red block are the lines that are not covered by tests.</p><h4>Refactoring</h4><p>Of course, after analysing our code, we have to do some refactoring to resolve the issues highlighted by SonarQube. Why would we do code analysis if we don’t want to fix our code?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ewuRm5kJfxF9svzi4B98_w.png" /><figcaption>Passed Quality Gate Status</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0B9WCayydf3_CQzGrTU-3w.png" /><figcaption>Activity Graph</figcaption></figure><p>In the example above, after some refactoring and reanalyse the code I managed to pass the Quality Gate and reduce some code smells. The activity graph can be a good indicator to see our source code analysis result history.</p><p>Ensuring code quality is not just a matter of writing functional code. It’s about fostering a culture of excellence, where maintainability, readability, and reliability are important. Using tools like SonarQube helps developers teams to identify and address code smells and other quality issues, ultimately enhancing the long-term sustainability of the software projects.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bdc09d022033" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[NestJS Containerization Using Docker: Simple, yet Effective]]></title>
            <link>https://medium.com/@bonaventuragal/nestjs-containerization-using-docker-simple-yet-effective-eb76285d433a?source=rss-70a6887809ad------2</link>
            <guid isPermaLink="false">https://medium.com/p/eb76285d433a</guid>
            <category><![CDATA[containerization]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[containers]]></category>
            <category><![CDATA[nestjs]]></category>
            <category><![CDATA[deployment]]></category>
            <dc:creator><![CDATA[Bonaventuragal]]></dc:creator>
            <pubDate>Sun, 31 Mar 2024 08:40:13 GMT</pubDate>
            <atom:updated>2024-04-20T09:46:36.871Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Op4kDY4K4d126AL8ohW4aw.png" /><figcaption>Docker Logo. <a href="https://blog.codewithdan.com/wp-content/uploads/2023/06/Docker-Logo.png">source</a></figcaption></figure><p>In today’s fast-changing world of software development, Docker has emerged as one of the front runner in shipping applications. It’s like a magic box where you can put all the stuff your app needs to run, making it easy to move around and work on different computers. In this article, we will explore how to containerize a NestJS app using Docker and utilize some of Docker mechanisms to reduce image build time and size.</p><h3>First Things First, What is Docker?</h3><p>Docker is a platform for developing, shipping, and running applications in an isolated environment. With Docker, we can isolate our application together with its dependencies. Therefore, we ensure the consistency between different environments and reduce the “It works on my machine!” problems.</p><p>Docker utilizes operating system level virtualization to create its containers. The Docker containers leverage the host operating system’s kernel and share resources more efficiently. This way, the created containers are lightweight and faster to start when compared with the traditional hardware virtualization.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*HUVeF82ABkfq-LZbg9gOXA.png" /><figcaption>Docker Infrastructure. <a href="https://miro.medium.com/v2/resize:fit:1200/1*BFVWkEd1X4-vJKgtH73IPg.png">source</a></figcaption></figure><h4>Docker Container</h4><p>Docker Container is an isolated instance of an application, wrapped together with its dependencies. We can interact with the container using Docker CLI or other interfaces provided by Docker. Docker Container is created based on Docker Image and other options given when the container is created.</p><h4>Docker Image</h4><p>Docker Image is a template for creating Docker Containers. One way of creating Docker Image is using Dockerfile. Dockerfile is a file containing instructions to create an Image based on the code repository where the file is located. Docker Image can be stored in an image registry, such as Docker Hub.</p><h3>Containerizing NestJS</h3><p>Docker can be used to containerized many things. In this section, we will explore how to create a Dockerfile for NestJS application and publish the image to the Docker Hub for later use.</p><h4>The Project</h4><p>Before creating the Dockerfile, we have to identify our NestJS application first. Let’s assume that we use nest-cli to bootstrap our project, using yarn package management, and will listen on port 3000.</p><h4>Dockerfile</h4><p>We will use Dockerfile to containerize our application. Usually, Dockerfile is placed at the root directory of our project. We will create a multi stage Dockerfile with three stages:</p><ul><li><strong>base</strong>: for declaring the base image.</li><li><strong>build</strong>: for building the source code into a runnable application.</li><li><strong>production</strong>: for defining the final image definition.</li></ul><p>Why 3 stages? Although single stage Dockerfile will work fine, by using 3 stages, we can minimize the time taken to build our image by utilizing Docker caching mechanism. We also can reduce the size of the resulting image size. We will see more about these benefits later.</p><h4>First Stage: base</h4><p>This stage is only used to declare the base image for our resulting image. The image declared in this stage will be used by other stages.</p><pre>FROM node:alpine AS base</pre><p>Each line of the Dockerfile is an instruction for Docker on how to build our app into an image. This stage only contains one instruction.</p><ul><li>The FROM keyword is used to declare a stage. FROM node:alpine as base basically means that we create a stage called base using node:alpine image as the base image. The node:alpine image itself is a lightweight Linux distribution with node runtime environment installed.</li></ul><p>Why do we create this single line stage? Each line in a Dockerfile creates a layer in the image container. Docker caches these layers to speed up subsequent builds. By declaring the base image in the first stage, Docker can cache this stage’s layers. This means that if the Dockerfile doesn’t change and the base image is already available locally, Docker can reuse the cached layers for this stage in subsequent builds. This caching mechanism speeds up the build process by avoiding redundant operations.</p><h4>Second Stage: build</h4><p>This stage will focus on building our source code into a runnable application. With nest-cli, we can build our app using the build command.</p><pre>FROM base AS build<br><br>WORKDIR /usr/src/app<br>COPY package.json yarn.lock ./<br>RUN yarn install --no-lockfile<br>COPY . .<br>RUN yarn build</pre><p>In this stage we have much more instruction than previous stage.</p><ul><li>FROM base AS build means that this stage is derived from the base stage. By using the base stage instead of manually using the node:alpine image again, Docker can reuse the cached layer across stages.</li><li>The WORKDIR keyword is used to determine the location of our working directory inside the container. This instruction set our working directory to /usr/src/app.</li><li>The COPY &lt;src&gt; &lt;src&gt; ... &lt;dest&gt; instruction is used to copy folder/files declared as src and adds them to the filesystem of the container at the path &lt;dest&gt;. COPY package.json yarn.lock ./ means that the package.json and yarn.lock from local directory is copied to the working directory of the container.</li><li>The RUN keyword is used to run a command inside the container. RUN yarn install --no-lockfile will run the command yarn install --no-lockfile. The command itself will install the dependencies defined by package.json and yarn.lock, but won’t update the yarn.lock to avoid dependency mismatch between production and development environment. This command will generate the node_modules folder in our root directory, which contains our application dependencies. This folder is needed to run our code later.</li><li>COPY . . will copy the rest of our source code to the container. The source code is needed for building our application.</li><li>Lastly, we use RUN yarn build to build our application. By default, this command will generate the dist folder in our root directory. This folder contains the production ready application code.</li></ul><p>You may be wondering why we separate copying the package.json and yarn.lock from the rest of our code. Once again, this is to utilize Docker caching mechanism. Those two files are much less volatile than the rest of our source code. Docker caching mechanism is smart enough to not run the yarn install command when those two files are not changed and therefore reduce the time needed to build our image. If we copy all file at once and then run yarn install, any file changes will force Docker to reinstall our dependencies.</p><h4>Third Stage: production</h4><p>This stage will focus on managing our production application image size. Because this is the last stage, the resulting image will be created based on the container from this stage.</p><pre>FROM base AS production<br><br>COPY --from=build /usr/src/app/node_modules ./node_modules<br>COPY --from=build /usr/src/app/dist ./dist<br>COPY --from=build /usr/src/app/package.json .<br>COPY --from=build /usr/src/app/yarn.lock .<br><br>CMD [&quot;sh&quot;, &quot;-c&quot;, &quot;yarn start:prod&quot;]</pre><ul><li>This stage use COPY keyword to copy production ready files that is needed for minimal size image. The file is copied from the previous build stage.</li><li>The CMD keyword is used to provide defaults for an executing container. When our container is started, the container will run yarn start:prod on the default shell. The yarn start:prod itself is a command created by nest-cli to start the production application.</li></ul><p>And that’s concludes our Dockerfile! Here is the full version:</p><pre>FROM node:alpine AS base<br><br>FROM base AS build<br><br>WORKDIR /usr/src/app<br>COPY package.json yarn.lock ./<br>RUN yarn install --no-lockfile<br>COPY . .<br>RUN yarn build<br><br>FROM base AS production<br><br>COPY --from=build /usr/src/app/node_modules ./node_modules<br>COPY --from=build /usr/src/app/dist ./dist<br>COPY --from=build /usr/src/app/package.json .<br>COPY --from=build /usr/src/app/yarn.lock .<br><br>CMD [&quot;sh&quot;, &quot;-c&quot;, &quot;yarn start:prod&quot;]</pre><h4>Building the image</h4><p>After the Dockerfile is complete, we have to build the image using command docker build -t &lt;IMAGE_NAME&gt; .. This commands tell Docker to build an image based on Dockerfile located in the current (.) directory and give it a name IMAGE_NAME.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/628/1*ygzXhi0ZeMUkD5T1PreZDQ.png" /><figcaption>Docker build</figcaption></figure><p>After the image is built, we can push the image to Docker Hub using command docker push &lt;DOCKER_IMAGE_NAME&gt;. Make sure that you have a Docker Hub account and login to your account using docker login.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/903/1*tJjs6SVpCZnPY-eHidBwNA.png" /><figcaption>Docker push</figcaption></figure><p>To run a container based on your image, we can use command docker run -d --name &lt;CONTAINER_NAME&gt; -p 8000:3000 &lt;DOCKER_IMAGE_NAME&gt;. This commands will pull image &lt;DOCKER_IMAGE_NAME&gt; (if one is not available locally) and create a container called &lt;CONTAINER_NAME&gt;. The -d flag tells Docker to run the container in detached mode (in the background). The -p flag is used to maps port 8000 of your machine to port 3000 of the container, which is where your application will listens to. Therefore, to access your application from your local machine, you have to access localhost:8000.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sHOqq9sRM3jNptdlinKJBA.png" /><figcaption>Docker run</figcaption></figure><h4>What’s Next?</h4><p>Your image is published to Docker Hub and ready to use. You can use it to deploy your application to various platform or share it with your team to improve development workflow. You also can automate the image building stage in a CI/CD pipeline.</p><p>Docker’s efficiency and versatility make it the go-to tool for developers looking to streamline their workflow and deploy applications with ease. With its robust features and widespread adoption, Docker empowers teams to build, ship, and run applications reliably across any environment. Embracing Docker not only enhances productivity but also future-proofs your development process.</p><h4>References</h4><ul><li><a href="https://docs.docker.com/get-started/overview/">https://docs.docker.com/get-started/overview/</a></li><li><a href="https://docs.docker.com/reference/dockerfile/">https://docs.docker.com/reference/dockerfile/</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=eb76285d433a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How Our Development Team Support Ourselves With Tools]]></title>
            <link>https://medium.com/@bonaventuragal/how-our-development-team-support-ourselves-with-tools-c2cebd5346ad?source=rss-70a6887809ad------2</link>
            <guid isPermaLink="false">https://medium.com/p/c2cebd5346ad</guid>
            <category><![CDATA[project-management-tool]]></category>
            <category><![CDATA[project-management]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Bonaventuragal]]></dc:creator>
            <pubDate>Sat, 30 Mar 2024 06:23:09 GMT</pubDate>
            <atom:updated>2024-03-30T06:23:09.949Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/563/1*tVJjTsVjfHcAAwTbncFSyw.jpeg" /><figcaption>Project Management. <a href="https://id.pinterest.com/pin/495677502750681178/">source</a></figcaption></figure><p>Software development is a team effort. As teams navigate the complexities of management, coding, and communication, having access to the most effective tools can help reduce confusion and boost the productivity of the team. In this article, I will share what tools my team uses in our current project.</p><h3>Scrum Board</h3><p>Scrum Board is used as a project management tools in a project using Scrum methodology. This board is used to plan and manage the project in a sprintly basis. There are many columns in the board that gives various information, such as <strong>Task, Status, Assignee, Due Date, </strong>and <strong>Story Point.</strong></p><p>There are many Scrum Board tools out there, such as <strong>Jira</strong>, <strong>ClickUp</strong>, and <strong>Notion, </strong>which is more of a note tools, but can be used as a Scrum Board. There are also boards provided by code repository such as <strong>GitHub Projects </strong>and <strong>GitLab Issues Board.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zOMtWcx_axMBtzMbjnMI4g.png" /><figcaption>Notion Scrum Board</figcaption></figure><p>Using Scrum Board, team members can effectively plan their sprint in a centralized location. They can also track their own and other’s progress, which helps improve team’s accountability.</p><p>Each task also have a detail page, which can be filled with more detailed information. Team members can add notes and/or comments to the page to specify the detailed information for the task.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QXhPO0QI3NY8yk2Qr0Dv4w.png" /><figcaption>Task Detail Page</figcaption></figure><h3>Code Management</h3><p>Every project have their own source code repository. But a good repository is more than just storing code in a repository. It’s storing code with a standard.</p><p>For example, my team uses a format for commit messages. We uses tag <strong>[RED], [GREEN], [REFACTOR]</strong> for TDD commits and <strong>(file)</strong> to tell others which file is modified. With a commit messages format, our team managed to tidy up our commits, and therefore easier for us to review our commit history.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*K3hmcVCnL1zR7NBd7ZiiZA.png" /><figcaption>Commit Messages Format</figcaption></figure><p>Our team also review each other codes when someone create a Merge Request. The merge request needs to be approved by other member first before it can be merged. We also communicate any comment/review through the merge request comment section. This way, we can centralized our communication for that merge request. Using the comment section also allows us to point out specific code that needs improvement.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dHEste-kKE32G9Mkd7y1aQ.png" /><figcaption>Merge Request</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8pbYrvC1Ujk8VYA_I3i4Iw.png" /><figcaption>Merge Request Activity</figcaption></figure><h3><strong>Communication Platform</strong></h3><p>Our team uses two different communication platforms, <strong>LINE</strong> and <strong>Discord</strong>. We uses LINE for non technical communication, such as generic information and communication with TA. On the other hand, we uses Discord for technical communication, such as issues and GitLab notification.</p><p>We uses Discord webhook feature to connect our GitLab repository to Discord. Therefore, any commit, merge request, comments, and pipeline activity in the repository is forwarded as a notification to Discord. This way, we can monitor the repository activity asynchronously.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dKCLi1KZgocMdrw8QO0LWw.png" /></figure><p>Having some tools to support your team development is crucial to improve communication, productivity, collaboration, and avoid any confusion. This article just provides some example what tools can be used. Of course, there are some better tools out there, but this is how our team works. How about yours?</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c2cebd5346ad" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[NestJS Testing Recipe: Mocking Prisma]]></title>
            <link>https://medium.com/@bonaventuragal/nestjs-testing-recipe-mocking-prisma-274c212d4b80?source=rss-70a6887809ad------2</link>
            <guid isPermaLink="false">https://medium.com/p/274c212d4b80</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[prisma]]></category>
            <category><![CDATA[nestjs]]></category>
            <category><![CDATA[testing]]></category>
            <dc:creator><![CDATA[Bonaventuragal]]></dc:creator>
            <pubDate>Sun, 25 Feb 2024 14:48:09 GMT</pubDate>
            <atom:updated>2024-02-25T14:48:09.030Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/563/1*MbjbCOFfHJwoaX-E-aJMew.jpeg" /><figcaption>Prisma database. <a href="https://id.pinterest.com/pin/885238870475629169/">source</a></figcaption></figure><p>NestJS is one of the most popular framework for building backend application, offering developers an elegant and structured environment for building scalable server-side solutions. One of the fundamental aspects of a backend application is the ability to connecting to and querying databases. Within NestJS environment, many approaches exists for this purpose. One such method, gaining popularity for its simplicity and efficiency, is integrating Prisma for database interactions.</p><p>Rather than exploring the broader landscape of using Prisma, our focus lies in the process of designing and creating robust unit tests for a NestJS application that utilizes Prisma for database operations. For this article, we will use Jest as our testing framework.</p><p>Before starting, let’s define our example project configuration.</p><h3>Prisma Schema</h3><p>We will create a simple schema for this example. One table User is sufficient.</p><pre>// schema.prisma<br>generator client {<br>  provider = &quot;prisma-client-js&quot;<br>}<br><br>datasource db {<br>  provider = &quot;postgresql&quot;<br>  url      = env(&quot;DATABASE_URL&quot;)<br>}<br><br>model User {<br>  username String @id<br>  name     String<br>}</pre><h3><strong>PrismaModule</strong></h3><p>This module will allows us to inject our PrismaService to other modules. PrismaService will act as an abstraction for PrismaClient from Prisma.</p><pre>// prisma.service.ts<br>@Injectable()<br>export class PrismaService<br>  extends PrismaClient<br>  implements OnModuleInit, OnModuleDestroy<br>{<br>  async onModuleInit() {<br>    await this.$connect();<br>  }<br><br>  async onModuleDestroy() {<br>    await this.$disconnect();<br>  }<br>}</pre><h3>UserModule</h3><p>This module is our test subject. UserService will have PrismaService as a dependency that will be used to query the database.</p><pre>// user.service.ts<br>@Injectable()<br>export class UserService {<br>  constructor(private readonly prismaService: PrismaService) {}<br><br>  async getUserByUsername(username: string) {<br>    const user = await this.prismaService.user.findUnique({<br>      where: {<br>        username,<br>      },<br>    });<br><br>    if (!user) {<br>      throw new NotFoundException({<br>        message: &#39;User with the given username not found&#39;,<br>      });<br>    }<br><br>    return user;<br>  }<br><br>  async getAllUser() {<br>    const allUser = await this.prismaService.user.findMany({});<br><br>    return allUser;<br>  }<br>}</pre><p>We have two simple functions, the first one is to find user based on username, and the second one is to find all users.</p><p>With the configuration complete, let’s get to the unit testing. Our main goal is to test the functions within UserService. But, as you can see, our UserService functions are dependent on the PrismaService. We need to isolate the functions from PrismaService. Why? There are a lot of reason to this:</p><ol><li>By isolating the function from its dependencies, we can focus solely on the logic of our function.</li><li>Our test can be designed more efficiently because we don’t have to deal with the complexity of external dependency, especially in Test Driven Development.</li><li>Introduces more control to our input and output prediction/assertion. Our test environment will be more stable and reproducible.</li></ol><p>How do we isolate the function? We can do so by stubbing or mocking the dependency.</p><h3>Stub VS Mock</h3><p>Stub and mock are two core techniques in unit testing. Both are used for isolating a component for it’s dependency.</p><p>Stubbing is a technique where we isolate our function from its dependency by replacing the actual implementation of the dependency with a simplified, predefined behavior. This allows us to simulate the response or result of our dependency.</p><p>Meanwhile, mocking is a technique similar to stubs. It involves replacing dependencies, but it goes a step further by recording interactions with the mocked dependency. By using a mock, we can verify various aspects of the interaction inside our function, such as the number of times our dependency is called, the parameters it’s called with, and more.</p><p>In Jest, its pretty easy to stubbing and mocking. Jest already provide us with a built-in mock utility, which can be used to both stubbing and mocking dependency.</p><h3>Creating Unit Test</h3><p>Let’s start creating simple test structure for our functions.</p><pre>// user.spec.ts<br>describe(&#39;UserService&#39;, () =&gt; {<br>  let userService: UserService;<br><br>  beforeEach(async () =&gt; {<br>    const module: TestingModule = await Test.createTestingModule({<br>      providers: [UserService],<br>    }).compile();<br><br>    userService = module.get&lt;UserService&gt;(UserService);<br>  });<br><br>  describe(&#39;getUserByUsername&#39;, () =&gt; {<br>    it(&#39;should return user if exists&#39;, async () =&gt; {<br>      const existingUser = {<br>        username: &#39;existing-user&#39;,<br>        name: &#39;Existing User&#39;,<br>      };<br><br>      const result = await userService.getUserByUsername(existingUser.username);<br>      expect(result).toEqual(existingUser);<br>    });<br><br>    it(&#39;should throw NotFoundException if user not exists&#39;, async () =&gt; {<br>      await expect(<br>        userService.getUserByUsername(&#39;non-existing-user&#39;),<br>      ).rejects.toThrow(NotFoundException);<br>    });<br>  });<br><br>  describe(&#39;getAllUser&#39;, () =&gt; {<br>    it(&#39;should return all user&#39;, async () =&gt; {<br>      const allUser = [<br>        {<br>          username: &#39;user1&#39;,<br>          name: &#39;User 1&#39;,<br>        },<br>        {<br>          username: &#39;user2&#39;,<br>          name: &#39;User 2&#39;,<br>        },<br>      ];<br><br>      const result = await userService.getAllUser();<br>      expect(result).toEqual(allUser);<br>    });<br><br>    it(&#39;should return empty array if there are no users&#39;, async () =&gt; {<br>      const result = await userService.getAllUser();<br>      expect(result).toEqual([]);<br>    });<br>  });<br>});</pre><p>This is a pretty standard unit test for a NestJS component. Next, let’s start mocking the PrismaService.</p><h3>Simple Mocking</h3><p>This is the most straightforward method for mocking a dependency. We can create an object that mirrors the structure of our actual dependency, we essentially create a mock. For our current PrismaService, we have a user table. We also called two functions from the UserService, that isfindUnique and findMany. So, we can create a mock object such as:</p><pre>const prismaMock = {<br>  user: {<br>    findUnique: jest.fn(),<br>    findMany: jest.fn(),<br>  },<br>};</pre><p>The jest.fn() is used to declare a mock function. This function will allows us to mock a response and do assertions. Incorporating that mock to our test, we can create a working mock behavior.</p><pre>const prismaMock = {<br>  user: {<br>    findUnique: jest.fn(),<br>    findMany: jest.fn(),<br>  },<br>};<br><br>describe(&#39;UserService&#39;, () =&gt; {<br>  let userService: UserService;<br><br>  beforeEach(async () =&gt; {<br>    const module: TestingModule = await Test.createTestingModule({<br>      providers: [<br>        UserService,<br>        {<br>          provide: PrismaService,<br>          useValue: prismaMock,<br>        },<br>      ],<br>    }).compile();<br><br>    userService = module.get&lt;UserService&gt;(UserService);<br><br>    prismaMock.user.findUnique.mockClear();<br>    prismaMock.user.findMany.mockClear();<br>  });<br><br>  describe(&#39;getUserByUsername&#39;, () =&gt; {<br>    it(&#39;should return user if exists&#39;, async () =&gt; {<br>      const existingUser = {<br>        username: &#39;existing-user&#39;,<br>        name: &#39;Existing User&#39;,<br>      };<br><br>      prismaMock.user.findUnique.mockResolvedValue(existingUser);<br><br>      const result = await userService.getUserByUsername(existingUser.username);<br>      expect(result).toEqual(existingUser);<br>      expect(prismaMock.user.findUnique).toBeCalledTimes(1);<br>      expect(prismaMock.user.findUnique).toBeCalledWith({<br>        where: { username: existingUser.username },<br>      });<br>    });<br><br>    it(&#39;should throw NotFoundException if user not exists&#39;, async () =&gt; {<br>      prismaMock.user.findUnique.mockResolvedValue(null);<br><br>      await expect(<br>        userService.getUserByUsername(&#39;non-existing-user&#39;),<br>      ).rejects.toThrow(NotFoundException);<br>      expect(prismaMock.user.findUnique).toBeCalledTimes(1);<br>      expect(prismaMock.user.findUnique).toBeCalledWith({<br>        where: { username: &#39;non-existing-user&#39; },<br>      });<br>    });<br>  });<br><br>  describe(&#39;getAllUser&#39;, () =&gt; {<br>    it(&#39;should return all user&#39;, async () =&gt; {<br>      const allUser = [<br>        {<br>          username: &#39;user1&#39;,<br>          name: &#39;User 1&#39;,<br>        },<br>        {<br>          username: &#39;user2&#39;,<br>          name: &#39;User 2&#39;,<br>        },<br>      ];<br><br>      prismaMock.user.findMany.mockResolvedValue(allUser);<br><br>      const result = await userService.getAllUser();<br>      expect(result).toEqual(allUser);<br>      expect(prismaMock.user.findMany).toBeCalledTimes(1);<br>      expect(prismaMock.user.findMany).toBeCalledWith({});<br>    });<br><br>    it(&#39;should return empty array if there are no users&#39;, async () =&gt; {<br>      prismaMock.user.findMany.mockResolvedValue([]);<br><br>      const result = await userService.getAllUser();<br>      expect(result).toEqual([]);<br>      expect(prismaMock.user.findMany).toBeCalledTimes(1);<br>      expect(prismaMock.user.findMany).toBeCalledWith({});<br>    });<br>  });<br>});</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/595/1*Yr1L6MY2k6O0TLtGiM8B9g.png" /><figcaption>Simple mocking test result</figcaption></figure><h3>Dynamic Mocking</h3><p>The previous mocking technique is working as expected. But, you may catch some problem in that technique. We manually define our mock as an object based on the usage in our UserService. What if there are changes made in the service? If we invoke a user.findFirst() or a user.create() functions in our service, we have to manually add those functions to our mock object. What if we create another table to our schema? We have to add those table to our mock object too. What if we create another unit test file? We have to create the mock object there too. This manual upkeep can be error-prone, potentially leading to mismatches between the mock object and the evolving service. Thankfully, there exists a way to dynamically create our mock object.</p><p>For this technique, we will use the jest-mock-extended package. This package is recommended by Prisma, as it allows us to deeply mock the PrismaClient and provides us a type-safe mock object. Let’s change our test file to:</p><pre>describe(&#39;UserService&#39;, () =&gt; {<br>  let userService: UserService;<br>  let prismaMock: DeepMockProxy&lt;PrismaClient&gt;;<br><br>  beforeEach(async () =&gt; {<br>    prismaMock = mockDeep&lt;PrismaClient&gt;();<br><br>    const module: TestingModule = await Test.createTestingModule({<br>      providers: [<br>        UserService,<br>        {<br>          provide: PrismaService,<br>          useValue: prismaMock,<br>        },<br>      ],<br>    }).compile();<br><br>    authService = module.get&lt;AuthService&gt;(AuthService);<br>  });<br><br>// ... the rest of our code</pre><p>The mockDeep function is used to deeply mock the PrismaClient. Therefore, any modification to the database schema will be automatically mirrored to our mock object.</p><p>Mocking external dependencies in unit tests allows us to introduce more control to our test environment. It may seem complicated at first, but with more hands-on experience, we can gain a detailed understanding of how to tailor mocks to specific scenarios and improve the quality of our unit test. With a high quality test, we can ensure the robustness of our codebase. Happy testing!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=274c212d4b80" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[TDD: Testing the Waters with Jest]]></title>
            <link>https://medium.com/@bonaventuragal/tdd-testing-the-waters-with-jest-31badf12b009?source=rss-70a6887809ad------2</link>
            <guid isPermaLink="false">https://medium.com/p/31badf12b009</guid>
            <category><![CDATA[software-testing]]></category>
            <category><![CDATA[jest]]></category>
            <category><![CDATA[tdd]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Bonaventuragal]]></dc:creator>
            <pubDate>Sun, 25 Feb 2024 11:03:35 GMT</pubDate>
            <atom:updated>2024-04-20T09:25:17.614Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*i_v2UW4OXOJJesG3QolrIQ.jpeg" /><figcaption>Testing illustration. <a href="https://ru.freepik.com/free-vector/developers-testing-software_7416545.htm"><em>source</em></a></figcaption></figure><p>In software development, testing is an integral part of development to ensure the quality of the code. One of the most adopted testing paradigm is Test Driven Development (TDD). In this article, we will explore more about TDD and how to implement it using Jest, a testing framework.</p><h3><strong>What is TDD?</strong></h3><p>Test Driven Development (TDD) is an approach of software development that put emphasis on writing tests before the actual code. This way, developers can catch eventual issues early on and reduce the time needed for debugging later.</p><p>TDD follows a cyclic process that consists of 3 primary steps: writing failing tests, implementing the minimum code to pass the tests, and refactoring the code while ensuring the tests continue to pass. After the refactoring is done, the cycle continues on for the next functionality to implement.</p><ol><li><strong>Writing Failing Tests</strong></li></ol><p>The TDD process starts with writing tests for the functionality that will be implemented later. These tests will fail since the corresponding code implementation hasn’t realized yet. While writing tests, we have to consider various scenarios that the eventual code implementation should address.</p><p>The first scenario is the positive cases. In these scenarios, we explore the expected and desired behavior of the functionality under normal, ideal conditions. Positive cases represent the typical user interactions or system inputs that the code is designed to handle successfully. The tests created usually in a form of asserting the result of a function against the expected result. Another form of asserting used is validating whether specific functions are expectedly invoked.</p><p>The second scenario is the negative cases. In these scenarios, we intentionally test the boundaries and limitations of the functionality by introducing inputs or conditions that are expected to trigger errors, exceptions, or undesired outcomes. The tests for these scenarios usually asserting whether or not an exception is thrown, examining the type of the thrown exception, and validating whether specific functions are expectedly not invoked.</p><p>The third scenario is the edge cases. These scenarios test out the extreme variations of input. Tests for edge cases can either be positive or negative, depending on the nature of the input and the functionality being examined.</p><p><strong>2. Implementing Minimum Passing Code</strong></p><p>After the tests are complete, the next step is writing the code to pass the tests. The goal here is to write as minimum code as can be to pass the tests. This goal allows developer to focus more on the functionality of the code and avoid overcomplexity.</p><p><strong>3. Refactoring</strong></p><p>After all tests have passed, it’s time to refactor the code. In this step, we identify and eliminate redundancy in our codebase. By refactoring, we can increase the quality, maintainability, and scalability of our code.</p><p>While refactoring, we should always check the status of our tests. The passed tests should not reverted to failed, or else the refactoring process won’t have any value in it.</p><h3>TDD using Jest</h3><p>Jest is one of many JavaScript testing framework. Jest offers user-friendly syntax and powerful features, such as automatic test runners and built-in assertions. When integrated with TypeScript, we can create more type-safe tests, ensuring the quality of our code. In the next section, we will explore how to implement a TDD cycle using Jest.</p><p><strong>The Functionality</strong></p><p>Before we start our TDD, we have to define what our functionality will be. For this example, we will create a function to handle user registration.</p><pre>// register.ts<br>export type RegisterRequest = {<br>  username: string<br>  password: string<br>  name: string<br>}<br><br>class RegisterFailedException extends Error {<br>  constructor(message: string) {<br>    super(message);<br>    Object.setPrototypeOf(this, RegisterFailedException.prototype);<br>  }<br>}<br><br>export const register = (request: RegisterRequest) =&gt; {<br>  throw new RegisterFailedException(&quot;Function not implemented&quot;);<br>}</pre><p>The register function will handle the incoming RegisterRequest, and then returns true if the register is success. If it fails, we will throw a RegisterFailedException.</p><p><strong>Step 1: Writing Failing Tests</strong></p><p>Now let’s identify some test cases for our register function.</p><ul><li>Positive case: the incoming request is valid and the return value is true.</li><li>Negative case: the incoming request have missing values, such as empty strings. The function will throw RegisterFailedException.</li><li>Edge case: the incoming request is valid, but the password is not at least 6 characters long. We want to ensure our user’s password is pretty secure. The function will throw RegisterFailedException.</li></ul><pre>// register.test.ts<br>describe(&#39;register test&#39;, () =&gt; {<br>  it(&#39;should return true if register success&#39;, () =&gt; {<br>    const request: RegisterRequest = {<br>      username: &#39;testuser&#39;,<br>      password: &#39;test1234&#39;,<br>      name: &#39;Test User&#39;,<br>    };<br><br>    const result = register(request);<br>    expect(result).toBe(true);<br>  });<br><br>  it(&#39;should throw exception if request invalid&#39;, () =&gt; {<br>    const request: RegisterRequest = {<br>      username: &#39;testuser&#39;,<br>      password: &#39;test1234&#39;,<br>      name: &#39;&#39;,<br>    };<br><br>    const result = () =&gt; register(request);<br><br>    expect(result).toThrow(RegisterFailedException);<br>    expect(result).toThrow(&#39;Incomplete request&#39;);<br>  });<br><br>  it(&#39;should throw exception if request invalid&#39;, () =&gt; {<br>    const request: RegisterRequest = {<br>      username: &#39;testuser&#39;,<br>      password: &#39;test&#39;,<br>      name: &#39;Test User&#39;,<br>    };<br><br>    const result = () =&gt; register(request);<br><br>    expect(result).toThrow(RegisterFailedException);<br>    expect(result).toThrow(&#39;Password must be at least 6 characters long&#39;);<br>  });<br>});</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/554/1*v3HAVjiMeZQMwjYm0P7ReA.png" /><figcaption>Test result failed</figcaption></figure><p><strong>Step 2: Implementing Minimum Passing Code</strong></p><p>Based on our test, we know what cases that we should consider when implementing the register function.</p><pre>// register.ts<br>export const register = (request: RegisterRequest) =&gt; {<br>  const { username, password, name } = request;<br>  if (username.length === 0 || password.length === 0 || name.length === 0) {<br>    throw new RegisterFailedException(&#39;Incomplete request&#39;);<br>  }<br><br>  if (password.length &lt; 6) {<br>    throw new RegisterFailedException(<br>      &#39;Password must be at least 6 characters long&#39;,<br>    );<br>  }<br><br>  // do register logic<br><br>  return true;<br>};</pre><p>All tests should be passing now.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/523/1*qR6lJ-hLiN9eIhAKq30pVw.png" /><figcaption>Test result passed</figcaption></figure><p><strong>Step 3: Refactoring</strong></p><p>Now, let’s start refactoring our code. Our code is pretty small and straightforward right now, so not much can be done. Let’s just reduce some redundancy. Of course, for a real implementation, we should identify many aspect such as the usage of design pattern, code smells, etc.</p><pre>// register.ts<br>export const register = ({ username, password, name }: RegisterRequest) =&gt; {<br>  if (username.length === 0 || password.length === 0 || name.length === 0) {<br>    throw new RegisterFailedException(&#39;Incomplete request&#39;);<br>  }<br><br>  if (password.length &lt; 6) {<br>    throw new RegisterFailedException(<br>      &#39;Password must be at least 6 characters long&#39;,<br>    );<br>  }<br><br>  // do register logic<br><br>  return true;<br>};</pre><p>Keep in mind that while refactoring, we should always maintain the passing status of our tests.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/507/1*ap1fvxHKfVDG5A3qTOIeDQ.png" /><figcaption>Test result still passed</figcaption></figure><p>And just like that, we completed our TDD cycle! Of course, this is just an example. There are a lot of cases that still needs to be considered for such small register function, especially in a real world application. To name a few, we should create tests for input with missing username or password, username duplication, etc.</p><p>TDD can significantly enhance the quality and maintainability of your codebase. By writing tests before implementing functionality, you not only ensure that your code meets the specified requirements but also create a safety net for future changes. Also, remember that the true value lies not just in passing tests but in building a foundation for robust and reliable software development. Happy testing!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=31badf12b009" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A SOLID Approach to NestJS Development]]></title>
            <link>https://medium.com/@bonaventuragal/a-solid-approach-to-nestjs-development-9df83efdee76?source=rss-70a6887809ad------2</link>
            <guid isPermaLink="false">https://medium.com/p/9df83efdee76</guid>
            <category><![CDATA[solid-principles]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[nestjs]]></category>
            <category><![CDATA[backend]]></category>
            <category><![CDATA[nodejs]]></category>
            <dc:creator><![CDATA[Bonaventuragal]]></dc:creator>
            <pubDate>Sun, 25 Feb 2024 08:46:41 GMT</pubDate>
            <atom:updated>2024-02-25T08:46:41.175Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/768/1*EbVWDYHc4nPHu9drf-GG1Q.png" /><figcaption>NestJS. <a href="https://astconsulting.in/java-script/nodejs/nestjs/how-to-secure-your-nestjs-application/">source</a></figcaption></figure><p>NestJS is a popular Node.js framework for building server side application. Fueled by the capabilities of TypeScript, NestJS seamlessly incorporates various aspects of Object-Oriented Programming (OOP). In this paradigm, following the SOLID principles is crucial for developing applications not only maintainable, scalable, and robust but also align with NestJS’s opinionated nature. With developers typically following the framework’s guidelines, optimizing your application through the implementation of SOLID principles in NestJS is a recommended approach.</p><h3>1. Single Responsibility Principle (SRP)</h3><p>SRP states that each class should be responsible for a single part or functionality of a system. NestJS encourages a modular structure where each module, class, and function has a well-defined responsibility.</p><p>Suppose that we have created a module called auth. This module will be responsible for handling anything related to authentication. Within this module, we create classes such asAuthController that handles the routing logic for/auth endpoint group and AuthService that handles the corresponding business process.</p><pre>src/auth<br> |- auth.controller.ts<br> |- auth.service.ts<br> |- auth.module.ts</pre><p>To further refine the structure, individual functions within the AuthController can be dedicated to specific endpoints. For example, the register function is used to handle the /auth/registerendpoint and the login function to handle the /auth/login endpoint. The same process can be applied to the AuthService class.</p><pre>// auth.controller.ts<br>@Controller(&#39;auth&#39;)<br>export class AuthController {<br><br>  @Post(&#39;/register&#39;)<br>  async register() {<br>    # routing logic for /auth/register<br>  }<br><br>  @Post(&#39;/login&#39;)<br>  async login() {<br>    # routing logic for /auth/login<br>  }<br>}</pre><p>By following this modular and responsibility-driven design, not only our code will be easier to understand and maintain, but also scalable as the application grows. This separation of concerns allows us to make changes or additions to specific functionalities without affecting the entire system.</p><h3>2. Open Closed Principle (OCP)</h3><p>OCP emphasizes that a software component should be open for extension but closed for modification. In the context of NestJS, this principle can be effectively implemented using decorators.</p><p>Consider a scenario where every incoming request is authenticated by default, but certain API endpoints, such as our previous /auth/login endpoint, need to bypass authentication. To adhere to the OCP, we can create a custom decorator, let’s call it IsPublic, to easily mark specific endpoints as public, indicating that they should not undergo the authentication process.</p><pre>// auth.controller.ts<br>@Controller(&#39;auth&#39;)<br>export class AuthController {<br><br>  @IsPublic()<br>  @Post(&#39;/auth&#39;)<br>  async register() {<br>    # routing logic for /auth/login<br>  }<br>}</pre><blockquote>Keep in mind that IsPublic is a custom decorator. Full implementation can be found <a href="https://docs.nestjs.com/security/authentication#enable-authentication-globally">here</a>.</blockquote><p>This way, we have extended the behavior of the register function without modifying the logic of our code. NestJS provides other useful built-in decorator to extends the functionality of our code such as the @Body() and @Param() decorators.</p><h3>3. Liskov Substitution Principle (LSP)</h3><p>NestJS relies on dependency injection, allowing the runtime to manage class instantiation and inject instances where needed. In a scenario where we have two services sharing the same abstraction, dynamic injection on a module can be achieved by using the useClass property to conditionally switch between services. This approach aligns with the Liskov Substitution Principle (LSP), emphasizing that objects of a superclass should seamlessly replace objects of its subclasses without disrupting the system.</p><pre>// abstract.service.ts<br>export abstract class AbstractService {<br>  abstract performTask(): string;<br>}<br><br>// first.service.ts<br>@Injectable()<br>export class FirstService implements AbstractService {<br>  performTask(): string {<br>    return &#39;FirstService is performing the task.&#39;;<br>  }<br>}<br><br>// second.service.ts<br>@Injectable()<br>export class SecondService implements AbstractService {<br>  performTask(): string {<br>    return &#39;SecondService is performing the task differently.&#39;;<br>  }<br>}</pre><pre>// other.module.ts<br>@Module({<br>  providers: [<br>    {<br>      provide: AbstractService,<br>      useClass: condition ? FirstService : SecondService,<br>    },<br>    FirstService,<br>    SecondService<br>  ],<br>  exports: [AbstractService],<br>})<br>export class OtherModule {}</pre><p>This dynamic injection adheres to the LSP, allowing us to switching between services implementing the same abstraction.</p><h3>4. Interface Segregation Principle (ISP)</h3><p>JavaScript, being a dynamically-typed language, allows flexibility but may lead to runtime errors due to lack of strict typing. With the introduction of TypeScript as a superset of JavaScript, we now can enable static typing through interfaces and types. In NestJS, leveraging these interfaces/types is common, particularly when defining request body structures.</p><p>However, it’s crucial to be mindful of the Interface Segregation Principle. ISP asserts that clients should not be compelled to depend on fields or methods they don’t use. In the context of NestJS, it’s tempting to create a generic DTO (Data Transfer Objects) that includes fields for various operations.</p><pre># user.dto.ts<br>export interface UserDTO {<br>  email: string;<br>  password: string;<br>  name: string;<br>}<br><br>// auth.controller.ts<br>@Controller(&#39;auth&#39;)<br>export class AuthController {<br><br>  @Post(&#39;/register&#39;)<br>  async register(@Body() body: UserDTO) {<br>    # routing logic for /auth/register<br>  }<br><br>  @Post(&#39;/login&#39;)<br>  async login(@Body() body: UserDTO) {<br>    # routing logic for /auth/login<br>  }<br>}</pre><p>However, this can lead to unnecessary dependency, as the login function does not need the name properties. It’s important to create a separate interfaces to adhere to ISP.</p><pre>// register.dto.ts<br>export interface LoginDTO {<br>  email: string;<br>  password: string;<br>}<br><br>// register.dto.ts<br>export interface RegisterDTO {<br>  email: string;<br>  password: string;<br>  name: string;<br>}<br><br>// auth.controller.ts<br>@Controller(&#39;auth&#39;)<br>export class AuthController {<br><br>  @Post(&#39;/register&#39;)<br>  async register(@Body() body: RegisterDTO) {<br>    # routing logic for /auth/register<br>  }<br><br>  @Post(&#39;/login&#39;)<br>  async login(@Body() body: LoginDTO) {<br>    # routing logic for /auth/login<br>  }<br>}</pre><p>Or, if you want to use extension instead.</p><pre>// register.dto.ts<br>export interface LoginDTO {<br>  email: string;<br>  password: string;<br>}<br><br>// register.dto.ts<br>export interface RegisterDTO extends LoginDTO {<br>  name: string;<br>}</pre><p>By adhering to ISP, each DTO accurately represents the required data for its respective operation, reducing dependencies and contributing to a more maintainable and modular codebase.</p><h3>5. Dependency Inversion Principle (DIP)</h3><p>Dependency injection in NestJS gives us the ability to modularize our code effectively. When designing our modules, it is recommended to follow the Dependency Inversion Principle, that is high-level modules remain independent of low-level modules. To achieve this, we create abstractions for the low-level modules, treating them as contracts that define the interaction between modules. For example, suppose that we have a DatabaseService that will be injected to our AuthService.</p><pre>// database.service.ts<br>@Injectable()<br>export class DatabaseService {<br>  // Database operations<br>}<br><br>// database.module.ts<br>@Module({<br>  providers: [DatabaseService],<br>  exports: [DatabaseService]<br>})<br>export class DatabaseModule {}</pre><pre>// auth.module.ts<br>@Module({<br>  imports: [DatabaseModule],<br>  controllers: [AuthController],<br>  providers: [AuthService],<br>})<br>export class AuthModule{}<br><br>// auth.service.ts<br>@Injectable()<br>export class AuthService {<br>  constructor(private readonly databaseService: DatabaseService) {}<br>}</pre><p>Instead of injecting the DatabaseService directly, we can create an abstraction of the DatabaseService, and then export that abstraction instead.</p><pre>// database.abstract.ts<br>@Injectable()<br>export abstract class DatabaseAbstract {<br>  // Abstract database operations<br>}<br><br>// database.service.ts<br>@Injectable()<br>export class DatabaseService extends DatabaseAbstract{<br>  // Concrete database operations<br>}<br><br>// database.module.ts<br>@Module({<br>  providers: [{<br>      provide: DatabaseAbstract,<br>      useClass: DatabaseService<br>    },<br>    DatabaseService<br>  ],<br>  exports: [DatabaseAbstract]<br>})<br>export class DatabaseModule {}</pre><pre>// auth.service.ts<br>@Injectable()<br>export class AuthService {<br>  constructor(private readonly databaseService: DatabaseAbstract) {}<br>}</pre><p>This practice not only minimizes coupling between modules but also significantly enhances code flexibility, particularly within the low-level module context. Any changes made on the low-level module will not disrupt the high-level module.</p><p>In conclusion, NestJS offers a robust foundation for server-side application development. By integrating the SOLID principles, developers can elevate their applications to new levels of maintainability, scalability, and resilience. Embrace the principles, explore the possibilities, and witness your NestJS-powered applications flourish in the dynamic realm of web development.</p><p>References:</p><ul><li><a href="https://www.educative.io/answers/what-are-the-solid-principles-in-java">https://www.educative.io/answers/what-are-the-solid-principles-in-java</a></li><li><a href="https://docs.nestjs.com/">https://docs.nestjs.com/</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9df83efdee76" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>