<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Ahmed Atwa</title>
    <description>The latest articles on Forem by Ahmed Atwa (@deadreyo).</description>
    <link>https://forem.com/deadreyo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F964886%2F7ed05493-d25b-48a2-85e6-0e08b8ed5042.jpeg</url>
      <title>Forem: Ahmed Atwa</title>
      <link>https://forem.com/deadreyo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/deadreyo"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>Ahmed Atwa</dc:creator>
      <pubDate>Sat, 04 Apr 2026 12:48:09 +0000</pubDate>
      <link>https://forem.com/deadreyo/-2b3c</link>
      <guid>https://forem.com/deadreyo/-2b3c</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/deadreyo/why-docker-is-a-game-changer-for-development-not-just-production-677" class="crayons-story__hidden-navigation-link"&gt;Why Docker is a Game-Changer for Development, Not Just Production&lt;/a&gt;


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

          &lt;a href="/deadreyo" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F964886%2F7ed05493-d25b-48a2-85e6-0e08b8ed5042.jpeg" alt="deadreyo profile" class="crayons-avatar__image" width="459" height="459"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/deadreyo" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Ahmed Atwa
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Ahmed Atwa
                
              
              &lt;div id="story-author-preview-content-1968135" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/deadreyo" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F964886%2F7ed05493-d25b-48a2-85e6-0e08b8ed5042.jpeg" class="crayons-avatar__image" alt="" width="459" height="459"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Ahmed Atwa&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/deadreyo/why-docker-is-a-game-changer-for-development-not-just-production-677" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 21 '24&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/deadreyo/why-docker-is-a-game-changer-for-development-not-just-production-677" id="article-link-1968135"&gt;
          Why Docker is a Game-Changer for Development, Not Just Production
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/docker"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;docker&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devops"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devops&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/productivity"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;productivity&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/deadreyo/why-docker-is-a-game-changer-for-development-not-just-production-677" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;11&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/deadreyo/why-docker-is-a-game-changer-for-development-not-just-production-677#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


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

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

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

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Why Docker is a Game-Changer for Development, Not Just Production</title>
      <dc:creator>Ahmed Atwa</dc:creator>
      <pubDate>Wed, 21 Aug 2024 16:01:49 +0000</pubDate>
      <link>https://forem.com/deadreyo/why-docker-is-a-game-changer-for-development-not-just-production-677</link>
      <guid>https://forem.com/deadreyo/why-docker-is-a-game-changer-for-development-not-just-production-677</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In today’s fast-paced software development landscape, Docker is not just a tool for deployment but a transformative force in development workflows. While its role in streamlining production environments is well-known, Docker's impact on development is equally significant and often overlooked.&lt;/p&gt;

&lt;p&gt;In this blog, we'll discover the hidden gems of using Docker for your everyday work and development. We'll go over the current problems in modern application development so that it will be clearer how Docker can solve these problems and make our lives less miserable. We will also look at practical examples of how to set up a Dockerized workflow in 2 different ways and compare them. By the end of this blog, you should be able to convince yourself or your manager why you should containerize your development workflow!&lt;/p&gt;

&lt;p&gt;Let's dive into it!&lt;/p&gt;




&lt;h2&gt;
  
  
  Challenges in Traditional Development
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Complex Setup and Configuration
&lt;/h3&gt;

&lt;p&gt;Today, it has become a norm that onboarding new members to the team and installing and configuring the project may take a day or more, following a huge and (could be) poorly documented README, and a lot of debugging and StackOverflow-ing. I am not talking about simple Node.js apps that are simply installed via &lt;code&gt;npm install&lt;/code&gt;, I am talking about huge applications and open-source projects, which will require you to install dozens of dependencies and tools just to run the project. I still remember going days over installing a particular open-source project and still couldn't get the tests to run properly...&lt;/p&gt;

&lt;h3&gt;
  
  
  Inconsistent Environments, Dependency Conflicts
&lt;/h3&gt;

&lt;p&gt;Each developer has their own environment, software packages and dependencies installed. This leads to problems and conflicts when installing and running the project, leading to more debugging and wasted time.&lt;/p&gt;

&lt;p&gt;Another major issue, what if you have a backend that runs on Node.js v16 and a frontend app that runs on Node.js v20? Which Node.js version will you install? Even if you use NVM (Node Version Manager), only 1 version of Node.js will be running at a time, meaning you can't run the backend and frontend together to test them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Learning, Testing and Experimentation Challenges
&lt;/h3&gt;

&lt;p&gt;How would you go about testing the full project stack? You would need to install all the projects and run them together. You're most likely to suffer from one of the previously mentioned problems.&lt;/p&gt;

&lt;p&gt;Need to learn load balancing and test out a load balancer, or test out scaling your application? You would need to run multiple instances of your application. How difficult that is will depend on your runtime, but you will have to assign different ports manually.&lt;/p&gt;

&lt;p&gt;Want to learn SQL databases or Redis? Go into huge installation steps.&lt;/p&gt;

&lt;p&gt;At the end, you hope that all the new software dependencies and installations didn't mess up your system and impact your daily life usage. BTW, good luck uninstalling a project's dependencies.&lt;/p&gt;

&lt;p&gt;Now to the solution!&lt;/p&gt;




&lt;h2&gt;
  
  
  How Docker can fix your life (Err, your development problems)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Eases Onboarding and Contribution to Complex Projects
&lt;/h3&gt;

&lt;p&gt;After cloning a project, and with a single command &lt;code&gt;docker compose up --watch&lt;/code&gt; (no matter how complex the project is), the project will just magically run. Zero installations or configurations needed. Onboarding new members or contributing to new projects has never been easier!&lt;/p&gt;

&lt;p&gt;And as a bonus, all changes they make to the code will instantly reflect in the container, allowing for a seamless and effortless development process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simplifies README Files
&lt;/h3&gt;

&lt;p&gt;No need to spend hours documenting how to run the project, just to find out that only you can understand what you wrote. The setup section of the README will now be as small as: "run &lt;code&gt;docker compose up --watch&lt;/code&gt; to run the development workflow".&lt;/p&gt;

&lt;h3&gt;
  
  
  Ensures Environment Consistency, Simplifies Dependency Management, and Offers Portability
&lt;/h3&gt;

&lt;p&gt;With Docker, you can ensure that all developers are running the same environment, the same software versions, and the same dependencies, no matter what OS or configuration they have. Develop everywhere, run everywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  Provides Isolation and Handles Conflicting Dependencies Securely
&lt;/h3&gt;

&lt;p&gt;Remember the problem of having different Node.js versions? Now you can run them both in separate containers, each with their Node.js version, and they won't interfere with each other. Everything is isolated. Everyone is happy. Want to delete a project? Just &lt;code&gt;docker compose down&lt;/code&gt; and that's it, no need to worry about uninstalling anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enables Practical Learning and Experimentation
&lt;/h3&gt;

&lt;p&gt;Want to learn a new technology or tool? Just pull a Docker image and run it. No need to install anything or worry about messing up your system. Could be a database, a cache, a load balancer, a message broker, etc.&lt;/p&gt;

&lt;p&gt;Learning frontend and want some ready-made backend to test your frontend with? Just pull a Docker image and run it. Same for backend developers.&lt;/p&gt;

&lt;p&gt;Want to learn system design and how to scale your application? Just run multiple instances of your application in separate containers. No need to worry about ports or configurations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Facilitates Consistent Testing Across Versions
&lt;/h3&gt;

&lt;p&gt;Want to test something on an older version of your project? Just keep track of your Docker images and run that older version. No need to rollback your code or worry about conflicting dependencies.&lt;/p&gt;

&lt;p&gt;Want to test across different environments? Just run your project in different containers with different configurations.&lt;/p&gt;

&lt;p&gt;Want to test your whole stack? Easy as well. If you have some Docker knowledge, you can do anything you want.&lt;/p&gt;

&lt;p&gt;Let's get our hands dirty!&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting up Dockerized Development Workflow
&lt;/h2&gt;

&lt;p&gt;For this section, we will be working on a Node.js application. Assuming it runs on Node.js v16.&lt;/p&gt;

&lt;h3&gt;
  
  
  First step is making the Dockerfile.
&lt;/h3&gt;

&lt;p&gt;Dockerfiles allow us to describe the installation and configuration steps that will be run to build the final usable image. This is the part where you install all the dependencies and tools.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Dockerfile
FROM node:16.20.2-slim # Defines a Docker image to build on

WORKDIR /app # Creates a directory and switches to it, inside the image
COPY package*.json ./ # Copy files into the image

ENV PORT=3000 # Sets environment variables
EXPOSE $PORT # Exposes a PORT to the outside

RUN npm install --force # Runs an arbitrary command, like installing npm modules
COPY . . # Copies all files in the directory into the image
ENTRYPOINT [ "npm", "run" ] # Sets up the entry point of the image
CMD [ "dev:docker" ] # Sets up the command, which will act as an argument to the entry point in this case
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;dev:docker&lt;/code&gt; is a package.json script defined as &lt;code&gt;nodemon server.js --legacy-watch&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Second, creating .dockerignore
&lt;/h3&gt;

&lt;p&gt;Similar to .gitignore, we don't want to copy all files into the Docker image we are creating. For example, we shouldn't copy the node_modules or the secrets. The &lt;code&gt;.dockerignore&lt;/code&gt; file helps us to specify what not to copy.&lt;/p&gt;

&lt;p&gt;Ex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules
.git
.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A very simple Dockerfile that first copies the package.json and package-lock.json, then installs the packages, and then copies the rest of the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Third, Creating the Docker Compose file
&lt;/h3&gt;

&lt;p&gt;Docker Compose files allow us to specify how the Docker image should be run and the specifications of the Docker container. Here we specify how changes will propagate from our local machine to the container.&lt;/p&gt;

&lt;p&gt;There are 2 ways for the Dockerized development workflow:&lt;/p&gt;

&lt;h4&gt;
  
  
  Using Volumes
&lt;/h4&gt;

&lt;p&gt;The old way. You will mount your project directory to the container, so any changes you make in your project will be reflected in the container.&lt;/p&gt;

&lt;p&gt;Docker Compose file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;services:
  backend:
    container_name: my-nodejs-app
    image: my-nodejs-app:dev
    build: .
    ports:
      - 3000:3000
    volumes:
      - ./:/app
      - NOT_USED:/app/node_modules

volumes:
  NOT_USED:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;volumes&lt;/code&gt; section: In the first line, we are mounting the current directory to the &lt;code&gt;/app&lt;/code&gt; directory in the container. However, we want to exclude the &lt;code&gt;node_modules&lt;/code&gt; directory from the mounting, so we are mounting a volume that is not used to the &lt;code&gt;node_modules&lt;/code&gt; directory in the container. The end result is that now any changes made in our project will be reflected in the container, except &lt;code&gt;node_modules&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In other runtimes, you exclude the equivalent of &lt;code&gt;node_modules&lt;/code&gt; directory in that runtime.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For this method to work, the command running must be a dev command; it should watch for changes and restart the server automatically. If you install a new dependency, you will need to manually rebuild the image.&lt;/p&gt;

&lt;p&gt;You run it via &lt;code&gt;docker compose up&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Using Develop Specification
&lt;/h4&gt;

&lt;p&gt;The new way, we will use the new &lt;code&gt;develop&lt;/code&gt; specification in the Docker Compose that was made exactly for this purpose.&lt;/p&gt;

&lt;p&gt;Docker Compose file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;services:
  backend:
    container_name: my-nodejs-app
    image: my-nodejs-app:dev
    build: .
    ports:
      - 3000:3000
    develop:
      watch:
        - action: sync
          path: ./
          target: /app
          ignore:
            - node_modules
        - action: rebuild
          path: package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;develop.watch&lt;/code&gt;, we have 2 items that define what happens when we change a file in the project directory.&lt;/p&gt;

&lt;p&gt;In each item, we define the &lt;code&gt;action&lt;/code&gt; to take, the &lt;code&gt;path&lt;/code&gt; to watch, and the &lt;code&gt;target&lt;/code&gt; in the container. Actions can be &lt;code&gt;sync&lt;/code&gt;, &lt;code&gt;rebuild&lt;/code&gt;, or &lt;code&gt;sync+restart&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Our first item tells Docker to sync any changed file in the current directory on our machine to the target path on the container, excluding &lt;code&gt;node_modules&lt;/code&gt;. I am using &lt;code&gt;sync&lt;/code&gt; here because I am using a dev command inside the container, but I could have also used a production command with &lt;code&gt;sync+restart&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The second item tells Docker to rebuild the image and restart the container if we change the &lt;code&gt;package.json&lt;/code&gt; file, like when we install a new dependency.&lt;/p&gt;

&lt;p&gt;You run it via &lt;code&gt;docker compose watch&lt;/code&gt; or &lt;code&gt;docker compose up --watch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Learn more here: &lt;a href="https://docs.docker.com/reference/compose-file/develop/" rel="noopener noreferrer"&gt;https://docs.docker.com/reference/compose-file/develop/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can already see why the new method is superior. Now we declare what we need, and Docker will do it for us. We did not need a workaround for excluding &lt;code&gt;node_modules&lt;/code&gt;, we are not forced into using a dev command, and we can properly handle new dependencies without manual intervention.&lt;/p&gt;




&lt;p&gt;With the Dockerfile, .dockerignore, and Docker Compose made, you are ready to start your development! All changes you make will be reflected in your application.&lt;/p&gt;

&lt;p&gt;Anyone cloning your project and running &lt;code&gt;docker compose up --watch&lt;/code&gt; will immediately start the development process without any additional steps.&lt;/p&gt;




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

&lt;p&gt;With that, we have seen how Docker can transform your development workflow and make your life easier. We have seen the problems in traditional development and how Docker can solve them. We have seen the benefits of using Docker for development and how it can make your life easier. We have seen 2 ways to set up a Dockerized development workflow and compared them.&lt;/p&gt;

&lt;p&gt;I would like to request, especially from open-source maintainers, to consider Dockerizing their projects. Instead of pages of installation steps, you can make the required Docker image and Docker Compose file and let the contributors run the project with a single command. This will make your project more accessible and easier to contribute to.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this blog and learned something new!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>docker</category>
      <category>devops</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Personalized Social Images: Enhancing User Profiles with Opengraph</title>
      <dc:creator>Ahmed Atwa</dc:creator>
      <pubDate>Wed, 24 May 2023 10:15:32 +0000</pubDate>
      <link>https://forem.com/opensauced/personalized-social-images-enhancing-user-profiles-with-opengraph-1iio</link>
      <guid>https://forem.com/opensauced/personalized-social-images-enhancing-user-profiles-with-opengraph-1iio</guid>
      <description>&lt;p&gt;Hello! I am &lt;a href="https://github.com/Deadreyo"&gt;Ahmed Atwa&lt;/a&gt;, a Software Engineer and OpenSauced contributor, and today I'll be showcasing a backend project I worked on when I was working for OpenSauced, and open-source it by the end of the blog!&lt;/p&gt;

&lt;h2&gt;
  
  
  Origin of the idea
&lt;/h2&gt;

&lt;p&gt;It all started when we noticed that many social media platforms have boring and generic profile preview images for their users. We wanted to spice things up and make them more personalized and fun. So we decided to create a service that generates social images based on your name, repositories, used languages, and achievements. You can use these images to showcase your personality and skills on your profile or on your highlights when sharing them through social media.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wGTjx-UE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ylq3erhya1yewcijbuyt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wGTjx-UE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ylq3erhya1yewcijbuyt.png" alt="Image of a social card for a highlight" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It was my task to create and manage this new backend service from Proof of Concept (POC) to production &amp;amp; connecting with the frontend.&lt;/p&gt;

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

&lt;p&gt;This is the tech stack we used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Satori (for the image generation), &lt;/li&gt;
&lt;li&gt;satori-html (to replace the need of React), &lt;/li&gt;
&lt;li&gt;Resvg (to convert SVG into image)&lt;/li&gt;
&lt;li&gt;NestJs &amp;amp; TypeScript&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;a href="https://github.com/Deadreyo/Opengraph-Proof-of-Concept"&gt;Proof of Concept&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Before officially starting on it, we needed a proof of concept to determine what's needed and the potential problems we could face. I threw in a quick nodejs/express app that should just create a dummy image.&lt;/p&gt;

&lt;p&gt;During the process, we found two key challenges:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The first problem we faced was that Satori library was made for React environments and required React elements as input. Luckily, we discovered &lt;code&gt;satori-html&lt;/code&gt; which would allow the input to be a string (in the form of HTML) instead.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The second problem was with &lt;code&gt;satori-html&lt;/code&gt; itself. The project is on &lt;code&gt;CommonJS&lt;/code&gt; module mode and the package was on &lt;code&gt;ECMAScript&lt;/code&gt; module mode. This was pretty tiresome to solve, but in the end the final solution was:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;satori-html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// @ts-ignore&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and setting &lt;code&gt;"moduleResolution": "node16"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After solving these 2 problems, the proof of concept was done and it was time for the next step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// @ts-ignore&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;satori&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;satori&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Resvg&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@resvg/resvg-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;HTMLTemplate&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./HTMLTemplate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;satori-html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// @ts-ignore&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;createImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HTMLTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;robotoArrayBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public/Roboto-Regular.ttf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;svg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;satori&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;fonts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Roboto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;robotoArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;normal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resvg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Resvg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;svg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rgba(238, 235, 230, .9)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pngData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resvg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pngBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pngData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asPng&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;pngBuffer&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;More technical context behind the decisions made is &lt;a href="https://github.com/open-sauced/insights/issues/687"&gt;available here&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a href="https://github.com/open-sauced/opengraph.opensauced.pizza"&gt;Opengraph Repo&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;With the POC tested and approved, it was the moment for this creation to rise and shine. I worked with &lt;a href="https://dev.to/0vortex"&gt;@0vortex&lt;/a&gt; on the Opengraph repo, I focused on the frontend side of the service, i.e. the card generation process, the card design, testing, etc., while he focused on the backend side of the service, basically setting up the infrastructure, fetching data from GitHub with GraphQL, the caching and storage of the cards, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  User Cards
&lt;/h3&gt;

&lt;p&gt;The first card we made displayed a user's name, image, top repos, and top languages. The card was used on Insights user profile page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GlX5zHOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nat3o2itcxbp8d4jpi49.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GlX5zHOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nat3o2itcxbp8d4jpi49.png" alt="User card for Deadreyo" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Highlight Cards
&lt;/h3&gt;

&lt;p&gt;The second card we developed was the highlight card, which provided information about a specific highlight, including the owner's name, image, highlight's body, project details, languages used, and reactions count.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FTG_Sca3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nensbosngniin2by4ebh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FTG_Sca3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nensbosngniin2by4ebh.png" alt="example on Highlight image" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Process from Start to Finish
&lt;/h3&gt;

&lt;p&gt;I'll be talking specifically about User cards process, which involved two endpoints: one for generation and another for status checking.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The status endpoint is the initial step, allowing us to determine whether the card is up-to-date, out-dated, or non-existent. In all cases, a URL is provided to locate the card.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The generation endpoint internally checks the status first. If the card is up-to-date, it redirects to the card URL. If not, the card is re-generated and uploaded to the cloud.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Problem with Testing
&lt;/h3&gt;

&lt;p&gt;We focused on getting the project ready for production first, and there was a little problem; we had no way to locally test the cards, without activating the whole process of generating and uploading to the cloud.&lt;/p&gt;

&lt;p&gt;I thought of making some checks on the environment whether it's production or local and branched the behavior depending on the environment, but this would have turned the straight-forward code into a big complex spaghetti, and would've been very hard to maintain.&lt;/p&gt;

&lt;p&gt;To address this challenge, I devised a solution that involved separate node scripts that would activate only the generation functions and generate images in a local folder. Now I wouldn't have to mess with the production process and keep everything simple and elegant. These new node scripts could be called separately without running the server.&lt;/p&gt;

&lt;p&gt;This is the node script for testing user profiles: &lt;code&gt;local-dev/UserCards.ts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;testUsernames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bdougie&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deadreyo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;defunkt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0-vortex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Anush008&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;diivi&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;folderPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;testUserCards&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;moduleFixture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TestingModule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createTestingModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;AppModule&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;moduleFixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createNestApplication&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UserCardService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;promises&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;testUsernames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;svg&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;generateCardBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;folderPath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;folderPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;folderPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.svg`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;svg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// generating sequential: 10.5 seconds, parallel: 4.5 seconds&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;promises&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;testUserCards&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A common pattern I made in testing and when fetching the image on Insight frontend was to run promises in parallel, to speed up the process by 100%.&lt;/p&gt;

&lt;p&gt;And with this, the Opengraph was done and it was time to integrate the whole thing into the User Profiles on Insights!&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a href="https://github.com/open-sauced/insights/blob/beta/pages/user/%5Busername%5D/index.tsx"&gt;Insights User Profiles&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;By default, Next.js uses SSG (Static Site Generation), and this was simply not suitable for our purposes. If we wanted &lt;strong&gt;dynamic&lt;/strong&gt; user profiles with &lt;strong&gt;dynamic&lt;/strong&gt; social cards, then it was a must to turn User Profiles into SSR (Server Side Rendering). For a detailed explanation, please refer to this &lt;a href="https://github.com/open-sauced/insights/issues/1091#issuecomment-1507501436"&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The process involved two main steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The first step was turning it SSR. This activated the meta-tags for the profiles which were installed but not running due to SSG.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The second step was fetching the image from Opengraph. First we hit the metadata endpoint to check the status of the image, if OK, get the card URL (sent in the response headers) and insert it into the meta-tags. If not, generate it and still insert the location to meta-tags. (Cards URLs are static, so even if the image isn't there, we know where it will be.)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note: An issue with this is that the URL preview may be displayed before the card finishes generation, leading to a broken preview image. We chose to ignore this issue for the time being. (It would break only on the first time anyways.)&lt;/p&gt;

&lt;p&gt;This is the SSR code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getServerSideProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserSSRPropsContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;handleUserSSR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UserSSRPropsContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GetServerSidePropsContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handleUserSSR&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;GetServerSidePropsContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sessionResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sessionToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sessionResponse&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ogImage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_API_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sessionToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;DbUser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;fetchSocialCardURL&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;socialCardUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_OPENGRAPH_URL&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ogReq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;socialCardUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/metadata`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//status returned: 204 or 304 or 404&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ogReq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;socialCardUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HEAD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// trigger the generation of the social card&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;ogImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ogReq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-amz-meta-location&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Runs the data fetching in parallel. Decreases the loading time by 50%.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allSettled&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;fetchSocialCardURL&lt;/span&gt;&lt;span class="p"&gt;()]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ogImage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Separated the contents of the &lt;code&gt;getServerSideProps&lt;/code&gt; into another function &amp;amp; created &lt;code&gt;UserSSRPropsContext&lt;/code&gt; as I would need to duplicate the code into 2 more pages/links that basically point to the same page. Now I don't need to touch the other 2 ever.&lt;/p&gt;

&lt;p&gt;Another thing was running data fetching in parallel, since each one is independent of the other, then we can save time. (Saving time is critical since this is Server-side rendering, i.e. user waits for the server.)&lt;/p&gt;




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

&lt;p&gt;This was the whole story behind getting Opengraph repo from ground till production &amp;amp; integrating with the frontend!&lt;br&gt;
I really enjoyed working on this project very much and it's only the beginning of my journey! I'm looking forward to making future contributions ✨&lt;/p&gt;

&lt;p&gt;The project is about to start a new adventure, as an Open Source project!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/open-sauced/opengraph.opensauced.pizza"&gt;https://github.com/open-sauced/opengraph.opensauced.pizza&lt;/a&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
