# 5 Reasons Why I Quit My Job And Started Freelancing I recently quit my job and decided to start a new chapter in my life as a freelancer. This idea was in my head for about one year, but to be honest, back in these days, I was not brave enough to go this step. In this article, I want to tell you why I quit and started freelancing. These are the five reasons which I will talk about in detail: 1. Free choice of projects and technologies 2. Self-planned professional development 3. Bad experience with freelancers 4. Better payment for the same job 5. Possibility to become a digital nomad ## 1. Free choice of projects and technologies In my previous jobs, it was always quite hard to switch projects due to different reasons. This also depends on your personality but I personally get quite fast bored of a certain project and technology and that's why I like to switch projects and technologies every 6-12 months. But independent of the company, I always encountered the same problem if I wanted to switch project: My current project manager didn't want to let me go because I delivered good work and already had a lot of project-specific domain knowledge. The project manager of the new project wanted to have me as fast as possible as he heard good stuff about my work, and so the battle of the project managers started, and I was just a puppet in this game. As a freelancer, I think I can now more effortlessly switch to new projects with different technologies, company sizes, industries, team sizes, and durations. Additionally, I can mix various projects, e.g., work some days per month for workshops/training and the remaining days for one or many other projects. I think this freedom fits more with my personality. ## 2. Self-planned professional development I worked in a company where they didn't invest 100€ for a conference ticket in my town. In general, I made the experience that I am the best person who can decide how I can improve my professional development. Now I choose which training, conferences, or workshops I want to attend without needing anyone to approve it. As a drawback, I need to pay it myself, but at least I get some money back from the taxes. ## 3. Bad experience with freelancers In my last four years as a professional software developer, I worked with about ten software developer freelancers on different projects. Unfortunately, more than half of them were terrible developers. What is a terrible developer? For example, I worked with many so-called "senior" developers who could not use git, could not write tests, and did not use standard best practices of software engineering like separation of concerns. ## 4. Better payment for the same job This point is related to the previous point about my bad experience with freelancers. I often asked myself: "Why should I do the same job for less money?". As a freelancer, you usually earn a lot more monthly money, but you also have a higher risk. You should have prepared some good savings if you don't have a project for multiple weeks or months. ## 5. Possibility to become a digital nomad I currently live in Munich but I grew up in the Bavarian Forest and I still have family and friends there. I moved to Munich as I began to study for my master degree and I stayed there as there are way better job opportunities. In my home town and surrounding area are nearly no software projects available. But as a freelancer, I am now completely free, and in theory, I could work 100% remotely from any place in the world and work on cool projects but live in an area where I would typically not find a good software project. ## Summary Now you know why I quit my job and started freelancing. At this point, I cannot tell you if it was a good or bad decision, but I will keep you updated in other blog posts on how my career as a freelancer evolves. # A Comprehensive Guide to Data Fetching in Nuxt 3 With Nuxt 3's [rendering modes](https://nuxt.com/docs/guide/concepts/rendering#rendering-modes){rel=""nofollow""}, you can execute API calls and render pages both on the client and server, which has some challenges. For example, we want to avoid duplicate network calls, efficient caching, and ensure that the calls work across environments. To address these challenges, Nuxt provides a built-in data fetching library (`$fetch`) and two composable (`useFetch` and `useAsyncData`). In this article, I'll explain everything you need to know about the different data fetching methods available in Nuxt 3 and when to use them. ## Data Fetching Library Nuxt has a built-in library for data fetching: [ofetch](https://github.com/unjs/ofetch){rel=""nofollow""} `ofetch` is built on top of the [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API){rel=""nofollow""} and provides some handy features like: - Works on node, browser, and workers. - Smartly parses JSON and native values in the response. - Automatically throw errors when `response.ok` is `false` with a friendly error message and compact stack (hiding internals). - Automatically retries the request if an error happens. - You can provide async interceptors to hook into lifecycle events of `ofetch` calls. You can use `ofetch` in your whole application with the `$fetch` alias: ```ts const todos = await $fetch('/api/todos').catch((error) => error.data) ``` ## useFetch `useFetch` is the most straightforward way to handle data fetching in a component setup function. ```vue [Component.vue] {2,6-9} ``` `useFetch` returns three reactive variables and a function: - `data`: a reactive variable that contains the result of the asynchronous function that is passed in. - `error`: a reactive error object containing information about the request error. - `pending`: a reactive boolean indicating whether the request is in progress. - `refresh/execute`: a function to refresh the data returned by the `handler` function. By default, Nuxt waits until a `refresh` is finished before it can be executed again. The `useFetch` composable is responsible for forwarding the data to the client if the API call was executed on the server. This way, the client doesn't need to refetch the same data on the client side when the page hydrates. You can inspect this payload via `useNuxtApp.payload()`; the [Nuxt DevTools](https://devtools.nuxtjs.org/){rel=""nofollow""} visualize this data in the payload tab. `useFetch` additionally reduces API calls by using a key to cache API responses. The key is automatically generated based on the URL and the fetch options. The `useFetch` composable is auto-imported and can be used in `setup` functions, lifecycle hooks, and plugin or route middleware. You can use the value of a `ref` in the URL string to ensure your component updates when the reactive variable changes: ```ts const todoId = ref('uuid') const { data: tracks, pending, error } = useFetch(() => `/api/todos/${todoId.value}`) ``` If the `todoId` value changes, the URL will update accordingly and the data will be fetched again. If you want, you can check out the [official documentation](https://nuxt.com/docs/api/composables/use-fetch){rel=""nofollow""} for more information. ### Options `useFetch` accepts a set of options as the last argument, which can be used to control the behavior of the composable. ### Lazy Data-fetching composables will automatically wait for the asynchronous function to resolve before navigating to a new page when using Vue's Suspense. Nuxt uses Vue’s `` component under the hood to prevent navigation before every async data is available to the view. However, if you want to bypass this behavior during client-side navigation, you can use the `lazy` option: ```vue [Component.vue] {3} ``` In such cases, you'll need to handle the loading state manually by using the `pending` value. Alternatively, you have the option of using `useLazyFetch` which is a convenient method to achieve the same result: ```ts const { pending, data: todos } = useLazyFetch('/api/todos') ``` ### Client-only By default, data-fetching composables execute their asynchronous function in both client and server environments. To restrict the execution to the client side only, you can set the `server` option to `false`: ```ts {3} const { pending, data: posts } = useFetch('/api/comments', { lazy: true, server: false, }) ``` This can be particularly useful when combined with the `lazy` option for data that is not required during the initial rendering, such as non-SEO sensitive data. ::warning If you have not fetched data on the server, for instance using `server: false`, the data will not be fetched until the hydration process is complete. This implies that even if you await `useFetch` on the client side, the `data` variable will continue to be null within ` ``` To gain more control or iterate over multiple objects, you can utilize the `transform` function to modify the query result: ```ts {2-4} const { data: todos } = await useFetch('/api/todos', { transform: (todos) => { return todos.map((todo) => ({ name: todo.title, id: todo.description })) }, }) ``` ### Refetching To manually fetch or update data, you can employ the `execute` or `refresh` function offered by the composables: ```vue [Component.vue] {2,8} ``` Both functions serve the same purpose, but `execute` is an alias for `refresh` and is more semantically suitable when `immediate: false` is used. When the `immediate` option is set to `false` (defaults to `true`), it will prevent the request from firing immediately. Utilize the `watch` option to rerun your fetching function whenever other reactive values in your application undergo changes: ```ts {1,4} const count = ref(1) const { data, error, refresh } = await useFetch('/api/todos', { watch: [count], }) ``` #### When to use refresh vs. a watch option? Use `refresh()` when you are aware that the data on the server side has been modified, and you need to update the data on the client side accordingly. When the user modifies parameters that need to be sent to the server, set those parameters as a watch source. For instance, if you want to filter API results using a search parameter, watch that parameter. This ensures that whenever users change their query, fresh and accurate data will be reloaded from the API. ### Query Search Params With the use of the `query` option, you can include search parameters in your query: ```ts {1,4} const queryValue = ref('anyValue') const { data, pending, error, refresh } = await useFetch('/api/todos', { query: { queryKey: queryValue, anotherQueryKey: 'anotherQueryValue' }, }) ``` This option is an extension of [ofetch](https://github.com/unjs/ofetch){rel=""nofollow""} and leverages [ufo](https://github.com/unjs/ufo){rel=""nofollow""} to generate the URL. The objects provided are automatically converted to string format. ### Interceptors You can define async interceptors to hook into lifecycle events of the API call: ```ts {2-5} const { data, pending, error, refresh } = await useFetch('/api/todo', { onRequest({ request, options }) {}, onRequestError({ request, options, error }) {}, onResponse({ request, response, options }) {}, onResponseError({ request, response, options }) {}, }) ``` These options are provided by the built-in [ofetch](https://github.com/unjs/ofetch){rel=""nofollow""} library. ## useAsyncData `useFetch` is designed to fetch data from a given URL, while `useAsyncData` allows for more intricate logic. Essentially, `useFetch(url)` is almost equivalent to `useAsyncData(url, () => $fetch(url))`, providing a more streamlined developer experience for the most common use case. However, there are situations where employing the `useFetch` composable may not be suitable, such as when a CMS or a third-party service offers its own query layer. In such cases, you can utilize `useAsyncData` to encapsulate your calls and still enjoy the benefits provided by the composable: ```ts const { data, error } = await useAsyncData('getTodos', () => fetchTodos()) ``` In `useAsyncData`, the first argument serves as the unique key for caching the response obtained from the second argument, which is the querying function. However, if you prefer, you can omit this argument and directly pass the querying function itself. In such cases, the unique key will be automatically generated. ::note Both `useAsyncData` and `useFetch` provide the same object type as their return value and accept a shared set of options as their last argument. These options allow you to customize the behavior of the composables, including features such as navigation blocking, caching, and execution control. :: You can check out the [official documentation](https://nuxt.com/docs/api/composables/use-async-data){rel=""nofollow""} for more information. ## Conclusion Let's summarize and explain when you should use which data fetching method: - The `$fetch` function is a suitable choice if you intend to initiate a network request based on user interaction. It is recommended to utilize `$fetch` when sending data to an event handler, performing client-side logic exclusively, or in conjunction with the `useAsyncData` composable. - The `useFetch` composable is the simplest approach to handle data fetching within a component's setup function. - If you require more precise control over the data fetching process, you can opt for `useAsyncData` in combination with `$fetch`. If you liked this article, follow me on [Twitter](https://twitter.com/mokkapps){rel=""nofollow""} to get notified about new blog posts and more content from me. Alternatively (or additionally), you can [subscribe to my weekly Vue newsletter](https://weekly-vue.news){rel=""nofollow""}: # Analyze Memory Leaks in Your Nuxt App In one of my client projects, we recently had to analyze and fix a memory leak in our Nuxt 3+ application. In this article, I will share my experience and our steps to identify and fix the memory leak. ## What is a Memory Leak? A memory leak occurs when a program allocates memory but doesn't release it when it's no longer needed. Over time, this can lead to the exhaustion of system resources and the application crashing. In our case, the memory leak caused our Kubernetes pods to restart frequently, forcing us to increase the resources allocated to the pods. However, this was only a temporary solution, and we had to find and fix the root cause of the memory leak. ## Finding the Memory Leak The first step in fixing a memory leak is identifying the root cause. Here are the steps we took to find the memory leak in our Nuxt application. First, it's important to note that we use [Hybrid Rendering](https://nuxt.com/docs/guide/concepts/rendering#hybrid-rendering){rel=""nofollow""} in our application, which means that the server-side rendering (SSR) and client-side rendering (CSR) are combined. Technically, a Node.js server is deployed, so we had to monitor the memory usage of the Node.js process. ### Step 1: Monitor Memory Usage The first step is to monitor your application's memory usage. In our case, this only happened in production, so we had to monitor the memory usage of our application's production build. To debug a production build of your Nuxt app in Visual Studio Code, you have to define a launch configuration in your `.vscode/launch.json` file. Here is an example configuration: ```json [.vscode/launch.json] { "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Debug Prod Build", "cwd": "${workspaceFolder}", "outputCapture": "std", "runtimeExecutable": "pnpm", "runtimeArgs": ["build:preview"] }, ] } ``` In this configuration, we use the `pnpm build:preview` command to build the production version of our Nuxt app. You can replace this command with the command you use to build your production app. In this example, the command is configured like this: ```json [package.json] {5} { "scripts": { "build": "nuxt build", "preview": "nuxt preview", "build:preview": "nuxt build && nuxt preview", } } ``` After configuring the launch configuration, you can start the debugger in Visual Studio Code and monitor the memory usage of the Node.js process. I recommend using the [Flame Chart Visualizer for JavaScript Profiles](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-js-profile-flame){rel=""nofollow""} extension, which enables you to profile the heap and CPU usage of your Node.js process **in real-time**. The following picture shows the Flame Chart Visualizer in action: ![Flame Chart Visualizer](https://mokkapps.de/blog/analyze-memory-leaks-in-your-nuxt-app/flame-chart-visualizer.png) ::note For a more detailed analysis, you can [record a heap profile](https://code.visualstudio.com/docs/nodejs/profiling){rel=""nofollow""} and analyze it in VS Code or with the Chrome DevTools. :: To simulate traffic, we used [oha](https://github.com/hatoo/oha){rel=""nofollow""}, a tiny program that sends some load to a web application. An exemplary command to simulate traffic with `oha` is: ```bash oha -c 2 -n 250 --disable-keepalive http://localhost:3000/route-that-should-be-tested ``` `-c` specifies the number of concurrent requests, `-n` the number of total requests, and `--disable-keepalive` prevents the re-use of TCP connections between different HTTP requests. ### Step 2: Analyze the Memory Usage In our scenario, we saw that the memory usage of the Node.js process increased over time and with each request, which indicated that there was a memory leak. ### Step 3: Find the Root Cause One approach to finding the root cause of a memory leak is to use [git bisect](https://mokkapps.de/blog/use-git-bisect-to-find-the-commit-that-introduced-a-bug){rel=""nofollow""} to identify the commit that introduced the memory leak. This approach can be time-consuming, but it can help you narrow down the code that caused the memory leak. In our project, we identified that we defined some watchers after an `await` in a Nuxt page component which caused the memory leak. Because we only needed those watchers on the client, we wrapped them in the `onMounted` lifecycle hook which fixed the memory leak. ## Conclusion Memory leaks can be challenging to identify and fix, but with the right tools and approach, you can find and fix them. In our case, monitoring the memory usage of the Node.js process and analyzing the memory usage helped us identify the root cause of the memory leak. If you liked this article, follow me on [X](https://x.com/mokkapps){rel=""nofollow""} to get notified about my new blog posts and more content. Alternatively (or additionally), you can [subscribe to my weekly Vue & Nuxt newsletter](https://weekly-vue.news){rel=""nofollow""}: # Boost Your Productivity By Using The Terminal (iTerm & ZSH) Using the terminal is one of the biggest productivity boosts you can gain in your daily work as a developer. If you know your shortcuts, you will be way faster than using the mouse. In this article, I want to show you my terminal setup and how I use it on a daily basis. The cover image shows my current setup in action. > I am a macOS user so the article is mainly focused on this operating system but most of the software I demonstrate is also available for Windows and Linux users. ## Install Homebrew [Homebrew](https://brew.sh/){rel=""nofollow""} is *the missing package manager for macOS (or Linux)* and makes installing packages super easy. To install it on macOS you just need to paste this command in your terminal: ```bash /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" ``` After that you should be able to run `brew`, you can check the successful installation by running `brew -v` in your terminal: ```bash ▶ brew -v Homebrew 2.2.4 Homebrew/homebrew-core (git revision 22532; last commit 2020-01-31) Homebrew/homebrew-cask (git revision 19d828; last commit 2020-01-31) ``` ## iTerm2 I would recommend replacing the default `Terminal.app` in macOS by [iTerm2](https://www.iterm2.com/){rel=""nofollow""}. You can install it using `brew`: ```bash brew install --cask iterm2 ``` Some of the best [iTerm features](https://iterm2.com/features.html){rel=""nofollow""}: - Split your terminal into multiple panes which you can switch by hotkeys - Register a hotkey that brings the terminal to the foreground when you're in another application - A robust find-on-page feature - Different user profiles to save your window arrangements and more - Paste history that shows everything you’ve pasted into the terminal - and [many more](https://iterm2.com/features.html){rel=""nofollow""} ## ZSH and Oh My ZSH Since macOS Catalina (10.15.2) the default shell is now ZSH instead of Bash. You can enrich ZSH by using the [Oh My ZSH](http://ohmyz.sh/){rel=""nofollow""} framework which provides some functionality that will boost your productivity: - Autocompletion by pressing `Tab` key which allows selecting available directories, commands and files. ![ZSH Autocompletion](https://mokkapps.de/blog/boost-your-productivity-by-using-the-terminal-iterm-and-zsh/autocompletion_vgia6u.jpg) - Use alias commands, you can get a list of all available alias by running `alias` in your terminal - You can omit the `cd` (change directory) command: `..` (instead of `cd ..`), `../..` (instead of `cd ../..`) `/` (for root directory) and `~` (for home directory) - `take` command creates a new directory and changes the path to it. Example: `take testFolder` is the same as `mkdir testFolder && cd testFolder` - Use `-` to quickly navigate between your last and current path - Many cool themes - A list of amazing plugins - Git integration - And many more... You can install it using this terminal command: ```bash sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" ``` `Oh My ZSH` can be configured via the `.zshrc` configuration file: ```bash vi ~/.zshrc ``` My `.zshrc` configuration looks similar to this one: ```bash # If you come from bash you might have to change your $PATH. export PATH=$HOME/bin:/usr/local/bin:$PATH export JAVA_HOME="/Library/Java/JavaVirtualMachines/openjdk-11.0.2.jdk/Contents/Home/" # jenv export PATH="$HOME/.jenv/bin:$PATH" eval "$(jenv init -)" # Path to your oh-my-zsh installation. export ZSH=/Users/mhoffman/.oh-my-zsh # Set name of the theme to load. Optionally, if you set this to "random" # it'll load a random theme each time that oh-my-zsh is loaded. # See https://github.com/robbyrussell/oh-my-zsh/wiki/Themes ZSH_THEME="avit" # Which plugins would you like to load? (plugins can be found in ~/.oh-my-zsh/plugins/*) # Custom plugins may be added to ~/.oh-my-zsh/custom/plugins/ # Example format: plugins=(rails git textmate ruby lighthouse) # Add wisely, as too many plugins slow down shell startup. plugins=( git brew docker npm osx bgnotify zsh-syntax-highlighting zsh-autosuggestions web-search ) source $ZSH/oh-my-zsh.sh # Set personal aliases, overriding those provided by oh-my-zsh libs, # plugins, and themes. Aliases can be placed here, though oh-my-zsh # users are encouraged to define aliases within the ZSH_CUSTOM folder. # For a full list of active aliases, run `alias`. alias zshconfig="nano ~/.zshrc" alias ohmyzsh="nano ~/.oh-my-zsh" alias gpf='git push -f' # Docker alias alias dkps="docker ps" alias dkst="docker stats" alias dkpsa="docker ps -a" alias dkimgs="docker images" alias dkcpup="docker-compose up -d" alias dkcpdown="docker-compose down" alias dkcpstart="docker-compose start" alias dkcpstop="docker-compose stop" # Kubectl alias alias kdev='kubectl -n dev' alias kpg='kubectl -n playground' alias ktest='kubectl -n test' alias kprod='kubectl -n prod' alias kpreprod='kubectl -n preprod' ``` I use the [avit theme](https://github.com/ohmyzsh/ohmyzsh/wiki/Themes#avit){rel=""nofollow""} but there [are many other cool themes](https://github.com/ohmyzsh/ohmyzsh/wiki/Themes){rel=""nofollow""} available. Some words about the used plugins, [here](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins){rel=""nofollow""} you can find a list of all available `Oh My ZSH` plugins: - git: provides many [aliases](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/git#aliases){rel=""nofollow""} and a few useful [functions](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/git#functions){rel=""nofollow""} for [git](https://git-scm.com/){rel=""nofollow""}. - brew: adds several aliases for common [brew](https://brew.sh/){rel=""nofollow""} commands. - docker: adds auto-completion for [docker](https://www.docker.com/){rel=""nofollow""}. - npm: provides completion as well as adding many useful aliases for [npm](https://www.npmjs.com/){rel=""nofollow""}. - osx: provides a few utilities for OSX. - bgnotify: cross-platform background notifications for long running commands ![bgnotify](https://mokkapps.de/blog/boost-your-productivity-by-using-the-terminal-iterm-and-zsh/bgnotify_qkefwj.png) - web-search: adds aliases for searching with Google, Wiki, Bing, YouTube and other popular services. - [zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions){rel=""nofollow""}: suggests commands as you type based on history and completions :br:br[![](https://asciinema.org/a/37390.svg)](https://asciinema.org/a/37390) - [zsh-syntax-highlighting](https://github.com/zsh-users/zsh-syntax-highlighting){rel=""nofollow""}: provides syntax highlighting for the shell zsh, red for invalid and green for valid commands: ![Invalid ZSH syntax highlighting](https://mokkapps.de/blog/boost-your-productivity-by-using-the-terminal-iterm-and-zsh/invalid-zsh-syntax-highlighting_fwroz3.png)![Valid ZSH syntax highlighting](https://mokkapps.de/blog/boost-your-productivity-by-using-the-terminal-iterm-and-zsh/valid-zsh-syntax-highlighting_pvs1q4.png) ### Use Material Theme I really like Material Design so I also use it in iTerm thanks to this [iTerm2 color scheme](https://github.com/MartinSeeler/iterm2-material-design){rel=""nofollow""}. Installation instructions can be found [here](https://github.com/MartinSeeler/iterm2-material-design#how-to-use-it){rel=""nofollow""}. The result should look similar to my terminal: ![iTerm Material Design](https://mokkapps.de/blog/boost-your-productivity-by-using-the-terminal-iterm-and-zsh/iterm-material-design_zevagm.png) ### Use Minimal Theme Choose *Minimal* theme to have a cleaner UI with smaller tabs as shown in the screenshot above: ![Minimal Theme Setting](https://mokkapps.de/blog/boost-your-productivity-by-using-the-terminal-iterm-and-zsh/minimal-theme-setting_ncs1mq.png) ### Change font to Cascadia Font I use the [Cascadia Font](https://github.com/microsoft/cascadia-code){rel=""nofollow""} from Microsoft in iTerm. After installing the font on your operating system you need to select it as a font in your iTerm profile: ![iTerm Font](https://mokkapps.de/blog/boost-your-productivity-by-using-the-terminal-iterm-and-zsh/iterm-font_hha7fj.jpg) ## Good CLI tools In this chapter I want to demonstrate some CLI tools which I regularly use in my terminal and which can highly increase your productivty: - [lazygit](https://github.com/jesseduffield/lazygit){rel=""nofollow""}: a simple but amazing terminal UI for git commands :br:br![lazygit](https://mokkapps.de/blog/boost-your-productivity-by-using-the-terminal-iterm-and-zsh/lazygit_zlmtd0.gif) - [HTTPie](https://httpie.org){rel=""nofollow""}: *a command line HTTP client with an intuitive UI, JSON support, syntax highlighting, wget-like downloads, plugins, and more* which I often use instead of graphical programs like [Postman](https://www.getpostman.com/){rel=""nofollow""} or [Insomnia](https://insomnia.rest/){rel=""nofollow""}![HTTPie](https://mokkapps.de/blog/boost-your-productivity-by-using-the-terminal-iterm-and-zsh/httpie_gwr0f6.png) - [htop](https://hisham.hm/htop/){rel=""nofollow""}: "an interactive process viewer for Unix systems", which I use instead of the macOS `Activity Monitor.app`![htop](https://mokkapps.de/blog/boost-your-productivity-by-using-the-terminal-iterm-and-zsh/htop_a1dvzt.jpg) - [Midnight Commander](https://midnight-commander.org/){rel=""nofollow""}: a visual file manager ![Midnight Commander](https://mokkapps.de/blog/boost-your-productivity-by-using-the-terminal-iterm-and-zsh/midnight-commander_lrxq62.jpg) - [tree](https://github.com/MrRaindrop/tree-cli){rel=""nofollow""}: List contents of directories in tree-like format ![tree](https://mokkapps.de/blog/boost-your-productivity-by-using-the-terminal-iterm-and-zsh/tree_lqovlf.png) - [bat](https://github.com/sharkdp/bat){rel=""nofollow""}: a `cat` clone with syntax highlighting and Git integration ![bat](https://mokkapps.de/blog/boost-your-productivity-by-using-the-terminal-iterm-and-zsh/bat_jtq8ak.png) - [lnav](https://lnav.org/){rel=""nofollow""}: an advanced log file viewer ![lnav](https://mokkapps.de/blog/boost-your-productivity-by-using-the-terminal-iterm-and-zsh/lnav_i7vgwm.jpg) - [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/){rel=""nofollow""}: Kubernetes command-line tool to run commands against Kubernetes clusters - [watch](https://linuxize.com/post/linux-watch-command/){rel=""nofollow""}: Linux watch command, which is really helpful to run commands at a regular interval ## Free course I highly recommend the free [Command Line Poweruser](https://commandlinepoweruser.com/){rel=""nofollow""} course from [Wes Bos](https://wesbos.com/){rel=""nofollow""} if you want to learn more about ZSH. ## Conclusion I am still at the beginning of my terminal journey but I really enjoy it so far. Using the terminal more often I could reduce the time I need to grab my mouse and many operations can be done much faster using the CLI than using a graphical interface. And of course, you look like a cool hacker even if you are just browsing directories. Let me know what useful CLI tools you are using and what productivity tips you can share with me and the community. # Build and Deploy a Serverless GraphQL React App Using AWS Amplify Recently I recognized that some SaaS (Software as a Service) products use [AWS Amplify](https://aws.amazon.com/amplify/){rel=""nofollow""} which helps them to build serverless full-stack applications. I think [serverless computing](https://www.cloudflare.com/learning/serverless/why-use-serverless/){rel=""nofollow""} will be the future of apps and software. Therefore, I wanted to gather some hands-on experience, and I built a serverless application using AWS Amplify that uses React as frontend framework and GraphQL as backend API. In this article, I want to guide you through the process how to build and deploy such an Amplify application. ## Set up Amplify [AWS Amplify](https://aws.amazon.com/amplify/){rel=""nofollow""} describes itself as: > Fastest, easiest way to build mobile and web apps that scale Amplify provides tools and services to build scalable full-stack applications powered by [AWS (Amazon Web Services)](https://aws.amazon.com/){rel=""nofollow""}. With Amplify configuring backends and deploying static web apps is easy. It supports web frameworks like Angular, React, Vue, JavaScript, Next.js, and mobile platforms including iOS, Android, React Native, Ionic, and Flutter. You'll need to [create an AWS account](https://portal.aws.amazon.com/billing/signup?redirect_url=https%3A%2F%2Faws.amazon.com%2Fregistration-confirmation#/start){rel=""nofollow""} to follow the following steps. No worries, after signing up you have access to the AWS Free Tier which does not include any upfront charges or term commitments. The next step is to install the Amplify Command Line Interface (CLI). In my case I used cURL on macOS: ```shell curl -sL https://aws-amplify.github.io/amplify-cli/install | bash && $SHELL ``` Alternatively, you can watch [this video](https://www.youtube.com/watch?v=fWbM5DLh25U){rel=""nofollow""} to learn how to install and configure the Amplify CLI. Now we can configure Amplify using the CLI ```shell amplify configure ``` which will ask us to sign in to AWS Console. Once we’re signed in, Amplify CLI will ask us to create an [AWS IAM](https://aws.amazon.com/iam/){rel=""nofollow""} user: ```shell Specify the AWS Region ? region: # Your preferred region Specify the username of the new IAM user: ? user name: # User name for Amplify IAM user Complete the user creation using the AWS console ``` We'll be redirected to IAM where we need to finish the wizard and create a user with `AdministratorAccess` in our account to provision AWS resources. Once the user is created, Amplify CLI will ask us to provide the `accessKeyId` and `secretAccessKey` to connect Amplify CLI with our created IAM user: ```shell Enter the access key of the newly created user: ? accessKeyId: # YOUR_ACCESS_KEY_ID ? secretAccessKey: # YOUR_SECRET_ACCESS_KEY This would update/create the AWS Profile in your local machine ? Profile Name: # (default) Successfully set up the new user. ``` ## Set up full-stack Amplify project At this point, we are ready to set up our full-stack project using a [React](https://reactjs.org/){rel=""nofollow""} application in the frontend and [GraphQL](https://graphql.org/){rel=""nofollow""} as backend API. We'll build a Todo CRUD (create, read, update, delete) application that uses this architecture: ![Amplify demo architecture](https://mokkapps.de/blog/build-and-deploy-a-serverless-graphql-react-app-using-aws-amplify/amplify-architecture_tomj94.jpg) The complete source code of this demo is available at [GitHub](https://github.com/Mokkapps/amplify-react-graphql-todo-demo/){rel=""nofollow""}. ### Create React frontend Let's start by creating a new React app using [create-react-app](https://reactjs.org/docs/create-a-new-react-app.html){rel=""nofollow""}. From our projects directory we run the following commands to create our new React app in a directory called `amplify-react-graphql-demo` and to navigate into that new directory: ```shell npx create-react-app amplify-react-graphql-demo cd amplify-react-graphql-demo ``` To start our React app we can run ```shell npm start ``` which will start the development server at `http://localhost:3000`. ### Initialize Amplify Now it's time to initialize Amplify in our project. From the root of the project we run ```shell amplify init ``` which will prompt some information about the app: ```shell ▶ amplify init ? Enter a name for the project amplifyreactdemo The following configuration will be applied: Project information | Name: amplifyreactdemo | Environment: dev | Default editor: Visual Studio Code | App type: javascript | Javascript framework: react | Source Directory Path: src | Distribution Directory Path: build | Build Command: npm run-script build | Start Command: npm run-script start ? Initialize the project with the above configuration? Yes Using default provider awscloudformation ? Select the authentication method you want to use: AWS profile ? Please choose the profile you want to use: default ``` When our new Amplify project is initialized, the CLI: - created a file called `aws-exports.js` in the src directory that holds all the configuration for the services we create with Amplify - created a top-level directory called `amplify` that contains our backend definition - modified the `.gitignore` file and adds some generated files to the ignore list Additionally, a new cloud project is created in the [AWS Amplify Console](https://docs.aws.amazon.com/amplify/latest/userguide/welcome.html){rel=""nofollow""} that can be accessed by running `amplify console`. Amplify Console provides two main services: hosting and the Admin UI. More information can be found [here](https://docs.aws.amazon.com/amplify/latest/userguide/welcome.html){rel=""nofollow""}. The next step is to install some Amplify libraries: ```shell npm install aws-amplify @aws-amplify/ui-react typescript ``` - `aws-amplify`: the main library for working with Amplify in your apps - `@aws-amplify/ui-react`: includes React specific UI components - `typescript`: we will use [TypeScript](https://www.typescriptlang.org/){rel=""nofollow""} in some parts of this demo Next, we need to configure Amplify on the client. Therefore, we need to add the following code below the last import in `src/index.js` : ```javascript import Amplify from 'aws-amplify' import awsExports from './aws-exports' Amplify.configure(awsExports) ``` At this point wee have a running React frontend application, Amplify is configured, and we can now add our GraphQL API. ### Create GraphQL API We will now create a backend that provides a GraphQL API using AWS AppSync (a managed GraphQL service) that uses Amazon DynamoDB (a NoSQL database). To add a new API we need to run the following command in our project’s root folder: ```shell ▶ amplify add api ? Please select from one of the below mentioned services: GraphQL ? Provide API name: demoapi ? Choose the default authorization type for the API: API key ? Enter a description for the API key: ? After how many days from now the API key should expire (1-365): 7 ? Do you want to configure advanced settings for the GraphQL API: No, I am done. ? Do you have an annotated GraphQL schema? No ? Choose a schema template: Single object with fields (e.g., “Todo” with ID, name, description) ``` After the process finished successfully we can inspect the GraphQL schema at `amplify/backend/api/demoapi/schema.graphql`: ```graphql type Todo @model { id: ID! name: String! description: String } ``` The generated Todo type is annotated with a `@model` directive that is part of the [GraphQL transform](https://docs.amplify.aws/cli/graphql-transformer/model){rel=""nofollow""} library of Amplify. The library contains multiple directives which can be used for authentication, to define data models, and more. Adding the `@model` directive will create a database table for this type (in our example a Todo table), the CRUD (create, read, update, delete) schema, and the corresponding GraphQL resolvers. Now it's time to deploy our backend: ```shell ▶ amplify push ✔ Successfully pulled backend environment dev from the cloud. Current Environment: dev | Category | Resource name | Operation | Provider plugin | | -------- | ------------- | --------- | ----------------- | | Api | demoapi | Create | awscloudformation | ? Are you sure you want to continue? Yes ? Do you want to generate code for your newly created GraphQL API: Yes ? Choose the code generation language target: typescript ? Enter the file name pattern of graphql queries, mutations and subscriptions: src/graphql/**/*.ts ? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions: Yes ? Enter maximum statement depth [increase from default if your schema is deeply nested] 2 ? Enter the file name for the generated code: src/API.ts ``` After it is finished successfully our GraphQL API is deployed and we can interact with it. To see and interact with the GraphQL API in the AppSync console at any time we can run: ```bash amplify console api ``` ![AppSync GraphQL API Console](https://mokkapps.de/blog/build-and-deploy-a-serverless-graphql-react-app-using-aws-amplify/amplify-appsync-api_g8tjok.jpg) Alternatively, we can run this command ```bash amplify console api ``` to view the entire app in the Amplify console. ### Connect frontend to API The GraphQL mutations, queries and subscriptions are available at `src/graphql`. To be able to interact with them we can use the generated `src/API.ts` file. So we need extend `App.js` to be able to create, edit and delete Todos via our GraphQL API: ```javascript {2,28,48,58,69-79} import React, { useEffect, useState } from 'react' import { API, graphqlOperation } from '@aws-amplify/api' import { listTodos } from './graphql/queries' import { createTodo, deleteTodo, updateTodo } from './graphql/mutations' import TodoList from './components/TodoList' import CreateTodo from './components/CreateTodo' const initialState = { name: '', description: '' } function App() { const [formState, setFormState] = useState(initialState) const [todos, setTodos] = useState([]) const [apiError, setApiError] = useState() const [isLoading, setIsLoading] = useState(false) useEffect(() => { fetchTodos() }, []) function setInput(key, value) { setFormState({ ...formState, [key]: value }) } async function fetchTodos() { setIsLoading(true) try { const todoData = await API.graphql(graphqlOperation(listTodos)) const todos = todoData.data.listTodos.items setTodos(todos) setApiError(null) } catch (error) { console.error('Failed fetching todos:', error) setApiError(error) } finally { setIsLoading(false) } } async function addTodo() { try { if (!formState.name || !formState.description) { return } const todo = { ...formState } setTodos([...todos, todo]) setFormState(initialState) await API.graphql(graphqlOperation(createTodo, { input: todo })) setApiError(null) } catch (error) { console.error('Failed creating todo:', error) setApiError(error) } } async function removeTodo(id) { try { await API.graphql(graphqlOperation(deleteTodo, { input: { id } })) setTodos(todos.filter((todo) => todo.id !== id)) setApiError(null) } catch (error) { console.error('Failed deleting todo:', error) setApiError(error) } } async function onItemUpdate(todo) { try { await API.graphql( graphqlOperation(updateTodo, { input: { name: todo.name, description: todo.description, id: todo.id, }, }) ) setApiError(null) } catch (error) { console.error('Failed updating todo:', error) setApiError(error) } } const errorMessage = apiError && (

{apiError.errors.map((error) => (

{error.message}

))}

) if (isLoading) { return 'Loading...' } return (

Amplify React & GraphQL Todos

{errorMessage}
) } export default App ``` The full source code of this demo is available at [GitHub](https://github.com/Mokkapps/amplify-react-graphql-todo-demo/){rel=""nofollow""}. The application should show a list of available Todos which can be edited or deleted. Additionally, we have the possibility to create new Todos: ![Amplify Frontend Running Locally](https://mokkapps.de/blog/build-and-deploy-a-serverless-graphql-react-app-using-aws-amplify/amplify-frontend-running_hryqu8.jpg) ### Add authentication Amplify uses [Amazon Cognito](https://aws.amazon.com/cognito/){rel=""nofollow""} as the main authentication provider. We'll use it to add authentication to our application by adding a login that requires a password and username. To add authentication we need to run ```bash ▶ amplify add auth Using service: Cognito, provided by: awscloudformation The current configured provider is Amazon Cognito. Do you want to use the default authentication and security configuration? Default configuration Warning: you will not be able to edit these selections. How do you want users to be able to sign in? Username Do you want to configure advanced settings? No, I am done. ``` and deploy our service by running ```bash amplify push ``` Now we can add the login UI to our frontend. The login flow can easily be handled by using the `withAuthenticator` wrapper from the `@aws-amplify/ui-react` package. We just need to adjust our `App.js` and import `withAuthenticator`: ```javascript import { withAuthenticator } from '@aws-amplify/ui-react' ``` Now we need to wrap the main component with the `withAuthenticator` wrapper: ```javascript export default withAuthenticator(App) ``` Running `npm start` will now start the app with an authentication flow allowing users to sign up and sign in: ![Amplify Login](https://mokkapps.de/blog/build-and-deploy-a-serverless-graphql-react-app-using-aws-amplify/amplify-login_hqfyjg.jpg) ### Deploy and host app Finally, we want to deploy our app which can be either done manually or via automatic continuous deployment. In this demo I want to deploy it manually and host it as static web app. If you want to use continuous deployment instead, please check out [this official guide](https://docs.aws.amazon.com/amplify/latest/userguide/multi-environments.html#standard){rel=""nofollow""}. First, we need to add hosting: ```bash ▶ amplify add hosting ? Select the plugin module to execute: Hosting with Amplify Console (Managed hosting with custom domains, Continuous deployment) ? Choose a type: Manual deployment ``` and then we are ready to publish our app: ```bash amplify publish ``` After publishing, we can see the app URL where our application is hosted on an \`amplifyapp.com domain in our terminal. ## What's next Amplify provides also a way to run your API locally, [check out this tutorial](https://docs.amplify.aws/start/getting-started/data-model/q/integration/react#optional-test-your-api){rel=""nofollow""}. Here are some cool things that you can additionally add to your Amplify application: - [DataStore](https://docs.amplify.aws/lib/datastore/getting-started/q/platform/js){rel=""nofollow""} - [User File Storage](https://docs.amplify.aws/lib/storage/getting-started/q/platform/js){rel=""nofollow""} - [Serverless APIs](https://docs.amplify.aws/lib/graphqlapi/getting-started/q/platform/js){rel=""nofollow""} - [Analytics](https://docs.amplify.aws/lib/analytics/getting-started/q/platform/js){rel=""nofollow""} - [AI/ML](https://docs.amplify.aws/lib/predictions/getting-started/q/platform/js){rel=""nofollow""} - [Push Notification](https://docs.amplify.aws/lib/push-notifications/getting-started/q/platform/js){rel=""nofollow""} - [PubSub](https://docs.amplify.aws/lib/pubsub/getting-started/q/platform/js){rel=""nofollow""} - [AR/VR](https://docs.amplify.aws/lib/xr/getting-started/q/platform/js){rel=""nofollow""} Take a look at the [official Amplify docs](https://docs.amplify.aws){rel=""nofollow""} for further information about the framework. ## Conclusion In this article I showed you that building and deploying a full-stack serverless application using AWS Amplify requires a minimum amount of work. Without using such a framework it would be much harder and this way you can focus more on the end product instead of what is happening inside. If you liked this article, follow me on [Twitter](https://twitter.com/mokkapps){rel=""nofollow""} to get notified about new blog posts and more content from me. # Building a Polite Newsletter Popup With Nuxt 3 This year I launched a [free eBook with 27 helpful tips for Vue developers](https://weekly-vue.news/ebook/27-helpful-tips-for-vue-developers){rel=""nofollow""} for subscribers of my [weekly Vue newsletter](https://weekly-vue.news){rel=""nofollow""}. For marketing purposes, I showed a popup on the landing page of my [portfolio page](https://mokkapps.de) each time a user visited my site. I was aware that users probably could get annoyed by that popup. Thus I added a "Don't show again" button to that popup. I thought I solved the problem! But soon, I realized that many other sites solved that problem more elegantly. If you visit their website, stay for some time, and scroll through the content, a small notification appears at the bottom of the screen. It asks if you are interested in a specific product, and if you agree, it redirects to a page with information about this product. In this article, I'll explain how I built a polite popup to ask people if they would like to subscribe to [my newsletter](https://weekly-vue.news){rel=""nofollow""} using Nuxt 3. ## What is a polite popup? ![Photo by Emily Morter on Unsplash](https://mokkapps.de/blog/building-a-polite-newsletter-popup-with-nuxt-3/photo-1484069560501-87d72b0c3669_yn7mhd.jpg) The goal of a so-called polite popup is to only ask for visitors emails if it detects that visitors are engaged with your content. This means they’ll be more likely to sign up by the time we ask them because it’ll be **after** they’ve decided they liked our content. In the following sections, we'll build a popup that - waits for a visitor to browse the website - makes sure visitors are interested in the website - appears off to the side in a non-intrusive way - is easy to dismiss or ignore - asks for permission first - waits a bit before it appears again ## Implementation Now that we know the criteria of a polite popup let's start implementing it using [Nuxt 3](https://v3.nuxtjs.org/){rel=""nofollow""}. ::note I use Nuxt.js in this example, but the concepts and solutions are not tied to any framework. :: The demo code is interactively available at StackBlitz: :stackblitz{project-id="polite-popup-nuxt-3"} ### Implement the composable The most exciting challenge of the polite popup is only showing it if visitors are interested in the website and content we want to promote. Technically, we'll solve it this way: - The visitor must be **visiting a page with Vue-related content** as my newsletter targets Vue developers. - The visitor must be actively scrolling the current page for **6 seconds or more**. - The visitor must scroll through at least **35% of the current page** during their visit. ::note If these numbers aren’t generating the amount of engagement you want to see from visitors, you can enable a more **aggressive** mode that will lower the threshold by about 20-30%. :: Let's start by writing a Vue composable for our polite popup: ```ts [composables/usePolitePopup.ts] {37-41} import { useWindowScroll, useWindowSize, useTimeoutFn } from '@vueuse/core' const config = { timeoutInMs: 3000, contentScrollThresholdInPercentage: 300, } export const usePolitePopup = () => { const visible = useState('visible', () => false) const readTimeElapsed = useState('read-time-elapsed', () => false) const { start } = useTimeoutFn( () => { readTimeElapsed.value = true }, config.timeoutInMs, { immediate: false } ) const { y: scrollYInPx } = useWindowScroll() const { height: windowHeight } = useWindowSize() // Returns percentage scrolled (ie: 80 or NaN if trackLength == 0) const amountScrolledInPercentage = computed(() => { const documentScrollHeight = document.documentElement.scrollHeight const trackLength = documentScrollHeight - windowHeight.value const percentageScrolled = Math.floor((scrollYInPx.value / trackLength) * 100) return percentageScrolled }) const scrolledContent = computed(() => amountScrolledInPercentage.value >= config.contentScrollThresholdInPercentage) const trigger = () => { readTimeElapsed.value = false start() } watch([readTimeElapsed, scrolledContent], ([newReadTimeElapsed, newScrolledContent]) => { if (newReadTimeElapsed && newScrolledContent) { visible.value = true } }) return { visible, trigger, } } ``` We defined two state variables: - `visible`: a boolean indicating if the popup should be visible or not. - `readTimeElapsed`: a boolean indicating if the user has spent the defined time on the page. The `trigger` method is exposed and triggers the a timer which is used to check if the visitor has spent a predefined amount of time on the page. A Vue watcher is used to set `visible` to `true` if the timer has expired and the scroll threshold is exceeded. For the timer, we use [VueUse's useTimeoutFn composable](https://vueuse.org/shared/usetimeoutfn){rel=""nofollow""} which runs a `setTimeout` function and sets the `readTimeElapsed` state variable to `true` after the timer has expired. Let's take a detailed look at the `amountScrolledInPercentage` computed property: ```ts [composables/usePolitePopup.ts] {24-28} import { useWindowSize } from '@vueuse/core' const { height: windowHeight } = useWindowSize() // Returns percentage scrolled (ie: 80 or NaN if trackLength == 0) const amountScrolledInPercentage = computed(() => { const documentScrollHeight = document.documentElement.scrollHeight const trackLength = documentScrollHeight - windowHeight.value const percentageScrolled = Math.floor((scrollYInPx.value / trackLength) * 100) return percentageScrolled }) ``` To get the total scrollable area of a document, we need to retrieve the following two measurements of the page: 1. **The height of the browser window**: We use [VueUse's useWindowSize composable](https://vueuse.org/core/usewindowsize/){rel=""nofollow""} to get reactive variable of the browser window height. 2. **The height of the entire document**: We use `document.documentElement.scrollHeight` to get the height of the document, including content not visible on the screen due to overflow. By subtracting 2 from 1, we get the total scrollable area of the document. [VueUse's useWindowScroll composable](https://vueuse.org/core/usewindowscroll){rel=""nofollow""} is used to access the number of pixels the document is currently scrolled along the vertical axis. Move your eyes down to the `trackLength` variable, which gets the total available scroll length of the document. The variable will contain 0 if the page is **not** scrollable. The `percentageScrolled` variable then divides the `scrollYInPx` variable (amount the user has scrolled) with `trackLength` to derive how much the user has scrolled percentage wise. Finally, we need to trigger the popup on certain pages. In our case, we only want to trigger it on the Vue route path: ```vue [[...slug\\].vue] {8,10,12-14} ``` ### Write the popup component Now it's time to write the Vue component that renders the popup: ```vue [PolitePopup.vue] {12} ``` ::note In this example, I'm using [Nuxt Tailwind](https://tailwindcss.nuxtjs.org/){rel=""nofollow""} to style the component. :: `PolitePopup.vue` is rendered at a fixed position of the viewport if the `visible` reactive variable value is `true`. It offers two buttons, one to accept the offer and one to decline it. As you can see, we are using `setClosed` from our `useShowPopup` composable, which we haven't defined yet. Let's define it: ```ts [composables/usePolitePopup.ts] {5-7} export const usePolitePopup = () => { const visible = useState('visible', () => false) ... const setClosed = () => { visible.value = false } return { ... setClosed } } ``` The last step is to add our new `PolitePopup.vue` component to the template of `app.vue`: ```vue [app.vue] {6} ``` At this point, we finished the basic implementation of the polite popup. If we navigate to `/vue`, spend 3 seconds on the page, and scroll down more than 300 pixel the polite popup appears: ![Polite Popup Demo](https://mokkapps.de/blog/building-a-polite-newsletter-popup-with-nuxt-3/polite-popup-demo_jhz4jz.png) ### Add logic to wait a bit before the popup appears again One problem with the current implementation: each time we reload the page and scroll down, the popup is triggered. But our popup should wait a bit before it appears again. So let's implement that logic. First, we need a way to store the status of the polite popup in LocalStorage. For this, we use the following data model: ```ts interface PolitePopupStorageDTO { status: 'unsubscribed' | 'subscribed' seenCount: number lastSeenAt: number } ``` - `status` is per default `unsubscribed` and is set to `subscribed` if a visitor subscribes to the newsletter. - `seenCount` tracks how often the user has seen the popup. - `lastSeenAt` tracks the timestamp when the visitor has seen the popup. Let's add that interface to our `usePolitePopup` composable: ```ts [composables/usePolitePopup.ts] {9-13,20-21} interface PolitePopupStorageDTO { status: 'unsubscribed' | 'subscribed' seenCount: number lastSeenAt: number } export const usePolitePopup = () => { ... const storedData: Ref = useLocalStorage('polite-popup', { status: 'unsubscribed', seenCount: 0, lastSeenAt: 0, }) ... watch( [readTimeElapsed, scrolledContent], ([newReadTimeElapsed, newScrolledContent]) => { if (newReadTimeElapsed && newScrolledContent) { visible.value = true; storedData.value.seenCount += 1; storedData.value.lastSeenAt = new Date().getTime(); } } ); ... return { ... } } ``` We use [VueUse's useLocalStorage composable](https://vueuse.org/core/uselocalstorage/#uselocalstorage){rel=""nofollow""} to get a reactive variable of a LocalStorage entry. Each time our watcher is fired and set the popup visible, we increment `seenCount` and set the current timestamp at `lastSeenAt` in our LocalStorage object. Let's store the information that the visitor has subscribed to the newsletter. Let's add that logic to `newsletter.vue`: ```vue [pages/newsletter.vue] {4,9} ``` and in ```ts [composables/usePolitePopup.ts] {4-6} export const usePolitePopup = () => { ... const setSubscribed = () => { storedData.value.status = 'subscribed' } return { ... setSubscribed } } ``` ### Extend visibility logic The next step is only to show the popup if the current visitor - hasn't subscribed yet - has seen the popup more than three times - has already seen the popup today Let's implement that logic: ```ts [composables/usePolitePopup.ts] {21-34} const isToday = (date: Date): boolean => { const today = new Date(); return ( date.getDate() === today.getDate() && date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear() ); }; const config = { timeoutInMs: 3000, maxSeenCount: 5, scrollYInPxThreshold: 300, }; export const usePolitePopup = () => { ... watch( [readTimeElapsed, scrolledContent], ([newReadTimeElapsed, newScrolledContent]) => { if (storedData.value.status === 'subscribed') { return; } if (storedData.value.seenCount >= config.maxSeenCount) { return; } if ( storedData.value.lastSeenAt && isToday(new Date(storedData.value.lastSeenAt)) ) { return; } if (newReadTimeElapsed && newScrolledContent) { visible.value = true; storedData.value.seenCount += 1; storedData.value.lastSeenAt = new Date().getTime(); } } }; ... return { ... } } ``` We are done! You will probably also have seen this polite popup on this page if you read it on my [portfolio website](https://mokkapps.de/blog/building-a-polite-newsletter-popup-with-nuxt-3). ## Conclusion In my opinion, polite popups are the best way to convert visitors to my newsletter. This way, I can ensure that they are interested in my content and do not get annoyed by modals. If you liked this article, follow me on [Twitter](https://twitter.com/mokkapps){rel=""nofollow""} to get notified about new blog posts and more content from me. Alternatively (or additionally), you can also [subscribe to my newsletter](https://weekly-vue.news){rel=""nofollow""}. # Building a Vue 3 Desktop App With Pinia, Electron and Quasar Recently, I planned to rewrite my ["Scrum Daily Standup Picker" Electron application](https://github.com/Mokkapps/scrum-daily-standup-picker/){rel=""nofollow""} in Vue 3. I wrote the initial release in Angular, but I wanted to refactor the code base and rewrite it in Vue 3. Why? Because I love Vue and want to have a public showcase that I can reference to potential customers. ## Why Quasar? [Quasar](https://quasar.dev/){rel=""nofollow""} is an MIT licensed open-source Vue.js based framework that targets SPA, SSR, PWA, mobile app, desktop app, and browser extension all using one codebase. It handles the build setup and provides a complete collection of Material Design compliant UI components. Quasar's motto is: > Write code once and simultaneously deploy it as a website, a Mobile App and/or an Electron App. Using Quasar drastically saves development time due to these reasons: - It's based on Vue.js. - It provides many UI components that follow Material Design guidelines. - It has a regular release cycle inclusive of new features. - It provides support for each build mode (SPA, SSR, PWA, Mobile app, Desktop app & Browser Extension). - It has its own CLI that provides a pleasant developer experience. For example, we can build our application as SPA, mobile, or desktop app within the same project folder. [Read more](https://quasar.dev/introduction-to-quasar){rel=""nofollow""} about why Quasar might be a good choice for your next project. ## Install Quasar CLI ::note The source code for the following demo is [available at GitHub](https://github.com/Mokkapps/quasar-electron-vue3-pinia-demo){rel=""nofollow""} :: ```bash # Node.js >=12.22.1 is required. $ yarn global add @quasar/cli # or $ npm install -g @quasar/cli ``` Let's start by creating a new project using the Quasar CLI: ```bash ▶ quasar create vue3-electron-demo ___ / _ \ _ _ __ _ ___ __ _ _ __ | | | | | | |/ _` / __|/ _` | '__| | |_| | |_| | (_| \__ \ (_| | | \__\_\\__,_|\__,_|___/\__,_|_| ? Project name (internal usage for dev) vue3-electron-demo ? Project product name (must start with letter if building mobile apps) Quasar App ? Project description A Quasar Framework app ? Author Michael Hoffmann ? Pick your CSS preprocessor: SCSS ? Check the features needed for your project: ESLint (recommended), TypeScript ? Pick a component style: Composition ? Pick an ESLint preset: Prettier ? Continue to install project dependencies after the project has been created? (recommended) NPM ``` We chose [SCSS](https://sass-lang.com/){rel=""nofollow""} as our CSS preprocessor, [ESLint](https://eslint.org/){rel=""nofollow""} & [Typescript](https://www.typescriptlang.org/){rel=""nofollow""} as additional features, [Vue 3's Composition API](https://vuejs.org/guide/introduction.html#api-styles){rel=""nofollow""} and [Prettier](https://prettier.io/){rel=""nofollow""} for code formatting. ::warning Do not choose Vuex as we will add another state library in the next chapter. If you accidentally added Vuex, remove it manually from your `package.json`. :: [Read the official docs](https://quasar.dev/quasar-cli/installation){rel=""nofollow""} for additional information about the Quasar CLI. ## Add Pinia as Vue store library We'll use [Pinia](https://pinia.vuejs.org/){rel=""nofollow""} as Vue store library, which is now the recommended state library for Vue. First, we need to install Pinia: ```bash yarn add pinia # or with npm npm install pinia ``` To be able to register Pinia at our Vue application instance we need to create a [Quasar Boot File](https://quasar.dev/quasar-cli/boot-files){rel=""nofollow""}: > A common use case for Quasar applications is to run code before the root Vue app instance is instantiated, like injecting and initializing your own dependencies (examples: Vue components, libraries…) or simply configuring some startup code of your app. Our boot file is called `pinia.ts` and is located at `src/boot`: ```ts import { boot } from 'quasar/wrappers' import { createPinia } from 'pinia' export default boot(({ app }) => { app.use(createPinia()) }) ``` We also need to add this new file to `quasar.conf.js`: ```js {7} module.exports = configure(function (ctx) { return { ... // app boot file (/src/boot) // --> boot files are part of "main.js" // https://quasar.dev/quasar-cli/boot-files boot: ['pinia'], ... } } ``` Now, we can create a new folder called `pinia` in `src`. ::warning We cannot name this folder `store` as this name is reserved for the official Vuex integration. :: A basic store could look like this: ```js import { defineStore } from 'pinia' // useStore could be anything like useUser, useCart // the first argument is a unique id of the store across your application const useStore = defineStore('storeId', { state: () => { return { counter: 0, lastName: 'Michael', firstName: 'Michael', } }, getters: { fullName: (state) => `${state.firstName} ${state.lastName}`, }, actions: { increment() { this.counter++ }, }, }) export default useStore ``` We can use this store in any Vue component: ```vue ``` Now we can run the Vue application using the Quasar CLI: ```bash quasar dev ``` The Vue application is served at `http://localhost:8080`: ![Quasar Dev Mode](https://mokkapps.de/blog/building-a-vue-3-desktop-app-with-pinia-electron-and-quasar/quasar-vue-dev_lhuqpd.jpg) ## Setup Electron ::note Read this [introduction](https://quasar.dev/quasar-cli/developing-electron-apps/introduction){rel=""nofollow""} if you are new to Electron. :: To develop/build a Quasar Electron app, we need to add the Electron mode to our Quasar project: ```bash quasar mode add electron ``` Every Electron app has two threads: the main thread (deals with the window and initialization code – from the newly created folder `/src-electron`) and the renderer thread (which deals with the actual content of your app from `/src`). The new folder has the following structure: ```text . └── src-electron/ ├── icons/ # Icons of your app for all platforms | ├── icon.icns # Icon file for Darwin (MacOS) platform | ├── icon.ico # Icon file for win32 (Windows) platform | └── icon.png # Tray icon file for all platforms ├── electron-preload.js # (or .ts) Electron preload script (injects Node.js stuff into renderer thread) └── electron-main.js # (or .ts) Main thread code ``` Now we are ready to start our Electron application: ```bash quasar dev -m electron ``` This command will open up an Electron window which will render your app along with Developer Tools opened side by side: ![Quasar Electron Dev](https://mokkapps.de/blog/building-a-vue-3-desktop-app-with-pinia-electron-and-quasar/quasar-electron-dev_z6uocz.jpg) [Read the official docs](https://quasar.dev/quasar-cli/developing-electron-apps/){rel=""nofollow""} for additional and detailed information about developing Electron apps with Quasar. ## Control Electron from Vue code If we want to use Electron features like opening a file dialog, we need to write some code to be able to access Electron's API. For example, if we want to show a dialog to open files, Electron provides the [dialog API](https://www.electronjs.org/docs/latest/api/dialog/){rel=""nofollow""} to display native system dialogs for opening and saving files, alerting, etc. First, we need to install `@electron/remote`: ```bash npm install -D @electron/remote ``` Then we need to modify `src-electron/electron-main.js` and initialize `@electron/remote`: ```js {2,6,29} import { app, BrowserWindow, nativeTheme } from 'electron' import { initialize, enable } from '@electron/remote/main' import path from 'path' import os from 'os' initialize() let mainWindow function createWindow() { /** * Initial window options */ mainWindow = new BrowserWindow({ icon: path.resolve(__dirname, 'icons/icon.png'), // tray icon width: 1000, height: 600, useContentSize: true, webPreferences: { contextIsolation: true, // More info: /quasar-cli/developing-electron-apps/electron-preload-script preload: path.resolve(__dirname, process.env.QUASAR_ELECTRON_PRELOAD), }, }) // .... enable(mainWindow.webContents) } ``` If we want to use Electron API from our Vue code we need to add some code to `src-electron/electron-preload.js`: ```js {2,8-12} import { contextBridge } from 'electron' import { dialog } from '@electron/remote' // 'electronApi' will be available on the global window context contextBridge.exposeInMainWorld('electronApi', { openFileDialog: async (title, folder, filters) => { // calling showOpenDialog from Electron API: https://www.electronjs.org/docs/latest/api/dialog/ const response = await dialog.showOpenDialog({ title, filters, properties: ['openFile', 'multiSelections'], }) return response.filePaths }, }) ``` Next we create `src/api/electron-api.ts` to access this code from within our Vue application: ```ts export interface ElectronFileFilter { name: string extensions: string[] } export interface ElectronApi { openFileDialog: (title: string, folder: string, filters: ElectronFileFilter) => Promise } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore export const electronApi: ElectronApi = (window as { electronApi: ElectronApi }).electronApi ``` Now we can use this API anywhere in our Vue component: ```vue ``` Clicking on the button should now open the native OS file dialog: ![Electron File Dialog](https://mokkapps.de/blog/building-a-vue-3-desktop-app-with-pinia-electron-and-quasar/quasar-electron-file-dialog_mulokn.jpg) ## Conclusion Quasar allows us to quickly develop Electron desktop applications in Vue with high-quality UI components that follow Material Design guidelines. The most significant advantage against a custom Electron + Vue boilerplate project from GitHub is that Quasar has a regular release cycle and provides [upgrade guides](https://quasar.dev/quasar-cli/developing-electron-apps/electron-upgrade-guide){rel=""nofollow""} for older versions. Take a look at my ["Scrum Daily Standup Picker" GitHub repository](https://github.com/Mokkapps/scrum-daily-standup-picker){rel=""nofollow""} to see a more complex "Quasar-Electron-Vue3-Typescript-Pinia" project. The demo source code for the following demo is [available at GitHub](https://github.com/Mokkapps/quasar-electron-vue3-pinia-demo){rel=""nofollow""}. If you liked this article, follow me on [Twitter](https://twitter.com/mokkapps){rel=""nofollow""} to get notified about new blog posts and more content from me. Alternatively (or additionally), you can also [subscribe to my newsletter](https://weekly-vue.news){rel=""nofollow""}. # Chrome Recorder: Record, Replay and Measure User Flows Typically, a user needs to process multiple pages or steps to finish his journey, such as submitting an order or completing a registration. If we as developers need to develop one of the last pages of this user flow, we need to manually process all the previous pages/steps every time we refresh the page or need to restart the flow. If multiple team members (also tester) have to do the same steps repeatedly, this costs a lot of time and, therefore, money. In previous projects, we often developed custom tools to proceed to certain pages/steps in the application automatically. But now, the [Chrome browser](https://chrome.com/){rel=""nofollow""} provides this functionality as a preview feature. ## What is Chrome Recorder? [Chrome Recorder](https://developer.chrome.com/docs/devtools/recorder/){rel=""nofollow""} is a preview feature in the Chrome browser designed to record, replay and measure user flows of an application. You can start a recording, execute the steps you'd like to record in the app (such as typing or clicking), and export the recording as JSON file, [Puppeteer](https://pptr.dev/){rel=""nofollow""} script or [@puppeteer/replay](https://github.com/puppeteer/replay){rel=""nofollow""} script. It's then possible to replay the recorded user flow and measure the performance of the run. ## Open Recorder To find the Recorder, you first open up the Chrome DevTools. > Command+Option+C (Mac) or Control+Shift+C (Windows, Linux, ChromeOS). You can open the Recorder from the options menu: ![Open Chrome Recorder from options menu](https://mokkapps.de/blog/chrome-recorder-record-replay-and-measure-user-flows/chrome-recorder-open-more-tools_g7lktw.png) Alternatively, you can open it from [Command Menu](https://developer.chrome.com/docs/devtools/command-menu/){rel=""nofollow""}: ![Open Chrome Recorder from Command Menu](https://mokkapps.de/blog/chrome-recorder-record-replay-and-measure-user-flows/chrome-recorder-open-command-menu_mxa4cb.png) ## Record I'll be using [Vue 3 Form Wizard](https://github.com/Anivive/vue3-form-wizard){rel=""nofollow""} to demonstrate the recording & replaying of a simple user flow. [The demo page](https://vue3-form-wizard-demo.stackblitz.io/){rel=""nofollow""} provides a simple wizard with multiple steps containing common input types like text & select inputs. Let's start the recording: ![Start Recording](https://mokkapps.de/blog/chrome-recorder-record-replay-and-measure-user-flows/chrome-recorder-start-recording_cdblgh.jpg) ::note The selector attribute textbox is optional. See [Customize the recording's selector](https://developer.chrome.com/docs/devtools/recorder/#customize-selector){rel=""nofollow""}. :: Once you hit the record button, you can enter all data in the wizard. If you are done, hit the "End recording" button at the bottom of the recorder panel. ![Recorded User Flow](https://mokkapps.de/blog/chrome-recorder-record-replay-and-measure-user-flows/chrome-recorder-finished-recording_iz71ys.jpg) The following GIF visualizes this process: ![Record user flow (GIF)](https://mokkapps.de/blog/chrome-recorder-record-replay-and-measure-user-flows/chrome-recorder-record_bntzkt.gif) It's also possible to manually edit the recorded steps. For example, you can manually change selectors: ![Change Selector](https://mokkapps.de/blog/chrome-recorder-record-replay-and-measure-user-flows/chrome-recorder-selector_w1bmrd.png) Additionally, you can manually add or remove steps: ![Add/Remove Steps](https://mokkapps.de/blog/chrome-recorder-record-replay-and-measure-user-flows/chrome-add-steps_q6bwj6.png) ## Replay After recording a user flow, you can replay it by clicking on the "Replay" button. ![Replay Recording (GIF)](https://mokkapps.de/blog/chrome-recorder-record-replay-and-measure-user-flows/chrome-recorder-replay_mz0jub.gif) ::note When replaying a user flow recording, the Recorder waits until the element is visible or clickable in the viewport or tries to automatically scroll the element into the viewport before replaying the corresponding step. :: It's also possible to simulate a slow network connection in the "Replay" settings: ![Simulate Slow Network](https://mokkapps.de/blog/chrome-recorder-record-replay-and-measure-user-flows/chrome-recorder-replay-setting_hh9nma.png) ## Measure performance You can also measure the performance of your recording by clicking the "Measure performance" button. This way, you can regularly measure the performance of critical user flows. ![Performance Panel](https://mokkapps.de/blog/chrome-recorder-record-replay-and-measure-user-flows/chrome-recorder-performance_prvrjf.jpg) ## Conclusion The Chrome Recorder is valuable tool that will boost my productivity during development. It's still a preview feature, but I think it will become a must-have tool for web developers. I recommend reading the [official Chrome blog post](https://developer.chrome.com/docs/devtools/recorder/){rel=""nofollow""}. If you liked this article, follow me on [Twitter](https://twitter.com/mokkapps){rel=""nofollow""} to get notified about new blog posts and more content from me. Alternatively (or additionally), you can also [subscribe to my newsletter](https://weekly-vue.news){rel=""nofollow""}. # Connecting a MySQL Database in Nuxt with Drizzle ORM I’ve been integrating databases with Nuxt apps for years, and lately I’ve been leaning heavily on [Drizzle ORM](https://orm.drizzle.team/){rel=""nofollow""} for its type-safety, modern API, and good developer experience. In this post I’ll walk you through connecting a MySQL database to a Nuxt 3+ app using Drizzle - from project setup all the way to query patterns, testing, and performance tips. I’ll show concrete examples you can copy into your project, and explain the reasoning behind key decisions. ## Introduction to Nuxt and Drizzle ORM I picked Nuxt because of its excellent full‑stack support via Nitro (server API routes, server middleware, and easy runtime config). Drizzle ORM fits nicely into that stack because it’s designed for modern TypeScript-first workflows, and it plays well with database drivers like `mysql2`. Together they give you: - A server-first architecture (Nitro) that avoids shipping DB code to the browser. - Type-safe schemas and query builders from Drizzle, reducing runtime surprises. - A small and predictable runtime / bundle on the server side. Before we dive into the code, a small note about continuous learning: adopting new tools like Drizzle is part of *staying current*. Industry research shows that developers consider continuous upskilling essential - 89% say it’s vital for career growth - and 68% see a skills gap in modern languages and tools. If you’re reading this, you’re already taking steps to bridge that gap. See the reports from [ZipDo](https://zipdo.co/upskilling-and-reskilling-in-the-software-industry-statistics){rel=""nofollow""} and [Ecomuch](https://ecomuch.com/the-importance-of-continuous-learning-in-software-development/){rel=""nofollow""} for context. ## Setting Up Your Nuxt Project I prefer starting with Nuxt 3 for server routes and Nitro. If you don’t have a project yet: - Create a new Nuxt app: ```bash npx nuxi init my-nuxt-drizzle-app cd my-nuxt-drizzle-app pnpm install ``` - Make sure you have TypeScript enabled in the project (Nuxt prompts for that). I recommend enabling strict TS checks — Drizzle benefits from it. Project structure I use: - `server/utils` - database connection and exported database instance - `server/database/schema` - Drizzle schema definitions - `server/database/migrations` - migrations (if using `drizzle-kit`) - `server/api` - Nitro API handlers that use the database Keep DB code strictly on the server side — never import it into client components. Nuxt’s directory structure makes this straightforward. ## Installing Drizzle ORM and MySQL Connector Install the packages you’ll need: - `drizzle-orm` (core) - `mysql2` (driver) - `drizzle-orm/mysql2` (Drizzle binding for mysql2) - `drizzle-kit` (optional, for migrations) Install them: ```bash pnpm add drizzle-orm mysql2 pnpm add -D drizzle-kit ``` If you prefer the named binding import, Drizzle exposes a `mysql2` adapter you can import from (the package surface may be provided by the `drizzle-orm` package). In code we’ll import `drizzle` from the `mysql2` adapter and create a connection with `mysql2/promise`. ::note Package names and exports evolve; check the [Drizzle docs](https://orm.drizzle.team/docs/overview){rel=""nofollow""} for the current import paths. :: ## Configuring Drizzle ORM for MySQL Keep your connection and Drizzle initialization in a single server-only module so other server files can import the configured database: ```ts [server/db/utils/drizzle.ts] import { drizzle } from 'drizzle-orm/mysql2' import mysql from 'mysql2/promise' import * as schema from '../database/schema' export { and, asc, desc, eq, or, sql } from 'drizzle-orm' export const tables = schema export async function useDrizzle () { const { private: { databaseUrl } } = useRuntimeConfig() const connection = await mysql.createConnection(databaseUrl) return drizzle({ client: connection, mode: 'default', schema }) } export type PublishedArticle = typeof schema.publishedArticles.$inferSelect ``` - Use Nuxt runtime config (`nuxt.config.ts` -> `runtimeConfig.private`) to inject DB credentials without bundling them. - Keep this file in `server/` so it’s not included in the client bundle. Also create `nuxt.config.ts` runtime config entries: ```ts export default defineNuxtConfig({ runtimeConfig: { // private values only available on server private: { databaseUrl: '' } // public: { ... } if you need client-visible values (you shouldn't for DB creds) } }); ``` If you plan to use `drizzle-kit` for migrations, add a `drizzle.config.ts` (example later). ## Creating and Managing Models in Drizzle Drizzle uses a schema definition API that is strongly typed. For MySQL you’ll define tables with `mysqlTable` and typed columns. Example schema: server/database/schema.ts ```ts [server/database/schema/publishedArticles.ts] import { datetime, int, mysqlTable, text } from 'drizzle-orm/mysql-core' export const publishedArticles = mysqlTable('published_articles', { id: int('id').primaryKey().autoincrement(), published_at: datetime('published_at').notNull(), title: text('title').notNull(), url: text('url').notNull().unique(), }) ``` Notes and best practices: - Define your schema in dedicated files under `server/schema` to keep separation of concerns. - Use appropriate column types and length limits to avoid unnecessarily large storage and to enable query planner accuracy. - If you need migrations, generate SQL with `drizzle-kit` rather than hand-editing. A migration tool helps maintain schema across environments. ```ts [drizzle.config.ts] import { defineConfig } from 'drizzle-kit' export default defineConfig({ dbCredentials: { url: process.env.NUXT_PRIVATE_DATABASE_URL!, }, dialect: 'mysql', out: './server/database/migrations', schema: './server/database/schema.ts', }) ``` Run migrations with `drizzle-kit` CLI after installing it as a dev dependency. ## Executing Queries in Nuxt with Drizzle With `useDrizzle` exported from `server/utils/drizzle.ts`, you can use Drizzle in Nitro API routes and server handlers. Example API route to read published articles: ```ts [server/api/articles.get.ts] export default defineEventHandler(async () => { const db = await useDrizzle() const all = await db.select().from(publishedArticles).orderBy(desc(publishedArticles.publishedAt)).limit(100); return all; }); ``` Tips: - Validate request payloads (for example with Zod) to keep DB interactions safe. - Always perform DB operations in server routes (`server/api` or `server/routes`). Accessing database on client is both insecure and impossible with server-only modules. - Use typed schema objects when building queries - Drizzle’s type inference reduces mistakes. ::note API specifics (methods, return types) can slightly differ across Drizzle releases. If a method name changes, refer to the Drizzle docs. :: ## Handling Database Connections Securely Security is a must. Here’s how I lock this down in Nuxt: - Use Nuxt runtime config (private) to store DB credentials; don’t export them to the client. - Store secrets in environment variables (and in your secrets manager when in production). - Use least privilege DB users: create a DB user that only has necessary rights (no admin/root access). - Use TLS for DB connections if your provider supports it (set SSL options in mysql2 config). - Use connection pooling and timeouts: - set connectionLimit to a reasonable number based on your server concurrency - set acquireTimeout and connectTimeout to avoid hanging requests - Avoid logging raw SQL or full DB responses in production logs. Finally, don’t commit any `.env` files or migration SQL with credentials into version control. Use CI secrets / environment variables. ## Testing Database Operations in a Nuxt Environment Tests should be deterministic and isolated. My go-to patterns: - Use a Docker Compose ephemeral MySQL instance for integration tests. - Reset schema between tests (automated migrations + teardown). - For unit tests, mock the database layer or use an in-memory test double when possible (MySQL lacks a standard in-memory DB like SQLite’s memory mode — so use Docker). Example docker-compose.test.yml for CI: ```yaml version: '3.8' services: mysql: image: mysql:8 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: test_db MYSQL_USER: test_user MYSQL_PASSWORD: test_pass ports: - "3307:3306" healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 5s retries: 10 ``` In your test setup: - Wait until MySQL is healthy. - Run migrations (`drizzle-kit`) programmatically or via CLI. - Run tests (Vitest is a great choice with Nuxt). Integration tests are slower but give the highest confidence. For CI, spin up MySQL in a job service or via Docker-in-Docker. ## Optimizing Performance for Database Operations I optimize on three layers: schema/indexing, connection usage, and query patterns. Schema & Indexing: - Index columns used in `WHERE`, `JOIN`, and `ORDER BY` clauses. - Keep rows minimal — avoid storing large JSON blobs if relational tables can solve the problem. - Normalize where appropriate; use denormalization selectively for read-heavy workloads. Connection & Pooling: - Use a connection pool (`mysql2.createPool`) and tune `connectionLimit` according to your server’s concurrency. - Reuse the pool across requests (the single `server/utils/drizzle.ts` module handles this). - Avoid opening and closing connections per request. Query Patterns: - Select only needed columns (avoid `SELECT *`). - Use `LIMIT` with pagination instead of fetching massive result sets. - Batch writes where possible (insert many rows in a single query). - Use prepared statements (`mysql2` does this under the hood when you pass parameters rather than string interpolation). Caching: - Add a cache layer (Redis or in-memory) for hot reads if latency is critical. - Cache results at application level with TTLs for read-heavy endpoints. Monitoring: - Use slow query logging and `EXPLAIN` to understand costly queries. - Measure with APM or logs and adjust indexes/queries based on evidence. ## Conclusion and Best Practices Putting Nuxt and Drizzle together gives you a fast, type-safe, server-first way to work with MySQL. To recap the flow I use in production: - Keep DB connection and schemas on the server side. - Use `mysql2` pools + drizzle(pool) for stable connections. - Validate input and use typed schemas to prevent bad writes. - Manage migrations with `drizzle-kit` and keep migrations in source control (without secrets). - Test using ephemeral MySQL instances in CI and mock the db for unit tests. - Optimize by indexing, selecting only needed fields, batching, and caching when appropriate. - Secure credentials with Nuxt runtimeConfig and environment variables; never expose them to the client. ::tip Take a look at my [Nuxt Starter Kit](https://nuxtstarterkit.com){rel=""nofollow""} which is a production-ready foundation for building modern web apps with Nuxt 4+. And of course it uses Drizzle 😇. :: And one last personal note: learning and adapting to new tools is a key part of being a developer today. As I mentioned earlier, studies show that a large majority of developers consider continuous learning essential, and many see skill gaps in modern tooling. Diving into Drizzle and Nuxt is precisely the kind of skill upgrade that helps keep you productive and marketable - and it’s the kind of hands-on learning I try to prioritize in my own workflow. # Create a Blog With Nuxt Content v2 I prefer simple Markdown files as the content source for my blog posts. In this article, I want to show you how can set up a simple blog using [Nuxt Content v2](https://content.nuxtjs.org/){rel=""nofollow""}. ## Nuxt Content v2 [Nuxt Content v2](https://content.nuxtjs.org/){rel=""nofollow""} is a [Nuxt 3](https://v3.nuxtjs.org/){rel=""nofollow""} module that reads local files from the `/content` directory in your project. It supports `.md`, `.yml`, `.csv` and `.json` files. Additionally, it's possible to use Vue components in Markdown with the [MDC Syntax](https://content.nuxtjs.org/guide/writing/mdc){rel=""nofollow""}. ## Setup Nuxt App First, let's start a new Nuxt Content project with: ```bash npx nuxi init nuxt-demo-blog -t content ``` Then we need to install the dependencies in the `nuxt-demo-blog` folder: ```bash yarn install ``` Now we can start the Nuxt content app in development mode: ```bash yarn dev ``` A browser window should automatically open for `http://localhost:3000`. Alternatively, you can start playing with Nuxt Content in your browser using [StackBlitz](https://stackblitz.com/github/nuxt/starter/tree/content){rel=""nofollow""} or [CodeSandbox](https://codesandbox.io/s/github/nuxt/starter/tree/content){rel=""nofollow""}. The following StackBlitz sandbox demonstrates the simple blog application we create in this article: :stackblitz{project-id="nuxt-content-v2-blog-demo"} ## Blog Content Structure Our demo blog will have this structure inside the `/content` directory: ```text ├── blog │ ├── _index.md │ ├── a-great-article │ └── cover.jpg │ │ └── index.md │ └── another-great-article │ └── cover.jpg │ └── index.md ``` `blog/_index.md` is a [Partial](https://content.nuxtjs.org/guide/writing/content-directory#partials){rel=""nofollow""} content that will show a list of all available blog posts. Each blog post has its directory, including an `index.md` and a `cover.jpg` file. The `index.md` files include [Front-matter](https://content.nuxtjs.org/guide/writing/markdown#front-matter){rel=""nofollow""} at the top of the file to provide meta-data to pages, like title, date, and the cover image URL: ```text --- title: A Great Article date: 2018-05-11 cover: /content/blog/a-great-article/cover.jpg --- This is a great article body! ``` ## Simple Navigation First, we need simple navigation in our application to be able to navigate to our blog page. Let's start by adding a [default layout](https://v3.nuxtjs.org/guide/directory-structure/layouts){rel=""nofollow""} in `layouts`: ```vue ``` In our `app.vue` we need to wrap the NuxtPage component with the NuxtLayout component: ```vue ``` Finally, we create a `index.vue` in `pages` directory: ```vue ``` ![Home Page](https://mokkapps.de/blog/create-a-blog-with-nuxt-content-v2/home_vwsgue.png) ## Blog List Let's look at how we can implement a list of all available blog posts. First, we need to create a `BlogPosts.vue` Vue component in `components/content/` that queries and renders all available blog posts: ```vue ``` We use the [queryContent function](https://content.nuxtjs.org/guide/displaying/querying#querying-content){rel=""nofollow""} from Nuxt to query a list of our blog posts. Now we can reference this Vue component inside our `content/blog/_index.md` file: ```text --- title: Blog --- ::blog-posts ``` We can use any component in the `components/content/` directory or any component made available globally in your application in Markdown files. If we now click on the "Blog" navigation link in our application, we can see a list of all available blog posts: ![Blog Post List](https://mokkapps.de/blog/create-a-blog-with-nuxt-content-v2/blog_ycha55.png) ## Blog Post Page Finally, we need to create a [dynamic route](https://v3.nuxtjs.org/guide/directory-structure/pages#dynamic-routes=){rel=""nofollow""} for the blog posts. Thus, we create a `[...slug].vue` file in `pages/blog`: ```vue ``` We use the current slug in the route parameters (`$route.params.slug`) to determine whether we want to render the blog post list or an individual blog post. We can now see the content of the corresponding blog post: ![Blog Post](https://mokkapps.de/blog/create-a-blog-with-nuxt-content-v2/blog-post_gek23n.png) ## Conclusion It's effortless to create a Markdown file-based blog using [Nuxt Content v2](https://content.nuxtjs.org/){rel=""nofollow""}. This article demonstrates the basic steps to set up such a blog. You can expect more [Nuxt 3](https://v3.nuxtjs.org/){rel=""nofollow""} posts in the following months as I plan to blog about interesting topics that I discover while rewriting my portfolio website. If you liked this article, follow me on [Twitter](https://twitter.com/mokkapps){rel=""nofollow""} to get notified about new blog posts and more content from me. Alternatively (or additionally), you can also [subscribe to my newsletter](https://weekly-vue.news){rel=""nofollow""}. # Create a Table of Contents With Active States in Nuxt 3 I'm a big fan of a table of contents (ToC) on the side of a blog post page, especially if it is a long article. It helps me gauge the article's length and allows me to navigate between the sections quickly. In this article, I will show you how to create a sticky table of contents sidebar with an active state based on the current scroll position using [Nuxt 3](https://nuxt.com/){rel=""nofollow""}, [Nuxt Content](https://nuxt.com/modules/content){rel=""nofollow""} and [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API){rel=""nofollow""}. ## Demo The following StackBlitz contains the source code that is used in the following chapters: :stackblitz{project-id="nuxt-content-table-of-contents-demo"} ## Setup For this demo, we need to [initialize a Nuxt 3](https://nuxt.com/docs/getting-started/installation){rel=""nofollow""} project and install the [Nuxt Content](https://content.nuxtjs.org/get-started){rel=""nofollow""} and [Nuxt Tailwind](https://tailwindcss.nuxt.dev/getting-started/setup){rel=""nofollow""} (optional) modules. We need to add these modules to `nuxt.config.ts`: ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: ['@nuxt/content', '@nuxtjs/tailwindcss'], }) ``` Of course, we need some content to show the table of contents. For this demo, I will reference the `index.md` file from my [StackBlitz demo](https://stackblitz.com/edit/nuxt-content-table-of-contents-demo?file=content/index.md){rel=""nofollow""}. To render this content, let's create a [catch-all route](https://nuxt.com/docs/guide/directory-structure/pages#catch-all-route){rel=""nofollow""} in the `pages` directory: ```vue [[...slug\\].vue] {3,7,11} ``` The `` component fetches and renders a single document, and the `` component renders the body of a Markdown document. [Check the official docs](https://content.nuxtjs.org/api/components/content-renderer){rel=""nofollow""} for more information about these Nuxt Content components. Now let's add a `TableOfContents.vue` component to this template: ```vue [[...slug\\].vue] {2,13-17} ``` I'll explain the `activeTocId` prop in the following "Intersection Observer" chapter. Let's take a look at the component's code: ```vue [TableOfContents.vue] {8-9} ``` Let's analyze this code: To get a list of all available headlines, we use the [queryContent composable](https://content.nuxtjs.org/api/composables/query-content){rel=""nofollow""} and access them via `body.toc.links`: ```ts const { data: blogPost } = await useAsyncData(`blogToc`, () => queryContent(`/`).findOne()) const tocLinks = computed(() => blogPost.value?.body.toc.links ?? []) ``` If someone clicks on a link in the ToC, we query the HTML element from the DOM, push the hash route and smoothly scroll the element into the viewport: ```ts const onClick = (id: string) => { const el = document.getElementById(id) if (el) { router.push({ hash: `#${id}` }) el.scrollIntoView({ behavior: 'smooth', block: 'center' }) } } ``` At this point, we can show a list of all the headlines of our content in the sidebar, but our ToC does not indicate which headline is currently visible. ## Intersection Observer We use the [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API){rel=""nofollow""} to handle detecting when an element scrolls into our viewport. It's [supported by almost every browser](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#browser_compatibility){rel=""nofollow""}. Nuxt Content automatically adds an `id` to each heading of our content files. Using `document.querySelectorAll`, we query all `h2` and `h3` elements associated with an `id` and use the Intersection Observer API to get informed when they scroll into view. Let's go ahead and implement that logic: ```vue [[...slug\\].vue] ``` Let's break down the single steps that are happening in this code. First, we define some reactive variables: - `activeTocId` is used to track the currently active DOM element to be able to add some CSS styles to it. - `nuxtContent` is a [Template Ref](https://vuejs.org/guide/essentials/template-refs.html#template-refs){rel=""nofollow""} to access the DOM element of the `ContentRenderer` component. - `observer` is used to track the `h2` and `h3` HTML elements that scroll into the viewport. - `observerOptions` contains a set of options that define when the observer callback is invoked. It contains the `nuxtContent` ref as root for the observer and a threshold of 0.5, which means that if 50% of the way through the viewport is visible, the callback will fire. You can also set it to `0`; it will fire the callback if one element pixel is visible. In the `onMounted` lifecycle hook, we are initializing the observer. We iterate over each article heading and set the `activeTocId` value if the entry intersects with the viewport. We also use `document.querySelectorAll` to target our `.nuxt-content` article and get the DOM elements that are either `h2` or `h3` elements with IDs and observe those using our previously initialized `IntersectionObserver`. Finally, we are disconnecting our observer in the `onUnmounted` lifecycle hook to inform the observer to no longer track these headings when we navigate away. ## Style Active Link Let's improve the code by applying styles to the `activeTocId` element in our table of contents component. It should be highlighted and show an indicator: ```vue [TableOfContents.vue] {25-39,47-59,68,79} ``` We use the [VueUse's watchDebounced composable](https://vueuse.org/shared/watchdebounced/#watchdebounced){rel=""nofollow""} to debounced watch changes of the active ToC element ID: ```ts watchDebounced( () => props.activeTocId, (newActiveTocId) => { const h2Link = tocLinksH2.value.find((el: HTMLElement) => el.id === `toc-${newActiveTocId}`) const h3Link = tocLinksH3.value.find((el: HTMLElement) => el.id === `toc-${newActiveTocId}`) if (h2Link) { sliderHeight.value = h2Link.offsetHeight sliderTop.value = h2Link.offsetTop - 100 } else if (h3Link) { sliderHeight.value = h3Link.offsetHeight sliderTop.value = h3Link.offsetTop - 100 } }, { debounce: 200, immediate: true } ) ``` Based on the current active ToC element ID, we find the HTML element from the list of available links and set the slider height & top values accordingly. Check the [StackBlitz demo](https://stackblitz.com/edit/nuxt-content-table-of-contents-demo){rel=""nofollow""} for the full source code and to play around with this implementation. A similar ToC is also available on my [blog](https://mokkapps.de/blog). ## Conclusion I'm pleased with my table of contents implementation using Nuxt 3, Nuxt Content, and Intersection Observer. Of course, you can use the Intersection Observer in a traditional Vue application without Nuxt. The Intersection Observer API is mighty and can also be used to implement features like [lazy-loading images](https://www.webtips.dev/how-to-lazy-load-images-with-intersection-observer){rel=""nofollow""}. Leave a comment if you have a better solution to implement such a ToC. If you liked this article, follow me on [Twitter](https://twitter.com/mokkapps){rel=""nofollow""} to get notified about new blog posts and more content from me. Alternatively (or additionally), you can also [subscribe to my newsletter](https://mokkapps.de/newsletter). # Create an RSS Feed With Nuxt 3 and Nuxt Content v2 My [portfolio website](https://mokkapps.de){rel=""nofollow""} is built with [Nuxt 3](https://v3.nuxtjs.org/){rel=""nofollow""} and [Nuxt Content v2](https://content.nuxtjs.org/){rel=""nofollow""}. An RSS feed with my latest five blog posts is available [here](https://mokkkapps.de/rss.xml){rel=""nofollow""}. In this article, you'll learn how to add an RSS feed to your Nuxt website. ## Setup First, let's [create a new Nuxt 3 project](https://v3.nuxtjs.org/getting-started/quick-start){rel=""nofollow""}. As the next step, we need to [add the Nuxt Content v2 module](https://content.nuxtjs.org/get-started){rel=""nofollow""} to our application. Finally, let's add some content that will be included in the RSS feed: ```text ├── content | └── blog | └── blog | | ├── article-1.md | | ├── article-2.md | | ├── article-3.md | | ├── article-4.md | | ├── article-5.md ``` Each `.md` file has this simple structure: ```md --- title: 'Article 1' description: 'Article 1 description' date: '2022-01-01' --- Article 5 Content ``` The source code for this demo is available at [GitHub](https://github.com/Mokkapps/rss-feed-nuxt-3-and-nuxt-content-v2){rel=""nofollow""} and in this StackBlitz playground: :stackblitz{project-id="rss-feed-nuxt-3-and-nuxt-content-v2"} ## Add Server Route We will be utilizing the [server routes](https://v3.nuxtjs.org/guide/features/server-routes){rel=""nofollow""} available within Nuxt, and to do so, we'll need to create the `server/` directory within our app root directly. Once this is done, we create a `routes/` directory inside this and add a `rss.xml.ts` file. It will translate to `/rss.xml`: ```ts [server/routes/rss.xml.ts] export default defineEventHandler(async (event) => { const feedString = '' event.res.setHeader('content-type', 'text/xml') event.res.end(feedString) }) ``` The next step is to query our blog posts: ```ts [server/routes/rss.xml.ts] {4-5} import { serverQueryContent } from '#content/server' export default defineEventHandler(async (event) => { const docs = await serverQueryContent(event).sort({ date: -1 }).where({ _partial: false }).find() const blogPosts = docs.filter((doc) => doc?._path?.includes('/blog')) const feedString = '' event.res.setHeader('content-type', 'text/xml') event.res.end(feedString) }) ``` Now let's add the [rss](https://www.npmjs.com/package/rss){rel=""nofollow""} library to generate the RSS XML string based on our content: ```ts [server/routes/rss.xml.ts] {2,4-8,13-20,22} import { serverQueryContent } from '#content/server' import RSS from 'rss' const feed = new RSS({ title: 'Michael Hoffmann', site_url: 'https://mokkapps.de', feed_url: `https://mokkapps.de/rss.xml`, }) const docs = await serverQueryContent(event).sort({ date: -1 }).where({ _partial: false }).find() const blogPosts = docs.filter((doc) => doc?._path?.includes('/blog')) for (const doc of blogPosts) { feed.item({ title: doc.title ?? '-', url: `https://mokkapps.de${doc._path}`, date: doc.date, description: doc.description, }) } const feedString = feed.xml({ indent: true }) event.res.setHeader('content-type', 'text/xml') event.res.end(feedString) ``` When using `nuxt generate`, you may want to pre-render the feed since the server route won't be able to run on a static hosting. We can do this by using the `nitro.prerender` option in `nuxt.config`: ```ts [nuxt.config.ts] {6-10} import { defineNuxtConfig } from 'nuxt' // https://v3.nuxtjs.org/api/configuration/nuxt.config export default defineNuxtConfig({ modules: ['@nuxt/content'], nitro: { prerender: { routes: ['/rss.xml'], }, }, content: { // https://content.nuxtjs.org/api/configuration }, }) ``` If we now navigate to `/rss.xml`, we get our generated RSS feed: ```xml <![CDATA[ Michael Hoffmann ]]> https://mokkapps.de RSS for Node Sun, 14 Aug 2022 18:14:16 GMT <![CDATA[ Article 5 ]]> https://mokkapps.de/blog/article-5 https://mokkapps.de/blog/article-5 Thu, 05 May 2022 00:00:00 GMT <![CDATA[ Article 4 ]]> https://mokkapps.de/blog/article-4 https://mokkapps.de/blog/article-4 Mon, 04 Apr 2022 00:00:00 GMT <![CDATA[ Article 3 ]]> https://mokkapps.de/blog/article-3 https://mokkapps.de/blog/article-3 Thu, 03 Mar 2022 00:00:00 GMT <![CDATA[ Article 2 ]]> https://mokkapps.de/blog/article-2 https://mokkapps.de/blog/article-2 Wed, 02 Feb 2022 00:00:00 GMT <![CDATA[ Article 1 ]]> https://mokkapps.de/blog/article-1 https://mokkapps.de/blog/article-1 Sat, 01 Jan 2022 00:00:00 GMT ``` --- If you liked this article, follow me on [Twitter](https://twitter.com/mokkapps){rel=""nofollow""} to get notified about new blog posts and more content from me. Alternatively (or additionally), you can also [subscribe to my newsletter](https://weekly-vue.news){rel=""nofollow""}. # Dark Mode Switch With Tailwind CSS & Nuxt 3 I am currently rewriting my [portfolio website](https://github.com/mokkapps/website){rel=""nofollow""} with [Nuxt 3](https://v3.nuxtjs.org/){rel=""nofollow""} which is still in beta. In this article, I want to show you how I implemented a dark mode switch in Nuxt 3 using [Tailwind CSS](https://tailwindcss.com/){rel=""nofollow""} that I will use in my new portfolio website. ## Create Nuxt 3 project To create a new Nuxt 3 project, we need to run this command in our terminal: ```bash npx nuxi init nuxt3-app ``` ## Add Tailwind CSS 3 Next, we add the [nuxt/tailwind](https://tailwindcss.nuxtjs.org){rel=""nofollow""} module, which provides a [prerelease version](https://tailwindcss.nuxtjs.org/releases/#Nuxt%203%20and%20Tailwindcss%203%20support){rel=""nofollow""} that supports Nuxt 3 and Tailwind CSS v3: ```bash npm install --save-dev @nuxtjs/tailwindcss@5.0.0-4 ``` Then we need to add this module to the `buildModules` section in `nuxt.config.js`: ```js {5} import { defineNuxtConfig } from 'nuxt3' // https://v3.nuxtjs.org/docs/directory-structure/nuxt.config export default defineNuxtConfig({ buildModules: ['@nuxtjs/tailwindcss'], }) ``` Now, we can create the Tailwind configuration file `tailwind.config.ts` by running the following command: ```bash npx tailwindcss init ``` Let's add a basic CSS file at `./assets/css/tailwind.css` (see [official docs](https://tailwindcss.nuxtjs.org/setup#tailwind-files){rel=""nofollow""} for further configuration options): ```css @tailwind base; @tailwind components; @tailwind utilities; .theme-light { --background: #f8f8f8; --text: #313131; } .theme-dark { --background: #313131; --text: #f8f8f8; } ``` We define two CSS classes for the dark and light theme. [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties){rel=""nofollow""} (indicated by `--`) are used to change CSS values based on the selected theme dynamically. Therefore, we need to define these colors in our `tailwind.conf.js`: ```js {12-15} module.exports = { content: [ `components/**/*.{vue,js,ts}`, `layouts/**/*.vue`, `pages/**/*.vue`, `app.vue`, `plugins/**/*.{js,ts}`, `nuxt.config.{js,ts}`, ], theme: { extend: { colors: { themeBackground: 'var(--background)', themeText: 'var(--text)', }, }, }, plugins: [], } ``` ## Implement Theme Switch Let's start to implement a theme switch by adding this simple template to our `app.vue` component: ```vue ``` On the `div` container element, we dynamically set `theme-light` or `theme-dark` CSS class based on the reactive `darkMode` variable value, which we will implement later in the `script` part of the component. The `h1` and container `div` elements use our Tailwind CSS classes `bg-themeBackground` and `text-themeText` to use theme-specific colors for the background and text color. Additionally, we use the [Vue 3 Toggle](https://github.com/vueform/toggle){rel=""nofollow""} library to switch between our themes. Let's take a look at the `script` part of `app.vue`: ```vue ``` We store the selected theme value in [Local Storage](https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector/Local_Storage_Session_Storage){rel=""nofollow""} and use [useState](https://v3.nuxtjs.org/docs/usage/state){rel=""nofollow""} to define a reactive variable called `darkMode`: ```ts const darkMode = useState('theme', () => false) ``` If the component is mounted, we first detect if the user has requested light or dark color theme by using [the CSS media feature "prefers-color-scheme"](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme){rel=""nofollow""}: ```ts const isDarkModePreferred = window.matchMedia('(prefers-color-scheme: dark)').matches ``` Then we set the theme value based on the local storage value: ```ts {11-19} const setTheme = (newTheme: Theme) => { localStorage.setItem(LOCAL_STORAGE_THEME_KEY, newTheme) darkMode.value = newTheme === 'dark' } onMounted(() => { const isDarkModePreferred = window.matchMedia('(prefers-color-scheme: dark)').matches const themeFromLocalStorage = localStorage.getItem(LOCAL_STORAGE_THEME_KEY) as Theme if (themeFromLocalStorage) { setTheme(themeFromLocalStorage) } else { setTheme(isDarkModePreferred ? 'dark' : 'light') } }) ``` This the complete `app.vue` component code: ```vue ``` Now we can use run the following command to start our Nuxt app in development mode: ```bash npm run dev ``` Finally, we can test our dark mode theme switch at `http://localhost:3000`: ![Theme Switch In Action](https://mokkapps.de/blog/dark-mode-switch-with-tailwind-css-and-nuxt-3/dark-mode-switch_yu6yoy.gif) ## StackBlitz Demo My simple demo is available as interactive StackBlitz demo: :stackblitz{project-id="nuxt-3-tailwind-3-dark-mode-switch-demo"} ## Alternative Alternatively, you could also use the [color-mode](https://color-mode.nuxtjs.org/){rel=""nofollow""} module that supports Nuxt Bridge and Nuxt 3 or [useDark from VueUse](https://vueuse.org/core/usedark/){rel=""nofollow""}. ## Conclusion This article showed you how to create a simple dark mode switch in Nuxt 3 with Tailwind CSS v3. You can expect more Nuxt 3 posts in the following months as I plan to blog about interesting topics that I discover while I rewrite my portfolio website. If you liked this article, follow me on [Twitter](https://twitter.com/mokkapps){rel=""nofollow""} to get notified about new blog posts and more content from me. Alternatively (or additionally), you can also [subscribe to my weekly Vue.js newsletter](https://weekly-vue.news){rel=""nofollow""}. # Debug Why React (Re-)Renders a Component [React](https://reactjs.org/){rel=""nofollow""} is known for its performance by using the Virtual DOM (VDOM). It only triggers an update for the parts of the real DOM that have changed. In my opinion, it is important to know when React triggers a re-rendering of a component to be able to debug performance issues and develop fast and efficient components. After reading this article, you should have a good understanding of how React rendering mechanism is working and how you can debug re-rendering issues. ## What is rendering? First, we need to understand what rendering in the context of a web application means. If you open a website in the browser, what you see on your screen is described by the [DOM (Document Object Model)](https://www.w3.org/DOM/Overview){rel=""nofollow""} and represented through [HTML (Hypertext Markup Language)](https://en.wikipedia.org/wiki/HTML){rel=""nofollow""}. > The W3C Document Object Model (DOM) is a platform and language-neutral interface that allows programs and scripts to dynamically access and update the content, structure, and style of a document. DOM nodes are created by React if the JSX code is converted. We should be aware that real DOM updates are slow as they cause a re-drawing of the UI. This becomes a problem if React components become too big or are nested on multiple levels. Each time a component is re-rendered its JSX is converted to DOM nodes which takes extra computation time and power. This is where React's Virtual DOM comes into the game. ## Virtual DOM React uses a Virtual DOM (VDOM) as an additional abstraction layer on top of the DOM which reduces real DOM updates. If we change the state in our application, these changes are first applied to the VDOM. The [React DOM library](https://www.npmjs.com/package/react-dom){rel=""nofollow""} is used to efficiently check what parts of the UI **really** need to be visually updated in the real DOM. This process is called **diffing** and is based on these steps: 1. VDOM gets updated by a state change in the application. 2. New VDOM is compared against a previous VDOM snapshot. 3. Only the parts of the real DOM are updated which have changed. There is no DOM update if nothing has changed. ![React VDOM DOM](https://mokkapps.de/blog/debug-why-react-re-renders-a-component/react-vdom-dom_hmargb.jpg) More details about this mechanism can be found in [React's documentation about reconciliation](https://reactjs.org/docs/reconciliation.html){rel=""nofollow""}. ## What causes a render in React? A rendering in React is caused by - changing the state - passing props - using [Context API](https://reactjs.org/docs/context.html){rel=""nofollow""} React is extremely careful and re-renders "everything all the same time". Losing information by not rendering after a state change could be very dramatic this is why re-rendering is the safer alternative. I created a demo project on [StackBlitz](https://stackblitz.com/edit/react-when-does-component-render-demo){rel=""nofollow""} which I will use in this article to demonstrate React's rendering behavior: :stackblitz{project-id="react-when-does-component-render-demo"} The project contains a parent component, which basically consists of two child components where one component receives props and the other not: ```jsx class Parent extends React.Component { render() { console.warn('RENDERED -> Parent') return (
) } } ``` As you can see, we log a warning message in the console each time the component's `render` function is called. In our example, we use functional components and therefore the execution of the whole function is similar to the `render` function of class components. If you take a look at the console output of the [StackBlitz demo](https://stackblitz.com/edit/react-when-does-component-render-demo){rel=""nofollow""}, you can see that the render method is called **three** times: 1. Render `Parent` component 2. Render `Child` even if it has no props 3. Render `Child` with `name` value from state as prop If you now modify the name in the input field we trigger a state change for each new value. Each state change in the parent component triggers a re-rendering of the child components even if they did not receive any props. Does it mean that React re-renders the real DOM each time we call the `render` function? No, React only updates the part of the UI that changed. A render is scheduled by React each time the state of a component is modified. For example, updating state via the `setState` hook will not happen immediately but React will execute it at the best possible moment. But calling the `render` function has some side-effects even if the real DOM is not re-rendered: - the code inside the render function is executed each time, which can be time-consuming depending on its content - the diffing algorithm is executed for each component to be able to determine if the UI needs to be updated ### Visualize rendering It is possible to visualize React's VDOM as well as the native DOM rendering in the web browser. To show the React's **virtual** render you need to install [React DevTools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi){rel=""nofollow""} in your browser. You can then enable this feature under `Components -> View Settings -> Highlight updated when component render`. This way we can see when React calls the render method of a component as it highlights the border of this component. This is similar to the console logs in my demo application. ![React DevTools Highlight component render GIF](https://mokkapps.de/blog/debug-why-react-re-renders-a-component/react-devtools-rendering_elni54.gif) Now we want to see what gets updated in the real DOM, therefore we can use the Chrome DevTools. Open it via `F12`, go to the three-dot menu on right and select `More tools -> Rendering -> Paint flashing`: ![Chrome DevTools render paint flashing GIF](https://mokkapps.de/blog/debug-why-react-re-renders-a-component/react-chrome-devtools-rendering-paint-flashing_bekm8h.gif) ## Debug why a component rendered In our small example, it was quite easy to analyze what action caused a component to render. In larger applications, this can be more tricky as components tend to be more complex. Luckily, we can use some tools which help us to debug what caused a component to render. ### React DevTools We can again use the Profiler of the [React DevTools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi){rel=""nofollow""}. This feature records why each component rendered while the profiling was active. You can enable it in the React DevTools Profiler tab: ![React DevTools Profiler](https://mokkapps.de/blog/debug-why-react-re-renders-a-component/react-devtools-profiler_d4yiho.jpg) If we now start the profiling, trigger a state change, and stop the profiling we can see that information: ![React DevTools Profiler Result](https://mokkapps.de/blog/debug-why-react-re-renders-a-component/react-devtools-profiler-result_xv2d4p.jpg) But as you can see, we only get the information that the component rendered because of a state change triggered by hook but we still don't know why this hook caused a rendering. ### Why did you render? To debug why a hook caused a React component to render we can use the npm package [Why Did You Render](https://github.com/welldone-software/why-did-you-render){rel=""nofollow""}. > It monkey patches React to notify you about avoidable re-renders. So it is very useful to track when and why a certain component re-renders. I included the npm package in my demo project on [StackBlitz](https://stackblitz.com/edit/react-when-does-component-render-demo){rel=""nofollow""}, to enable it you need to enable it inside the `Parent.jsx` component: ```jsx Parent.whyDidYouRender = true ``` If we now trigger a parent re-rendering by toggling the "Toggle Context API" checkbox we can see additional console logs from the library: ![Why did you render?](https://mokkapps.de/blog/debug-why-react-re-renders-a-component/react-wdyr_vxxh0g.jpg) The console output is: ```text {Parent: ƒ} Re-rendered because the props object itself changed but its values are all equal. This could have been avoided by making the component pure, or by preventing its father from re-rendering. More info at http://bit.ly/wdyr02 prev props: {} !== {} :next props ``` ```text {App: ƒ} Re-rendered because of hook changes: different objects. (more info at http://bit.ly/wdyr3) {prev : false} !== {next : true} ``` As you can see from the output we get detailed information on what caused the re-rendering (for example if it was a prop or hook change) and which data were compared, for example, which props and state were used for the diffing. ## Conclusion In this article, I explained why React re-renders a component and how you can visualize and debug this behavior. I learned a lot while writing this article and building the demo application. I also hope that you got a better understanding of how React rendering works and that you now know how to debug your re-rendering issues. In the future, I will write more about React, so follow me on [Twitter](https://twitter.com/mokkapps){rel=""nofollow""} to get notified about the latest articles. # Dockerizing a Nuxt App: A Comprehensive Guide [Docker](https://www.docker.com/){rel=""nofollow""} has revolutionized the way developers build, ship, and run applications by providing a consistent environment for development, testing, and production. In this guide, I'll walk you through the steps to dockerize a Nuxt 3+ application, enabling you to create a containerized version of your app that can be easily deployed across different environments. ## Why Dockerize Your Nuxt App? Before we dive into the details, let's discuss why you might want to dockerize your Nuxt 3 app: 1. **Consistency**: Docker ensures that your application runs the same way on any machine, eliminating the "works on my machine" problem. 2. **Isolation**: Each application runs in its own container, isolated from other applications and their dependencies. 3. **Scalability**: Docker makes it easier to scale your applications horizontally by running multiple containers. 4. **Portability**: Docker containers can run on any system that supports Docker, making it easy to move applications between environments. ## Prerequisites To follow along with this guide, you'll need the following: - I'm using [pnpm](https://pnpm.io/){rel=""nofollow""} as the package manager in this guide, but you can use `npm`, `yarn` or `bun` if you prefer. - Basic knowledge of Docker and Docker Compose. - A Nuxt 3+ application. - Docker installed on your machine. ## Step 1: Set Up Your Nuxt 3 Application If you don't have a Nuxt 3+ app already, create one using the following commands: ```bash pnpm dlx nuxi@latest init my-nuxt-app cd my-nuxt-app pnpm install ``` This will create a new Nuxt 3+ application in the `my-nuxt-app` directory and install all the necessary dependencies. ## Step 2: Create a Dockerfile In the root of your Nuxt 3+ application, create a file named `Dockerfile`. This file will define the environment and the steps needed to run your application inside a Docker container. Here's an example `Dockerfile` for a Nuxt 3+ application: ```dockerfile [Dockerfile] ARG NODE_VERSION=20.14.0 # Create build stage FROM node:${NODE_VERSION}-slim AS build # Enable pnpm ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable # Set the working directory inside the container WORKDIR /app # Copy package.json and pnpm-lock.yaml files to the working directory COPY ./package.json /app/ COPY ./pnpm-lock.yaml /app/ ## Install dependencies RUN pnpm install --shamefully-hoist # Copy the rest of the application files to the working directory COPY . ./ # Build the application RUN pnpm run build # Create a new stage for the production image FROM node:${NODE_VERSION}-slim # Set the working directory inside the container WORKDIR /app # Copy the output from the build stage to the working directory COPY --from=build /app/.output ./ # Define environment variables ENV HOST=0.0.0.0 NODE_ENV=production ENV NODE_ENV=production # Expose the port the application will run on EXPOSE 3000 # Start the application CMD ["node","/app/server/index.mjs"] ``` ## Step 3: Build and Run the Docker Image With the `Dockerfile` in place, you can build the Docker image using the following command: ```bash docker build -t my-nuxt-app . ``` This command tells Docker to build an image with the tag `my-nuxt-app` using the current directory (denoted by the `.`). Once the image is built, you can run a container using the following command: ```bash docker run -p 3000:3000 my-nuxt-app ``` This command runs the `my-nuxt-app` container and maps port 3000 on your host machine to port 3000 inside the container. You should now be able to access your Nuxt 3 application by navigating to `http://localhost:3000` in your web browser. ## Step 4: Using Docker Compose (Optional) For more complex applications with multiple services (e.g., a database and a web server), you can use Docker Compose to define and run multi-container Docker applications. Create a `docker-compose.yml` file in the root of your project: ```yaml version: '3' services: web: build: . ports: - "3000:3000" ``` With this `docker-compose.yml` file, you can build and run your multi-container application using a single command: ```bash docker-compose up ``` ## Conclusion Dockerizing your Nuxt 3 application provides numerous benefits, including consistency, isolation, scalability, and portability. By following this guide, you can easily create a Docker container for your Nuxt 3+ app and run it in any environment that supports Docker. Whether you're a solo developer or part of a larger team, Docker can help streamline your development workflow and simplify deployment processes. If you liked this article, follow me on [X](https://x.com/mokkapps){rel=""nofollow""} to get notified about my new blog posts and more content. Alternatively (or additionally), you can [subscribe to my weekly Vue & Nuxt newsletter](https://weekly-vue.news){rel=""nofollow""}: # Document & Test Vue 3 Components With Storybook [Storybook](https://storybook.js.org/){rel=""nofollow""} is my tool of choice for UI component documentation. Vue.js is very well supported in the Storybook ecosystem and has first-class integrations with [Vuetify](https://github.com/vuetifyjs/vue-cli-plugins/tree/master/packages/vue-cli-plugin-vuetify-storybook){rel=""nofollow""} and [NuxtJS](https://storybook.nuxtjs.org/){rel=""nofollow""}. It also has official support for [Vue 3](https://v3.vuejs.org/){rel=""nofollow""}, the latest major installment of Vue.js. This article will demonstrate how you can set up Storybook with zero-config and built-in TypeScript support, auto-generate controls & documentation, and perform automated snapshot tests for your Vue components. ## Why Storybook? We have components that can have many props, states, slots, etc., which influences its visual representation and more. This circumstance causes some typical problems for any front-end developer: - How can I create documentation for my component that doesn't get outdated? - How can I get an overview of all different states and kinds of my component? - How can I guarantee that my changes don't influence other states and kinds? - How can I show the current implementation to non-developer team members? Storybook will help us here. ## Storybook Setup First, we need to create a Vue 3 application. We'll use [Vite](https://vitejs.dev/){rel=""nofollow""}, a new build tool from [Evan You](https://twitter.com/youyuxi){rel=""nofollow""}, the creator of Vue.js: ```bash npm init vite@latest ``` Setting up Storybook in an existing Vue 3 project can be done with zero configuration: ```bash npx sb init ``` This command installs Storybook with its dependencies, configures the Storybook instance, and generates some demo components and stories which are located at `src/stories`: ![Storybook Vue 3 Generated Files](https://mokkapps.de/blog/document-and-test-vue-3-components-with-storybook/storybook-init-folder_jk9wc9.png) We can now run the following command, which starts a local development server for Storybook and automatically opens it in a new browser tab: ```bash npm run storybook ``` ![Storybook Vue 3 Demo](https://mokkapps.de/blog/document-and-test-vue-3-components-with-storybook/storybook-demo_ebynbq.gif) These generated Vue components and stories are good examples of how to write Vue 3 stories. I want to show you some advanced documentation examples using a custom component. ## Custom Component Demo I created a `Counter.vue` demo component to demonstrate the Storybook integration for this article. The source code is available at [GitHub](https://github.com/Mokkapps/vue-3-storybook-demo){rel=""nofollow""}. The component provides basic counter functionality, has two different visual variants and two slots for custom content. Let's take a look at the component's code: ```vue {3,10,18-22,25-28,32-35,39-41,46-48} ``` In the above code, you can see that I've annotated the Vue component with [JSDoc](https://jsdoc.app/){rel=""nofollow""} comments. Storybook converts them into living documentation alongside our stories. ::warning Unfortunately, I found no way to add JSDoc comments to the `counter-update` event. I think it is currently not supported in [vue-docgen-api](https://github.com/vue-styleguidist/vue-styleguidist/tree/dev/packages/vue-docgen-api){rel=""nofollow""}, which Storybook uses under the hood to extract code comments into descriptions. Leave a comment if you know a way how to document events in Vue 3. :: Storybook uses so-called [stories](https://storybook.js.org/docs/react/get-started/whats-a-story){rel=""nofollow""}: > A story captures the rendered state of a UI component. Developers write multiple stories per component that describe all the “interesting” states a component can support. A component’s stories are defined in a story file that lives alongside the component file. The story file is for development-only, it won't be included in your production bundle. Now, let's take a look at the code of our `Counter.stories.ts`: ```ts import Counter from './Counter.vue' import { Variant } from './types' //👇 This default export determines where your story goes in the story list export default { title: 'Counter', component: Counter, //👇 Creates specific argTypes with options argTypes: { variant: { options: Variant, }, }, } //👇 We create a “template” of how args map to rendering const Template = (args) => ({ components: { Counter }, setup() { //👇 The args will now be passed down to the template return { args } }, template: '{{ args.slotContent }}', }) //👇 Each story then reuses that template export const Default = Template.bind({}) Default.args = { label: 'Default', } export const Colored = Template.bind({}) Colored.args = { label: 'Colored', variant: Variant.Colored, } export const NegativeValues = Template.bind({}) NegativeValues.args = { allowNegativeValues: true, initialValue: -1, } export const Slot = Template.bind({}) Slot.args = { slotContent: 'SLOT CONTENT', } ``` This code is written in [Component Story Format](https://storybook.js.org/docs/vue/writing-stories/introduction){rel=""nofollow""} and generates four stories: - Default: The counter component in its default state - Colored: The counter component in the colored variation - NegativeValue: The counter component that allows negative values - Slot: The counter component with a slot content Let's take a look at our living documentation in Storybook: ![Storybook Demo Running](https://mokkapps.de/blog/document-and-test-vue-3-components-with-storybook/storybook-demo_ebynbq.gif) As already mentioned, Storybook converts the JSDoc comments from our code snippet above into documentation, shown in the following picture: ![Storybook Generated Docs](https://mokkapps.de/blog/document-and-test-vue-3-components-with-storybook/storybook-custom-demo-docs_ho6vqk.jpg) ## Testing Now that we have our living documentation in Storybook we can run tests against them. ### Jest Setup I chose [Jest](https://jestjs.io/){rel=""nofollow""} as the test runner. It has a fast & straightforward setup process and includes a test runner, an assertion library, and a DOM implementation to mount our Vue components. To install Jest in our existing Vue 3 + Vite project, we need to run the following command: ```bash npm install jest @types/jest ts-jest vue-jest@next @vue/test-utils@next --save-dev ``` Then we need to create a `jest.config.js` config file in the root directory: ```js module.exports = { moduleFileExtensions: ['js', 'ts', 'json', 'vue'], transform: { '^.+\\.ts$': 'ts-jest', '^.+\\.vue$': 'vue-jest', }, collectCoverage: true, collectCoverageFrom: ['/src/**/*.vue'], } ``` The next step is to add a script that executes the tests in our `package.json`: ```json "scripts": { "test": "jest src" } ``` ### Unit testing with Storybook Unit tests help verify functional aspects of components. They prove that the output of a component remains the same given a fixed input. Let's take a look at a simple unit test for our Storybook story: ```ts import { mount } from '@vue/test-utils' import Counter from './Counter.vue' //👇 Imports a specific story for the test import { Colored, Default } from './Counter.stories' it('renders default button', () => { const wrapper = mount(Counter, { propsData: Default.args, }) expect(wrapper.find('.container').classes()).toContain('default') }) it('renders colored button', () => { const wrapper = mount(Counter, { propsData: Colored.args, }) expect(wrapper.find('.container').classes()).toContain('colored') }) ``` We wrote two exemplary unit tests Jest executes against our Storybook story `Counter.stories.ts`: - `renders default button`: asserts that the component container contains the CSS class `default` - `renders colored button`: asserts that the component container contains the CSS class `colored` The test result looks like this: ```bash PASS src/components/Counter.test.ts ✓ renders default button (25 ms) ✓ renders colored button (4 ms) ----------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s ----------|---------|----------|---------|---------|------------------- All files | 0 | 0 | 0 | 0 | ----------|---------|----------|---------|---------|------------------- Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 0 total Time: 3.674 s, estimated 4 s ``` ## Snapshot Testing Snapshot tests compare the rendered markup of every story against known baselines. It’s an easy way to identify markup changes that trigger rendering errors and warnings. A snapshot test renders the markup of our story, takes a snapshot, and compares it to a reference snapshot file stored alongside the test. The test case will fail if the two snapshots do not match. There are two typical causes why a snapshot test fails: - The change is expected - The reference snapshot needs to be updated We can use [Jest Snapshot Testing](https://jestjs.io/docs/snapshot-testing){rel=""nofollow""} as Jest library for snapshot tests. Let's install it by running the following command: ```bash npm install --save-dev jest-serializer-vue ``` Next, we need to add it as `snapshotSerializers` to our `jest.config.js` config file: ```js {9} module.exports = { moduleFileExtensions: ['js', 'ts', 'json', 'vue'], transform: { '^.+\\.ts$': 'ts-jest', '^.+\\.vue$': 'vue-jest', }, collectCoverage: true, collectCoverageFrom: ['/src/**/*.vue'], snapshotSerializers: ['jest-serializer-vue'], } ``` Finally, we can write a snapshot test for Storybook story: ```js it('renders snapshot', () => { const wrapper = mount(Counter, { propsData: Colored.args, }) expect(wrapper.element).toMatchSnapshot() }) ``` If we now run our tests, we get the following result: ```bash > vite-vue-typescript-starter@0.0.0 test > jest src PASS src/components/Counter.test.ts ✓ renders default button (27 ms) ✓ renders colored button (4 ms) ✓ renders snapshot (6 ms) ----------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s ----------|---------|----------|---------|---------|------------------- All files | 0 | 0 | 0 | 0 | ----------|---------|----------|---------|---------|------------------- Test Suites: 1 passed, 1 total Tests: 3 passed, 3 total Snapshots: 1 passed, 1 total Time: 1.399 s, estimated 2 s ``` The test run generates snapshot reference files that are located at `src/components/__snapshots__`. ## Conclusion Storybook is a fantastic tool to create living documentation for components. If you keep the story files next to your component's source code, the chances are high that the story gets updated if you modify the component. Storybook has first-class support for Vue 3, and it works very well. If you want more information about Vue and Storybook, you should look at the [official Storybook documentation](https://storybook.js.org/docs/vue/get-started/introduction){rel=""nofollow""}. If you liked this article, follow me on [Twitter](https://twitter.com/mokkapps){rel=""nofollow""} to get notified about new blog posts and more content from me. Alternatively (or additionally), you can also [subscribe to my newsletter](https://weekly-vue.news){rel=""nofollow""}. # Document Your Nuxt Endpoints With OpenAPI and Visualize With Swagger or Scalar When you're building an API with Nuxt 3+, it's essential to have clear and accessible documentation. [OpenAPI](https://swagger.io/specification/){rel=""nofollow""} provides a structured way to describe your API, and tools like [Swagger UI](https://swagger.io/tools/swagger-ui/){rel=""nofollow""} or [Scalar](https://scalar.com/){rel=""nofollow""} make it easy to visualize and interact with your endpoints. In this article, we’ll explore how to document Nuxt 3 endpoints using OpenAPI and display them using Swagger UI or Scalar. ## Enable OpenAPI in Nuxt 3 To enable OpenAPI in your Nuxt 3 project, you need to enable the experimental Nitro feature. You can do this by adding the following configuration to your `nuxt.config.ts`: ```ts [nuxt.config.ts] export default defineNuxtConfig({ nitro: { experimental: { openAPI: true, }, }, }) ``` If you now start your Nuxt app locally, you can access the OpenAPI documentation at `http://localhost:3000/_swagger` or `http://localhost:3000/_scalar`. ::note These routes are disabled by default in production. To enable them, use the production key. `runtime` allows middleware usage, and `prerender` is the most efficient because the JSON response is constant. ```ts [nuxt.config.ts] {6-8} export default defineNuxtConfig({ nitro: { experimental: { openAPI: true, }, openAPI: { production: 'runtime', } }, }) ``` :: Visit the [official Nitro documentation](https://nitro.build/config#openapi){rel=""nofollow""} for further customization options. ## Document Your Endpoints By default, your endpoints will have no custom documentation like description text or information about the query parameters. To add such documentation, you can use the `defineRouteMeta` method in your server route file: ```ts [server/routes/api/test.ts] {1-7} defineRouteMeta({ openAPI: { tags: ['test'], description: 'Test route description', parameters: [{ in: 'query', name: 'test', required: true }], }, }); export default defineEventHandler(() => "OK"); ``` This will add the route to the OpenAPI documentation with the specified tags, description, and parameters. The following picture shows the Scalar UI with the test route: ![Scalar](https://mokkapps.de/blog/document-your-nuxt-endpoints-with-open-api-and-visualize-with-swagger-or-scalar/scalar.png) The next picture shows the Swagger UI with the test route: ![Swagger](https://mokkapps.de/blog/document-your-nuxt-endpoints-with-open-api-and-visualize-with-swagger-or-scalar/swagger.png) ## StackBlitz Demo Try it yourself in this demo: :stackblitz{project-id="nuxt-blog-open-api"} ## Conclusion If you liked this article, follow me on [X](https://x.com/mokkapps){rel=""nofollow""} to get notified about my new blog posts and more content. Alternatively (or additionally), you can [subscribe to my weekly Vue & Nuxt newsletter](https://weekly-vue.news){rel=""nofollow""}: # Focus & Code Diff in Nuxt Content Code Blocks Custom code blocks are essential for my blog as my articles usually contain a lot of code snippets. My blog is powered by [Nuxt Content v2](https://content.nuxtjs.org/){rel=""nofollow""}, which is a [Nuxt 3](https://v3.nuxtjs.org/){rel=""nofollow""} module. I already wrote an article about how you can [create custom code blocks](https://mokkapps.de/blog/how-to-create-a-custom-code-block-with-nuxt-content-v2) using Nuxt Content v2. In this article, I'll show you how to focus certain lines of your code or highlight a diff inside a custom code block. This feature is adopted from [Vitepress](https://vitepress.dev/guide/markdown#focus-in-code-blocks){rel=""nofollow""} which provides similar functionality. ## Focus Lines Sometimes, you want to focus on certain lines of your code. For example, you want to highlight the most important lines of your code snippet. In our example, we can do this by adding `// [!code focus]` in the line that should be highlighted: ````markdown ```js [focus.js] export default { data() { return { msg: 'Focused!', // [!code focus] } }, } ``` ```` The above code results in the following code block: ```js [focus.js] export default { data() { return { msg: 'Focused!', // [!code focus] } }, } ``` If you hover over the code block, you can see that the whole code is visible without any highlighting. ## Diff Lines Another use case is to highlight a diff inside a code block. For example, you want to show the difference between two code snippets. In our example, we can do this by adding `// [!code ++]` in the line that should be highlighted as added and `// [!code --]` in the line that should be highlighted as removed: ````markdown ```js [diff.js] export default { data () { return { msg: 'Removed' // [!code --] msg: 'Added' // [!code ++] } } } ``` ```` The above code results in the following code block: ```js [diff.js] export default { data () { return { msg: 'Removed' // [!code --] msg: 'Added' // [!code ++] } } } ``` ## Implementation ::note Update from January 2024: The following implementation is only necessary if you don't use [shikiji](https://shikiji.netlify.app/packages/transformers){rel=""nofollow""}, which provides transformers for the focus and diff syntax. :: Let's now take a look at the implementation of this feature. We opt out of the default code highlighting provided by Nuxt Content and handle it ourselves. We use [Shiki](https://github.com/shikijs/shiki/){rel=""nofollow""} to highlight the code, which is also used by Nuxt Content under the hood. The process of custom rendering of code blocks is documented in [the Shiki README](https://github.com/shikijs/shiki#custom-rendering-of-code-blocks){rel=""nofollow""}. Let's start by writing a composable that returns a Shiki highlighter instance. As we'll have [multiple Shiki instances on the same page](https://github.com/shikijs/shiki#multiple-shiki-instances-on-the-same-page){rel=""nofollow""} we need to make sure that we only create one instance and reuse it. This is achieved by putting the highlighter instance in a `ref` **outside** of the composable. Additionally, the composable exports the `renderToHtml` function from Shiki which we use later to render the highlighted code to HTML: ```ts [composables/useShikiHighlighter.ts] import { getHighlighter, Highlighter, renderToHtml } from 'shiki-es' const highlighter = ref(null) export const useShikiHighlighter = () => { if (highlighter.value === null) { getHighlighter({ theme: 'dark-plus', themes: ['dark-plus'], langs: ['css', 'scss', 'js', 'ts', 'groovy', 'java', 'diff', 'vue', 'html', 'json', 'xml'], }).then((_highlighter) => { highlighter.value = _highlighter }) } return { highlighter, renderToHtml } } ``` Now it's time to create the custom code block component. ::note If you never did this before, I'd recommend you to read my article about [how to create a custom code block with Nuxt Content v2](https://mokkapps.de/blog/how-to-create-a-custom-code-block-with-nuxt-content-v2) first. :: The basic structure of our custom `ProseCode` component looks like this: ```vue [components/content/ProseCode.vue] ``` Let's extend that component by using our `useShikiHighlighter` composable to highlight the code: ```vue [components/content/ProseCode.vue] {4-5,7-34,39} ``` Let's go through the code step by step: 1. We create a `html` ref that will contain the highlighted code as HTML 2. We use the `useShikiHighlighter` composable to get the highlighter instance and the `renderToHtml` function 3. We watch the `highlighter` ref and call `renderToHtml` when the highlighter is available 4. We use the `codeToThemedTokens` function to get the tokens for the code 5. We use the `renderToHtml` function to render the tokens to HTML 6. We use the `elements` option to customize the HTML output of the code block. The `line` element can be used to customize the HTML output of each line. We use it to add a `div` around each line to make it possible to highlight single lines. 7. We use the `v-html` directive to render the highlighted code as HTML You can now easily extend the code to highlight lines if certain comments are inside the code passed via props: ```vue [components/content/ProseCode.vue] {7-12,14-18,35-37,39-41,47-51,55-61,66-71,83-112} skip-line-highlighting ``` The idea is quite simple: We look for our predefined set of comments inside the code and add a custom class to the line if the comment is present. We can then use this class to style the line accordingly. Of course, you also need to remove these comments from the code before passing it to the `codeToThemedTokens` function. Otherwise, the comments would be rendered as HTML. ## StackBlitz Demo The code for this article is interactively available on StackBlitz: :stackblitz{project-id="nuxt-content-code-focus-diff"} ## Conclusion In this article, you learned how to use the Nuxt content module to render code blocks that highlight single lines or highlight lines that were added or removed. By opting out of the default code highlighting of the Nuxt content module, you can use the Shiki library to render any custom code block that you need. I like such small customizations that can make a big difference in the user experience. I hope you enjoyed this article and learned something new. If you liked this article, follow me on [Twitter](https://twitter.com/mokkapps){rel=""nofollow""} to get notified about new blog posts and more content from me. Alternatively (or additionally), you can [subscribe to my weekly Vue newsletter](https://weekly-vue.news){rel=""nofollow""}: # How I Built A Custom Stepper/Wizard Component Using The Angular Material CDK **Update 12.02.2018:** *Meanwhile, I have created [a PR](https://github.com/angular/material2/pull/14710){rel=""nofollow""} to the Angular Material repository and added there an [official guide](https://material.angular.io/guide/creating-a-custom-stepper-using-the-cdk-stepper){rel=""nofollow""}* I recently had to refactor a quite complex legacy Angular component and want to share my experiences with you. ## The Legacy Component The component should look this way per design: ![Angular Legacy Component Design](https://mokkapps.de/blog/how-i-built-a-custom-stepper-wizard-using-angular-material-cdk/legacy-component-design_nkx1kh.svg) You can freely navigate through the content by clicking either the navigation arrows or clicking on a certain step in the navigation area at the bottom. The HTML template of the legacy component looked similar to this simple example: ```html
Content 1
Content 2
``` So basically, there was a `div` container for each content page with multiple `*ngIf` statements. As only one content container could be visible per time, the `*ngIf` directives controlled their visibility. Maybe at first glance, this sounds not that bad for you, but this approach had some significant problems: - It contained large and confusing `ngIf` statements. - Accessibility was not considered. - Keyboard interactions were not supported by default. - Managing which state is active had to be implemented manually. - It is a custom solution that needs to be tested. - It provided a non-scalable component architecture. Additionally, we got a new requirement: It should be possible to switch between the content pages linearly. That means going from the first content page to the second content page should only be possible if the first page is completed, and going backward should not be allowed. ## Refactoring To fulfill the new requirement, I started research for existing components that provide a similar logic and found, for example, [Angular Archwizard](https://github.com/madoar/angular-archwizard){rel=""nofollow""}. This excellent component also worked fine with the latest Angular version, but I could not easily modify the styling for our design requirements. So I continued my research and stumbled upon the [Angular Material CDK Stepper](https://material.angular.io/cdk/stepper/overview){rel=""nofollow""}, which was exactly what I was looking for. ## Angular Material CDK On the [official website](https://material.angular.io/cdk/categories){rel=""nofollow""}, they describe the Component Dev Kit (CDK) as: > The Component Dev Kit (CDK) is a set of tools that implement common interaction patterns whilst being unopinionated about their presentation. It represents an abstraction of the core functionalities found in the Angular Material library, without any styling specific to Material Design. Think of the CDK as a blank state of well-tested functionality upon which you can develop your own bespoke components. The CDK is divided into two parts: "Common Behaviors" and "Components". ### Common Behaviors > Tools for implementing common application features This is a list of common behaviors provided by the CDK: ![Angular Material Common Behaviors](https://mokkapps.de/blog/how-i-built-a-custom-stepper-wizard-using-angular-material-cdk/cdk-common-behavior_qgdyxu.png) ### Components > Unstyled components with useful functionality The following image shows the list of components provided by the CDK: ![Angular Material Components](https://mokkapps.de/blog/how-i-built-a-custom-stepper-wizard-using-angular-material-cdk/cdk-components_y2z4ru.png) ### CDK Stepper The [CdkStepper](https://material.angular.io/cdk/stepper/overview){rel=""nofollow""} was exactly what I was looking for: A well-tested stepper functionality that I can design however I want to. It consists of a `CdkStep` used to manage the state of each step in the stepper and the `CdkStepper`, which contains the steps (`CdkStep`) and primarily handles which step is active. ### Getting Started It is straightforward to add the CDK to your Angular project: ```bash npm install --save @angular/cdk ``` Or alternatively for `Yarn`: ```bash yarn add @angular/cdk ``` You also need to add the `CdkStepperModule` to your Angular module: ```typescript import { NgModule } from '@angular/core' import { BrowserModule } from '@angular/platform-browser' import { CdkStepperModule } from '@angular/cdk/stepper' // this is the relevant important import { AppComponent } from './app.component' @NgModule({ imports: [BrowserModule, CdkStepperModule], // add the module to your imports declarations: [AppComponent], bootstrap: [AppComponent], }) export class AppModule {} ``` ### Demo Stepper Project As the [official documentation](https://material.angular.io/cdk/stepper/overview){rel=""nofollow""} does not provide any code examples, I created a [simple demo project on Stackblitz](https://stackblitz.com/edit/angular-basic-cdk-stepper?embed=1&ctl=1&file=src/app/custom-stepper/custom-stepper.component.ts){rel=""nofollow""} which I want to describe in the following sections. #### Create CustomStepperComponent The first step was to create a new Angular component for the `CdkStepper` to be able to modify it. Therefore, the component needs to extend from `CdkStepper`. The following example is a minimal implementation of a custom CDK stepper component: ```typescript import { Directionality } from '@angular/cdk/bidi' import { ChangeDetectorRef, Component } from '@angular/core' import { CdkStepper } from '@angular/cdk/stepper' @Component({ selector: 'app-custom-stepper', templateUrl: './custom-stepper.component.html', styleUrls: ['./custom-stepper.component.css'], providers: [{ provide: CdkStepper, useExisting: CustomStepperComponent }], }) export class CustomStepperComponent extends CdkStepper { constructor(dir: Directionality, changeDetectorRef: ChangeDetectorRef) { super(dir, changeDetectorRef) } onClick(index: number): void { this.selectedIndex = index } } ``` The HTML template for this basic component: ```html
``` We can now use our new `CustomStepperComponent` in another component: ```html ``` You must wrap each step inside a `` tag. For multiple steps, you can of course, use `*ngFor` and use your custom step component inside: ```html ``` ### Linear Mode The above example allowed the user to navigate between all steps freely. The `CdkStepper` additionally provides the [linear mode](https://material.angular.io/cdk/stepper/overview#linear-stepper){rel=""nofollow""}, which requires the user to complete previous steps before proceeding. You can either use a single form for the entire stepper or a form for each step to validate if a step is completed. Alternatively, you can pass the `completed` property to each step and set the property value depending on your logic without using a form. A simple example without using forms could look this way: ```html ``` ```typescript export class MyComponent { completed = false completeStep(): void { this.completed = true } } ``` The steps are marked as `editable="false"` which means that the user cannot return to this step once it has been marked as completed. It is impossible to navigate to the second step until the first one has been completed by clicking the `Complete Step` button. If you are then on step 2 it is impossible to navigate back to step 1. ## Conclusion I am pleased with the `CdkStepper,` and it provided all the functionality I needed to refactor my legacy component. It was not necessary to write tests for this logic, and it automatically includes keyboard interaction support and cares about accessibility. My advice is: If you ever need to implement a common behavior or component logic for your Angular application, please first look at the Angular Material CDK or similar libraries. Do not implement them yourself, as you will never get the same level of quality as from a maintained, widely-used open-source project like Angular Material. # How I Built A Self-Updating README On My Github Profile On [Hacker News](https://news.ycombinator.com/item?id=23807881){rel=""nofollow""} I discovered the article [Building a self-updating profile README for GitHub](https://simonwillison.net/2020/Jul/10/self-updating-profile-readme/){rel=""nofollow""}. I was very fascinated about this new [GitHub](https://github.com){rel=""nofollow""} feature and wanted to build something similar for [my GitHub profile](https://github.com/Mokkapps){rel=""nofollow""}. ## GitHub Profile README GitHub profile READMEs are a new feature that allows users to have the content of a README markdown file rendered at the profile page. To use this feature you just need to create a new repository that has the same name as your GitHub account. Mine is located at `github.com/mokkapps/mokkapps`.This repository needs to be public and initialized with a README: ![Create a new GitHub repository](https://mokkapps.de/blog/how-i-built-a-self-updating-readme-on-my-git-hub-profile/create-repo_abqnnh.jpg) Now you will see a new section at the top of your profile page which renders the content of this new README file: ![Cover Image](https://mokkapps.de/blog/how-i-built-a-self-updating-readme-on-my-git-hub-profile/cover_y020iu.jpg) In my example, I am showing five links to the latest blog posts on my website and the latest tweet I published on Twitter. This information is automatically updated and I want to show you how I implemented this functionality. ## Automatically Update The README All the magic is happening in a GitHub Action defined in [build.yml](https://github.com/Mokkapps/mokkapps/blob/master/.github/workflows/build.yml){rel=""nofollow""}. This action runs on every Git push, every 32 minutes past the hour (configured via a cron schedule) or by manually clicking a button in the GitHub Action UI (by using `workflow_dispatch` event). The workflow performs these actions: 1. Fetches the latest tweet from my Twitter account using the Twitter API, renders it to a PNG using headless Chrome (from an R script) and saves it as PNG which is then embedded in the README (taken from [zhiiiyang](https://github.com/zhiiiyang){rel=""nofollow""}). 2. Runs a JavaScript script which fetches the five latest blog posts from my RSS feed and generates the final `README.md` (inspired by [simonw](https://github.com/simonw){rel=""nofollow""}) 3. Commits and pushes the changes to the master branch of this repo The JS script is quite simple and has only [\~50 lines of code](https://github.com/Mokkapps/mokkapps/blob/master/index.js){rel=""nofollow""}. ## Conclusion The GitHub profile READMEs are a cool feature and by using GitHub Actions it can help us to provide up-to-date information for profile visitors. But most importantly I had a lot of fun building it and this is more important than everything else. # How I Built a Twitter Keyword Monitoring Using a Serverless Node.js Function With AWS Amplify In this article, I will demonstrate to you how I built a simple serverless Node.js function on [AWS](https://aws.amazon.com/){rel=""nofollow""} that sends me a daily email with a list of tweets that mention me on [Twitter](https://twitter.com/mokkapps){rel=""nofollow""}. Recently, I used [Twilert](https://twilert.com/){rel=""nofollow""} and [Birdspotter](https://birdspotter.net/){rel=""nofollow""} for that purpose, which are specialized tools for Twitter keyword monitoring. But their free plans/trials don't fulfill my simple requirements, so I decided to implement them independently. ## Prerequisites I chose [again](https://www.mokkapps.de/categories/aws){rel=""nofollow""} AWS Amplify to deploy the serverless function to [AWS](https://aws.amazon.com/){rel=""nofollow""}. If you don't already have an AWS account, you'll need to create one to follow the steps outlined in this article. Please follow [this tutorial](https://portal.aws.amazon.com/billing/signup?redirect_url=https%3A%2F%2Faws.amazon.com%2Fregistration-confirmation#/start){rel=""nofollow""} to create an account. Next, you need to install and configure the [Amplify Command Line Interface (CLI)](https://docs.amplify.aws/start/getting-started/installation/q/integration/js/#install-and-configure-the-amplify-cli){rel=""nofollow""}. The serverless function will need access to secrets stored in the [AWS Secret Manager](https://aws.amazon.com/secrets-manager/){rel=""nofollow""}. My article [“How to Use Environment Variables to Store Secrets in AWS Amplify Backend”](https://www.mokkapps.de/blog/how-to-use-environment-variables-to-store-secrets-in-aws-amplify-backend/){rel=""nofollow""} will guide you through this process. ## Add Serverless Function to AWS The first step is to add a new Lambda (serverless) function with the Node.js runtime to the Amplify application. The function gets invoked on a recurring schedule. In my case, it will be invoked every day at 08:00 PM. Let's add the serverless function using the Amplify CLI: ```bash ▶ amplify add function ? Select which capability you want to add: Lambda function (serverless function) ? Provide an AWS Lambda function name: twittersearchfunction ? Choose the runtime that you want to use: NodeJS ? Choose the function template that you want to use: Hello World ? Do you want to configure advanced settings? Yes ? Do you want to access other resources in this project from your Lambda function? No ? Do you want to invoke this function on a recurring schedule? Yes ? At which interval should the function be invoked: Daily ? Select the start time (use arrow keys): 08:00 PM ? Do you want to enable Lambda layers for this function? No ? Do you want to configure environment variables for this function? No ? Do you want to configure secret values this function can access? No ? Do you want to edit the local lambda function now? No ``` ## Get a list of tweets for a specific Twitter keyword Now it's time to write the JavaScript code that returns a list of tweets for a given keyword. Let's start by writing the `twitter-client.js` module. This module uses [FeedHive’s Twitter Client](https://github.com/FeedHive/twitter-api-client){rel=""nofollow""} to access the [Twitter API](https://developer.twitter.com/en/docs/twitter-api){rel=""nofollow""}. The first step is to initialize [the Twitter API client](https://github.com/FeedHive/twitter-api-client){rel=""nofollow""} and trigger the request: ```js const mokkappsTwitterId = 481186762 const searchQuery = 'mokkapps' const searchResultCount = 100 const fetchRecentTweets = async (secretValues) => { // Configure Twitter API Client const twitterClient = new twitterApiClient.TwitterClient({ apiKey: secretValues.TWITTER_API_KEY, apiSecret: secretValues.TWITTER_API_KEY_SECRET, accessToken: secretValues.TWITTER_ACCESS_TOKEN, accessTokenSecret: secretValues.TWITTER_ACCESS_TOKEN_SECRET, }) // Trigger search endpoint: https://github.com/FeedHive/twitter-api-client/blob/main/REFERENCES.md#twitterclienttweetssearchparameters const searchResponse = await twitterClient.tweets.search({ q: searchQuery, count: searchResultCount, result_type: 'recent', }) // Access statuses from response const statuses = searchResponse.statuses } ``` Next, we want to filter the response into three groups: - Tweets: Tweets from the last 24 hours that were not published by my Twitter account and are no replies or retweets - Replies: Tweets from the last 24 hours that were not published by my Twitter account and are replies - Retweets: Tweets from the last 24 hours that were not published by my Twitter account and are retweets Let's start by the filtering the `statuses` response for "normal" tweets that are no replies or retweets: ```js {13-23} const isTweetedInLast24Hours = (status) => { const tweetDate = new Date(status.created_at) const now = new Date() const timeDifference = now.getTime() - tweetDate.getTime() const daysDifference = timeDifference / (1000 * 60 * 60 * 24) return daysDifference <= 1 } const fetchRecentTweets = async (secretValues) => { // ... const statuses = searchResponse.statuses const tweets = statuses.filter((status) => { const isNotOwnAccount = status.user.id !== mokkappsTwitterId const isNoReply = status.in_reply_to_status_id === null const isNoRetweet = status.retweeted_status === null return isNotOwnAccount && isNoReply && isNoRetweet && isTweetedInLast24Hours(status) }) } ``` Now we can filter for retweets and replies in a similar way: ```js const retweets = statuses.filter((status) => { const isNotOwnAccount = status.user.id !== mokkappsTwitterId const isRetweet = status.retweeted_status return isNotOwnAccount && isRetweet && isTweetedInLast24Hours(status) }) const replies = statuses.filter((status) => { const isNotOwnAccount = status.user.id !== mokkappsTwitterId const isReply = status.in_reply_to_status_id !== null return isNotOwnAccount && isReply && isTweetedInLast24Hours(status) }) ``` The last step is to map the results to a very simple HTML structure that will be rendered inside the email body: ```js {54} const { formatDistance } = require('date-fns') const mapStatus = (status) => { const { id_str: id, created_at, in_reply_to_screen_name, in_reply_to_status_id_str, text, retweet_count, favorite_count, user: { screen_name: user_screen_name, followers_count, created_at: userCreatedAt, friends_count }, } = status const createdAtLocaleString = new Date(created_at).toLocaleString() const url = `https://twitter.com/${user_screen_name}/status/${id}` const userUrl = `https://twitter.com/${user_screen_name}` const originalUrl = in_reply_to_screen_name ? `https://twitter.com/${in_reply_to_screen_name}/status/${in_reply_to_status_id_str}` : null const userCreatedDateDistance = formatDistance(new Date(), new Date(userCreatedAt)) return `

From ${user_screen_name} at ${createdAtLocaleString}

Followers: ${followers_count}, Following: ${friends_count}, Account Created: ${userCreatedDateDistance} ago

${text}

Tweet (Likes: ${favorite_count}, Retweets: ${retweet_count}) ${originalUrl ? `` : ''}
` } const fetchRecentTweets = async (secretValues) => { // ... const retweets = statuses .filter((status) => { const isNotOwnAccount = status.user.id !== mokkappsTwitterId const isRetweet = status.retweeted_status return isNotOwnAccount && isRetweet && isTweetedInLast24Hours(status) }) .map((status) => mapStatus(status)) } ``` This is the code for the whole `twitter-client.js` module: ```js const twitterApiClient = require('twitter-api-client') const { formatDistance } = require('date-fns') const mokkappsTwitterId = 481186762 const searchQuery = 'mokkapps' const searchResultCount = 100 const mapStatus = (status) => { const { id_str: id, created_at, in_reply_to_screen_name, in_reply_to_status_id_str, text, retweet_count, favorite_count, user: { screen_name: user_screen_name, followers_count, created_at: userCreatedAt, friends_count }, } = status const createdAtLocaleString = new Date(created_at).toLocaleString() const url = `https://twitter.com/${user_screen_name}/status/${id}` const userUrl = `https://twitter.com/${user_screen_name}` const originalUrl = in_reply_to_screen_name ? `https://twitter.com/${in_reply_to_screen_name}/status/${in_reply_to_status_id_str}` : null const userCreatedDateDistance = formatDistance(new Date(), new Date(userCreatedAt)) return `

From ${user_screen_name} at ${createdAtLocaleString}

Followers: ${followers_count}, Following: ${friends_count}, Account Created: ${userCreatedDateDistance} ago

${text}

Tweet (Likes: ${favorite_count}, Retweets: ${retweet_count}) ${originalUrl ? `` : ''}
` } const isTweetedInLast24Hours = (status) => { const tweetDate = new Date(status.created_at) const now = new Date() const timeDifference = now.getTime() - tweetDate.getTime() const daysDifference = timeDifference / (1000 * 60 * 60 * 24) return daysDifference <= 1 } const fetchRecentTweets = async (secretValues) => { const twitterClient = new twitterApiClient.TwitterClient({ apiKey: secretValues.TWITTER_API_KEY, apiSecret: secretValues.TWITTER_API_KEY_SECRET, accessToken: secretValues.TWITTER_ACCESS_TOKEN, accessTokenSecret: secretValues.TWITTER_ACCESS_TOKEN_SECRET, }) const searchResponse = await twitterClient.tweets.search({ q: searchQuery, count: searchResultCount, result_type: 'recent', }) const statuses = searchResponse.statuses const tweets = statuses .filter((status) => { const isNotOwnAccount = status.user.id !== mokkappsTwitterId const isNoReply = status.in_reply_to_status_id === null const isNoRetweet = status.retweeted_status === null return isNotOwnAccount && isNoReply && isNoRetweet && isTweetedInLast24Hours(status) }) .map((status) => mapStatus(status)) const retweets = statuses .filter((status) => { const isNotOwnAccount = status.user.id !== mokkappsTwitterId const isRetweet = status.retweeted_status return isNotOwnAccount && isRetweet && isTweetedInLast24Hours(status) }) .map((status) => mapStatus(status)) const replies = statuses .filter((status) => { const isNotOwnAccount = status.user.id !== mokkappsTwitterId const isReply = status.in_reply_to_status_id !== null return isNotOwnAccount && isReply && isTweetedInLast24Hours(status) }) .map((status) => mapStatus(status)) return { tweets, retweets, replies, } } module.exports = fetchRecentTweets ``` ## Serverless function code We can now use the `twitter-client.js` in our serverless function: ```js const AWS = require('aws-sdk') const nodemailer = require('nodemailer') const fetchRecentTweets = require('./twitter-client') const secretsManager = new AWS.SecretsManager() const responseHeaders = { 'Content-Type': 'application/json', } exports.handler = async (event) => { console.log(`👷 Function is ready to search for tweets`) const secretData = await secretsManager.getSecretValue({ SecretId: 'YOUR_SECRET_ID' }).promise() const secretValues = JSON.parse(secretData.SecretString) const transporter = nodemailer.createTransport({ service: secretValues.MAIL_HOST, auth: { user: secretValues.MAIL_USER, pass: secretValues.MAIL_PW, }, }) const defaultMailOptions = { from: secretValues.MAIL_USER, to: secretValues.MAIL_SUCCESS, subject: `[Mokkapps API] Twitter Search Results`, } try { // Fetch recent tweets const { tweets, replies, retweets } = await fetchRecentTweets(secretValues) // Skip sending email if we have no results if (tweets.length === 0 && replies.length === 0 && retweets.length === 0) { return { statusCode: 200, headers: responseHeaders, body: [], } } // Send email await transporter.sendMail({ ...defaultMailOptions, html: `

Tweets that mentioned "mokkapps" in the last 24 hours

${tweets.length === 0 ? '

No results

' : tweets.join('')}

Replies that mentioned "mokkapps" in the last 24 hours

${replies.length === 0 ? '

No results

' : replies.join('')}

Retweets that mentioned "mokkapps" in the last 24 hours

${retweets.length === 0 ? '

No results

' : retweets.join('')} `, }) return { statusCode: 200, headers: responseHeaders, body: JSON.stringify({ tweets, replies, retweets }), } } catch (e) { console.error('☠ Twitter Search Function Error:', e) return { statusCode: 500, headers: responseHeaders, body: e.message ? e.message : JSON.stringify(e), } } } ``` At this point, we can publish our function by running: ```bash amplify push ``` If we successfully pushed the function to AWS, we can manually invoke the function in [AWS Lamba](https://aws.amazon.com/lambda/){rel=""nofollow""} by clicking the "Test" button: ![AWS Lambda Function Test](https://mokkapps.de/blog/how-i-built-a-twitter-keyword-monitoring-using-a-serverless-node-js-function-with-aws-amplify/aws-lambda-test_utsxwg.jpg) The serverless function should then send an email with a list of tweets if someone mentioned the monitored keyword in the last 24 hours: ![Email sent from serverless Node.js function](https://mokkapps.de/blog/how-i-built-a-twitter-keyword-monitoring-using-a-serverless-node-js-function-with-aws-amplify/twitter-keyword-monitoring-email_pjmbj8.jpg) ## Conclusion I had a lot of fun building this simple serverless function to monitor keywords on Twitter. Serverless functions are a perfect choice for such a monitoring tool, as we only have to pay for the execution time of the serverless function. What do you think about my solution? Leave a comment and tell me how you monitor your Twitter keywords. If you liked this article, follow me on [Twitter](https://twitter.com/mokkapps){rel=""nofollow""} to get notified about new blog posts and more content from me. Alternatively (or additionally), you can also [subscribe to my newsletter](https://weekly-vue.news){rel=""nofollow""}. # How I Built My Website With Hugo And Netlify At the end of last year, I started working on my [private portfolio website](https://www.mokkapps.de){rel=""nofollow""} and researching how to build and deploy such static websites quickly. ## The Tools ### Hugo I discovered [Hugo](https://gohugo.io/){rel=""nofollow""}, a viral open-source static site generator. It is speedy and flexible, and it is fun to build websites with this generator. Just follow the [official "Quick Start"](https://gohugo.io/getting-started/quick-start/){rel=""nofollow""}, and you will be running a beautiful static website locally on your machine in less than five minutes. There are many [themes](http://themes.gohugo.io/){rel=""nofollow""} available, which are often highly customizable. The basic workflow looks this way: - Serve your website locally using `hugo serve` - Generate the static website content using `hugo` - Publish the generated website content (see next chapter) ### Netlify [Netlify](https://www.netlify.com/){rel=""nofollow""} provides a platform to automate code to create high-performant sites and web apps. Push your code and Netlify takes care of the rest. Setting up Netlify is easy if your code is already on GitHub, GitLab or Bitbucket: Select your Git provider, define which build commands should be executed and in which folder the final content is located. For more details, check the [official "Getting Started" guide](https://www.netlify.com/docs/#getting-started){rel=""nofollow""}. Netlify provides a free subscription model, which I am currently using. Additionally, there are many additional features which you have to pay for. Check [the official Pricing page](https://www.netlify.com/docs/#getting-started){rel=""nofollow""} for more details. ## My Setup I started using a [simple static website](https://github.com/Mokkapps/mokkapps-website){rel=""nofollow""} which I hosted manually on a web server without using a service like Netlify. The custom domain I used is `www.mokkapps.de`. Some month ago, I decided to start my tech blog about software development topics, and I wanted to continue using Hugo. Therefore I had to choose another Hugo theme as [the current theme](https://github.com/sethmacleod/prologue){rel=""nofollow""} was not capable of content management which is necessary for a blog. After a short research, I found [KISS](https://github.com/ribice/kiss){rel=""nofollow""}, which had the style and the functionality I was looking for. Blog posts are written in [Markdown](https://en.wikipedia.org/wiki/Markdown){rel=""nofollow""}, which I like for writing articles and other text-based stuff. I wanted the blog to be accessible via `www.mokkapps.de/blog`, so I had to generate the blog page using Hugo and drop it on the web server in a `blog` folder. This was a manual process that I wanted to automate. Luckily, platforms like Netlify can help at automating such tasks. I have integrated both websites in Netlify but still had to made the connection from one Hugo website to another. Netlify provides [Redirects](https://www.netlify.com/docs/redirects/){rel=""nofollow""} for such cases. So I added a static `_redirects` file to my main page, and now it correctly links to the second page hosted on Netlify: `/blog/* https://mokkapps-blog.netlify.com/:splat 200` Now all I have to do is to write my blog posts or make any other changes on my websites and push them to my Git provider. Netlify then automatically builds and deploys the pages. ## Conclusion It's fun to build and deploy websites using services like Hugo and Netlify. I highly recommend looking at them, and maybe you can need them for your current or future projects. ## Links - [Source Code Website](https://github.com/Mokkapps/mokkapps-website){rel=""nofollow""} - [Source Code Blog Website](https://github.com/Mokkapps/mokkapps-blog){rel=""nofollow""} - [Hugo](https://gohugo.io/){rel=""nofollow""} - [Netlify](https://www.netlify.com/){rel=""nofollow""} # How I Increased My Productivity With Visual Studio Code In this post, I will describe how I increased my productivity by learning to use [Visual Studio Code](https://code.visualstudio.com/){rel=""nofollow""} more efficiently. But in general, always consider this advice as it is essential: > Learn your IDE/Editor so that you can use it in the most efficient way! ## Why Is This Important Looking back at myself as a programmer at the beginning of my professional software developer career, I would give myself the advice mentioned above. In my first days as a developer, I did most of my code interactions with the mouse and did not optimize my IDE or text editor. Today I think I can navigate my code more efficiently and have more time for more important things. [![](https://i.imgflip.com/2beoio.jpg "made at imgflip.com")](https://imgflip.com/i/2beoio) ## My Productivity Tips ### Learn The Most Important Keyboard Shortcuts In my opinion, this is the most crucial step you can take as a developer. Take the time and learn the most often used shortcuts you need throughout the day. Here are some of my most used [OS X shortcuts](https://code.visualstudio.com/shortcuts/keyboard-shortcuts-macos.pdf){rel=""nofollow""}: > If you are a Windows or Linux user, please check the appropriate shortcuts: [Windows Shortcuts](https://go.microsoft.com/fwlink/?linkid=832145){rel=""nofollow""}, > [Linux Shortcuts](https://go.microsoft.com/fwlink/?linkid=832144){rel=""nofollow""}. - `CMD + P`: Opens the command palette, and you can search for any file. Example: Enter *cdcts* to search for `customer-details.component.ts`, which is the fastest way to jump to a specific file. You should use this approach instead of navigating in the *Explorer* by mouse. ![Quick Open](https://code.visualstudio.com/assets/docs/getstarted/tips-and-tricks/QuickOpen.gif) - `CMD + D`: Finds and selects the next match for the currently selected word. ![Multicursor word](https://code.visualstudio.com/assets/docs/editor/codebasics/multicursor-word.gif) - `CMD + arrow down/up`: Move cursor to end/beginning of the current file - `CMD + arrow right/left`: Move cursor to end/beginning of current line - `Option + arrow right/left`: Move cursor by word - `Option + Shift + arrow right/left`: Make selection by word - `Option + arrow up/down`: Move current line up or down - `Option + Shift + arrow up/down`: Duplicate current line one line above or below - `CMD + Shift + K`: Delete current line - `CMD + B`: Toggle Sidebar visibility - `CMD + Shift + F`: Search across files - `CMD + .`: Provides quick fixes. For example, I use this mostly to automatically rearrange my imports by the given linting rules. - `CMD + Option + arrow left/right`: - [Multi-cursor](https://code.visualstudio.com/docs/editor/codebasics#_multiple-selections-multicursor){rel=""nofollow""}: Multi-cursor are very helpful to edit code on multiple lines. ![Multi-cursor](https://code.visualstudio.com/assets/docs/editor/codebasics/multicursor.gif) See [Basic Editing](https://code.visualstudio.com/docs/editor/codebasics){rel=""nofollow""} for other basic shortcuts and details. #### Command Palette With `CMD + Shift + P`, you can open the *Command Palette*, a powerful tool in Visual Studio Code. Start typing any command you want to execute, and you will find it (if it is available). Additionally, you can see the corresponding shortcut next to the command. This is also an elegant way to learn the keyboard shortcuts for your most-used commands. ![Command Palette](https://code.visualstudio.com/assets/docs/getstarted/tips-and-tricks/OpenCommandPalatte.gif) ### Emmet I was blown away as I recognized that VS Code supports Emmet by default and how powerful it is. Emmet is a markup expansion tool that makes writing HTML much more effortless. It is easy to learn and has a simple syntax. Checkout the [Emmet Cheat Sheet](https://docs.emmet.io/cheat-sheet/){rel=""nofollow""} to learn more about the Emmet syntax. And here you can see Emmet in action: :iframe{allow="autoplay; encrypted-media" allowFullScreen="true" frameBorder="0" height="400" src="https://www.youtube.com/embed/e1zhJjM4p0k" width="700"} ### Use Workspaces One thing I started to use recently is [multi-root workspaces](https://code.visualstudio.com/docs/editor/multi-root-workspaces){rel=""nofollow""} in VS Code. They can be beneficial when you are working on several related projects simultaneously. For example, I have created a workspace for all my private projects. Using workspaces, I do not have to handle multiple VS Code editor windows but always work with one window, including my current workspace. :iframe{allow="autoplay; encrypted-media" allowFullScreen="true" frameBorder="0" height="400" src="https://www.youtube.com/embed/xYyPAUukFfg?start=30" width="700"} ### Use Plugins Subsequent are some of my most used VS Code plugins: - [Auto Close Tag](https://github.com/formulahendry/vscode-auto-close-tag){rel=""nofollow""}: Automatically add HTML/XML close tag - [Auto Rename Tag](https://github.com/formulahendry/vscode-auto-rename-tag){rel=""nofollow""}: Auto rename paired HTML/XML tag - [Better Comments](https://github.com/aaron-bond/better-comments){rel=""nofollow""}: Improve your code commenting by annotating with alert, informational, TODOs, and more - [Bracket Pair Colorizer](https://github.com/CoenraadS/BracketPair){rel=""nofollow""}: A customizable extension for colorizing matching brackets - [Code Spell Checker](https://github.com/Jason-Rev/vscode-spell-checker){rel=""nofollow""}: Spelling checker for source code - [Git History](https://github.com/DonJayamanne/gitHistoryVSCode){rel=""nofollow""}: View git log, file history, compare branches or commits - [Mark Jump](https://github.com/spywhere/vscode-mark-jump){rel=""nofollow""}: Jump to the marked section in the code - [Markdown All In One](https://github.com/neilsustc/vscode-markdown){rel=""nofollow""}: All you need to write Markdown (keyboard shortcuts, table of contents, auto preview and more) - [npm](https://github.com/Microsoft/vscode-npm-scripts){rel=""nofollow""}: npm support for VS Code - [npm Intellisense](https://github.com/ChristianKohler/NpmIntellisense){rel=""nofollow""}: Visual Studio Code plugin that autocomplete npm modules in import statements - [Prettier](https://github.com/prettier/prettier-vscode){rel=""nofollow""}: VS Code plugin for prettier/prettier (code formatting) - [Quick and Simple Text Selection](https://github.com/dbankier/vscode-quick-select){rel=""nofollow""}: Jump to select between quote, brackets, tags, etc What I did not mention here are all the framework-specific plugins. So, of course, I recommend installing available plugins for the framework/technology/programming language you are using. They can also save you a ton of time. ## Conclusion These are just some productivity tips that I can give you. Of course, VS Code provides many more features that can assist you in all matters (see links below). For example, VS Code releases each month a major update with many new features and improvements. Please read the release notes of these releases as they often contain new features which further can increase your productivity. And please take the time to learn your IDE/editor (if you haven't done it yet). This will make you a better programmer. ### Links - [VS Code Documentation](https://code.visualstudio.com/docs/){rel=""nofollow""} - [VS Code Updates](https://code.visualstudio.com/updates/){rel=""nofollow""} - [VS Code can do that?!](https://vscodecandothat.com/){rel=""nofollow""} # How I Replaced Google Analytics With a Private, Open-Source & Self-Hosted Alternative For me, it is important to see analytics about my portfolio website. This way, I can see which posts got the most views, which country my users are from, and which browser & operating system they are using. The simplest solution to add analytics to your site is [Google Analytics](https://analytics.google.com/analytics/web/){rel=""nofollow""} as it is free and easy to set up. But as we all know, this service is only free as we pay it indirectly by providing data to it. [What you need to know about Google Analytics and privacy](https://www.comparitech.com/blog/vpn-privacy/google-analytics-privacy/){rel=""nofollow""}. In this blog post, I will show you how I replaced [Google Analytics](https://analytics.google.com/analytics/web/){rel=""nofollow""} with [Umami](https://umami.is/){rel=""nofollow""} which is a simple, easy to use, self-hosted web analytics solution. ## Umami I chose [Umami](https://umami.is/){rel=""nofollow""} because it - is [open-source](https://github.com/mikecao/umami){rel=""nofollow""} - is privacy-focused - simple - easy to use - has a [beautiful UI](https://app.umami.is/share/ISgW2qz8/flightphp.com){rel=""nofollow""} - has [good documentation](https://umami.is/docs/about){rel=""nofollow""} ![Umami Intro](https://umami.is/intro.jpg) Umami does not provide a hosting solution. Therefore, we need to host the service on our own. All you need to get Umami up and running is a database (either MySQL or PostgreSQL) and a server that can run Node.js (10.13 or newer). Check the [list of available hosting solutions](https://umami.is/docs/hosting){rel=""nofollow""}. I will show you two different approaches I tried to host Umami. ### Running on Heroku > Heroku is a container-based cloud Platform as a Service (PaaS). Developers use Heroku to deploy, manage, and scale modern apps. The platform is elegant, flexible, and easy to use, offering developers the simplest path to getting their apps to market. You can read more about [Heroku](https://www.heroku.com/){rel=""nofollow""} on their ["What is Heroku?"](https://www.heroku.com/about){rel=""nofollow""} page. We can host Umami and a corresponding database for free on Heroku. The setup is well described in the [Umami documentation](https://umami.is/docs/running-on-heroku){rel=""nofollow""}. To get it running, I just had to modify the npm `start` script command to include the Heroku port: ```bash "start": "next start -p $PORT" ``` Using Heroku is for sure the easiest & fastest way to set up a running Umami instance but there is one drawback: It is expensive. I collected analytics data from my website for about 2 days and I quickly realized that the free "Hobby Dev" [Heroku Postgres plan](https://elements.heroku.com/addons/heroku-postgresql#pricing){rel=""nofollow""} will not be enough. ![Heroku Postgres Ressources](https://mokkapps.de/blog/how-i-replaced-google-analytics-with-a-private-open-source-and-self-hosted-alternative/heroku-postgres_icxvuu.jpg) This free plan includes 10,000 database rows and I filled \~1000 per day. So the free plan would be reached in about 10 days. The next "Hobby Basic" plan for 9$/month would include 10,000,000 rows which would last for approximately 27 years (assuming 1000 new rows per day, so no increasing traffic on my website). The "Standard 0" plan for 50$/month provides unlimited rows but this is way too much money I would spend for a self-hosted analytics solution. ### Running on DigitalOcean & Vercel An alternative to Heroku is to host the database on [Digital Ocean](https://m.do.co/c/833a8650eb62){rel=""nofollow""} and Umami on [Vercel](https://vercel.com/){rel=""nofollow""}. #### DigitalOcean [Digital Ocean](https://m.do.co/c/833a8650eb62){rel=""nofollow""} is an affordable cloud hosting provider. Starting with 5$/month you get a cloud server for personal use and can scale it up as needed. Using [this link](https://m.do.co/c/833a8650eb62){rel=""nofollow""} you get a $100 credit for the first 60 days. I host a MySQL database on DigitalOcean which required these steps to set up: 1. [Initial setup the server with Ubuntu 18.04](https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-18-04){rel=""nofollow""} 2. [Install MySQL on Ubuntu](https://www.digitalocean.com/community/tutorials/how-to-install-mysql-on-ubuntu-18-04){rel=""nofollow""} 3. Setup the MySQL database schema with the [Umami MySQL schema](https://github.com/mikecao/umami/blob/master/sql/schema.mysql.sql){rel=""nofollow""} 4. [Allow remote access to the database](https://www.digitalocean.com/community/questions/how-to-allow-remote-mysql-database-connection){rel=""nofollow""} ![Digital Ocean Droplet](https://mokkapps.de/blog/how-i-replaced-google-analytics-with-a-private-open-source-and-self-hosted-alternative/digital-ocean-droplet_gy4ygo.jpg) DigitalOcean also provides a [Node.js](https://www.digitalocean.com/community/questions/how-to-allow-remote-mysql-database-connection){rel=""nofollow""} droplet template that comes with Node.js, Ubuntu, and Nginx to host the Umami frontend. We will instead use [Vercel](https://vercel.com/){rel=""nofollow""} as it is completely free. #### Vercel [Vercel](https://vercel.com/){rel=""nofollow""} is the company behind the framework [Next.js](https://nextjs.org/){rel=""nofollow""} which is used by Umami and they provide a free frontend hosting service. As you can imagine, it is really easy to deploy a [Next.js](https://nextjs.org/){rel=""nofollow""} application on [Vercel](https://vercel.com/){rel=""nofollow""} as both applications are developed by the same company. The setup is described in the [official documentation](https://umami.is/docs/running-on-vercel){rel=""nofollow""}. ![Vercel Deployment](https://mokkapps.de/blog/how-i-replaced-google-analytics-with-a-private-open-source-and-self-hosted-alternative/vercel_mtcaku.jpg) If you now open the deployed Vercel app at `.vercel.app` you need to perform these steps - [Login](https://umami.is/docs/login){rel=""nofollow""} - [Add your website to Umami](https://umami.is/docs/add-a-website){rel=""nofollow""} - [Add tracking code to your website](https://umami.is/docs/collect-data){rel=""nofollow""} - Optional: Umami is also able to [track events](https://umami.is/docs/track-events){rel=""nofollow""} that occur on your website This should result in a working private, open-source, self-hosted analytics solution: ![Umami Dashboard](https://mokkapps.de/blog/how-i-replaced-google-analytics-with-a-private-open-source-and-self-hosted-alternative/umami-dashboard_z7bnoo.jpg)![Umami Realtime](https://mokkapps.de/blog/how-i-replaced-google-analytics-with-a-private-open-source-and-self-hosted-alternative/umami-realtime_gjlgd4.jpg) ## Conclusion I can sleep better as I now know that no more data is sent from my website to Google. I still have the possibility to track my website analytics but in a simpler and privacy-focused way. Setting up Umami is quite easy if you are familiar with software like Ubuntu and MySQL/Postgres. Of course, I know need to pay some money to store this analytics data on my server but for me, it is worth the money. # How I Replaced Revue With a Custom-Built Newsletter Service Using Nuxt 3, Supabase, Serverless, and Amazon SES Twitter will shut down [Revue](https://www.getrevue.co/){rel=""nofollow""} on January 18, 2023, which I previously used as a newsletter provider for [Weekly Vue News](https://weekly-vue.news){rel=""nofollow""}. There exist potent alternatives like [Substack](https://substack.com/){rel=""nofollow""}, [Buttondown](https://buttondown.email/){rel=""nofollow""}, [beehiv](https://www.beehiiv.com/){rel=""nofollow""}, and more. But I decided to build a custom solution for these reasons: - Manage my content as Markdown files in my project's repository. - Emails can use the same CSS styles as the website. - A cheap solution that does not get too expensive. This article explains how I built a custom newsletter service using [Nuxt 3](https://nuxt.com){rel=""nofollow""}, [Supabase](https://supabase.com){rel=""nofollow""}, [Serverless](https://serverless.com){rel=""nofollow""}, and [Amazon SES](https://aws.amazon.com/ses/){rel=""nofollow""}. ::note My proposed solution is not only limited to the mentioned frameworks & tools. You could easily accomplish the same functionality with other frameworks & tools of your choice. :: ## Backend Let's start by looking at the backend code I use for my newsletter solution. ### Database Model I use [Supabase](https://supabase.com){rel=""nofollow""} to store two database tables. The first table stores the list of subscribers and has the following schema: - `id`: primary key as an integer - `created_at`: timestamp indicating when the user was added to the table - `email`: email address that should receive the newsletter - `verification_token`: a UUID used for the confirmation email - `unsubscribe_token`: a UUID used for the unsubscribe mechanism - `verified`: A boolean indicating if the user has confirmed the confirmation mail The second table stores the list of scheduled issues and has the following schema: - `id`: primary key as an integer - `issue_id`: the ID of the newsletter issue - `html`: the html string that should be sent to the subscribers in the body of the email - `title`: a string which will is used as a subject in the emails sent to the subscribers - `scheduled_at`: timestamp when the issue should or has been published - `published`: A boolean indicating if the issue is published - `send_count`: A number that stores how many subscribers the issue has been sent to - `beacon_data`: JSON object that stores analytics information ### Amazon SES I decided to send emails using Amazon SES; [this tutorial](https://aws.amazon.com/getting-started/hands-on/send-an-email/){rel=""nofollow""} will teach you how to set up and get started using Amazon SES. I use [Nodemailer](https://nodemailer.com/transports/ses/){rel=""nofollow""} with [SES transport](https://nodemailer.com/transports/ses/){rel=""nofollow""} to send my emails using Amazon SES. Nodemailer SES transport is a wrapper around `aws.SES` from the [@aws-sdk/client-ses](https://www.npmjs.com/package/@aws-sdk/client-ses){rel=""nofollow""} package. The main benefit is that **Nodemailer provides rate limiting for SES out of the box**. SES can tolerate short spikes, but you can’t flush all your emails at once and expect these to be delivered. Luckily, Amazon granted my request to increase the sending quota to 50,000 messages per day and a maximum sending rate of 14 messages per second. A quick note about [SES pricing](https://aws.amazon.com/ses/pricing/){rel=""nofollow""}: You pay only for what you use with no minimum fees or mandatory service usage. The current price is **$0.10/1000 emails**, which is cheap compared to other newsletter services like [Substack](https://substack.com/){rel=""nofollow""}, [Buttondown](https://buttondown.email/){rel=""nofollow""} or [beehiv](https://www.beehiiv.com/){rel=""nofollow""}. Enough theory; let's take a look at the code I wrote to wrap the Nodemailer integration: ```ts [lib/ses-client.ts] {7-20} import nodemailer from 'nodemailer' import aws from '@aws-sdk/client-ses' export const sendEmail = async (fromAddress: string, toAddress: string, subject: string, bodyHtml: string) => { const config = useRuntimeConfig() const ses = new aws.SES({ region: 'eu-central-1', credentials: { accessKeyId: config.MY_AWS_ACCESS_KEY_ID, secretAccessKey: config.MY_AWS_SECRET_ACCESS_KEY, }, }) const transporter = nodemailer.createTransport({ SES: { ses, aws }, sendingRate: 14, // max 14 messages/second }) return transporter.sendMail({ from: fromAddress, to: toAddress, subject, html: bodyHtml }) } ``` ### Subscribe ::note As my [newsletter website](https://weekly-vue.news){rel=""nofollow""} is built with Nuxt 3, I use [Nuxt server routes](https://nuxt.com/docs/guide/directory-structure/server#server-routes){rel=""nofollow""} for the backend implementation. Additionally, I use [Nuxt Supabase](https://supabase.nuxtjs.org/){rel=""nofollow""} as a wrapper around [supabase-js](https://github.com/supabase/supabase-js){rel=""nofollow""} to enable usage and integration within Nuxt. :: If a new user wants to subscribe to the newsletter, we need to trigger an endpoint that receives the user's email address: ```ts [server/api/subscribe.post.ts] import { serverSupabaseServiceRole } from '#supabase/server' import { v4 as uuidv4 } from 'uuid' import * as EmailValidator from 'email-validator' import { sendEmail } from '~/lib/ses-client' export default defineEventHandler(async (event) => { const client = serverSupabaseServiceRole(event) const body = await readBody(event) const { email } = body if (!email) { console.error('Email is required') return { error: 'Email is required' } } if (!EmailValidator.validate(email)) { console.error(`Email ${email} is invalid`) return { error: `Email ${email} is invalid` } } try { const verificationToken = uuidv4() const { error: insertError } = await client .from('newsletter-subscribers') .insert({ email, verification_token: verificationToken, unsubscribe_token: uuidv4() }) if (insertError) { console.error('Failed to insert subscriber', insertError) if (insertError.code === '23505') { return { error: 'You are already subscribed with this email.' } } return { error: insertError } } const html = `

Hey, thanks for signing up for my weekly Vue newsletter!

Before I can send you any more emails though, I need you to confirm your subscription by clicking this link:

👉 Confirm subscription​
` return await sendEmail(email, 'Confirm registration', html) } catch (e) { console.error('Failed to send email.', e) return { error: e } } }) ``` Let's analyze the above code. The first step is to validate the email and return an error if it is missing or invalid: ```ts [server/api/subscribe.post.ts] import * as EmailValidator from 'email-validator' if (!email) { console.error('Email is required') return { error: 'Email is required' } } if (!EmailValidator.validate(email)) { console.error(`Email ${email} is invalid`) return { error: `Email ${email} is invalid` } } ``` Next, we try to insert a new subscriber into the subscriber table with the provided email and return an error if we already have a subscriber with the given email address: ```ts [server/api/subscribe.post.ts] const verificationToken = uuid4() const { error: insertError } = await client .from('newsletter-subscribers') .insert({ email, verification_token: verificationToken, unsubscribe_token: uuidv4() }) if (insertError) { console.error('Failed to insert subscriber', insertError) if (insertError.code === '23505') { return { error: 'You are already subscribed with this email.' } } return { error: insertError } } ``` Finally, we send the confirmation mail that contains a link with the generated `verificationToken` as query parameter: ```ts [server/api/subscribe.post.ts] const html = `

Hey, thanks for signing up for my weekly Vue newsletter!

Before I can send you any more emails though, I need you to confirm your subscription by clicking this link:

👉 Confirm subscription​
` return await sendEmail(email, 'Confirm registration', html) ``` Clicking on this link in the frontend will trigger the following backend endpoint: ```ts [server/api/email-verification.ts] import { serverSupabaseServiceRole } from '#supabase/server' export default defineEventHandler(async (event) => { const client = serverSupabaseServiceRole(event) const query = getQuery(event) const { token } = query if (!token) { return { error: 'Verification token is missing' } } const { data: subscriberData, error: selectError } = await client .from('newsletter-subscribers') .select() .eq('verification_token', token) if (selectError) { console.error('Failed to confirm subscription', selectError) return { error: selectError.details } } else { const { error: updateError } = await client .from('newsletter-subscribers') .update({ verified: true }) .eq('verification_token', token) if (updateError) { console.error('Update error', updateError) return { error: updateError } } return { error: null } } }) ``` We query the subscriber database for an entry where the `verification_token` equals the given `token` query parameter. If an entry is found, we set its `verified` value to `true`. **A verified user is subscribed and will receive the newsletter emails**. ::note The advantages of using subscription confirmation emails and implementing a double opt-in process: - **Ensuring compliance with the General Data Protection Regulation (GDPR):** The GDPR requires that you obtain explicit consent from users before adding them to your newsletter subscriber list and processing their personal data, such as their email address. - **Ensuring that your newsletter subscribers are actively engaged:** By requiring confirmation of subscription, you can ensure that only users who actively want to receive your newsletters will be added to your subscriber list. This can help prevent accidental or unwanted subscriptions and improve the quality of your subscriber list. - **Maintaining a clean and accurate contact list:** By requiring confirmation of subscription, you can ensure that only those users who are truly interested in receiving your company updates will be added to your subscriber list. This can help you maintain a high-quality list of engaged and interested contacts. :: #### Unsubscribe Of course, we must provide a way to unsubscribe from the newsletter. It's mainly based on the `unsubscribe_token` column of the subscribers database table: ```ts [server/api/unsubscribe.post.ts] import { serverSupabaseServiceRole } from '#supabase/server' export default defineEventHandler(async (event) => { const client = serverSupabaseServiceRole(event) const body = await readBody(event) const { token } = body if (!token) { console.error('Token is required') return { error: 'Token is required' } } try { const { error: deleteError, data } = await client .from('newsletter-subscribers') .delete() .eq('unsubscribe_token', token) if (deleteError) { console.error(`Failed to unsubscribe "${token}"`, deleteError) return { error: deleteError } } return { message: `Successfully unsubscribed "${token}"` } } catch (e) { console.error(`Failed to unsubscribe "${token}"`, e) return { error: e } } }) ``` We query the subscriber database for an entry where the `unsubscribe_token` equals the given `token` query parameter. If an entry is found, we remove it from the database, so he will not receive any further newsletter emails. ### Schedule Issue We need to provide an endpoint to schedule an issue: ```ts [server/api/issue.post.ts] import { serverSupabaseServiceRole } from '#supabase/server' import { Database } from '~/types/supabase' export default defineEventHandler(async (event) => { const client = serverSupabaseServiceRole(event) const body = await readBody(event) const { issueId, html, title, scheduleDate } = body if (!issueId || !html || !title || !scheduleDate) { return { error: `Parameter missing, required are [issueId, html, title, scheduleDate]. Received: ${JSON.stringify(body)}`, } } const { error: insertIssueError } = await client.from('newsletter-issues').upsert( { issue_id: issueId, html: html, title: title, published: false, scheduled_at: scheduleDate, }, { onConflict: 'issue_id' } ) if (insertIssueError) { console.error('Failed to upsert issue', insertIssueError) return { error: 'Failed to upsert issue' } } return { error: null } }) ``` This simple function upserts an issue based on the given parameters in the event body. ### Serverless Cron Function My newsletter is sent every Monday at 3 pm. I wrote a serverless cron function that checks if a scheduled issue exists and then sends it to all subscribers. Therefore I used the [Serverless framework](https://serverless.com){rel=""nofollow""}. The serverless configuration: ```yaml [serverless/serverless.yml] org: org app: app service: service frameworkVersion: '3' provider: name: aws region: eu-central-1 runtime: nodejs14.x environment: SUPABASE_URL: ${ssm:secret-supabase-url} SUPABASE_SERVICE_KEY: ${ssm:secret-supabase-service-key} functions: publish: handler: publish.run timeout: 900 events: - http: method: 'POST' path: /newsletter/publish async: true cors: true # Invoke Lambda function weekly on Monday at 3pm - schedule: cron(0 14 ? * MON *) ``` And the corresponding code for the Lambda function. I removed the error handling in the following code snippet to keep the code clean and concise: ```js [serverless/publish.js] 'use strict' const supabase = require('@supabase/supabase-js') const aws = require('aws-sdk') const nodemailer = require('nodemailer') const FROM_ADDRESS = 'newsletter@weekly-vue.news' module.exports.run = async (event, context) => { let requestBody = event if (event.body) { try { requestBody = JSON.parse(event.body) } catch (error) { requestBody = event.body } } const time = new Date() console.log(`Cron function "${context.functionName}" ran at ${time} with event ${JSON.stringify(requestBody)}`) const supabaseClient = supabase.createClient(process.env.SUPABASE_URL, process.env.SUPABASE_SERVICE_KEY) const ses = new aws.SES() const transporter = nodemailer.createTransport({ SES: { ses, aws }, sendingRate: 14, // max 14 messages/second }) // get verified subscribers const { data: subscriberData, error: selectError } = await supabaseClient .from('newsletter-subscribers') .select() .eq('verified', true) // get stored issue that haven't been published let { data: unpublishedNewsletterIssues, error: selectIssuesError } = await supabaseClient .from('newsletter-issues') .select() .eq('published', false) /** ⚠️☠️ SENDING MAILS TO ALL SUBSCRIBERS ⚠️☠️ */ // find scheduled issue const todayDate = new Date() const issueScheduledForToday = unpublishedNewsletterIssues.find((issue) => { const issueScheduleDate = new Date(issue.scheduled_at) return ( todayDate.getFullYear() === issueScheduleDate.getFullYear() && todayDate.getMonth() === issueScheduleDate.getMonth() && todayDate.getDate() === issueScheduleDate.getDate() ) }) if (!issueScheduledForToday) { return { statusCode: 400, body: JSON.stringify({ message: `Found no unpublished issue that is scheduled for today: ${JSON.stringify( unpublishedNewsletterIssues )}`, }), } } else { const emailValues = await Promise.allSettled( subscriberData.map(async (subscriber) => { const { email, unsubscribe_token } = subscriber return transporter.sendMail({ from: FROM_ADDRESS, to: email, subject: issueScheduledForToday.title, html: issueScheduledForToday.html, }) }) ) const successEmails = emailValues.filter((v) => v.status === 'fulfilled') const failedEmails = emailValues.filter((v) => v.status === 'rejected') console.log('Result sending emails', { successEmails: successEmails.length, failedEmails: failedEmails.length }) // update published status const { error: updatePublishedError } = await supabaseClient .from('newsletter-issues') .update({ published: true, send_count: successEmails.length, }) .eq('issue_id', issueScheduledForToday.issue_id) if (updatePublishedError) { console.error('Failed to update published status', updatePublishedError) } return { statusCode: 200, body: JSON.stringify({ total: subscriberData.length, success: successEmails.length, failures: failedEmails.length, }), } } } ``` A lot is going on in this function; let's break it down: 1. Initialize Supabase & Nodemailer clients 2. Get all verified subscribers 3. Get all stored issues that haven't been published yet 4. Find the unpublished issue which `schedule_at` timestamp is today 5. Send this issue to all verified subscribers 6. Set `published` to true and update `send_count` in the stored issue ## Frontend Let's look at the frontend part of my custom-built newsletter solution. I won't explain every component, as I mainly use [Nuxt's Data Fetching composables](https://nuxt.com/docs/getting-started/data-fetching){rel=""nofollow""} to trigger the above-defined backend endpoints and display the result. But one interesting aspect is the generation of the HTML string I send via email to my subscribers. ### Generating HTML string of the rendered Markdown file I use [Nuxt Content](https://content.nuxtjs.org/){rel=""nofollow""} to store my newsletter issues as Markdown files. Here is a simple example: ```md [content/issues/3.md] --- title: 'Weekly Vue News #3 - Any Tip' date: '2023-01-02T13:00:00.231Z' id: 3 --- :issue-header Hi 👋 Have a nice week ☀️ :divider ## Vue Tip: Any Tip ## Curated Vue Content ::external-link{url="https://github.com/RomanHotsiy/commitgpt" title="🛠️ commitgpt"} 👉🏻 Automatically generate commit messages using ChatGPT. :: ## Quote of the week ## JavaScript Tip: Any Tip ## Curated Web Development Content ``` These files are rendered on a Nuxt page using `` from Nuxt Content : ```vue [pages/issues/[...slug\\].vue] {16} ``` Using [Supabase Auth](https://supabase.com/docs/guides/auth/overview){rel=""nofollow""} I provide a way to log in as admin and scheduling issues: ```vue [components/IssueAdminControls.vue] ``` Let's now focus on the `getHtml()` method in `IssueAdminControls.vue` that we use to generate an HTML string of the rendered Markdown content: ```ts const getHtml = (): string => { if (!props.contentHtml) { return '

Oops, here should be some content....

' } const allCSS = [...document.styleSheets] .map((styleSheet) => { try { return [...styleSheet.cssRules].map((rule) => rule.cssText).join('') } catch (e) { console.log('Access to stylesheet %s is denied. Ignoring...', styleSheet.href) } }) .filter(Boolean) .join('\n') return juice(` ${props.contentHtml.replace(/)[\s\S])*-->/g, '')} `) } ``` The `allCss` variable collects all CSS stylesheets attached to the current document and joins their textual representation as string. I then use [juice](https://www.npmjs.com/package/juice){rel=""nofollow""} to inline all CSS properties into the `style` attribute. ::note [Inline CSS styles](https://customer.io/blog/how-to-make-css-play-nice-in-html-emails-without-breaking-everything/#smart-css-approach-use-inline-css-for-styling){rel=""nofollow""} are a smart approach to style HTML emails. :: I use `props.contentHtml.replace(/)[\s\S])*-->/g, '')` to replace HTML comments from the `outerHTML` string that is passed to the component via the `contentHtml` property (check again the `pages/issues/[...slug].vue` component above). This setup worked well for my content and styles, but you likely need to adjust your implementation. To schedule an issue, I send a POST request to `/api/issue`, which I already explained in the backend section. ## Conclusion I’m delighted with my solution. I’m 100% in control of my content and the emails I send to my subscribers! It was a lot of hard work to build this thing, but I also learned a lot during this process. I hope this will help me grow my newsletter and keep the costs low for an increasing number of subscribers. A special thanks to [Simon Høiberg](https://twitter.com/SimonHoiberg){rel=""nofollow""} and [Michael Thiessen](https://twitter.com/MichaelThiessen){rel=""nofollow""} that provided the technical inspiration for this solution. Leave a comment if you have questions or feedback or can provide an alternative solution for such a custom-built newsletter service. If you liked this article, follow me on [Twitter](https://twitter.com/mokkapps){rel=""nofollow""} to get notified about new blog posts and more content from me. Alternatively (or additionally), you can [subscribe to my weekly Vue newsletter](https://weekly-vue.news){rel=""nofollow""}. # How I Set Up A New Angular Project I think [Angular](https://angular.io/){rel=""nofollow""} is the best choice for large enterprise applications. The basic project setup, which is generated using the [Angular CLI](https://cli.angular.io/){rel=""nofollow""} is good, but I prefer another way to set up a new project. In this article, I want to talk about these topics: - Using Nx instead of the Angular CLI - TypeScript configuration - Internationalization - UI Component Explorer - Domain-driven Design for your models - Error Handling - Build Complex Components - Miscellaneous ## Nx [Nx](https://nx.dev/angular/getting-started/what-is-nx){rel=""nofollow""} is not a replacement for the Angular CLI but uses the Angular CLI's power and enhances it with additional tools. Anything you can do with the Angular CLI can also be done with Nx, and you configure your project (as usual) with the `angular.json` configuration file. I love Nx due to these facts: - I can easily integrate modern tools like [Cypress](https://cypress.io){rel=""nofollow""}, [Jest](https://jestjs.io/){rel=""nofollow""} and [Prettier](https://prettier.io/){rel=""nofollow""} to my Angular project - I can use effective development practices which are pioneered at Google, Facebook, and Microsoft > Nx is an easy to use version of the powerful monorepo tools used at companies like Google. Let us first talk about the usage of [Cypress](https://cypress.io){rel=""nofollow""} and [Jest](https://jestjs.io/){rel=""nofollow""} in Angular projects. ### Why should I consider using Cypress instead of Protractor? [Check out this nice comparison](https://techblog.fexcofts.com/2018/09/24/end-to-end-e2e-angular-testing-protractor-vs-cypress/){rel=""nofollow""} to get more information about the differences between the two technologies. Cypress is modern and interesting because it is not based on Selenium. Whereas Selenium executes remote commands through the network, Cypress runs in the same run-loop as your application. Additionally, it is fast and has nice features like: - Time travel - Debuggability - Real-time reloads - Automatic waiting - Spies, stubs and clocks - Network traffic control - Consistent results - Screenshots and videos You can find further details about these features on the [official feature website](https://www.cypress.io/features){rel=""nofollow""}. The most significant disadvantage of Cypress is, in my opinion, that it does not have full integration with tools like SauceLabs and BrowserStack and does not support other browsers than Chrome. This probably might change in the future, but these features are not available at the time of writing. In my opinion, Cypress is not a perfect choice for every Angular project but I would recommend that you should give it a try and make your own decision. ### Why should I consider using Jest instead of Karma/jasmine? In my experience, the testing experience using Karma + jasmine is worse when the projects become bigger: - Slow build times (especially initially) - Recompiling does not work reliably - HTML reporter like [karma-jasmine-html-reporter](https://www.npmjs.com/package/karma-jasmine-html-reporter){rel=""nofollow""} tend to be buggy [Jest](https://jestjs.io/){rel=""nofollow""} was created by Facebook and is faster than other test runners because it is parallelizing tests. Additionally, it provides a CLI and has less configuration effort than other testing frameworks. Some of the advantages of Jest compared to Karma + jasmine: - Tests run faster as it can execute tests without building the whole app - Using the CLI it is possible to filter by a filename or regex, which reduces the need for `fdescribe` - Nearly no configuration needed to get started - Stable tests - The syntax is similar to jasmine - Provides [snapshot testing](https://jestjs.io/docs/en/snapshot-testing){rel=""nofollow""} - More active community I haven't used Jest in any of my Angular projects yet, but I will try it in one of my following Angular projects. The main reason why I haven't used it yet is that I worked on existing codebases with many jasmine tests, and there was no need/time/budget to migrate them to Jest. But I already used Jest in a Vue.js project and liked it. If you are just annoyed with the verbose code produced by using Angular's [TestBed API](https://angular.io/guide/testing#component-dom-testing){rel=""nofollow""} I would suggest trying [Spectator](https://github.com/NetanelBasal/spectator){rel=""nofollow""}, which allows us to write "readable, sleek and streamlined unit tests". A summary of my testing suggestions: - Consider using Spectator instead of the TestBed API of Angular. - Consider using Jest instead of Karma/Jasmine (Migration is relatively easy) - Consider using [ng-mocks](https://www.npmjs.com/package/ng-mocks){rel=""nofollow""} to mock your component, directives, services, pipes, and more. Your unit tests should be pure and therefore isolated. - Consider using a functional component testing approach over the technical class testing approach. Test your component from the DOM and not the class. It will help if you think of user events instead of methods. ### Effective Development Practices Using Nx you can work in a "monorepo" way of building your application. This approach is used by large software companies like Google, Facebook, Twitter, and more to make it easier to work with multiple applications and libraries. These are some of the advantages of a monorepo approach: - You commit a working piece of software which may include multiple parts like frontend and backend - One toolchain setup - Dependency management is easier, e.g. all applications & libs in a Nx workspace share one `package.json` and can thus use the same Angular version - Code can be split into composable modules - Consistent developer experience What I also like is the possibility to create applications and libraries in Nx, which provide an excellent way to structure larger applications: > - An application is anything that can run in the browser or on the server. It's similar to a binary. > - A library is a piece of code with a well-defined public API. A library can be imported into another library or application. You cannot run a library. For example, we could define a TypeScript library that shares our TypeScript interfaces between our TS-based applications in our workspace. Of course, our workspace can contain applications that rely on different frontend (or backend) frameworks like React, Angular, NestJS, and even more. One of my favorite features is the dependency graph which can show me a graphical representation of my workspace by running `nx affected:dep-graph`: ![Angular Nx Dependency Graph](https://mokkapps.de/blog/how-i-set-up-a-new-angular-project/nx-dep-graph_je0tml.png) As we used `affected`, we can see what parts of our workspace are affected by our current changes (highlighted in red). This way, we can also run only tests or recompile code that was effected by our changes: ```bash nx affected:apps # prints the apps affected by a PR nx affected:build # reruns build for all the projects affected by a PR nx affected:test # reruns unit tests for all the projects affected by a PR nx affected:e2e # reruns e2e tests for all the projects affected by a PR nx affected --target=lint # reruns any target (for instance lint) for projects affected by a PR ``` See the [official documentation](https://nx.dev/angular/fundamentals/monorepos-automation){rel=""nofollow""} to learn how to use these mechanics in Nx. ## TypeScript Configuration I prefer to start with [this tslint configuration](https://github.com/mgechev/tslint-angular){rel=""nofollow""} as it uses the tslint configuration of [Angular CLI](https://github.com/angular/angular-cli){rel=""nofollow""} and aligns with the [Angular style guide](https://angular.io/guide/styleguide){rel=""nofollow""}. In my `tsconfig.json` file I enable [`strictNullChecks`](https://basarat.gitbooks.io/typescript/docs/options/strictNullChecks.html){rel=""nofollow""} which makes the code base more robust against possible `null` or `undefined` errors during runtime. ```json { "compilerOptions": { "strictNullChecks": true } } ``` From the [official documentation](https://www.typescriptlang.org/docs/handbook/compiler-options.html){rel=""nofollow""}: > In strict null checking mode, the null and undefined values are not in the domain of every type and are only assignable to themselves and any (the one exception being that undefined is also assignable to void). ## Internationalization (i18n) I configure internationalization from the beginning of a project even if the product is only planned for one country. It has two reasons: - You get used to storing your translated texts in one file and not as hardcoded strings across the whole application. - If the application needs to get translated into another language you are prepared for it. I always use [ngx-translate](https://github.com/ngx-translate/core){rel=""nofollow""} in my Angular projects, especially as it allows you to switch between languages during your application's runtime. This can be handy if you implement a language switcher in your app. ## UI Component Explorer If you develop your components, creating a custom view with all available components can be helpful, or using existing solutions like [StoryBook](https://storybook.js.org/){rel=""nofollow""}. In some projects, I created a separate page in the application (which was only visible to certain people) that showed a list of all available components. This page was used in manual testing sessions and provided a quick way to see if a new feature impacted any existing component. Additionally, it was possible to test the components in isolation. ## Use Domain-driven Design for your models One of the main ideas behind Domain-Driven Design is the separation of business logic (domain) from the rest of the application or implementation details. This can be easily implemented in Angular using TypeScript. The goal of our domain model is to represent business logic. We want to avoid that certain business logic is split across multiple components and services but is available at a certain place. This way, we can easily react and change the logic if something in the business requirement has changed. An example of such a domain model could look like this: ```typescript export class User { private firstName: string private lastName: string private age: number get firstName() { return this.firstName } get lastName() { return this.lastName } get fullName() { return `${this.firstName} ${this.lastName}` } get age() { return this.age } constructor(firstName: string, lastName: string, age: number) { this.setName(firstName, lastName) this.setAge(age) } setName(firstName: string, lastName: string) { if (this.validName(firstName) && this.validName(lastName)) { this.firstName = firstName this.lastName = lastName } } setAge(age: number) { if (age >= 18) { this.age = age } else { throw new Error('User age must be greater than 18') } } private validName(name: string) { if (name.length > 0 && /^[a-zA-Z]+$/.test(name)) { return true } else { throw new Error('Invalid name format') } } } ``` If, for example, the minimum age should be changed from 18 to 16 this logic needs only to be changed in this domain model class. [This article](https://coryrylan.com/blog/rich-domain-models-with-typescript){rel=""nofollow""} provides further details and a good approach to handling server-side business logic in your frontend application. ## Error Handling I would always add a `LoggerService` and global error handler at the beginning of the project. Additionally, try to use an error tracking software like [Sentry](https://sentry.io/){rel=""nofollow""} to be able to monitor and fix crashes in real-time. Example for a `LoggerService`: ```ts import { Injectable } from '@angular/core' @Injectable() export class LoggerService { debug(message: string, ...optionalParams: unknown[]): void { console.debug(message, ...optionalParams) } log(message: string, ...optionalParams: unknown[]): void { console.log(message, ...optionalParams) } warn(message: string, ...optionalParams: unknown[]): void { console.warn(message, ...optionalParams) } error(message: string, ...optionalParams: unknown[]): void { // Send error to Sentry Sentry.captureMessage(`Error message: ${message}, optionalParams: ${JSON.stringify(optionalParams)}`) console.error(message, ...optionalParams) } } ``` To catch global errors in Angular, you can use the [ErrorHandler](https://angular.io/api/core/ErrorHandler){rel=""nofollow""}: ```ts class MyErrorHandler implements ErrorHandler { constructor(loggerService: LoggerService) {} handleError(error) { // Send error to Sentry Sentry.captureError(error) } } @NgModule({ providers: [{ provide: ErrorHandler, useClass: MyErrorHandler }], }) class CoreModule {} ``` ## Build Complex Components Often we need to develop complex components in our applications. For this case, I suggest the following: Try to solve your problem using the fantastic [Angular CDK](https://material.angular.io/cdk/categories){rel=""nofollow""}, which provides a set of tools that implement common interaction patterns while being unopinionated about their presentation. Examples are tools for accessibility, overlays, scrolling, drag & drop, tables and more. If you build your component, look at existing open-source Angular libraries like [Angular Material](https://github.com/angular/components){rel=""nofollow""}. There you can see how components are written the "Angular way". You can also look for existing Angular components in npm. Therefore I can recommend taking a look at curated component lists like [Awesome Angular Components](https://github.com/brillout/awesome-angular-components){rel=""nofollow""} or [Awesome Angular](https://github.com/PatrickJS/awesome-angular){rel=""nofollow""}. Anyways, I would advise checking the following for each 3rd party library you want to integrate into your project: - When was it published the last time? - Is it actively maintained? How many open issues are on GitHub? - Is it actively used by checking npm weekly download numbers? ## Miscellaneous - Use [Prettier](https://prettier.io/){rel=""nofollow""} as code formatter - Use [Augury](https://augury.rangle.io/){rel=""nofollow""}, [Redux DevTools](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd){rel=""nofollow""} or any other useful browser dev tools - Use [Compodoc](https://github.com/compodoc/compodoc){rel=""nofollow""} (or any other similar tool) to generate documentation for your application. - Use [Husky](https://github.com/typicode/husky){rel=""nofollow""} to check if the commit message has the correct format, the code is formatted, the linter has no errors, and the run unit tests before you push your code. - [Lazy load](https://angular.io/guide/lazy-loading-ngmodules){rel=""nofollow""} all your modules. This way, you can split your application into smaller bundles that are only loaded if necessary. ## Conclusion It is essential to agree with your team on such an opinionated setup. I would propose this approach to the team, discuss alternatives, advantages, and disadvantages and try to find a good compromise. In the end, the project should be scalable, and the team should be able to deliver features quickly. This article showed you my approach to setting up a new Angular project. It is not complete and maybe not a perfect approach, but it is my experience, so your suggestions are always welcome in the comments. I recommend reading this free eBook from Manfred Steyer [Enterprise Angular - DDD, Nx Monorepos and Micro Frontends](https://leanpub.com/enterprise-angular){rel=""nofollow""}, which covers a lot of the discussed topics in more detail. # How I Write Marble Tests For RxJS Observables In Angular I am a passionate [Reactive Extensions](http://reactivex.io/){rel=""nofollow""} user and mostly use them in [RxJS](https://github.com/Reactive-Extensions/RxJS){rel=""nofollow""}, which is integrated into the [Angular framework](https://angular.io){rel=""nofollow""}. In Angular, I often use observables in my services and need to write tests for these asynchronous data streams. Unfortunately, testing observables is hard, and to be honest, I often need more time to write unit tests for my streams than to implement them in the production code itself. But luckily, there exists an integrated solution for RxJS which helps write this kind of test: the so-called marble tests. Marble testing is not difficult if you are already familiar with representing asynchronous data streams as marble diagrams. In this blog post, I want to introduce you to the concept of marble diagrams, the basics of marble testing, and examples of how I use them in my projects. > I will not provide a general introduction to RxJS in this article, but I can highly recommend [this article](https://dev.to/sagar/reactive-programming-in-javascript-with-rxjs-4jom){rel=""nofollow""} to refresh the basics. ## Marble Testing There exists an [official documentation](https://github.com/ReactiveX/rxjs/blob/master/doc/marble-testing.md){rel=""nofollow""} about marble testing for RxJS users but it can be tough to get started since there were a lot of changes from v5 to v6. Therefore I want to start by explaining the basics, show an exemplary Angular test implementation and in the end, talk about some of the new RxJS 6 features. ## Marble Diagrams For easier visualization of RxJS observables, a new domain-specific language called "marble diagram" was introduced. > Marble Diagrams are visual representations of how operators work and include the input Observable(s), the operator and its parameters, and the output Observable. The following image from the [official documentation](http://reactivex.io/rxjs/manual/overview.html#marble-diagrams){rel=""nofollow""} describes the anatomy of a marble diagram: ![Angular Marble Diagram Anatomy](https://mokkapps.de/blog/how-i-write-marble-tests-for-rxjs-observables-in-angular/marble-diagram-anatomy_l0bj2o.svg) > In a marble diagram, time flows to the right, and the diagram describes how values (“marbles”) are emitted on the Observable execution. ### Marble Syntax In RxJS marble tests, the marble diagrams are represented as a string containing a special syntax representing events happening over virtual time. The start of time (also called the zero frame) in any marble string is always represented by the first character in the string. - `-` time: 1 "frame" of time passage. - `|` complete: The successful completion of an observable. This is the observable producer signaling complete(). - `#` error: An error terminating the observable. This is the observable producer signaling error(). - `"a" any character`: All other characters represent a value being emitted by the producer signaling next(). - `()` sync groupings: When multiple events need to be in the same frame synchronously, parentheses are used to group those events. You can group nested values, a completion or an error in this manner. The position of the initial ( determines the time at which its values are emitted. - `^` subscription point: (hot observables only) shows the point at which the tested observables will be subscribed to the hot observable. This is the "zero frame" for that observable, every frame before the ^ will be negative. #### Examples `-` or `------`: Equivalent to Observable.never(), or an observable that never emits or completes `|`: Equivalent to Observable.empty() `#`: Equivalent to Observable.throw() `--a--`: An observable that waits for 20 "frames", emits value a and then never completes. `--a--b--|`: On frame 20 emit a, on frame 50 emit b, and on frame 80, complete `--a--b--#`: On frame 20 emit a, on frame 50 emit b, and on frame 80, error `-a-^-b--|`: In a hot observable, on frame -20 emit a, then on frame 20 emit b, and on frame 50, complete. `--(abc)-|`: on frame 20, emit a, b, and c, then on frame 80 complete `-----(a|)`: on frame 50, emit a and complete. ### A Practical Angular Example As you now know the theoretical basis, I want to show you a real-world Angular example. In this [GitHub repository](https://github.com/Mokkapps/rxjs-marble-testing-demo){rel=""nofollow""}, I have implemented a basic test setup which I will now explain in detail. The Angular CLI project consists of these components and services: #### UserService This service provides a public getter `getUsers()`, which returns an Observable that emits a new username each second. ```typescript [user.service.ts] import { Injectable } from '@angular/core' import { Observable, interval } from 'rxjs' import { take, map } from 'rxjs/operators' @Injectable({ providedIn: 'root', }) export class UserService { private readonly testData = ['Anna', 'Bert', 'Chris'] get getUsers(): Observable { return interval(1000).pipe( take(this.testData.length), map((i) => this.testData[i]) ) } } ``` #### AllMightyService This service injects the above introduced `UserService` and provides the public getter `getModifiedUsers`. This getter also returns an Observable and maps the emitted usernames from `userService.getUsers` to make them more "mighty". ```typescript [all-mighty.service.ts] import { Injectable } from '@angular/core' import { map } from 'rxjs/operators' import { Observable } from 'rxjs' import { UserService } from './user.service' @Injectable({ providedIn: 'root', }) export class AllMightyService { get getModifiedUsers(): Observable { return this.userService.getUsers.pipe(map((user) => `Mighty ${user}`)) } constructor(private userService: UserService) {} } ``` #### AppComponent In our `app.component.ts`, we inject the `UserService` and update a list each time a new username is emitted from the `getUsers` Observable. ```typescript [app.component.ts] import { Component, OnDestroy, OnInit } from '@angular/core' import { Subscription } from 'rxjs' import { UserService } from './services/user.service' @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) export class AppComponent implements OnInit, OnDestroy { title = 'MarbleDemo' users: string[] = [] private subscription: Subscription | undefined constructor(private userService: UserService) {} ngOnInit() { this.subscription = this.userService.getUsers.subscribe((user) => { this.users.push(user) }) } ngOnDestroy() { if (this.subscription) { this.subscription.unsubscribe() } } } ``` ```html [app.component.html]

Welcome to {{ title }}!

Here will users pop in asynchronously:

  • {{user}}

``` Now we can write different unit tests for this project: - Test that the AppComponent shows the correct list of usernames - Test that the AllMightyService correctly maps and emits the usernames Let us start with the unit test for the AppComponent. In these tests, I am using the npm package [jasmine-marbles](https://www.npmjs.com/search?q=jasmine%2Dmarbles){rel=""nofollow""} which is a helper library that provides a neat API for marble tests if you are using jasmine (which is used per default in Angular). **Basic idea is to mock the public observables from the provided services and test our asynchronous data streams in a synchronous way.** We mock the UserService and the `getUsers` observable. In the test case we flush all observables by calling `getTestScheduler().flush()`. This means that after this line has been executed our mocked observable has emitted all of its events and we can run our test assertions. I will talk more about the TestScheduler after this example. ```typescript [app.component.spec.ts] import { TestBed, async } from '@angular/core/testing' import { getTestScheduler, cold } from 'jasmine-marbles' import { AppComponent } from './app.component' import { UserService } from './services/user.service' import { By } from '@angular/platform-browser' describe('AppComponent', () => { let userService: any beforeEach(async(() => { // Here we mock the UserService to a cold Observable emitting three names userService = jasmine.createSpy('UserService') userService.getUsers = cold('a-b-c', { a: 'Mike', b: 'Flo', c: 'Rolf' }) TestBed.configureTestingModule({ declarations: [AppComponent], providers: [{ provide: UserService, useValue: userService }], }).compileComponents() })) it('should correctly show all user names', async () => { const fixture = TestBed.createComponent(AppComponent) fixture.detectChanges() // trigger change detection getTestScheduler().flush() // flush the observable fixture.detectChanges() // trigger change detection again const liElements = fixture.debugElement.queryAll(By.css('.user')) expect(liElements.length).toBe(3) expect(liElements[0].nativeElement.innerText).toBe('Mike') expect(liElements[1].nativeElement.innerText).toBe('Flo') expect(liElements[2].nativeElement.innerText).toBe('Rolf') }) }) ``` In the next step, let us analyze a service test, in this case for the AllMightyService. ```typescript [all-mighty.service.spec.ts] import { hot, cold } from 'jasmine-marbles' import { TestScheduler } from 'rxjs/testing' import { AllMightyService } from './all-mighty.service' import { fakeAsync } from '@angular/core/testing' describe('AllMightyService', () => { let sut: AllMightyService let userService: any beforeEach(() => { // we mock the getUsers Observable of the UserService userService = jasmine.createSpy('UserService') userService.getUsers = hot('^-a-b-c', { a: 'Hans', b: 'Martin', c: 'Julia', }) sut = new AllMightyService(userService) }) it('should be created', () => { expect(sut).toBeTruthy() }) it('should correctly return mighty users (using jasmine-marbles)', () => { // Here we define the Observable we expect to be returned by "getModifiedUsers" const expectedObservable = cold('--a-b-c', { a: 'Mighty Hans', b: 'Mighty Martin', c: 'Mighty Julia', }) expect(sut.getModifiedUsers).toBeObservable(expectedObservable) }) }) ``` ### The TestScheduler As we already saw in the first AppComponent test, RxJS provides a TestScheduler for "time manipulation". The internal schedulers control the emission order of events in RxJS. Most of the time, we do not have to care about the schedulers as they are handled mainly by RxJS internally. But we can provide a scheduler to operators, as we can see in the signature of the ["delay" operator](http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-delay){rel=""nofollow""}: ```javascript delay(delay: number | Date, scheduler: Scheduler): Observable ``` The last parameter is optional and defaults to the async Scheduler. RxJS includes the following Schedulers: - AsyncScheduler - AnimationFrameScheduler - AsapScheduler - QueueScheduler - TestScheduler - VirtualTimeScheduler To avoid using real time in our test, we can pass the `TestScheduler` (who derives from the `VirtualTimeScheduler`) to our operator. The `TestScheduler` allows us to manipulate the time in our test cases and synchronously write asynchronous tests. ## New RxJS 6 marble test features In RxJS v5 there was nearly no documentation for the `TestScheduler` as it was mainly used internally by the library authors. Since RxJS 6 this has changed and we can now use the TestScheduler to write marble tests. #### testScheduler.run(callback) In previous RxJS versions, we had to pass the Scheduler to our operators in production code to be able to test them with virtual time manipulation. ```javascript getUsers(scheduler) { const dummyData = Observable.from(['Anna', 'Bert', 'Chris']); return dummyData.delay(1000, scheduler); // each user is emitted after 1 second } ``` As you can see, we are now mixing our productive code with logic, we only need for tests. The scheduler parameter is only added and used for the tests. This issue is solved by the new run method. Every RxJS operator who uses the AsyncScheduler (for example "timer" or "debounce") will automatically use the TestScheduler when it is executed inside the run method and therefore uses virtual instead of real time. This way, the same method above can be rewritten without the scheduler parameter and has no more test code inside the production code: ```javascript getUsers() { const dummyData = Observable.from(['Anna', 'Bert', 'Chris'); return dummyData.delay(1000); // each user is emitted after 1 second } ``` A unit test for the AllMightyService's getModifiedUsers method using the new run method can look this way: ```typescript it('should correctly return mighty users (using RxJS 6 tools)', () => { const scheduler = new TestScheduler((actual, expected) => { // asserting the two objects are equal expect(actual).toEqual(expected) }) scheduler.run((helpers) => { const { expectObservable } = helpers const coldObservable = scheduler.createHotObservable('^-a-b-c', { a: 'Hans', b: 'Martin', c: 'Julia', }) userService.getUsers = coldObservable sut = new AllMightyService(userService) const expectedMarble = '--a-b-c' const expectedVales = { a: 'Mighty Hans', b: 'Mighty Martin', c: 'Mighty Julia', } expectObservable(sut.getModifiedUsers).toBe(expectedMarble, expectedVales) }) }) ``` It looks the same as in our `jasmine-marble` test above, but the new run method provides some interesting new features like the [Time progression syntax](https://github.com/ReactiveX/rxjs/blob/master/doc/marble-testing.md#time-progression-syntax){rel=""nofollow""}. > At this time the TestScheduler can only be used to test code that uses timers, like delay/debounceTime/etc (i.e. it uses AsyncScheduler > with delays > 1). If the code consumes a Promise or does scheduling with AsapScheduler/AnimationFrameScheduler/etc it cannot be reliably > tested with TestScheduler, but instead should be tested more traditionally. See the Known Issues section for more details. ### Conclusion Marble diagrams are an established concept to visualize asynchronous data, as seen on the popular website [RxMarbles](http://rxmarbles.com/){rel=""nofollow""}. Using marble strings, we can also use this clean way to test our observables. I recommend getting started by using helper libraries like `jasmine-marbles` as they are more beginner-friendly. You can combine your jasmine-marble tests with the new RxJS 6 features in the same project I demonstrate in [my example project](https://github.com/Mokkapps/rxjs-marble-testing-demo/blob/master/src/app/services/all-mighty.service.spec.ts){rel=""nofollow""}. From my experience, I can tell you that it is worth learning marble testing as you can then test very complex observable streams, understandably. I hope that you are now able to start using marble tests in your project and that you will begin enjoying writing unit tests for observables. # How I Write My Blog Posts I'm often asked how I write my blog posts and in this article, I want to describe my process from start to finish. In this blog post I will cover these topics: - Topic Selection - Planning & Preparation - Writing - Review - Publish - Prepare a talk - Conclusion ## Topic Selection In [Notion](https://www.notion.so/){rel=""nofollow""} I manage a backlog of blog post ideas that I collect from different sources. I try to pick each month the topic on top of my backlog and start the planning phase. But let me first tell you how I find possible topics for my blog. > Write about things you know Most of my blog post ideas came up during my daily development work. If I think the topic could be also interesting for other developers I write an article about it. An example of such a blog post would be [NestJS - The missing piece to easily develop full-stack TypeScript web applications](https://www.mokkapps.de/blog/nest-js-the-missing-piece-to-easily-develop-full-stack-typescript-web-applications/){rel=""nofollow""}. Sometimes I also want to share some of my career experiences with other developers, for example, [what my definition of a senior developer is](http://www.mokkapps.de/blog/my-definition-of-a-senior-software-developer/){rel=""nofollow""}. I am also not afraid if my topic was already covered by dozens of other blog posts. I try to put my perspective and touch to the article so that is not just a copy but a unique content > Write about uncovered topics Writing about topics that were not (or only partly) covered is the hardest but most valuable content you can create. For example, I wrote about [How I Built A Custom Stepper/Wizard Component Using The Angular Material CDK](https://www.mokkkapps.de/blog/how-i-built-a-custom-stepper-wizard-using-angular-material-cdk/){rel=""nofollow""} as I did not find good documentation and helped a lot of other developers with this article. ## Planning & Preparation My whole blog post planning is also done in [Notion](https://www.notion.so/){rel=""nofollow""}. I create a new page for the new blog article where I start collecting relevant articles, ideas, code snippets and more. ![Notion Screenshot](https://mokkapps.de/blog/how-i-write-my-blog-posts/notion_u19roe.jpg) During the preparation phase, I research for similar articles which I think are very good. I read through them, note interesting aspects and start writing a rough structure for my article. Like in this article, I first created the chapters defined in the introduction. Additionally, I also analyze the top-ranked Google articles for their headlines and create my own based on this inspiration. Most of the time this is just a working title, which I update after I have finished writing and reviewing the article. ## Writing The first step of the writing phase is to create a new branch in [my website repository](https://github.com/mokkapps/website){rel=""nofollow""} for the new blog article. Then I start writing the headlines and fill them with content in [Visual Studio Code](https://code.visualstudio.com/){rel=""nofollow""}. I am also using the spell checker plugin [Spell Right](https://marketplace.visualstudio.com/items?itemName=ban.spellright){rel=""nofollow""} to prevent typos. Typically, the writing itself takes 1-4 hours depending on the content and if demo code is involved. A big focus is on the outline of the post where I try to list the main points I want to teach with the article and keep the reader motivated to continue reading. My basic article structure is: - Introduction - Middle - Conclusion The next step is to add a nice cover image where I first look at [unsplash.com](https://unsplash.com/){rel=""nofollow""} which provides nice, free stock photos. If I do not find a good image there (or I want to modify it), I use [Vectr](https://vectr.com/){rel=""nofollow""} which is a free online vector graphics software: ![Vectr Screenshot](https://mokkapps.de/blog/how-i-write-my-blog-posts/vectr_njztib.jpg) To make the article more attractive for readers I also add some images in between the text to have not only large text blocks but also some visual parts. Quotes, videos or charts are also a good way to add more appeal to the post. ## Review At this phase, I read again through the article in my editor and I also run my website locally to see if the article looks good "in action". After that, I paste the article text in [Grammarly](https://app.grammarly.com/){rel=""nofollow""} to find grammar errors which happen quite often as I am no native English speaker but write my articles in English. ![Grammarly Screenshot](https://mokkapps.de/blog/how-i-write-my-blog-posts/grammarly_wtniks.jpg) I will sleep one night and read again through the article. If I have someone special in mind, I also ping that person to review the article. ## Publish If I am happy with the article I will merge my branch to master, push the changes and a new website deployment will automatically be triggered. Check [The Engineering Behind My Portfolio Website](http://www.mokkapps.de/blog/the-engineering-behind-my-portfolio-website/){rel=""nofollow""} if you want to learn more about how I deploy my blog. After this step, I will post the link to my new blog post on social channels like Twitter and LinkedIn (Instagram is coming soon). The latest blog post will also be mentioned in my [newsletter](http://www.mokkapps.de/newsletter){rel=""nofollow""}. The last step is to publish the article on [dev.to](https://dev.to/){rel=""nofollow""} which already fetched the blog content via my RSS feed so that I just need to review the prepared post there and publish it. ## Prepare A Talk If I have the feeling that a blog post could be an interesting topic for a talk, e.g. at a Meetup meeting I will propose it to a Meetup organizer. Most of the time, the preparation and talks are quite easy as I already invested enough time for the topic research during writing the article. ## Conclusion Writing a blog post is a time investment but you can benefit a lot from it. A good blog is a perfect self-marketing tool. It is a showcase for my experience, expertise, and passion for coding and blogging. Additionally, it demonstrates possible clients my communication and teaching skills which are important in the tech industry. A lot of people think that it takes guts to put yourself out there but I think differently. I want to share my knowledge and I feel proud if only 10 people read the article if I could provide them any kind of value. Of course, I also sometimes struggle to publish certain articles like [The Mistakes I Made In My First Software Project](https://www.mokkapps.de/blog/the-mistakes-i-made-in-my-first-software-project/){rel=""nofollow""} where I take about mistakes I made in my career. In general, it is also not easy to publish articles in English as I am not a native speaker but it helps me to improve my written English. But until now I only gain from my blog and will continue it for sure. # How To Automatically Generate A Helpful Changelog From Your Git Commit Messages Creating a changelog is a usual task if a new software version is going to be released. It contains all the changes which were made since the last release and is helpful to remember what has changed in the code and to be able to inform the users of our code. In many projects, creating the changelog is a manual process that is often undesired, error-prone, and time-consuming. This article describes some tools that can help to automate the changelog creation based on the Git history. Let's start with some basics. ## Semantic Versioning [Semantic Versioning (SemVer)](https://semver.org/){rel=""nofollow""} is a de facto standard for code versioning. It specifies that a version number always contains these three parts: ![SemVer](https://mokkapps.de/blog/how-to-automatically-generate-a-helpful-changelog-from-your-git-commit-messages/semver_ep1zac.jpg) - **MAJOR**: is incremented when you add breaking changes, e.g. an incompatible API change - **MINOR**: is incremented when you add backward compatible functionality - **PATCH**: is incremented when you add backward compatible bug fixes ## Conventional Commits > The Conventional Commits specification proposes introducing a standardized lightweight convention on top of commit messages. > This convention dovetails with SemVer, asking software developers to describe in commit messages, features, fixes, and breaking changes that they make. Developers tend to write commit messages that [serve no purpose](http://whatthecommit.com/){rel=""nofollow""}. Usually, the message does not describe where changes were made, what was changed, and what was the motivation for making the changes. So I recommend writing commit messages using the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0-beta.2/){rel=""nofollow""}: ```text [optional scope]: [optional body] [optional footer] ``` An example of such a message: ```text fix(ABC-123): Caught Promise exception We did not catch the promise exception thrown by the API call and therefore we could not show the error message to the user ``` The commit type `` can take one of these value: - `fix:` a commit of this type patches a bug in your codebase and correlates with the patch version in semantic versioning - `feat:` a commit of this type introduces a new feature to the codebase and correlates with a minor version in semantic versioning - `BREAKING CHANGE:` a commit that has the text `BREAKING CHANGE:` at the beginning of its optional body or footer section introduces a breaking API change and correlates with a major version in semantic versioning. A breaking change can be part of commits of any type. e.g., a `fix:`, `feat:` & `chore:` types would all be valid, in addition to any other type. Other types like `chore:`, `docs:`, `style:`, `refactor:`, `perf:`, `test:` are recommended by the [Angular convention](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines){rel=""nofollow""}. These types have no implicit effect on semantic versioning and are not part of the conventional commit specification. I also recommend reading [How to Write Good Commit Messages: A Practical Git Guide](https://www.freecodecamp.org/news/writing-good-commit-messages-a-practical-guide/){rel=""nofollow""}. ## Auto-Generate Changelog Now we can start to automate the changelog creation. 1. Follow the [Conventional Commits Specification](https://conventionalcommits.org/){rel=""nofollow""} in your repository. We will use [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional){rel=""nofollow""} to enforce this via [Git hooks](https://git-scm.com/docs/githooks){rel=""nofollow""}. 2. Use [standard-version](https://github.com/conventional-changelog/standard-version){rel=""nofollow""}, a utility for versioning using SemVer and changelog generation powered by [Conventional Commits](https://www.conventionalcommits.org/){rel=""nofollow""}. I will demonstrate the usage based on this [demo project](https://github.com/Mokkapps/changelog-generator-demo){rel=""nofollow""} which was initialized running `npm init` and `git init`. The next step is to install [husky](https://github.com/typicode/husky){rel=""nofollow""}, which sets up your [Git hooks](https://git-scm.com/docs/githooks){rel=""nofollow""}: ```text npx husky-init && npm install ``` Then install [commitlint](https://github.com/conventional-changelog/commitlint){rel=""nofollow""} with a config, which will be used to lint your commit message: ```text npm install @commitlint/{cli,config-conventional} ``` As we are using `config-conventional` we are automatically following the [Angular commit convention](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines){rel=""nofollow""}. Now we need to tell Husky to run `commitlint` during the Git commit hook. Therefore, we need to add a `commit-msg` file to the `.husky` folder: ```shell #!/bin/sh . "$(dirname "$0")/_/husky.sh" npx --no-install commitlint --edit "$1" ``` Finally, we create a `.commitlintrc.json` file which extends the rules from [config-conventional](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional){rel=""nofollow""}: ```json { "extends": ["@commitlint/config-conventional"] } ``` Running `git commit` with an invalid message will now cause an error: ```text ▶ git commit -m "this commit message is invalid" husky > commit-msg (node v14.8.0) ⧗ input: this commit message is invalid ✖ subject may not be empty [subject-empty] ✖ type may not be empty [type-empty] ✖ found 2 problems, 0 warnings ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint husky > commit-msg hook failed (add --no-verify to bypass) ``` and valid commits will work: ```text ▶ git commit -m "feat: initial feature commit" [master (root-commit) a87f2ea] feat: initial feature commit 5 files changed, 1228 insertions(+) create mode 100644 .commitlintrc.json create mode 100644 .gitignore create mode 100644 index.js create mode 100644 package-lock.json create mode 100644 package.json ``` Now we are safe and can guarantee that only valid commit messages are in our repository. ## Generate Changelog Finally, we can create our changelog from our Git history. First step is to install [standard-version](https://github.com/conventional-changelog/standard-version){rel=""nofollow""}: ```text npm i --save-dev standard-version ``` Now we can create some npm scripts in our `package.json`: ```json "scripts": { "release": "standard-version", "release:minor": "standard-version --release-as minor", "release:patch": "standard-version --release-as patch", "release:major": "standard-version --release-as major" }, ``` The changelog generation can be configured via a `.versionrc.json` file or placing a `standard-version` stanza in your `package.json`. In our demo we use a `.versionrc.json` file based on the [Conventional Changelog Configuration Spec](https://github.com/conventional-changelog/conventional-changelog-config-spec/blob/master/versions/2.1.0/README.md){rel=""nofollow""}: ```json { "types": [ { "type": "feat", "section": "Features" }, { "type": "fix", "section": "Bug Fixes" }, { "type": "chore", "hidden": true }, { "type": "docs", "hidden": true }, { "type": "style", "hidden": true }, { "type": "refactor", "hidden": true }, { "type": "perf", "hidden": true }, { "type": "test", "hidden": true } ], "commitUrlFormat": "https://github.com/mokkapps/changelog-generator-demo/commits/{{hash}}", "compareUrlFormat": "https://github.com/mokkapps/changelog-generator-demo/compare/{{previousTag}}...{{currentTag}}" } ``` An array of `type` objects represents the explicitly supported commit message types, and whether they should show up in the generated changelog file. `commitUrlFormat` is an URL representing a specific commit at a hash and `compareUrlFormat` is an URL representing the comparison between two git shas. The first release can be created by running `npm run release -- --first-release` in the terminal: ```text ▶ npm run release -- --first-release > changelog-generator-demo@0.0.0 release /Users/mhoffman/workspace/changelog-generator-demo > standard-version "--first-release" ✖ skip version bump on first release ✔ created CHANGELOG.md ✔ outputting changes to CHANGELOG.md ✔ committing CHANGELOG.md ✔ tagging release v0.0.0 ℹ Run `git push --follow-tags origin master` to publish ``` An exemplary `CHANGELOG.md` could look similar to this one: ![CHANGELOG First Release](https://mokkapps.de/blog/how-to-automatically-generate-a-helpful-changelog-from-your-git-commit-messages/changelog_csmcdx.png) What I like is that the changelog is divided by the type of commit, it contains links to the specific commits and link to the diff of the version. Of course, you can always edit the auto-generated changelog to make it more readable though. The generated changelog Markdown text can be pasted into GitHub releases so that it shows up next to each release tag. There are a lot more options in the tools to customize linting commits or the changelog generation. ## Conclusion For lazy developers like me, an automatic changelog generation is a nice tool that saves me a lot of time. Additionally, we have better commit messages in our code repository as they follow an established specification. It needs some time to get used to the commit convention. You could encounter some discussions in your team as all code contributors need to follow the convention. The Git hook solution should catch the wrong messages as early as possible but you could also add a guard in your CI/CD pipeline. In my opinion, it is worth the effort to introduce the Git commit convention and the changelog generation in projects. We as developers do not need to invest much time & brain capacity for the changelog generation and have a helpful document where we can look up what has changed between our software releases. Additionally, we can easily share this with the users of our software so that they also see what they can expect from each new release. # How To Build An Angular App Once And Deploy It To Multiple Environments In my last projects, we always had the same requirement: Build the application once and deploy the same build fragment to multiple environments. This leads to some technical challenges, as we need to be able to inject environment-specific information to our application during runtime. In this article, I want to propose some solutions to solve this problem. ## Build once — deploy everywhere Most software projects are done in an agile way so we often use [Continous Delivery](https://martinfowler.com/bliki/ContinuousDelivery.html){rel=""nofollow""}. The idea is to deliver releases in short cycles by an automated software release process. This process is realized by building a corresponding pipeline that typically checks out the code, installs dependencies, runs tests and builds a production bundle. This build artifact is then passed through multiple stages where it can be tested. Followed is an exemplary stage setup: - `DEV`: development environment which is mainly used by developers. A new deployment is automatically triggered by pushing a commit to `develop` branch. - `TEST`: test environment which is mostly used for automated tests and user tests. A new deployment is automatically triggered by pushing a commit to `master` branch - `STAGING`: this environment should be as similar as possible as the `PROD` environment. It is used for final acceptance tests before a `PROD` deployment of the build artifact is manually triggered. - `PROD`: the "final" environment which is used by the customers, deployment is triggered manually The following image shows this process as graphical representation: ![Angular build Once Process](https://mokkapps.de/blog/how-to-build-an-angular-app-once-and-deploy-it-to-multiple-environments/build-once-process_oremkq.jpg) ### Why build once? Of course, we could just rebuild our application for every environment in our pipelines. But, then there could be a chance that the build artifact on `TEST` is not the same as the one used in `PROD`. Unfortunately, a build process is not deterministic even if it is done in an automated pipeline as it depends on other libraries, different environments, operating systems, and environment variables. ### The Challenge Building only one bundle is quite easy but it leads to one big challenge we need to consider: How can we pass environment-specific variables to our application? Angular CLI provides environment files (like `environment.ts`) but these are only used at build time and cannot be modified at runtime. A typical use-case is to pass API URLs for each stage to the application so that the frontend can talk to the correct backend per environment. This information needs to be injected into our bundle per deployment on our environments. Backend services can read environment variables but unfortunately, the frontend runs in a browser and there exists no solution to access environment variables. So we need to implement custom solutions that I want to present to you in the next chapters. ### Solution 1: Quick & dirty This is the quickest but "dirtiest" way to implement runtime environment variables. The idea is to evaluate the browser URL and set the variables according to this information at the application initialization phase using Angular's [APP\_INITIALIZER](https://angular.io/api/core/APP_INITIALIZER){rel=""nofollow""}: ```ts [app.module.ts] providers: [{ provide: APP_INITIALIZER, useFactory: (envService: EnvService) => () => envService.init(), deps: [EnvService], multi: true }], ``` ```ts [env.service.ts] export enum Environment { Prod = 'prod', Staging = 'staging', Test = 'test', Dev = 'dev', Local = 'local', } @Injectable({ providedIn: 'root' }) export class EnvService { private _env: Environment private _apiUrl: string get env(): Environment { return this._env } get apiUrl(): string { return this._apiUrl } constructor() {} init(): Promise { return new Promise((resolve) => { this.setEnvVariables() resolve() }) } private setEnvVariables(): void { const hostname = window && window.location && window.location.hostname if (/^.*localhost.*/.test(hostname)) { this._env = Environment.Local this._apiUrl = '/api' } else if (/^dev-app.mokkapps.de/.test(hostname)) { this._env = Environment.Dev this._apiUrl = 'https://dev-app.mokkapps.de/api' } else if (/^test-app.mokkapps.de/.test(hostname)) { this._env = Environment.Test this._apiUrl = 'https://test-app.mokkapps.de/api' } else if (/^staging-app.mokkapps.de/.test(hostname)) { this._env = Environment.Staging this._apiUrl = 'https://staging-app.mokkapps.de/api' } else if (/^prod-app.mokkapps.de/.test(hostname)) { this._env = Environment.Prod this._apiUrl = 'https://prod-app.mokkapps.de.de/api' } else { console.warn(`Cannot find environment for host name ${hostname}`) } } } ``` Now we can inject the `EnvService` in our code to be able to access the values: ```ts @Injectable({ providedIn: 'root' }) export class AnyService { constructor(private envService: EnvService, private httpClient: HttpClient) {} users(): User[] { return this.httpClient.get(`${this.envService.apiUrl}/users`) } } ``` | Advantages | Disadvantages | | ------------------------------------- | ------------------------------------------------------------------------ | | Easy implementation | Secrets would be included in source code | | No change in build pipeline necessary | Each change of the environment variables would need a new build artifact | | No backend implementation necessary | | ### Solution 2: Provide environment configuration via REST endpoint As already mentioned, a backend service can read environment variables so we can use this mechanism to fetch an environment-specific configuration from such an endpoint. Frontend applications (and SPAs in general) usually always communicate with one (or multiple) backend services to fetch data. We assume that one of these backend services now provides an endpoint that delivers environment-specific variables (see interface `Configuration` below) and we take a look at a possible Angular implementation to read those configurations. First we need a `EnvConfigurationService` which fetches the configuration from the backend: ```ts export enum Environment { Prod = 'prod', Staging = 'staging', Test = 'test', Dev = 'dev', Local = 'local', } interface Configuration { apiUrl: string stage: Environment } @Injectable({ providedIn: 'root' }) export class EnvConfigurationService { private readonly apiUrl = 'http://localhost:4200' private configuration$: Observable constructor(private http: HttpClient) {} public load(): Observable { if (!this.configuration$) { this.configuration$ = this.http.get(`${this.apiUrl}/config`).pipe(shareReplay(1)) } return this.configuration$ } } ``` We want that each new subscriber gets the cached configuration without triggering a new HTTP request, therefore we use the `shareReplay` RxJS operator. This caching makes only sense if the configuration is not dynamic, otherwise, you might want to remove the `shareReplay` operator. The configuration can then be loaded in our `AppModule` at application initialization: ```ts providers: [{ provide: APP_INITIALIZER, useFactory: (envConfigService: EnvConfigurationService) => () => envConfigService.load().toPromise(), deps: [EnvConfigurationService], multi: true }], ``` | Advantages | Disadvantages | | -------------------------------------------- | -------------------------------------------------------------------- | | Secrets are not part of frontend source code | Backend needs to be under control to be able to add such an endpoint | | No changes in build pipeline necessary | | ### Solution 3: Mount configuration files from environment Sometimes we do not have control over our backend and therefore cannot add such a configuration endpoint. We can solve this problem by providing local configuration files in our `assets` folder. Loading such local JSON configurations can be done by using the same `EnvConfigurationService` demonstrated above, we just need to replace ```ts private readonly apiUrl = 'http://localhost:4200'; ``` by ```ts private readonly configUrl = 'assets/config/config.json'; ``` Now we need to replace this `config.json` file per environment with an environment-specific file. This is done by mounting a configuration to the `assets/config` folder if our pod is mounted. The technical implementation depends on your CI tool, for example using [Helm](https://helm.sh/){rel=""nofollow""} you can use a `ConfigMap` and mount a volume: ```yaml volumeMounts: - name: env-config mountPath: /usr/share/nginx/html/assets/config ``` | Advantages | Disadvantages | | -------------------------------------------- | ------------- | | Secrets are not part of frontend source code | | | No changes in build pipeline necessary | | | No backend necessary | | ### Solution 4: Override environment file values The idea is to use Angular's `environment.ts` (for local development) and `environment.prod.ts` (for all other stages) with placeholder values which are overwritten per deployment: ```ts export const environment = { apiUrl: 'MY_APP_API_URL', stage: 'MY_APP_STAGE', } ``` If our pod is started we can then run the following script, for example in a `Dockerfile`, that overrides these placeholder values: ```bash #!/bin/sh # replace placeholder value in JS bundle with environment specific values sed -i "s#MY_APP_API_URL#$API_URL#g" /usr/share/nginx/html/main.*.js ``` | Advantages | Disadvantages | | -------------------------------------------- | ---------------------------------------------------------------------- | | Secrets are not part of frontend source code | We modify our bundle code by scrip which includes the risk to break it | | No changes in build pipeline necessary | | | No backend necessary | | ## Conclusion In my opinion, it totally makes sense to build the application once and then deploy this artifact to all available stages. This way, we can at least ensure that we use the same artifact in each environment. But we then need to care about environment-specific variables which we need to pass to our build during runtime. Angular's environment files are just used during build time so cannot help in such a setup, except we override placeholder values in the bundle which feels a bit "hacky". The best solution is to load environment-specific configurations from a backend or from the local assets folder if you do not have control over the backend you are using in your Angular application. This way you do not have secrets in your frontend code and you are not modifying the source code of your build artifact. # How to Create a Custom Code Block With Nuxt Content v2 Code blocks are essential for blogs about software development. In this article, I want to show you how can define a custom code block component in [Nuxt Content v2](https://content.nuxtjs.org/){rel=""nofollow""} with the following features: - Custom styling for code blocks inside Markdown files - Show language name (if available) - Show file name (if available) - Show a "Copy Code" button ## Nuxt Content v2 [Nuxt Content v2](https://content.nuxtjs.org/){rel=""nofollow""} is a [Nuxt 3](https://v3.nuxtjs.org/){rel=""nofollow""} module that reads local files from the `/content` directory in your project. It supports `.md`, `.yml`, `.csv` and `.json` files. Additionally, it's possible to use Vue components in Markdown with the [MDC Syntax](https://content.nuxtjs.org/guide/writing/mdc){rel=""nofollow""}. ## Setup Nuxt App First, let's start a new Nuxt Content project with: ```bash npx nuxi init nuxt-custom-code-blocks -t content ``` Then we need to install the dependencies in the `nuxt-custom-code-blocks` folder: ```bash yarn install ``` Now we can start the Nuxt content app in development mode: ```bash yarn dev ``` A browser window should automatically open for `http://localhost:3000`. Alternatively, you can start playing with Nuxt Content in your browser using [StackBlitz](https://stackblitz.com/github/nuxt/starter/tree/content){rel=""nofollow""} or [CodeSandbox](https://codesandbox.io/s/github/nuxt/starter/tree/content){rel=""nofollow""}. The following [StackBlitz sandbox](https://stackblitz.com/edit/nuxt-content-v2-custom-code-blocks){rel=""nofollow""} demonstrates the application we create in this article: :stackblitz{project-id="nuxt-content-v2-custom-code-blocks"} ## Custom Prose Component [Prose](https://content.nuxtjs.org/guide/writing/markdown#prose){rel=""nofollow""} represents the HTML tags output from the Markdown syntax in Nuxt Content. Nuxt Content provides a Vue component for each HTML tag like links, title levels, etc. It's possible to override these Vue components, which is precisely what we'll do to create a custom code block component. To customize a Prose component, we have to perform these steps: - Check out the original component sources. - Use the same props. - Name it the same in our `components/content/` directory. In our example, we want to override [ProseCode](https://github.com/nuxt/content/blob/main/src/runtime/components/Prose/ProseCode.vue){rel=""nofollow""}, which is Nuxt Content's default Vue component to render code blocks in Markdown files. This component accepts the following props: - `code`: the provided code as a string - `language`: the provided language name - `filename`: the provided filename - `highlights`: a list of highlighted line numbers Let's take a look at how we can set these values in a Markdown file: ````text ```js [src/index.js] {1, 2-3} const a = 4; const b = a + 3; const c = a * b; ``` ```` In the above example: - `js` is the value passed to the `language` prop - `src/index.js` is the value passed to the `filename` prop - `[1, 2, 3]` is the value passed to the `highlights` prop To override the component, we create `ProseCode.vue` in the `components/content` directory and use the exact same props that are defined in the default component: ```vue ``` Now we can customize this component however we want. ## Style Container First, we want to style the container that includes the code. Therefore, we wrap the `` in a `div` and style it: ```vue ``` Let's take a look at our custom code block: ![Styled Code Block Container](https://mokkapps.de/blog/how-to-create-a-custom-code-block-with-nuxt-content-v2/code-container-styled_ewrpqs.png) ## Show Language Next, we want to show the name of the language on the top right, if it is available. ```vue {3-9} ``` We define a map called `languageMap` that contains the displayed text, the CSS background, and text color for each programming language. We style the `span` tag that renders the language inside our template based on this map and the provided `language` prop: ![Code block with language name](https://mokkapps.de/blog/how-to-create-a-custom-code-block-with-nuxt-content-v2/code-block-with-language-name_fdmv7j.png) ## Show File Name Next, we want to show the file's name on the top left, if it is available: ```vue ``` The result looks like this: ![Code block with file name](https://mokkapps.de/blog/how-to-create-a-custom-code-block-with-nuxt-content-v2/code-block-with-filename_ca4tun.png) ## Add Copy Code Button Finally, we want to show a button that copies the code to the clipboard. Therefore, we use [the useClipboard composable from VueUse](https://vueuse.org/core/useclipboard/#useclipboard=){rel=""nofollow""}: ```vue ``` Let's take a look at the final result with language & file name, copy code button, and line highlighting: ![Final custom code block](https://mokkapps.de/blog/how-to-create-a-custom-code-block-with-nuxt-content-v2/code-block-final_ul0ugd.png) ## Conclusion Custom code blocks are essential for my blog as my blog posts contain a lot of code snippets. Features like copy code or line highlighting provide excellent value to my readers, and it is straightforward to add such features by creating a custom code block component in Nuxt Content v2. The source code of this demo is available at [GitHub](https://github.com/Mokkapps/nuxt-content-v2-custom-code-blocks/tree/master){rel=""nofollow""} or as [StackBlitz sandbox](https://stackblitz.com/edit/nuxt-content-v2-custom-code-blocks){rel=""nofollow""}. You can expect more [Nuxt 3](https://v3.nuxtjs.org/){rel=""nofollow""} posts in the following months as I plan to blog about interesting topics that I discover while rewriting my portfolio website. If you liked this article, follow me on [Twitter](https://twitter.com/mokkapps){rel=""nofollow""} to get notified about new blog posts and more content from me. Alternatively (or additionally), you can also [subscribe to my newsletter](https://weekly-vue.news){rel=""nofollow""}. # How to Deploy a Heroku Backend to a Netlify Subdomain On my main domain [mokkapps.de](https://mokkapps.de) I have deployed my private portfolio website. For different use cases, I want to have a [Node.js backend](https://nodejs.org/){rel=""nofollow""} deployed to a subdomain, e.g. [api.mokkapps.de](http://api.mokkapps.de){rel=""nofollow""}. This blog post describes how you can deploy a [Heroku](https://www.heroku.com/){rel=""nofollow""} application to a [Netlify](https://www.netlify.com/){rel=""nofollow""} subdomain. ## What is a domain? Domain names provide a human-readable address for any web server available on the Internet and are a key part of the Internet infrastructure. You can reach any computer which is connected to the Internet through a public IP address. This IP can either be an IPv6 address (e.g. `2001:0DB8:0000:0001:0000:0000:0010:01FF`), or an IPv4 address (e.g. `174.195.122.45`) It is no problem for computers to handle such addresses, but we humans struggle to find out what service the website offers or who's running the server. For us, it is hard to remember IP addresses, and they also might change over time. All those problems are solved by domain names, like [mokkapps.de](https://mokkapps.de) in my case. ## What is a subdomain? A subdomain is a domain that is part of a larger domain. An example: ```text Root domain: www.mokkapps.de Subdomain: api.mokkapps.de ``` Why should you host projects on subdomains? I see two main advantages: 1. You are more flexible by using a different technology stack on your subdomain 2. Code can be in a different Git repository which can help to separate concerns ## Configure Heroku I wanted to deploy a [Node.js backend](https://nodejs.org/){rel=""nofollow""} to [Heroku](https://www.heroku.com/){rel=""nofollow""} and followed the [official tutorial](https://devcenter.heroku.com/articles/getting-started-with-nodejs){rel=""nofollow""} to set up the application. The next step is to configure the new subdomain in the Heroku dashboard in the `Settings` tab: ![Heroku Dashboard Settings Domain](https://mokkapps.de/blog/how-to-deploy-a-heroku-backend-to-a-netlify-subdomain/heroku-settings-domain_iblfnc.jpg) As you can see, I already have added my subdomain `api.mokkapps.de`, a new domain can be added by pressing the `Add domain` button. ::warning All default `appname.herokuapp.com` domains are already SSL-enabled and can be accessed by using HTTPS, for example, `https://appname.herokuapp.com`. To enable SSL on a custom domain you need to use the [SSL Endpoint](https://elements.heroku.com/addons/ssl){rel=""nofollow""} add-on which is a **paid** add-on service. :: ## Configure Netlify As a final step, we need to configure our root domain DNS provider (Netlify) to point to the DNS Target (the Node.js backend deployed via Heroku) shown in the [Heroku dashboard](https://dashboard.heroku.com/){rel=""nofollow""}. First, we need to navigate to the Netlify DNS settings and add a new record: ![Netlify Domain Setting](https://mokkapps.de/blog/how-to-deploy-a-heroku-backend-to-a-netlify-subdomain/netlify-domain-settings_vupgbd.jpg)![Netlify DNS Setting](https://mokkapps.de/blog/how-to-deploy-a-heroku-backend-to-a-netlify-subdomain/netlify-dns-settings_svibnf.jpg) Finally, we are now able to access our Node.js backend via `http://api.mokkapps.de`. ## Conclusion It is quite easy to configure a Heroku application to be accessible via a Netlify subdomain. The only drawback is that SSL for the Heroku custom domain is a paid add-on. If you do not pay for the SSL endpoint you will not be able to trigger HTTP requests from your root domain to your custom subdomain as these requests are blocked by [CORS](https://developer.mozilla.org/de/docs/Web/HTTP/CORS){rel=""nofollow""}. If your website delivers HTTPS pages, all active mixed content delivered via HTTP on these pages will be blocked by default. The best strategy to avoid mixed content blocking is to serve all the content as HTTPS instead of HTTP and therefore it makes sense to pay for the Heroku SSL endpoint. # How To Easily Write And Debug RxJS Marble Tests End of 2018, I wrote [an article](https://www.mokkapps.de/blog/how-i-write-marble-tests-for-rxjs-observables-in-angular/){rel=""nofollow""} about how I write marble tests for RxJS observables in Angular. The content is still valid, but I recently found a new library that I like and makes debugging marble tests easier. If you do not know RxJS marble tests yet, I recommend you first read [my article](https://www.mokkapps.de/blog/how-i-write-marble-tests-for-rxjs-observables-in-angular/){rel=""nofollow""}, which covers the basics. As quick catchup, the following example shows a marble diagram that can be used in tests to represent an observable: ```ts const obs = `-a-^-b--|` // 012345`, emits 'b' on frame 2, completes on 5 - hot observable ^ represents when the subscription started ``` In this article, I want to talk about [rx-sandbox](https://github.com/kwonoj/rx-sandbox){rel=""nofollow""}, a marble diagram DSL-based test suite for RxJS 6. It also has support for RxJS 5 in pre-1.x versions if you need that in your application. # Why rx-sandbox? I found this library as I was looking for a better way to debug marble tests as it was not possible to see such a test output using the [jasmine-marbles](https://github.com/synapse-wireless-labs/jasmine-marbles){rel=""nofollow""} library: ```diff Error: + Source: "--x-x--|" - Expected: "---x-x--|" ``` In my opinion, this is a straightforward and understandable representation of what went wrong in the test. The library also has some other nice features: - No dependencies on a specific test framework. - Near-zero configuration, works out of box. - Supports extended marble diagram DSL. - Provides feature parity to TestScheduler. ## Hello World Example This is simple example of a marble test using rx-sandbox from the [official GitHub repository](https://github.com/kwonoj/rx-sandbox#anatomy-of-test-interface){rel=""nofollow""}: ```ts import { expect } from 'chai' import { rxSandbox } from 'rx-sandbox' it('testcase', () => { const { hot, cold, flush, getMessages, e, s } = rxSandbox.create() const e1 = hot(' --^--a--b--|') const e2 = cold(' ---x--y--|', { x: 1, y: 2 }) const expected = e(' ---q--r--|') const sub = s(' ^ !') const messages = getMessages(e1.merge(e2)) flush() //assertion expect(messages).to.deep.equal(expected) expect(e1.subscriptions).to.deep.equal(sub) }) ``` ## More Realistic Example As things are typically more complicated than in the simple examples, I have created [a project which contains a more realistic scenario](https://github.com/Mokkapps/angular-rx-sandbox-marble-diagram){rel=""nofollow""} with this simple architecture: ![Angular test project architecture](https://mokkapps.de/blog/how-to-easily-write-and-debug-rxjs-marble-tests/architecture_ojqcv1.png) The demo application contains these services: - `NewsApiService`: Represents a service that simulates an API communication to fetch news - `AppFacadeService`: The facade which is used between `AppComponent` and `NewsApiService` to handle the communication and add additional functionality on top of the API calls The relevant marble tests are located in [app-facade.service.spec.ts](https://github.com/Mokkapps/angular-rx-sandbox-marble-diagram/blob/master/src/app/facade/app-facade.service.spec.ts){rel=""nofollow""}. ### Create Test Instance ```ts import { rxSandbox } from 'rx-sandbox' import { AppFacadeService } from './app-facade.service' import { NewsApiService, testData } from '../api/news-api.service' describe('AppFacadeService', () => { let sut: AppFacadeService let newsApiService: any let rx: any beforeEach(() => { // we need to create a sandbox for each test run rx = rxSandbox.create() const { cold, hot } = rx // we mock the API service and return mocked observables which are created by marble strings newsApiService = jasmine.createSpyObj('NewsApiService', ['fetchNews', 'connectToNewsStream']) newsApiService.fetchNews.and.returnValue( cold('a', { a: testData, }) ) newsApiService.connectToNewsStream.and.returnValue( hot('a-^-a-b-c|', { a: testData[0], b: testData[1], c: testData[2], }) ) // we create a new instance of the service and pass the mock service to its constructor sut = new AppFacadeService(newsApiService) }) }) ``` ### Marble Test After creating the test setup we are now ready for our first test: ```ts it('should return news from stream', () => { const { e, getMessages, flush } = rx // create the expected observable by using marble string const expectedObservable = e('--a-b-c|', { a: testData[0], b: testData[1], c: testData[2], }) // get metadata from observable to assert with expected metadata values const messages = getMessages(sut.connect()) // execute observables flush() // When assertion fails, 'marbleAssert' will display visual / object diff with raw object values for easier debugging. marbleAssert(messages).to.equal(expectedObservable) }) ``` A failed test will show a similar output: ![Angular Karma output failed test](https://mokkapps.de/blog/how-to-easily-write-and-debug-rxjs-marble-tests/rx-sandbox-karma-failure_q4n7m5.jpg) We can immediately see that the received observable emitted the events on different frames: ```text Error: "Source: --a-b-c|" "Expected: --a-b---c|" ``` Additionally, the frames may be correct, but the source and expected observable values differ. The output for each event is in this format: ```text { "frame": 2, // at which frame the event occurred "notification": { "error": undefined, // any error information "hasValue": true, // true if there is a value "kind": "N", // type of the event, N: next, E: error, C: complete "value": { // content of the next event "author": "Mike", "date": 2019-09-11T00:00:00.000Z, "title": "New Xbox revealed" } } ``` So you will then compare these values from the received and expected observables. rx-sandbox will print you a diff to see the difference in the values: ```diff @@ -17,18 +17,18 @@ "notification": Notification { "error": undefined, "hasValue": true, "kind": "N", "value": Object { - "author": "Chris", - "date": 2019-12-12T00:00:00.000Z, - "title": "Overwatch 5 announced", + "author": "Florian", + "date": 2019-05-12T00:00:00.000Z, + "title": "Halo X Review", }, }, }, ``` ## Conclusion In my experience, most developers struggle with interpreting the result of marble tests as libraries like `jasmine-marbles` do not provide a good visual representation of the expected and received streams. `rx-sandbox` solves this problem by providing a visual representation of the expected & received marble string and a more readable diff of the values. Additionally, you can use the library in any frontend test framework. Let me know your thoughts about this library in the comments. # How To Generate Angular & Spring Code From OpenAPI Specification If you are developing the backend and frontend part of an application you know that it can be tricky to keep the data models between the backend & frontend code in sync. Luckily, we can use generators that generate server stubs, models, configuration and more based on a [OpenAPI specification](https://swagger.io/specification/){rel=""nofollow""}. In this article, I want to demonstrate how you can implement such an OpenAPI generator in a demo application with an [Angular](https://angular.io){rel=""nofollow""} frontend and a [Spring Boot](https://spring.io/projects/spring-boot){rel=""nofollow""} backend. ## The Demo Application For this article, I have created a simple demo application that provides a backend REST endpoint based on Spring Boot that returns a list of gaming news. The frontend based on Angular requests this list from the backend and renders the list of news. The [source code is available on GitHub](https://github.com/Mokkapps/openapi-angular-spring-demo){rel=""nofollow""}. The Angular frontend was generated with the [Angular CLI](https://cli.angular.io/){rel=""nofollow""} and the Spring Boot backend with [Spring Initializr](https://start.spring.io/){rel=""nofollow""}. ## OpenAPI [The OpenAPI specification](https://swagger.io/specification){rel=""nofollow""} is defined as > a standard, language-agnostic interface to RESTful APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection Such an OpenAPI definition can be used by tools for testing, to generate documentation, server and client code in various programming languages, and many other use cases. The specification has undergone three revisions since its initial creation in 2010. The latest version is 3.0.2 (as of 02.03.2020). ## OpenAPI Generator In this article, I want to focus on code generators, especially on the [openapi-generator](https://github.com/OpenAPITools/openapi-generator){rel=""nofollow""} from [OpenAPI Tools](https://openapitools.org/){rel=""nofollow""}. This picture taken from the project's [GitHub repository](https://github.com/OpenAPITools/openapi-generator){rel=""nofollow""} shows the impressive list of supported languages and frameworks: ![OpenAPI Supported Languages & Frameworks](https://mokkapps.de/blog/how-to-generate-angular-and-spring-code-from-open-api-specification/openapi-languages-frameworks_cw0iil.jpg) For this article's demo project the [@openapitools/openapi-generator-cli](https://www.npmjs.com/package/@openapitools/openapi-generator-cli){rel=""nofollow""} package is used to generate the Angular code via npm and [openapi-generator-gradle-plugin](https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator-gradle-plugin){rel=""nofollow""} to generate the Spring code using Gradle. ## OpenAPI Schema Definition The OpenAPI code generator needs a `yaml` schema definition file which includes all relevant information about the API code that should be generated. Based on the [official petstore.yaml example](https://raw.githubusercontent.com/openapitools/openapi-generator/master/modules/openapi-generator/src/test/resources/2_0/petstore.yaml){rel=""nofollow""} I created a simple `schema.yaml` file for the demo news application: ```yaml openapi: '3.0.0' servers: - url: http://localhost:8080/api info: version: 1.0.0 title: Gaming News API paths: /news: summary: Get list of latest gaming news get: tags: - News summary: Get list of latest gaming news operationId: getNews responses: '200': description: Expected response to a valid request content: application/json: schema: $ref: '#/components/schemas/ArticleList' components: schemas: ArticleList: type: array items: $ref: '#/components/schema/Article' Article: required: - id - title - date - description - imageUrl properties: id: type: string format: uuid title: type: string date: type: string format: date description: type: string imageUrl: type: string ``` Let's take a look at the most important parts of this file: - `openapi`: The version of the OpenAPI specification - `servers -> url`: The backend URL - `info`: General API information - `paths`: This section defines the API endpoints. In our case, we have one GET endpoint at `/news` which returns a list of articles. - `components`: Describes the structure of the payload For more information about the schema definition, you can take a look at the [basic structure](https://swagger.io/docs/specification/basic-structure/){rel=""nofollow""} or at the [full specification (in this case for v3)](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md){rel=""nofollow""}. ### Generate backend code based on this schema In this section, I will demonstrate how the backend code for Spring Boot can be generated based on our schema definition. The first step is to modify the `build.gradle` file: ```groovy plugins { id "org.openapi.generator" version "4.2.3" } compileJava.dependsOn('openApiGenerate') sourceSets { main { java { srcDir "${rootDir}/backend/openapi/src/main/java" } } } openApiValidate { inputSpec = "${rootDir}/openapi/schema.yaml".toString() } openApiGenerate { generatorName = "spring" library = "spring-boot" inputSpec = "${rootDir}/openapi/schema.yaml".toString() outputDir = "${rootDir}/backend/openapi".toString() systemProperties = [ modelDocs : "false", models : "", apis : "", supportingFiles: "false" ] configOptions = [ useOptional : "true", swaggerDocketConfig : "false", performBeanValidation: "false", useBeanValidation : "false", useTags : "true", singleContentTypes : "true", basePackage : "de.mokkapps.gamenews.api", configPackage : "de.mokkapps.gamenews.api", title : rootProject.name, java8 : "false", dateLibrary : "java8", serializableModel : "true", artifactId : rootProject.name, apiPackage : "de.mokkapps.gamenews.api", modelPackage : "de.mokkapps.gamenews.api.model", invokerPackage : "de.mokkapps.gamenews.api", interfaceOnly : "true" ] } ``` As you can see, two new Gradle tasks are defined: `openApiValidate` and `openApiGenerate`. The first task can be used to validate the schema definition, and the second task generates the code. To be able to reference the generated code in the Spring Boot application it needs to be configured as `sourceSet`. Additionally, it is recommended to define `compileJava.dependsOn('openApiGenerate')` to ensure that the code is generated each time the Java code is compiled. For the backend code, we just want to generate models and interfaces, which is done in `configOptions` by setting `interfaceOnly: "true"`. Detailed documentation about all possible configuration options can be found at the [official GitHub repository](https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator-gradle-plugin){rel=""nofollow""}. Running `./gradlew openApiGenerate` produces this code: ![Generated Backend Code](https://mokkapps.de/blog/how-to-generate-angular-and-spring-code-from-open-api-specification/generated-backend-code_k1twjd.png) Make sure to add this folder with generated code to your `.gitignore` file and exclude it from code coverage & analysis tools. At this point, we can use the generated code in our Spring Boot backend. The first step is to create a `Controller` which implements the generated OpenAPI interface: ```java import de.mokkapps.gamenews.api.NewsApi; @RequestMapping("/api") @Controller public class NewsController implements NewsApi { private final NewsService newsService; public NewsController(NewsService newsService) { this.newsService = newsService; } @Override @GetMapping("/news") @CrossOrigin(origins = "http://localhost:4200") @ApiOperation("Returns list of latest news") public ResponseEntity> getNews() { return new ResponseEntity<>(this.newsService.getNews(), HttpStatus.OK); } } ``` This `GET` endpoint is available at `/api/news` and returns a list of news that is provided by `NewsService` which just returns a dummy news article: ```java @Service public class NewsService { public List
getNews() { List
articles = new ArrayList<>(); Article article = new Article(); article.setDate(LocalDate.now()); article.setDescription("An article description"); article.setId(UUID.randomUUID()); article.setImageUrl("https://images.unsplash.com/photo-1493711662062-fa541adb3fc8?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=3450&q=80"); article.setTitle("A title"); articles.add(article); return articles; } } ``` `@CrossOrigin(origins = "http://localhost:4200")` allows requests from our frontend during local development and `@ApiOperation("Returns list of latest news")` is used for Swagger UI which is configured in [SpringConfig.jav](https://github.com/Mokkapps/openapi-angular-spring-demo/blob/master/backend/src/main/java/de/mokkapps/openapidemobackend/config/SwaggerConfig.java){rel=""nofollow""}. Finally, we can run the backend using `./gradlew bootRun` and trigger the news endpoint ```bash curl -v http://localhost:8080/api/news ``` which returns this JSON payload: ```json [ { "id": "75f71b92-d1e5-43dd-862f-739b69cdf3aa", "title": "A title", "date": "2020-02-26", "description": "An article description", "imageUrl": "https://images.unsplash.com/photo-1493711662062-fa541adb3fc8?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=3450&q=80" } ] ``` ### Generate frontend code based on this schema In this section, I want to describe how Angular code can be generated based on our schema definition. First, the OpenAPI generator CLI needs to be added as npm dependency: ```bash npm add @openapitools/openapi-generator-cli ``` Next step is to create a new npm script in `package.json` that generates the code based on the OpenAPI schema: ```json { "scripts": { "generate:api": "openapi-generator generate -g typescript-angular -i ../openapi/schema.yaml -o ./build/openapi" } } ``` This script generates the code inside the `frontend/build/openapi` folder: ![Generated Frontend Code](https://mokkapps.de/blog/how-to-generate-angular-and-spring-code-from-open-api-specification/generated-frontend-code_xd0vsn.png) Make sure to add this folder with generated code to your `.gitignore` file and exclude it from code coverage & analysis tools. It is also important to run this code generation script each time you run, test or build your application. I would, therefore, recommend using the `pre` syntax for npm scripts: ```json { "scripts": { "generate:api": "openapi-generator generate -g typescript-angular -i ../openapi/schema.yaml -o ./build/openapi", "prestart": "npm run generate:api", "start": "ng serve", "prebuild": "npm run generate:api", "build": "ng build" } } ``` Finally, we can import the generated module in our Angular application in `app.module.ts`: ```typescript import { ApiModule } from 'build/openapi/api.module' @NgModule({ declarations: [AppComponent], imports: [BrowserModule, HttpClientModule, ApiModule], providers: [], bootstrap: [AppComponent], }) export class AppModule {} ``` Now we are ready and can use the generated code in the frontend part of the demo application. This is done in `app.component.ts`: ```typescript import { NewsService } from 'build/openapi/api/news.service' @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent { title = 'frontend' $articles = this.newsService.getNews() constructor(private readonly newsService: NewsService) {} } ``` Last step is to use the `AsyncPipe` in the HTML to render the articles: ```html

News

Title: {{article.title}}

Article image

Date: {{article.date}}

Description: {{article.description}}

ID: {{article.id}}

``` If your backend is running locally, you can now serve the frontend by calling `npm start` and open a browser on `http://localhost:4200` and you should see the dummy article: ![Running Demo](https://mokkapps.de/blog/how-to-generate-angular-and-spring-code-from-open-api-specification/running-example_qpqz9q.jpg) ## Alternative Of course, it is also possible to generate the frontend code if you have no control over the backend code but is supports OpenAPI. It is then necessary to adjust the npm script to use the backend URL instead of referencing the local schema file: ```json { "scripts": { "generate:api": "openapi-generator generate -g typescript-angular -i http://my.backend.example/swagger/v1/swagger.json -o ./build/openapi" } } ``` ## Conclusion Having one file to define your API is helpful and can save you a lot of development time and prevent possible bugs caused by different models or API implementations in your frontend and backend code. OpenAPI provides a good specification with helpful documentation. Additionally, many existing backends use Swagger for their API documentation, therefore it should also be possible to use this code generation for frontend applications where you cannot to modify the corresponding backend. Due to the many supported languages and frameworks, it can be used in nearly every project, and the initial setup is not very hard. In my current project, we use OpenAPI code generation for every new project and are very happy with it. Let me know in the comments what you think about this approach and if you also have some OpenAPI experiences to share. # How to Set Up an MCP Server for an Existing Nuxt App In this article, I'll show you how to add an MCP server to an existing Nuxt app with a practical, minimal example. We will build a mocked weather tool using the Nuxt MCP Toolkit and expose it at `/mcp`. Everything runs locally, so you don't need API keys or external services. ## What is MCP? (Beginner Intro) MCP (Model Context Protocol) is a standard way for applications and AI agents to talk to tools and data sources. In simple terms: - an MCP **server** exposes tools (for example: `get_weather`, `search_docs`, `query_database`) - an MCP **client** calls those tools - both communicate through a shared protocol The big benefit is that tools become reusable across different clients instead of being tightly coupled to one app. In my daily work, my most used MCP servers are Nuxt, Nuxt UI, and Directus. Those integrations are really valuable because they provide better context to the agent and usually lead to much better output quality. ## Why Nuxt MCP Toolkit? Because this tutorial is Nuxt-specific, the best default is [`@nuxtjs/mcp-toolkit`](https://mcp-toolkit.nuxt.dev/){rel=""nofollow""}: - native Nuxt module integration - less boilerplate than wiring the low-level MCP SDK manually - built-in conventions for tools, resources, and prompts You can absolutely use the plain MCP SDK, but in most Nuxt projects, the toolkit gives you the fastest path. ## What We Build Inside your existing Nuxt project, we will create: 1. Nuxt MCP Toolkit setup in `nuxt.config.ts` 2. A weather tool in `server/mcp/tools/get-weather.ts` 3. A local MCP endpoint at `http://localhost:3000/mcp` 4. A quick local test via MCP Inspector / your IDE ## 1) Install Dependencies Install the Nuxt MCP Toolkit: ```bash pnpm add @nuxtjs/mcp-toolkit zod ``` Or let Nuxt install and configure it for you: ```bash npx nuxt add mcp ``` ## 2) Enable the Module Add the module to your `nuxt.config.ts`: ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: ['@nuxtjs/mcp-toolkit'], mcp: { name: 'My Nuxt Weather MCP Server', route: '/mcp', }, }) ``` ## 3) Create the Weather Tool Create `server/mcp/tools/get-weather.ts`: ```ts [server/mcp/tools/get-weather.ts] import { z } from 'zod' export default defineMcpTool({ name: 'get-weather', description: 'Get mocked weather data for a city', inputSchema: { city: z.string().min(1).describe('City name, for example Berlin'), }, handler: async ({ city }) => { const normalizedCity = city.trim().toLowerCase() const mockedWeatherByCity: Record = { berlin: { temperatureC: 24, condition: 'Sunny' }, hamburg: { temperatureC: 20, condition: 'Cloudy' }, munich: { temperatureC: 22, condition: 'Partly cloudy' }, } const weather = mockedWeatherByCity[normalizedCity] ?? { temperatureC: 21, condition: 'Unknown (mock fallback)', } return { city, ...weather, } }, }) ``` That is the full MCP tool. No manual MCP server bootstrapping is needed. ## 4) Run the App Start Nuxt: ```bash pnpm dev ``` Now your MCP endpoint is available at: `http://localhost:3000/mcp` ## 5) Test the Tool You can test the tool in two quick ways: 1. Open MCP Inspector via Nuxt DevTools and call `get-weather` with: ```json { "city": "Berlin" } ``` 2. Connect your IDE to `http://localhost:3000/mcp` and run the tool from there. If you want a one-liner for local IDE setup, you can use: ```bash npx add-mcp http://localhost:3000/mcp ``` ## Why This Pattern Is Useful Even though this is a tiny example, the architecture scales nicely: - swap mocked data with a real API later - keep integrations behind MCP tools - reuse the same tools across different AI clients - stay inside Nuxt conventions instead of building MCP wiring from scratch You can start with one tool and grow your MCP server over time. ## Plain SDK vs Nuxt Toolkit If you are working in Nuxt, I recommend the Nuxt MCP Toolkit as the default. Use the plain MCP SDK directly if you need framework-agnostic infrastructure or want full low-level control. ## Conclusion Adding MCP to an existing Nuxt app is easier than it first looks. With the Nuxt MCP Toolkit, you can focus on tool logic instead of protocol plumbing. Start with one small tool like `get-weather`, validate your workflow locally, and then evolve it into real integrations. # How to Use Environment Variables to Store Secrets in AWS Amplify Backend The [twelve-factor app](https://12factor.net/){rel=""nofollow""} is a known methodology for building software-as-a-service apps. One factor describes that an application's configuration should be stored in the environment and not in the code to enforce a strict separation of config from code. In this article, I want to demonstrate how you can add sensitive and insensitive configuration data to an [AWS Amplify](https://aws.amazon.com/amplify/){rel=""nofollow""} backend using environment variables and [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/){rel=""nofollow""}. ## Types of configuration There exist many types of configuration data, for example: - timeouts - connection strings - external API configurations like URLs and endpoints - caching - hosting configuration for URL, port, or schema - file system paths - framework configuration - libraries configuration - business logic parameters - and many more Apart from the type, configuration data can also be categorized as sensitive or insensitive. ### Sensitive vs Insensitive Sensitive configuration data is anything that can be potentially exploited by a third party and therefore must be protected from unauthorized access. Examples of such sensitive data are API keys, usernames, passwords, emails, etc. This data should not be part of your version control. Insensitive configuration data is for example a timeout string for a backend endpoint that can be safely added to the version control. ## Init Amplify ::note You need an AWS account and the Amplify CLI installed and configured to be able to follow the following steps. [Check out the official docs](https://docs.amplify.aws/start/getting-started/installation/q/integration/js){rel=""nofollow""} to set up the prerequisites. :: Let's start by creating a new Amplify project: ```shell mkdir amplify-env-config-demo cd amplify-env-config-demo ▶ amplify init ? Enter a name for the project amplifyenvconfigdemo ? Initialize the project with the above configuration? Yes ? Select the authentication method you want to use: AWS profile ? Please choose the profile you want to use default ``` Next, we can add an API which we will use to add some environment configuration. ```shell ▶ amplify add api ? Please select from one of the below mentioned services: REST ? Provide a friendly name for your resource to be used as a label for this category in the project: amplifyenvconfigdemoapi ? Provide a path (e.g., /book/{isbn}): /handle ? Choose a Lambda source Create a new Lambda function ? Provide an AWS Lambda function name: amplifyenvconfigdemofunction ? Choose the runtime that you want to use: NodeJS ? Choose the function template that you want to use: Hello World ? Do you want to configure advanced settings? No ? Do you want to edit the local lambda function now? No ? Restrict API access No ? Do you want to add another path? No ``` We create a simple [Node.js](https://nodejs.org/){rel=""nofollow""} lambda function based on the "Hello World" Amplify template. It will provide a REST API with an endpoint at the path `/handle`. Amplify CLI generated the "Hello World" function code at `amplify/backend/function/amplifyenvconfigdemofunction/src/index.js`: ```javascript exports.handler = async (event) => { const response = { statusCode: 200, // Uncomment below to enable CORS requests // headers: { // "Access-Control-Allow-Origin": "*", // "Access-Control-Allow-Headers": "*" // }, body: JSON.stringify('Hello from Lambda!'), } return response } ``` ## Add insensitive configuration data As we now have a running API, we can add some insensitive configuration data as environment variables to our Amplify backend. Therefore, we need to modify the `amplify/backend/function/amplifyenvconfigdemofunction/amplifyenvconfigdemofunction-cloudformation-template.json` file. It includes a `Parameters` object where we can add a new environment variable. In our case we want to add a string variable that can be accessed with the key `MyEnvVariableKey` and has the value `my-environment-variable`: ```json {12-15} { "AWSTemplateFormatVersion": "2010-09-09", "Description": "Lambda Function resource stack creation using Amplify CLI", "Parameters" : { ... "env": { "Type": "String" }, "s3Key": { "Type": "String" }, "MyEnvVariableKey" : { "Type" : "String", "Default" : "my-environment-variable" } }, ... } ``` We also need to modify the `Resources > Environment > Variables` object in this file to be able to map our new environment key to a variable that is attached to the global `process.env` variable and is injected by the Node.js runtime: ```json {12-14} { "Resources": { "Environment": { "Variables": { "ENV": { "Ref": "env" }, "REGION": { "Ref": "AWS::Region" }, "MY_ENV_VAR": { "Ref": "MyEnvVariableKey" } } } } } ``` Finally, we need to run `amplify push` to build all of our local backend resources and provision them in the cloud. Now we can access this variable in our lambda function by accessing the global `process.env` variable: ```js {2} exports.handler = async (event) => { console.log('MY_ENV_VAR', process.env.MY_ENV_VAR) const response = { statusCode: 200, body: JSON.stringify('Hello from Lambda!'), } return response } ``` ## Add sensitive data using AWS Secrets Manager AWS provides the [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/){rel=""nofollow""} that helps to "protect secrets needed to access your applications, services, and IT resources". We will use this service to be able to access sensitive data from our backend. First, we need to click on "Store a new secret" to create a new secret: ![Store new secret](https://mokkapps.de/blog/how-to-use-environment-variables-to-store-secrets-in-aws-amplify-backend/aws-secrets-manager-store-new-secret_twzg42.jpg) Next, we click "Other type of secret" and enter key and value of our secret in the corresponding "Secret key/value" inputs: ![Secret type](https://mokkapps.de/blog/how-to-use-environment-variables-to-store-secrets-in-aws-amplify-backend/aws-secrets-manager-type_uqgrcw.jpg) It is possible to add multiple key/value pairs to a secret. A new pair can be added by clicking the "+ Add row" button. In the next screen we need to add a name and some other optional information to our secret: ![Secret name and description](https://mokkapps.de/blog/how-to-use-environment-variables-to-store-secrets-in-aws-amplify-backend/aws-secrets-manager-name-and-description_j829g1.jpg) Let's finish the wizard by skipping all the following screens by clicking the "Next" button. Now we can open the secret and inspect its values inside the AWS Secrets Manager: ![Secret details](https://mokkapps.de/blog/how-to-use-environment-variables-to-store-secrets-in-aws-amplify-backend/aws-secrets-manager-secret-details_g6kr7o.jpg) We need to copy the "Secret ARN" value as we need to add a new configuration object in our Cloudformation configuration file `amplifyenvconfigdemofunction-cloudformation-template.json`: ```json {10-26} "lambdaexecutionpolicy": { "DependsOn": ["LambdaExecutionRole"], "Type": "AWS::IAM::Policy", "Properties": { "PolicyName": "lambda-execution-policy", "Roles": [{ "Ref": "LambdaExecutionRole" }], "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["secretsmanager:GetSecretValue"], "Resource": { "Fn::Sub": [ "arn:aws:secretsmanager:${region}:${account}:secret:key_id", { "region": { "Ref": "AWS::Region" }, "account": { "Ref": "AWS::AccountId" } } ] } } ] } } } ``` Again, we need to run `amplify push` to build all of our local backend resources and provision them in the cloud. Now we need to add some JavaScript code to be able to access the secret inside our Node.js lambda function. First, we need to add the AWS SDK to `amplify/backend/function/amplifyenvconfigdemofunction/src/package.json` by running: ```shell npm install aws-sdk ``` Then we can use the `SecretsManager` to get our secret values by passing the "Secret name" which we defined in the AWS Secrets Manager: ```javascript {1-2,7-12} const AWS = require('aws-sdk') const secretsManager = new AWS.SecretsManager() exports.handler = async (event) => { console.log('MY_ENV_VAR', process.env.MY_ENV_VAR) const secretData = await secretsManager.getSecretValue({ SecretId: 'dev/demoSecret' }).promise() const secretValues = JSON.parse(secretData.SecretString) console.log('DEMO_API_KEY', secretValues.DEMO_API_KEY) const response = { statusCode: 200, body: JSON.stringify('Hello from Lambda!'), } return response } ``` ## Conclusion In this article, I demonstrated how you can add sensitive and insensitive environment configuration to your AWS Amplify backend. You can also watch [this video from Nader Dabit](https://www.youtube.com/watch?v=T3vy3ksa4oc){rel=""nofollow""} if you prefer a visual tutorial. If you liked this article, follow me on [Twitter](https://twitter.com/mokkapps){rel=""nofollow""} to get notified about new blog posts and more content from me. # Implementing Edge-Side Rendering (ESR) in Nuxt 3+ for Enhanced Performance In today's rapidly evolving web landscape, delivering fast and seamless user experiences is paramount. By bringing rendering closer to the user, Edge-Side Rendering (ESR) in Nuxt 3+ opens up new avenues to reduce latency and boost performance. This guide dives into the core concepts of ESR, demonstrates how to set it up on popular platforms, compares it with traditional server-side approaches, and offers best practices to maximize your application's potential. ## Introduction to Edge-Side Rendering (ESR) Edge-Side Rendering (ESR) leverages a distributed network of Content Delivery Network (CDN) edge servers to render dynamic content near the end-user. Unlike traditional server-side rendering, which centralizes the processing in one or several data centers, ESR minimizes the physical distance between the server and user. This proximity leads to lower latency and improved load times—critical factors in user satisfaction and engagement. Nuxt's server engine, [Nitro](https://nitro.build/){rel=""nofollow""}, is built with flexibility in mind, making it possible to deploy applications on various edge platforms. With ESR, the nuances of modern web development are addressed by rendering your application on platforms such as Cloudflare Pages, Vercel Edge Functions, and Netlify Edge Functions. This approach not only enhances performance but also taps into greater scalability by distributing the rendering workload across multiple edge servers. For more details on the rendering process, you can visit the [Nuxt Concepts – Rendering Modes](https://nuxt.com/docs/guide/concepts/rendering/){rel=""nofollow""}. ## Benefits of Using ESR in Nuxt 3+ The move to ESR in Nuxt 3+ offers several transformative benefits: - **Reduced Latency:** ESR processes requests on the nearest CDN edge server, meaning that the data travels a much shorter distance compared to centralized servers. This results in faster response times and an overall smoother experience for the user. Learn more at [Nuxt Concepts – Rendering Modes](https://nuxt.com/docs/guide/concepts/rendering/){rel=""nofollow""}. - **Improved Scalability:** By shifting the rendering process to the edge, the load is naturally distributed across many servers. This distribution means that your application can efficiently manage high traffic volumes without overburdening a single server or data center. For a deep dive into scalability benefits when using edge rendering, check the [Nuxt Blog on Edge Rendering](https://nuxt.com/blog/nuxt-on-the-edge){rel=""nofollow""}. - **Enhanced Performance:** With ESR, users experience quicker load times and a noticeable reduction in latency, which can significantly contribute to better SEO rankings and user retention. Additionally, Nuxt 3's Nitro engine helps mitigate typical issues such as cold start delays by optimizing response times even in edge environments. ## Setting Up ESR with Cloudflare Pages [Cloudflare Pages](https://pages.cloudflare.com/){rel=""nofollow""} offers a robust and easily accessible platform to deploy Nuxt 3+ applications using ESR. Here’s a step-by-step guide to get you started: 1. **Integrate Your Git Repository:** Begin by linking your GitHub, GitLab, or Bitbucket repository to Cloudflare Pages. This step ensures that your application is automatically pulled and kept up to date. 2. **Configure Build Settings:** In your repository, ensure that you have a proper build script defined (typically running `nuxt build`). Cloudflare Pages executes this command to generate the production-ready files. 3. **Deployment:** Once the build completes, Cloudflare serves your application from its globally distributed network of edge servers, thereby minimizing latency for users regardless of their location. By following these steps, your Nuxt 3+ application can benefit from Cloudflare's extensive edge network, resulting in rapid content delivery. More configuration details and insights are available on [Nuxt Concepts – Rendering Modes](https://nuxt.com/docs/guide/concepts/rendering/){rel=""nofollow""}. ::note [NuxtHub](https://hub.nuxt.com/){rel=""nofollow""} is a platform that allows you to deploy and scale Nuxt applications globally, powered by Cloudflare. It provides a seamless experience for deploying Nuxt applications with built-in support for ESR. :: ::tip My [Nuxt Starter Kit](https://nuxtstarterkit.com){rel=""nofollow""} is built on top of NuxtHub, which makes it easy to deploy and scale your Nuxt applications globally. It includes a collection of premium Vue components, composables, and utils built on top of [Nuxt UI Pro](https://ui.nuxt.com/pro?aff=z1NAy){rel=""nofollow""}. :: ## Implementing Vercel Edge Functions Vercel is renowned for its seamless integration with modern web frameworks and its powerful edge network. Deploying a Nuxt 3+ application with Vercel Edge Functions involves a few key modifications: 1. **Set the Environment Variable:** In your Vercel dashboard or local environment, set the environment variable `NITRO_PRESET` to `vercel-edge`. This informs Nuxt 3+ to build the application with Vercel’s edge functions in mind. 2. **Build Your Application:** Run the command `nuxt build`. This process generates an optimized application bundle suitable for edge deployment. 3. **Deploy to Vercel:** Push the changes to your repository, and Vercel will automatically detect the modifications, build, and deploy your application using their edge functions. This approach ensures that your application monitors Vercel's low-latency network, delivering content swiftly to users worldwide. For further reading about deploying edge functions, refer to [Nuxt Concepts – Rendering Modes](https://nuxt.com/docs/guide/concepts/rendering/){rel=""nofollow""}. ## Deploying on Netlify Edge Functions Netlify also supports ESR for Nuxt 3+ applications, and its integration is built to be straightforward: 1. **Set Up Environment Configuration:** In your project’s configuration, set the `NITRO_PRESET` variable to `netlify-edge`. This configuration instructs Nuxt 3+ to treat deployment as an edge function. 2. **Build Process:** Execute the `nuxt build` command. Similar to other platforms, this compiles your application into a format optimized for edge environments. 3. **Deploy Through Netlify:** The Git-based deployment process on Netlify ensures your latest changes are live almost instantly. With Netlify’s global edge network, your users receive low-latency responses no matter their geographic location. Deploying with Netlify Edge Functions allows for rapid content delivery by leveraging the platform’s extensive network. More detailed insights on this approach can be found in the [Nuxt Documentation on Rendering](https://nuxt.com/docs/guide/concepts/rendering/){rel=""nofollow""}. ## Comparing ESR with Traditional Server-Side Rendering Traditional Server-Side Rendering (SSR) involves processing requests in a centralized server environment, which may be optimal for certain use cases but can suffer from higher latency when serving users from distant geographies. ESR, on the other hand, shifts the rendering work to edge servers situated around the world. **Key Comparisons:** - **Latency:** - *Traditional SSR:* Requests travel longer distances, potentially causing delays. - *ESR:* Reduced distance leads to a noticeable decrease in latency. - **Scalability:** - *Traditional SSR:* May require load balancers and additional hardware to manage high traffic levels. - *ESR:* Automatically scales with distributed edge servers, distributing the rendering load seamlessly. - **Cold Start Time:** - *Traditional SSR:* Centralized servers may be optimized for fast response times but are not immune to performance bottlenecks with heavy loads. - *ESR:* Edge environments can experience cold starts. However, Nuxt 3+’s Nitro engine has optimized these to around 2 milliseconds, minimizing the typical delay ([Nuxt Blog – Nuxt on the Edge](https://nuxt.com/blog/nuxt-on-the-edge){rel=""nofollow""}). - **Development Flexibility:** - ESR in Nuxt 3+ provides the flexibility to target multiple platforms (Cloudflare, Vercel, Netlify) with minor configuration changes, while traditional SSR might require heavier infrastructure adjustments. These comparisons highlight why many developers now turn to ESR when performance, global reach, and scalability are top priorities. ## Best Practices for ESR in Nuxt 3+ Applications To fully reap the benefits of Edge-Side Rendering, consider the following best practices: - **Optimize Build Processes:** Regularly update your build scripts and configurations, ensuring they align with the requirements of edge platforms. This minimizes errors during deployment and maximizes performance benefits. - **Monitor Cold Start Times:** Although Nuxt 3+’s Nitro engine reduces cold start times, keep an eye on performance metrics. Use logging and monitoring tools to detect any unforeseen delays, especially during high-traffic periods. - **Selective Module Usage:** Be mindful of using Node.js modules that may not be supported in edge environments (e.g., the `fs` module). Instead, invest time in finding alternatives or refactoring code to ensure compatibility. - **Leverage Platform-Specific Features:** Each platform, whether Cloudflare, Vercel, or Netlify, offers unique optimizations and integrations. Familiarize yourself with the respective documentation and forums to take full advantage of these features. For example: - **Cloudflare Pages:** Use Cloudflare Workers KV for data caching. - **Vercel Edge Functions:** Implement Vercel Analytics to monitor performance. - **Netlify Edge Functions:** Utilize Netlify’s in-built logging for streamlined debugging. - **Ensure Cache Optimization:** Proper caching mechanisms can drastically improve efficiency. Configure caching headers appropriately to avoid redundant computations on frequently accessed requests. - **Test Across Regions:** Since ESR aims to serve users globally, test your application from multiple geographic locations. Tools such as Lighthouse and real user monitoring (RUM) can help identify performance bottlenecks. Adopting these best practices will help ensure that your Nuxt 3+ application not only leverages ESR effectively but also maintains robust performance under varying conditions. ## Conclusion: Maximizing Performance with ESR Edge-Side Rendering in Nuxt 3+ represents a significant advancement for web applications focused on performance and scalability. By rendering content closer to the user, ESR minimizes latency, distributes load across a global network, and enhances the overall user experience. Whether deploying on Cloudflare Pages, Vercel Edge Functions, or Netlify Edge Functions, the benefits are clear: reduced response times, improved scalability, and a smoother interaction regardless of traffic volume. Embracing ESR with Nuxt 3+, guided by the best practices and strategies discussed, empowers developers to build faster, more responsive applications that meet the demands of modern web users. For further insights, explore more on [Nuxt Concepts – Rendering Modes](https://nuxt.com/docs/guide/concepts/rendering/){rel=""nofollow""} and the [Nuxt Blog on Edge Rendering](https://nuxt.com/blog/nuxt-on-the-edge){rel=""nofollow""}. As the web continues to evolve, leveraging the edge will remain key to delivering superior user experiences. # JHipster - The Fastest Way To Build A Production-Ready Angular & Spring Boot Application In the last years, I mainly worked on the frontend part of web & mobile applications, but I also did some minor backend work. Since mid of this year, I have been working to improve my backend knowledge and started to focus on Java backend development using Spring Boot. As I watched multiple Java tutorials on [Pluralsight](https://www.pluralsight.com/){rel=""nofollow""} I stumbled upon [JHipster](https://www.jhipster.tech/){rel=""nofollow""} and felt immediately in love with it. In this article, I will tell you why I love JHipster and how you can quickly start a JHipster project. ## What Is JHipster? > JHipster is a development platform to generate, develop and deploy Spring Boot + Angular / React / Vue Web applications and Spring microservices. It is a [Yeoman](http://yeoman.io/){rel=""nofollow""} generator that creates applications that include Spring Boot, Bootstrap, and Angular (or React or Vue). Julien Dubois started the project in 2013 and is available on [GitHub](https://github.com/jhipster/generator-jhipster){rel=""nofollow""}. If you like to see JHipster in action, I can recommend the following screencast from [Matt Raible](https://twitter.com/mraible){rel=""nofollow""}: :iframe{allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowFullScreen="true" frameBorder="0" height="315" src="https://www.youtube-nocookie.com/embed/uQqlO3IGpTU" width="560"} ## Why Should I Use JHipster? In my opinion, JHipster is fantastic as it - is open source - supports React, Vue and Angular for the frontend - uses TypeScript for each frontend framework - uses Spring Boot 2.1 so we can develop our application in Java 11+ - provides out-of-the-box user management, including email verification and password reset - can be easily deployed to CloudFoundry, Heroku, OpenShift or AWS - provides a robust microservice architecture using Netflix OSS, Elastic Stack, and Docker - uses powerful tools Yeoman, Webpack, and Maven/Gradle - has a good test coverage for entities on frontend and backend side ## Quick Start ### Install Prerequisites Make sure to install [Java](http://www.oracle.com/technetwork/java/javase/downloads/index.html){rel=""nofollow""}, [Git](https://git-scm.com/){rel=""nofollow""} and [Node.js](https://nodejs.org/){rel=""nofollow""} which are prerequisites for JHipster. Then we can install JHipster as global npm package: `npm install -g generator-jhipster` ### Create New Project Now we can create a new project and get started: The first step, is to create a new directory and go into it `mkdir jhipster-demo && cd jhipster-demo` Now we can run `jhipster` which starts the generator ![JHipster Generator](https://mokkapps.de/blog/jhipster-the-fastest-way-to-build-a-production-ready-angular-and-spring-boot-application/jhipster-generator_fvktue.jpg) with the following selections: ![JHipster Generator Selection](https://mokkapps.de/blog/jhipster-the-fastest-way-to-build-a-production-ready-angular-and-spring-boot-application/jhipster-generator-selection_yutrwp.jpg) I chose a monolithic application as a microservice architecture would be an overkill for a simple demo project. Besides that, I selected Angular as the frontend framework with i18n support and some backend configuration for database, caching and monitoring. Next step is to model our entities with [JDL Studio](https://start.jhipster.tech/jdl-studio/){rel=""nofollow""} and download the resulting `jhipster-jdl.jh` file: ![JDL Studio](https://mokkapps.de/blog/jhipster-the-fastest-way-to-build-a-production-ready-angular-and-spring-boot-application/jdl-studio_s5lval.jpg) [JDL Studio](https://start.jhipster.tech/jdl-studio/){rel=""nofollow""} is a friendly graphical tool for drawing JHipster JDL diagrams based on the [JDL syntax](https://www.jhipster.tech/jdl/){rel=""nofollow""}. You do not need to use this visual tool but can also [create entities using the command-line interface](https://www.jhipster.tech/creating-an-entity/){rel=""nofollow""}. After downloading the `.jh` file, we can generate the entities with `jhipster import-jdl jhipster-jdl.jh`. In our example, we import the default JDL Studio file, which is also shown in the picture above. ### Start Backend Run `./mvnw`, which starts the Spring Boot application: ![JHipster Backend Running](https://mokkapps.de/blog/jhipster-the-fastest-way-to-build-a-production-ready-angular-and-spring-boot-application/jhipster-backend-running_zu47zo.jpg) ### Start Frontend Run `npm start` to serve the Angular application on `http://localhost:9000/`: ![JHipster Frontend Running](https://mokkapps.de/blog/jhipster-the-fastest-way-to-build-a-production-ready-angular-and-spring-boot-application/jhipster-frontend-running_rqdgb1.jpg) Finally, we can log in and see some of the out-of-the-box features like the possibility to see and edit our entities, ![JHipster Entities](https://mokkapps.de/blog/jhipster-the-fastest-way-to-build-a-production-ready-angular-and-spring-boot-application/jhipster-entity_dkcd68.jpg) view metrics of the application ![JHipster Metrics](https://mokkapps.de/blog/jhipster-the-fastest-way-to-build-a-production-ready-angular-and-spring-boot-application/jhipster-metric_kpbwo5.jpg) and a user management ![JHipster User Management](https://mokkapps.de/blog/jhipster-the-fastest-way-to-build-a-production-ready-angular-and-spring-boot-application/jhipster-user-management_pjutvp.jpg) ## Conclusion In this article, I just showed you a quick start JHipster project and mentioned its advantages. JHipster is much more potent, as shown in the [official documentation](https://www.jhipster.tech/){rel=""nofollow""}. I think it is also a good sign if large companies are using the framework, as you can see [in this official list](https://www.jhipster.tech/companies-using-jhipster/){rel=""nofollow""}. A disadvantage of JHipster is that you do not have a typical Angular CLI project. Angular CLI is included in JHipster, but the project structure looks different than the one of a default Angular CLI project. JHipster generates a lot of code, including many libraries you may not know. You can add or modify the code without learning the fundamentals behind these libraries, which could lead to future problems. You should also keep in mind that a JHipster project is more of a big start than a small, lean project start. # Lazy Load Vue Component When It Becomes Visible In today's fast-paced digital world, website performance is crucial for engaging users and achieving online success. Landing pages, serving as the virtual storefronts of businesses, hold immense importance in capturing audience attention and driving conversions. However, when it comes to large sites like landing pages, performance optimization becomes a challenge without compromising functionality. That's where lazy loading Vue components come in. By deferring the loading of non-essential elements until they are visible, developers can enhance the user experience while ensuring swift load times on vital landing pages. Lazy loading is a technique that prioritizes the initial rendering of critical content while postponing the loading of secondary elements. This approach not only reduces the initial page load time but also conserves network resources, resulting in a snappier and more responsive user interface. In this blog post, I'll show you a simple mechanism to lazy load your Vue components if they become visible using the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API){rel=""nofollow""}. ## Intersection Observer API The [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API){rel=""nofollow""} is a powerful tool that allows developers to efficiently track and respond to changes in the visibility of elements within the browser's viewport. It provides a way to asynchronously observe intersections between an element and its parent, or between an element and the viewport. It offers a performant and optimized solution for detecting when elements become visible or hidden, reducing the need for inefficient scroll event listeners and enabling developers to enhance user experiences by selectively loading or manipulating content precisely when it becomes necessary. It is typically used to implement features such as infinite scrolling and image lazy loading. ## Async Components Vue 3 provides a [defineAsyncComponent](https://vuejs.org/guide/components/async.html#async-components){rel=""nofollow""} to asynchronously load components only when they are needed. It returns a Promise that resolves to a component definition: ```js import { defineAsyncComponent } from 'vue' const AsyncComp = defineAsyncComponent(() => { return new Promise((resolve, reject) => { // ...load component from server resolve(/* loaded component */) }) }) ``` It is also possible to handle error and loading states: ```js const AsyncComp = defineAsyncComponent({ // the loader function loader: () => import('./Foo.vue'), // A component to use while the async component is loading loadingComponent: LoadingComponent, // Delay before showing the loading component. Default: 200ms. delay: 200, // A component to use if the load fails errorComponent: ErrorComponent, // The error component will be displayed if a timeout is // provided and exceeded. Default: Infinity. timeout: 3000 }) ``` We will use this functionality to load our components asynchronously when they become visible. ## Lazy Loading Components When They Become Visible Let's now combine the Intersection Observer API and the `defineAsyncComponent` function to load our components asynchronously when they become visible: ```ts [utils.ts] import { h, defineAsyncComponent, defineComponent, ref, onMounted, AsyncComponentLoader, Component, } from 'vue'; type ComponentResolver = (component: Component) => void export const lazyLoadComponentIfVisible = ({ componentLoader, loadingComponent, errorComponent, delay, timeout }: { componentLoader: AsyncComponentLoader; loadingComponent: Component; errorComponent?: Component; delay?: number; timeout?: number; }) => { let resolveComponent: ComponentResolver; return defineAsyncComponent({ // the loader function loader: () => { return new Promise((resolve) => { // We assign the resolve function to a variable // that we can call later inside the loadingComponent // when the component becomes visible resolveComponent = resolve as ComponentResolver; }); }, // A component to use while the async component is loading loadingComponent: defineComponent({ setup() { // We create a ref to the root element of // the loading component const elRef = ref(); async function loadComponent() { // `resolveComponent()` receives the // the result of the dynamic `import()` // that is returned from `componentLoader()` const component = await componentLoader() resolveComponent(component) } onMounted(async() => { // We immediately load the component if // IntersectionObserver is not supported if (!('IntersectionObserver' in window)) { await loadComponent(); return; } const observer = new IntersectionObserver((entries) => { if (!entries[0].isIntersecting) { return; } // We cleanup the observer when the // component is not visible anymore observer.unobserve(elRef.value); await loadComponent(); }); // We observe the root of the // mounted loading component to detect // when it becomes visible observer.observe(elRef.value); }); return () => { return h('div', { ref: elRef }, loadingComponent); }; }, }), // Delay before showing the loading component. Default: 200ms. delay, // A component to use if the load fails errorComponent, // The error component will be displayed if a timeout is // provided and exceeded. Default: Infinity. timeout, }); }; ``` Let's break down the code above: We create a `lazyLoadComponentIfVisible` function that accepts the following parameters: - `componentLoader`: A function that returns a Promise that resolves to a component definition - `loadingComponent`: A component to use while the async component is loading. - `errorComponent`: A component to use if the load fails. - `delay`: Delay before showing the loading component. Default: 200ms. - `timeout`: The error component will be displayed if a timeout is provided and exceeded. Default: Infinity. The function returns `defineAsyncComponent` which includes the logic to load the component asynchronously when it becomes visible. The main logic happens in `loadingComponent` inside of `defineAsyncComponent`: We create a new component using `defineComponent` which includes a render function that renders the `loadingComponent` inside a wrapper `div` that was passed to `lazyLoadComponentIfVisible`. The render function includes a template ref to the root element of the loading component. Inside `onMounted` we check if the `IntersectionObserver` is supported. If it is not supported, we immediately load the component. Otherwise, we create an `IntersectionObserver` that observes the root element of the mounted loading component to detect when it becomes visible. When the component becomes visible, we cleanup the observer and load the component. You can now use this function to lazy load your components when they become visible: ```vue [App.vue] {5-8,12} ``` ## StackBlitz Demo Try it yourself in the following StackBlitz demo: :stackblitz{project-id="lazy-load-vue-component-when-it-becomes-visible"} If you scroll the page down until the component becomes visible, you will see in the Network tab in your browser DevTools that the component is loaded asynchronously: ![Lazy Load Component](https://mokkapps.de/blog/lazy-load-vue-component-when-it-becomes-visible/lazy-load-vue-component.gif) ## Conclusion In this article, you learned how to lazy load Vue components when they become visible using the Intersection Observer API and the `defineAsyncComponent` function. This can be useful if you have a landing page with many components and want to improve the initial load time of your application. Special thanks to [Markus Oberlehner](https://markus.oberlehner.net/blog/lazy-load-vue-components-when-they-become-visible/){rel=""nofollow""} who wrote a similar article for Vue 2 which inspired me to write this article for Vue 3. If you liked this article, follow me on [Twitter](https://twitter.com/mokkapps){rel=""nofollow""} to get notified about new blog posts and more content from me. Alternatively (or additionally), you can [subscribe to my weekly Vue newsletter](https://weekly-vue.news){rel=""nofollow""}: # Lessons Learned: My First Smartphone Game In 2017, I have released my first smartphone game "Supermarket Challenge" for [iOS](https://itunes.apple.com/de/app/supermarket-challenge/id1207665675){rel=""nofollow""} and [Android](https://play.google.com/store/apps/details?id=de.mokkapps.supermarketchallenge){rel=""nofollow""}. I learned a lot during the game development and wanted to share my experiences with you. ## Why did I develop a game I have played and have loved video games since I was a little boy. Additionally, I started my software development career some years ago. As a result, I decided to combine both of my greatest passions to develop my own video game. Fortunately, I also had a good idea for my first game. ## The game idea I planned to develop a smartphone game like [Paper's Please](http://www.papersplea.se/){rel=""nofollow""} but in a supermarket scenario. Check the following trailer to see Paper's Please in action: :iframe{allow="autoplay; encrypted-media" allowFullScreen="true" frameBorder="0" height="400" src="https://www.youtube.com/embed/_QP5X6fcukM" width="700"} In my game, you would play a poor supermarket cashier who needs to spend the money for food, medicine, rent, and so on each day after work. ## Market analysis The first step was to analyze the market for similar existing smartphone games. My findings discovered an endless amount of supermarket-themed games. The main goal of these (primarily child-oriented) games was to take the customers' money and return them for the correct amount. I found two matches that included the game mechanic I had in my mind: ### Crazy Market :iframe{allow="autoplay; encrypted-media" allowFullScreen="true" frameBorder="0" height="315" src="https://www.youtube.com/embed/f-ix_6lbkPM" width="700"} This game nearly matched my expectations for the primary game mechanic at the supermarket checkout. But I was not too fond of the Japan-style theme, the aggressive In-App purchases, and the level-based approach. ### Checkout Challenge :iframe{allow="autoplay; encrypted-media" allowFullScreen="true" frameBorder="0" height="315" src="https://www.youtube.com/embed/SozFe1ES-S0" width="700"} Checkout Challenge isn't available anymore but provides a funny Arcade-focused supermarket checkout game. ### Another inspiration: Fruit Ninja :iframe{allow="autoplay; encrypted-media" allowFullScreen="true" frameBorder="0" height="315" src="https://www.youtube.com/embed/a8z8XG6uThU" width="700"} I played Fruit Ninja a lot and had a nice high-score challenge with my friends. For my game, I wanted to achieve the same high-score challenge feeling and implement the three-player lives as they were available in Fruit Ninja. ### Market Analysis Conclusion In summarizing, the market analysis resulted in these decisions: - The game name should be "Supermarket Challenge" (inspired by "Checkout Challenge") - It should be a 2D game - Combine the best parts of "Fruit Ninja", "Crazy Market", and "Checkout Challenge" ## Prototype Development Christmas 2016, I started developing a game prototype based on the [Unity](https://unity3d.com/){rel=""nofollow""} engine. I invested about 80 hours into the prototype, including Unity's training period. Gameplay video of the first prototype: :iframe{allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowFullScreen="true" frameBorder="0" height="315" src="https://www.youtube.com/embed/6NDQV2u1IT0" width="700"} I deployed the game to my smartphone and a web platform to let friends and family try the game. The response was positive, so I developed the prototype into a publishable game. ## Development Start In January 2017, I started the game development in my free time as I was in a full-time job during the whole process. As a first step, I set up some expectations I had for the final result: - The game should be a financial success. - It should attract a significant and recurring amount of gamers. - The game mechanic should be scalable. The first version should only include the Arcade mode with the primary game mechanic. - It should not look like a low-budget game. - It should include a minimal amount of ads. - First versions should be free without In-App purchases. - First release in App stores should be within one year. - Team size: One developer (myself) and maybe one designer (if necessary) As I tried to continue developing my Unity prototype, I had a rude awakening: My spaghetti code was unmaintainable and not expandable. In my full-time job as a software developer, I was used to developing text-based without a full-blown IDE as Unity provides it. Implementing a known software architecture pattern in Unity was very difficult, and the IDE itself is very complex. So I started researching a new game engine that suited my needs better. ## New Game Engine As I had concrete expectations for the new engine, my research led to [Corona](https://coronalabs.com/){rel=""nofollow""}: - Focused on 2D games - Cross-Platform (iOS, Android, Desktop applications, Smart TVs) - Free (with few restrictions) - Text-based with Lua as the scripting language - Includes a simulator with a Live-Testing feature - Good starting tutorials - Integrated advertising possibilities ## My Tools During the development I used the following tools: - [Atom](https://atom.io/){rel=""nofollow""} (later [Visual Code](https://code.visualstudio.com/){rel=""nofollow""}) as text editors - [Trello](https://trello.com/){rel=""nofollow""} as my project management tool - [Gimp](https://www.gimp.org/){rel=""nofollow""} and [Inkscape](https://inkscape.org/){rel=""nofollow""} for image editing - [Bitbucket](https://bitbucket.org/){rel=""nofollow""} for hosting my private repository ## Architecture I structured my code based on scenes and components: ```text scenes * game - lib scanner.lua supermarket-basket.lua item.lua ... * menu - images - sounds - menu.lua * game-over * ... ``` A `scene` is a visible screen available in the game. The `lib` folder contains all components which are reused in different scenes. ## Development Progress The following videos demonstrate the game's progress from the prototypes to the final version. ### Mid January 2017 Implemented basic game mechanic: :iframe{allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowFullScreen="true" frameBorder="0" height="315" src="https://www.youtube.com/embed/XI19bXruh3M" width="700"} ### Start February 2017 UX adjustments, tutorials, menus and more: :iframe{allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowFullScreen="true" frameBorder="0" height="315" src="https://www.youtube.com/embed/UEgTW_pMIBY" width="700"} ### Mid March 2017 I released the first beta version for about ten testers (friends & family). Negative feedback was given due to the serious difficulty and the inconsistent visual design. As a result, I asked a friend of mine to support and assist me in visual aspects of the game, which resulted in a better design: :iframe{allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowFullScreen="true" frameBorder="0" height="315" src="https://www.youtube.com/embed/Dvcqtvyaq2o" width="700"} ### Version 1.0 Start of May 2017 I released the first version of "Supermarket Challenge" on iOS and Android. It included only the Arcade mode: :iframe{allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowFullScreen="true" frameBorder="0" height="315" src="https://www.youtube.com/embed/hTc560JcyKg" width="700"} ### Version 2.0 I further developed the game and implemented a new level mode and an easier Arcade mode. Version 2.0 was released in December 2017. ![Level Select Screen](https://www.mokkapps.de/talks/my-first-smartphone-game/img/level-modus.png)![Level Success Screen](https://www.mokkapps.de/talks/my-first-smartphone-game/img/level-mode-development.png) ## Conclusion ### Interesting numbers - Invested time: \~500hours / \~21 days - Expenses: \~240€ (mostly for graphics, libraries and license) - Ad revenues: \~1€ ### Google Analytics Some Google Analytics numbers which might be interesting: ![Google Analytics Overview](https://mokkapps.de/blog/lessons-learned-my-first-smartphone-game/supermarket-challenge-analytics-overview_yesmak.png)![Google Analytics OS](https://mokkapps.de/blog/lessons-learned-my-first-smartphone-game/supermarket-challenge-analytics-os_bnfgv0.png)![Google Analytics Countries](https://mokkapps.de/blog/lessons-learned-my-first-smartphone-game/supermarket-challenge-analytics-countries_iw9fut.png)![Google Analytics Play Time](https://mokkapps.de/blog/lessons-learned-my-first-smartphone-game/supermarket-challenge-analytics-play-time_qkdtql.jpg) I think the custom events like playtime are exciting. Based on these numbers, I can assume that the game is still challenging as most players see the game over screen in less than one minute of playtime. ### My Insights - Keep it simple: Start with small and realistic goals - Help yourself, learn everything: Game design, writing code, image editing, and more. - Use free assets: Saves time and money, especially in the beginning - Develop prototypes as early as possible - Be active in social networks to build a vibrant community. Trailers and teasers are an excellent way to keep people up-to-date. - Be comfortable with your game engine and be not afraid to change it. ### Possible reasons for the missing success of the game - App icon is not ideal in my opinion - Bad ranking in the app stores - No frequent app updates - High-score challenge seems not to be attractive enough - Too few advertising campaigns for the game ### Final words I had a lot of fun developing the game and learned a lot. Unfortunately, the game was not a financial success, but at least I released my first video game 😜 ## Links - [Download "Supermarket Challenge" at iTunes](https://itunes.apple.com/de/app/supermarket-challenge/id1207665675){rel=""nofollow""} - [Download "Supermarket Challenge" at Google Play](https://play.google.com/store/apps/details?id=de.mokkapps.supermarketchallenge){rel=""nofollow""} # Login at Supabase via REST API in Playwright E2E Test I recently had to implement an end-to-end (E2E) test for a Nuxt.js application that uses Supabase as a backend and authentication provider. The test should log in a user via the Supabase REST API and then test some authenticated pages. In this article, I will show you how to implement this test with Playwright. The simplest way is to login the user in the E2E test via UI and then test the authenticated pages. But in my case, I wanted to login the user via the REST API to speed up the test execution and/or to avoid flaky tests. I didn't find any content on the internet on how to do this, so I decided to write this article. ## Setup File We use the [official docs about authentication](https://playwright.dev/docs/auth#basic-shared-account-in-all-tests){rel=""nofollow""} as a starting point and define a setup file that logs in the user via the Supabase REST API and stores the session in a file: The next step is to create a new `setup` project in the config and declare it as a dependency for all your testing projects that need authentication: ```ts [playwright.config.ts] {4,10} export default defineConfig({ // ... projects: [ { name: 'setup', testMatch: /.*\.setup\.ts/ }, { name: 'chromium', use: { ...devices['Desktop Chrome'], }, dependencies: ['setup'], }, ], // ... }) ``` The `setup` project will always run and authenticate before all the tests. The session is stored in a file that we can read in the tests for the authenticated pages and set the session in the browser's local storage which is needed by Supabase to authenticate the user: ```ts [authenticated-page.setup.ts] {6-12} import fs from 'fs' import { test, expect } from '@playwright/test' import { AUTH_FREE_USER_FILE, SUPABASE_APP_ID } from './utils/constants' test('authenticated page shows logout button', async ({ page, context }) => { const sessionStorage = JSON.parse(fs.readFileSync(AUTH_FREE_USER_FILE, 'utf-8')) await context.addInitScript( (data) => { localStorage.setItem(`sb-${data.appId}-auth-token`, JSON.stringify(data.sessionStorage)) }, { sessionStorage, appId: SUPABASE_APP_ID } ) // ... test your authenticated page }) ``` If you liked this article, follow me on [X](https://x.com/mokkapps){rel=""nofollow""} to get notified about my new blog posts and more content. Alternatively (or additionally), you can [subscribe to my weekly Vue & Nuxt newsletter](https://weekly-vue.news){rel=""nofollow""}: # Manually Lazy Load Modules And Components In Angular In Angular enterprise applications, it is often a requirement to load a configuration from a server via HTTP request which contains a UI configuration. Based on this configuration data, multiple modules and/or components need to be lazy-loaded and its routes dynamically added to the application. In this blog post, I want to demonstrate how modules and components can be lazy-loaded at runtime using Angular 9+. The following [StackBlitz demo](https://stackblitz.com/github/mokkapps/angular-manual-lazy-load-demo){rel=""nofollow""} includes the code described in the following chapters: :stackblitz{project-id="angular-manual-lazy-load-demo"} The source code of the demo is available on [GitHub](https://github.com/Mokkapps/angular-manual-lazy-load-demo){rel=""nofollow""}. ## Lazy Load Module Using Router > Lazy Loading: Load it when you need it Since Angular 8 we can use the browser's built-in [dynamic imports](https://v8.dev/features/dynamic-import){rel=""nofollow""} to load JavaScript modules asynchronous in Angular. A lazy-loaded module can be defined in the routing configuration using the new `import(...)` syntax for `loadChildren`: ```ts @NgModule({ imports: [ RouterModule.forRoot([ { path: 'lazy', loadChildren: () => import('./lazy/lazy.module').then((m) => m.LazyModule), }, ]), ], }) export class AppModule {} ``` ::warning Using Angular 8 (or previous versions) you need to write `loadChildren: './lazy/lazy.module#LazyModule` to enable lazy loading of a module using the Angular router as it does not support the `import(...)` syntax. :: Angular CLI will then automatically create a separate JavaScript bundle for this module which is only loaded from the server if the selected route gets activated. ::warning If you add `LazyModule` to any `imports` array of a module, it will be loaded eagerly (immediately). :: ## Manually Lazy Load Module Sometimes you want to have more control over the lazy loading process and trigger the loading process after a certain event occurred (e.g. a button press). Usually, after this event occurred, a resource is accessed asynchronous (e.g. via an HTTP call to a backend) to fetch a configuration file which includes information about the modules and/or components that should be lazy-loaded. In my [demo](https://github.com/Mokkapps/angular-manual-lazy-load-demo){rel=""nofollow""}, I have implemented a `LazyLoaderService` to demonstrate that behaviour: ```ts @Injectable({ providedIn: 'root', }) export class LazyLoaderService { private lazyMap: Map> = new Map() constructor() {} getLazyModule(key: string): Promise { return this.lazyMap.get(key) } loadLazyModules(): Observable { return of(1).pipe( delay(2000), tap(() => { this.lazyMap.set( 'lazy', import('./lazy/lazy.module').then((m) => m.LazyModule) ) }) ) } } ``` The `loadLazyModules` method simulates a backend request. After a successful request, a module is registered using the `import(...)` syntax. If you now run the application you will see that a separate chunk for the module is created but it will not be loaded in the browser yet. ![Angular lazy module chunk](https://mokkapps.de/blog/manually-lazy-load-modules-and-components-in-angular/lazy-module-chunk_wvhpg7.jpg) The module promise is stored in a `Map` with a key to be able to access it later. We can now call this method in an `onClick` handler in our `AppComponent` and dynamically add a route to our router config: ```ts constructor( private router: Router, private lazyLoaderService: LazyLoaderService ) {} loadLazyModule(): void { this.lazyLoaderService.loadLazyModules().subscribe(() => { const config = this.router.config; config.push({ path: 'lazy', loadChildren: () => this.lazyLoaderService.getLazyModule('lazy') }); this.router.resetConfig(config); this.router.navigate(['lazy']); }); } ``` We get the current router config from the Router via Dependency Injection and push our new routes into it. ::warning Be careful if you have a wildcard route (`**`) in your route configuration. The wildcard route always needs to be at the last index of your routes array because it matches every URL and should be selected only if no other routes are matched first. :: Next, we need to reset the router configuration used for navigation and generating links by calling `resetConfig` with our new configuration that includes the lazy-loaded module route. Finally, we navigate to the new loaded route and see if it works: ![Angular lazy load module gif](https://mokkapps.de/blog/manually-lazy-load-modules-and-components-in-angular/lazy-load-module_vriukn.gif) We see three things happening after the "Load Lazy Module" button was clicked: 1. The chunk for the lazy module is requested from the server, a loading indicator is shown in the meantime 2. The browser URL changes to the new route `/lazy` after the loading has been finished 3. The lazy-loaded module is loaded and its `LazyHomeComponent` is rendered 4. The toolbar shows a new entry Dynamically showing the available routes in the toolbar is done by iterating over the available routes from the router config in `app.component.html`: ```html {{ route.path | uppercase }} ``` ### Bookmark The Lazy-Loaded Route A typical requirement is that users want to create a bookmark for certain URLs in the application as they visit them very often. Let us try this with our current implementation: ![Angular reload lazy module gif](https://mokkapps.de/blog/manually-lazy-load-modules-and-components-in-angular/reload-lazy-module_nxxpks.gif) Reloading the lazy route leads to an error: `Error: Cannot match any routes. URL Segment: 'lazy'` In the current implementation, we only load the module by clicking the "Load Lazy Module" button but we also need a trigger depending on the currently activated route. Therefore, we need to add the following code block to the `ngOnInit` method of our `AppComponent`: ```ts ngOnInit(): void { this.router.events.subscribe(async routerEvent => { if (routerEvent instanceof NavigationStart) { if (routerEvent.url.includes('lazy') && !this.isLazyRouteAvailable()) { this.loadLazyModule(routerEvent.url); } } }); this.routes = this.router.config; } private isLazyRouteAvailable(): boolean { return this.router.config.filter(c => c.path === 'lazy').length > 0; } ``` We subscribe to the `NavigationStart` events of the Angular router and if the URL includes our lazy route, we check if it is already inside the Router config, otherwise we load it. Now it is possible to bookmark the URL and the application will lazy load the module after the route is activated. ### Manually Load Angular Component We can go one step further and dynamically load an Angular component in the manually lazy-loaded module. In Angular version 2 to 8, it was quite complex to dynamically load a component, if you need a solution for one of these versions please take a look at the popular [hero-loader package](https://www.npmjs.com/package/@herodevs/hero-loader){rel=""nofollow""}. Since Angular 9 it is much easier and I will describe the process for you. Our LazyModule contains a child route with a placeholder component, that should show our dynamically loaded component: ```ts export const LAZY_ROUTES: Routes = [ { path: '', component: LazyHomeComponent, children: [ { path: 'dynamic-component', component: PlaceholderComponent, }, ], }, ] ``` The template of the placeholder component consists only of a `` HTML tag: ```html ``` Inside `placeholder.component.ts` we now dynamically load a `DynamicLazyComponent` after the `PlaceholderComponent` got initialized: ```ts @Component({ selector: 'app-placeholder', templateUrl: './placeholder.component.html', styleUrls: ['./placeholder.component.css'], }) export class PlaceholderComponent implements OnInit { @ViewChild(TemplateRef, { read: ViewContainerRef }) private templateViewContainerRef: ViewContainerRef constructor(private readonly componentFactoryResolver: ComponentFactoryResolver) {} async ngOnInit() { import('../../dynamic-lazy/dynamic-lazy.component').then(({ DynamicLazyComponent }) => { const component = this.componentFactoryResolver.resolveComponentFactory(DynamicLazyComponent) const componentRef = this.templateViewContainerRef.createComponent(component) }) } } ``` Some notes to this code block: - We use the `@ViewChild()` decorator to be able to query the `TemplateRef` instance of our `` element. - The optional second argument of the `@ViewChild()` decorator (`{ read: ViewContainerRef }`) is used to read the `ViewContainerRef` instance from the view query. - The `templateViewContainerRef` is used to tell the rendering engine where the lazy-loaded component should be rendered. - We use the same `import(...)` syntax to lazy-load components the same way we did it for modules. ::warning Since Angular 9, we do not need to register and add the `DynamicLazyComponent` inside any module as an entry component. If you want to dynamically load a component in Angular 8, please check out [Manually Lazy load Components in Angular 8](https://dev.to/binarysort/manually-lazy-load-components-in-angular-8-ffi){rel=""nofollow""} :: The following picture demonstrates the lazy loading process of this component: ![Angular lazy load component gif](https://mokkapps.de/blog/manually-lazy-load-modules-and-components-in-angular/lazy-load-component_xalbuo.gif) ## Conclusion Angular 9 provides a very clean and elegant solution to manually import modules and components at runtime using the `import(...)` syntax. You should now be able to create very dynamic user interfaces, that can be configured in configuration files that are loaded at runtime and based on this information different modules and components are lazy-loaded with new routes. # Monitoring Spring Boot Application With Micrometer, Prometheus And Grafana Using Custom Metrics It is important to monitor an application's metrics and health which helps us to improve performance, manage the app in a better way, and notice unoptimized behavior. Monitoring each service is important to be able to maintain a system that consists of many microservices. In this blog post, I will demonstrate how a Spring Boot web application can be monitored using [Micrometer](https://micrometer.io){rel=""nofollow""} which exposes metrics from our application, [Prometheus](https://prometheus.io){rel=""nofollow""} which stores the metric data, and [Grafana](https://grafana.com){rel=""nofollow""} to visualize the data in graphs. Implementing these tools can be done quite easily by adding just a few configurations. Additional to the default JVM metrics I will show how you can expose custom metrics like a user counter. As always, the code for the demo used in this article can be found on [GitHub](https://github.com/Mokkapps/custom-metrics-spring-boot-demo){rel=""nofollow""}. ## Spring Boot The base for our demo is a Spring Boot application which we initialize using [Spring Initializr](https://start.spring.io/#!type=gradle-project&language=java&platformVersion=2.3.4.RELEASE&packaging=jar&jvmVersion=11&groupId=de.mokkapps&artifactId=custom-metrics-demo&name=custom-metrics-demo&description=Custom%20metrics%20demo%20project%20for%20Spring%20Boot&packageName=de.mokkapps.custom-metrics-demo&dependencies=devtools,lombok,web,actuator,prometheus){rel=""nofollow""}: ![Spring Initializr](https://mokkapps.de/blog/monitoring-spring-boot-application-with-micrometer-prometheus-and-grafana-using-custom-metrics/spring-initializr_aempo8.png) We initialized the project using `spring-boot-starter-actuator` which already exposes [production-ready endpoints](https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-endpoints.html){rel=""nofollow""}. If we start our application we can see that some endpoints like `health` and `info` are already exposed to the `/actuator` endpoint per default. Triggering the `/actuator/health` endpoint gives us a metric if the service is up and running: ```bash ▶ http GET "http://localhost:8080/actuator/health" HTTP/1.1 200 Connection: keep-alive Content-Type: application/vnd.spring-boot.actuator.v3+json Date: Wed, 21 Oct 2020 18:11:35 GMT Keep-Alive: timeout=60 Transfer-Encoding: chunked { "status": "UP" } ``` Spring Boot Actuator can be integrated into [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin){rel=""nofollow""} which provides a visual admin interface for your application. But this approach is not very popular and has some limitations. Therefore, we use [Prometheus](https://prometheus.io){rel=""nofollow""} instead of Spring Boot Actuator and [Grafana](https://grafana.com){rel=""nofollow""} instead of Spring Boot Admin to have a more popular and framework/language-independent solution. This solution approach needs vendor-neutral metrics and [Micrometer](https://micrometer.io){rel=""nofollow""} is a popular tool for this use case. ## Micrometer > Micrometer provides a simple facade over the instrumentation clients for the most popular monitoring systems, allowing you to instrument your JVM-based application code without vendor lock-in. > Think SLF4J, but for metrics. [Micrometer](https://micrometer.io){rel=""nofollow""} is an open-source project and provides a metric facade that exposes metric data in a vendor-neutral format that a monitoring system can understand. These monitoring systems are supported: - AppOptics - Azure Monitor - Netflix Atlas - CloudWatch - Datadog - Dynatrace - Elastic - Ganglia - Graphite - Humio - Influx/Telegraf - JMX - KairosDB - New Relic - Prometheus - SignalFx - Google Stackdriver - StatsD - Wavefront Micrometer is not part of the Spring ecosystem and needs to be added as a dependency. In our demo application, this was already done in the [Spring Initializr configuration](https://start.spring.io/#!type=gradle-project&language=java&platformVersion=2.3.4.RELEASE&packaging=jar&jvmVersion=11&groupId=de.mokkapps&artifactId=custom-metrics-demo&name=custom-metrics-demo&description=Custom%20metrics%20demo%20project%20for%20Spring%20Boot&packageName=de.mokkapps.custom-metrics-demo&dependencies=devtools,lombok,web,actuator,prometheus){rel=""nofollow""}. Next step is to expose the Prometheus metrics in `application.properties`: ```text management.endpoints.web.exposure.include=prometheus,health,info,metric ``` Now we can trigger this endpoint and see the Prometheus metrics: See response output ```bash ▶ http GET "http://localhost:8080/actuator/prometheus" HTTP/1.1 200 Connection: keep-alive Content-Length: 8187 Content-Type: text/plain; version=0.0.4;charset=utf-8 Date: Thu, 22 Oct 2020 09:19:36 GMT Keep-Alive: timeout=60 # HELP tomcat_sessions_rejected_sessions_total # TYPE tomcat_sessions_rejected_sessions_total counter tomcat_sessions_rejected_sessions_total 0.0 # HELP system_cpu_usage The "recent cpu usage" for the whole system # TYPE system_cpu_usage gauge system_cpu_usage 0.0 # HELP jvm_buffer_count_buffers An estimate of the number of buffers in the pool # TYPE jvm_buffer_count_buffers gauge jvm_buffer_count_buffers{id="mapped",} 0.0 jvm_buffer_count_buffers{id="direct",} 3.0 # HELP jvm_memory_used_bytes The amount of used memory # TYPE jvm_memory_used_bytes gauge jvm_memory_used_bytes{area="heap",id="G1 Survivor Space",} 1.048576E7 jvm_memory_used_bytes{area="heap",id="G1 Old Gen",} 3099824.0 jvm_memory_used_bytes{area="nonheap",id="Metaspace",} 3.9556144E7 jvm_memory_used_bytes{area="nonheap",id="CodeHeap 'non-nmethods'",} 1206016.0 jvm_memory_used_bytes{area="heap",id="G1 Eden Space",} 3.3554432E7 jvm_memory_used_bytes{area="nonheap",id="Compressed Class Space",} 5010096.0 jvm_memory_used_bytes{area="nonheap",id="CodeHeap 'non-profiled nmethods'",} 6964992.0 # HELP jvm_gc_pause_seconds Time spent in GC pause # TYPE jvm_gc_pause_seconds summary jvm_gc_pause_seconds_count{action="end of minor GC",cause="Metadata GC Threshold",} 1.0 jvm_gc_pause_seconds_sum{action="end of minor GC",cause="Metadata GC Threshold",} 0.009 # HELP jvm_gc_pause_seconds_max Time spent in GC pause # TYPE jvm_gc_pause_seconds_max gauge jvm_gc_pause_seconds_max{action="end of minor GC",cause="Metadata GC Threshold",} 0.009 # HELP jvm_gc_live_data_size_bytes Size of old generation memory pool after a full GC # TYPE jvm_gc_live_data_size_bytes gauge jvm_gc_live_data_size_bytes 4148400.0 # HELP jvm_gc_max_data_size_bytes Max size of old generation memory pool # TYPE jvm_gc_max_data_size_bytes gauge jvm_gc_max_data_size_bytes 4.294967296E9 # HELP tomcat_sessions_active_current_sessions # TYPE tomcat_sessions_active_current_sessions gauge tomcat_sessions_active_current_sessions 0.0 # HELP process_files_open_files The open file descriptor count # TYPE process_files_open_files gauge process_files_open_files 69.0 # HELP http_server_requests_seconds # TYPE http_server_requests_seconds summary http_server_requests_seconds_count{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/actuator/health",} 1.0 http_server_requests_seconds_sum{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/actuator/health",} 0.041047824 # HELP http_server_requests_seconds_max # TYPE http_server_requests_seconds_max gauge http_server_requests_seconds_max{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/actuator/health",} 0.041047824 # HELP jvm_threads_peak_threads The peak live thread count since the Java virtual machine started or peak was reset # TYPE jvm_threads_peak_threads gauge jvm_threads_peak_threads 32.0 # HELP process_uptime_seconds The uptime of the Java virtual machine # TYPE process_uptime_seconds gauge process_uptime_seconds 13.385 # HELP process_cpu_usage The "recent cpu usage" for the Java Virtual Machine process # TYPE process_cpu_usage gauge process_cpu_usage 0.0 # HELP jvm_memory_max_bytes The maximum amount of memory in bytes that can be used for memory management # TYPE jvm_memory_max_bytes gauge jvm_memory_max_bytes{area="heap",id="G1 Survivor Space",} -1.0 jvm_memory_max_bytes{area="heap",id="G1 Old Gen",} 4.294967296E9 jvm_memory_max_bytes{area="nonheap",id="Metaspace",} -1.0 jvm_memory_max_bytes{area="nonheap",id="CodeHeap 'non-nmethods'",} 7553024.0 jvm_memory_max_bytes{area="heap",id="G1 Eden Space",} -1.0 jvm_memory_max_bytes{area="nonheap",id="Compressed Class Space",} 1.073741824E9 jvm_memory_max_bytes{area="nonheap",id="CodeHeap 'non-profiled nmethods'",} 2.44105216E8 # HELP logback_events_total Number of error level events that made it to the logs # TYPE logback_events_total counter logback_events_total{level="warn",} 0.0 logback_events_total{level="debug",} 0.0 logback_events_total{level="error",} 0.0 logback_events_total{level="trace",} 0.0 logback_events_total{level="info",} 8.0 # HELP system_load_average_1m The sum of the number of runnable entities queued to available processors and the number of runnable entities running on the available processors averaged over a period of time # TYPE system_load_average_1m gauge system_load_average_1m 3.18994140625 # HELP jvm_gc_memory_promoted_bytes_total Count of positive increases in the size of the old generation memory pool before GC to after GC # TYPE jvm_gc_memory_promoted_bytes_total counter jvm_gc_memory_promoted_bytes_total 0.0 # HELP jvm_threads_states_threads The current number of threads having NEW state # TYPE jvm_threads_states_threads gauge jvm_threads_states_threads{state="runnable",} 14.0 jvm_threads_states_threads{state="blocked",} 0.0 jvm_threads_states_threads{state="waiting",} 11.0 jvm_threads_states_threads{state="timed-waiting",} 5.0 jvm_threads_states_threads{state="new",} 0.0 jvm_threads_states_threads{state="terminated",} 0.0 # HELP jvm_memory_committed_bytes The amount of memory in bytes that is committed for the Java virtual machine to use # TYPE jvm_memory_committed_bytes gauge jvm_memory_committed_bytes{area="heap",id="G1 Survivor Space",} 1.048576E7 jvm_memory_committed_bytes{area="heap",id="G1 Old Gen",} 1.31072E8 jvm_memory_committed_bytes{area="nonheap",id="Metaspace",} 4.1336832E7 jvm_memory_committed_bytes{area="nonheap",id="CodeHeap 'non-nmethods'",} 2949120.0 jvm_memory_committed_bytes{area="heap",id="G1 Eden Space",} 1.26877696E8 jvm_memory_committed_bytes{area="nonheap",id="Compressed Class Space",} 5767168.0 jvm_memory_committed_bytes{area="nonheap",id="CodeHeap 'non-profiled nmethods'",} 7012352.0 # HELP tomcat_sessions_active_max_sessions # TYPE tomcat_sessions_active_max_sessions gauge tomcat_sessions_active_max_sessions 0.0 # HELP jvm_buffer_memory_used_bytes An estimate of the memory that the Java virtual machine is using for this buffer pool # TYPE jvm_buffer_memory_used_bytes gauge jvm_buffer_memory_used_bytes{id="mapped",} 0.0 jvm_buffer_memory_used_bytes{id="direct",} 24576.0 # HELP jvm_gc_memory_allocated_bytes_total Incremented for an increase in the size of the young generation memory pool after one GC to before the next # TYPE jvm_gc_memory_allocated_bytes_total counter jvm_gc_memory_allocated_bytes_total 2.7262976E7 # HELP jvm_classes_loaded_classes The number of classes that are currently loaded in the Java virtual machine # TYPE jvm_classes_loaded_classes gauge jvm_classes_loaded_classes 7336.0 # HELP jvm_classes_unloaded_classes_total The total number of classes unloaded since the Java virtual machine has started execution # TYPE jvm_classes_unloaded_classes_total counter jvm_classes_unloaded_classes_total 0.0 # HELP tomcat_sessions_created_sessions_total # TYPE tomcat_sessions_created_sessions_total counter tomcat_sessions_created_sessions_total 0.0 # HELP process_files_max_files The maximum file descriptor count # TYPE process_files_max_files gauge process_files_max_files 10240.0 # HELP tomcat_sessions_alive_max_seconds # TYPE tomcat_sessions_alive_max_seconds gauge tomcat_sessions_alive_max_seconds 0.0 # HELP jvm_buffer_total_capacity_bytes An estimate of the total capacity of the buffers in this pool # TYPE jvm_buffer_total_capacity_bytes gauge jvm_buffer_total_capacity_bytes{id="mapped",} 0.0 jvm_buffer_total_capacity_bytes{id="direct",} 24576.0 # HELP system_cpu_count The number of processors available to the Java virtual machine # TYPE system_cpu_count gauge system_cpu_count 12.0 # HELP jvm_threads_live_threads The current number of live threads including both daemon and non-daemon threads # TYPE jvm_threads_live_threads gauge jvm_threads_live_threads 30.0 # HELP process_start_time_seconds Start time of the process since unix epoch. # TYPE process_start_time_seconds gauge process_start_time_seconds 1.603358363515E9 # HELP tomcat_sessions_expired_sessions_total # TYPE tomcat_sessions_expired_sessions_total counter tomcat_sessions_expired_sessions_total 0.0 # HELP jvm_threads_daemon_threads The current number of live daemon threads # TYPE jvm_threads_daemon_threads gauge jvm_threads_daemon_threads 26.0 ``` ### Custom Metrics We can also define some custom metrics, which I will demonstrate in this section. The demo contains a `Scheduler` class which periodically runs the included `schedulingTask` method. To be able to send custom metrics we need to import `MeterRegistry` from the Micrometer library and inject it into our class. For more detail please check the [official documentation](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#production-ready-metrics-custom){rel=""nofollow""}. It is possible to instantiate these types of meters from `MeterRegistry`: - [Counter](https://github.com/micrometer-metrics/micrometer/blob/master/micrometer-core/src/main/java/io/micrometer/core/instrument/Counter.java#L25){rel=""nofollow""}: reports merely a count over a specified property of an application - [Gauge](https://github.com/micrometer-metrics/micrometer/blob/master/micrometer-core/src/main/java/io/micrometer/core/instrument/Gauge.java#L23){rel=""nofollow""}: shows the current value of a meter - [Timers](https://github.com/micrometer-metrics/micrometer/blob/master/micrometer-core/src/main/java/io/micrometer/core/instrument/Timer.java#L34){rel=""nofollow""}: measures latencies or frequency of events - [DistributionSummary](https://github.com/micrometer-metrics/micrometer/blob/master/micrometer-core/src/main/java/io/micrometer/core/instrument/DistributionSummary.java#L29){rel=""nofollow""}: provides distribution of events and a simple summary I implemented a counter and a gauge for demonstration purposes: ```java @Component public class Scheduler { private final AtomicInteger testGauge; private final Counter testCounter; public Scheduler(MeterRegistry meterRegistry) { // Counter vs. gauge, summary vs. histogram // https://prometheus.io/docs/practices/instrumentation/#counter-vs-gauge-summary-vs-histogram testGauge = meterRegistry.gauge("custom_gauge", new AtomicInteger(0)); testCounter = meterRegistry.counter("custom_counter"); } @Scheduled(fixedRateString = "1000", initialDelayString = "0") public void schedulingTask() { testGauge.set(Scheduler.getRandomNumberInRange(0, 100)); testCounter.increment(); } private static int getRandomNumberInRange(int min, int max) { if (min >= max) { throw new IllegalArgumentException("max must be greater than min"); } Random r = new Random(); return r.nextInt((max - min) + 1) + min; } } ``` If we run the application we can see that our custom metrics are exposed via the `actuatuor/prometheus` endpoint: ```bash ▶ http GET "http://localhost:8080/actuator/prometheus" | grep custom # HELP custom_gauge # TYPE custom_gauge gauge custom_gauge 29.0 # HELP custom_counter_total # TYPE custom_counter_total counter custom_counter_total 722.0 ``` As we now have the metrics available in a format that Prometheus can understand, we will look at how to set up Prometheus. ## Prometheus [Prometheus](https://prometheus.io){rel=""nofollow""} stores our metric data in time series in memory by periodically pulling it via HTTP. The data can be visualized by a console template language, a built-in expression browser, or by integrating [Grafana](https://grafana.com){rel=""nofollow""} (which we will do after setting up Prometheus). In this demo, we will run Prometheus locally in a Docker container and we, therefore, need some configurations in a `prometheus.yml` file that you can place anywhere on your hard drive: ```yaml global: scrape_interval: 10s # How frequently to scrape targets by default scrape_configs: - job_name: 'spring_micrometer' # The job name is assigned to scraped metrics by default. metrics_path: '/actuator/prometheus' # The HTTP resource path on which to fetch metrics from targets. scrape_interval: 5s # How frequently to scrape targets from this job. static_configs: # A static_config allows specifying a list of targets and a common label set for them - targets: ['192.168.178.22:8080'] ``` All available configuration options can be seen in the [official documentation](https://prometheus.io/docs/prometheus/latest/configuration/configuration/){rel=""nofollow""}. As we want to run Prometheus in a Docker container we need to tell Prometheus our IP address instead of `localhost` in `static_configs -> targets`. Instead of `localhost:8080` we are using `192.168.178.22:8080` where `192.168.178.22` is my IP address at the moment. To get your system IP you can use `ifconfig` or `ipconfig` in your terminal depending on your operating system. Now we are ready to run Prometheus: ```bash docker run -d -p 9090:9090 -v :/etc/prometheus/prometheus.yml prom/prometheus ``` `` should be the path where you placed the `prometheus.yml` configuration file described above. Finally, we can open the Prometheus on `http://localhost:9090` in the web browser and search for our custom metric named `custom_gauge`: ![Prometheus Graph](https://mokkapps.de/blog/monitoring-spring-boot-application-with-micrometer-prometheus-and-grafana-using-custom-metrics/prometheus-graph_sfoiok.jpg) To check that Prometheus is correctly listening to our locally running Spring Boot application we can navigate to `Status -> Targets` in the top main navigation bar: ![Prometheus Targets](https://mokkapps.de/blog/monitoring-spring-boot-application-with-micrometer-prometheus-and-grafana-using-custom-metrics/prometheus-targets_zyqate.jpg) Prometheus provides a query language PromQL, check the [official documentation](https://prometheus.io/docs/prometheus/latest/querying/basics/){rel=""nofollow""} for more details. ## Grafana The included Prometheus browser graph is nice for basic visualization of our metrics but we will use [Grafana](https://grafana.com){rel=""nofollow""} instead. Grafana provides a rich UI where you create, explore and share dashboards that contain multiple graphs. Grafana can pull data from various data sources like Prometheus, Elasticsearch, InfluxDB, etc. It also allows you to set rule-based alerts, which then can notify you over Slack, Email, Hipchat, and similar. We start Grafana also locally in a Docker container: ```bash docker run -d -p 3000:3000 grafana/grafana ``` Opening `http://localhost:3000` in a browser should now show the following login page: ![Grafana Login](https://mokkapps.de/blog/monitoring-spring-boot-application-with-micrometer-prometheus-and-grafana-using-custom-metrics/grafana-login_sid1lk.jpg) You can log in using the default username `admin` and the default password `admin`. After login, you should change these default passwords by visiting `http://localhost:3000/profile/password`. The first step is to add our local Prometheus as our data source: ![Grafana Add Datasource](https://mokkapps.de/blog/monitoring-spring-boot-application-with-micrometer-prometheus-and-grafana-using-custom-metrics/grafana-add-datasource_qxa8hc.jpg)![Grafana Select Prometheus](https://mokkapps.de/blog/monitoring-spring-boot-application-with-micrometer-prometheus-and-grafana-using-custom-metrics/grafana-select-prometheus_bxuka2.jpg)![Grafana Prometheus Config](https://mokkapps.de/blog/monitoring-spring-boot-application-with-micrometer-prometheus-and-grafana-using-custom-metrics/grafana-prometheus-config_riykri.jpg) ### Community Dashboard The first dashboard we want to add is a [community dashboard](https://grafana.com/grafana/dashboards){rel=""nofollow""}. As we are using a Spring Boot application we choose the popular [JVM dashboard](https://grafana.com/grafana/dashboards/4701){rel=""nofollow""}: ![Grafana Import JVM Dashboard](https://mokkapps.de/blog/monitoring-spring-boot-application-with-micrometer-prometheus-and-grafana-using-custom-metrics/grafana-import-dashboard_lu4q0u.jpg) After loading the URL we can see the imported dashboard: ![Grafana Imported JVM Dashboard](https://mokkapps.de/blog/monitoring-spring-boot-application-with-micrometer-prometheus-and-grafana-using-custom-metrics/grafana-jvm-dashboard_eedkqy.jpg) ### Custom Metric Dashboard Finally, we want to create a new dashboard where we show our custom metrics. The first step is to create a new dashboard: ![Grafana Imported JVM Dashboard](https://mokkapps.de/blog/monitoring-spring-boot-application-with-micrometer-prometheus-and-grafana-using-custom-metrics/grafana-new-dashboard_qosn1t.jpg) Now we see a new dashboard where we can create a new panel: ![Grafana New Dashboard Panel](https://mokkapps.de/blog/monitoring-spring-boot-application-with-micrometer-prometheus-and-grafana-using-custom-metrics/grafana-dashboard-new-panel_pk3nzz.jpg) In the first panel we add a visualization for our `custom_gauge` metric. I use the `Stat` visualization as it shows the current value and a simple graph: ![Grafana Stat Gauge Vizualization](https://mokkapps.de/blog/monitoring-spring-boot-application-with-micrometer-prometheus-and-grafana-using-custom-metrics/grafana-stat-gauge-metric_uevln0.jpg) Additionally, a new panel for the `custom_counter` metric is added to our dashboard: ![Grafana Graph Counter Vizualization](https://mokkapps.de/blog/monitoring-spring-boot-application-with-micrometer-prometheus-and-grafana-using-custom-metrics/grafana-counter-metric_bhci5r.jpg) In the end, the dashboard looks like this: ![Grafana Custom Dashboard](https://mokkapps.de/blog/monitoring-spring-boot-application-with-micrometer-prometheus-and-grafana-using-custom-metrics/grafana-final-dashboard_rcf89z.jpg) ## Conclusion It is important to monitor an application's metrics and health which helps us to improve performance, manage the app in a better way and notice unoptimized behavior. Monitoring each service is important to be able to maintain a system that consists of many microservices. In this article, I showed how a Spring Boot web application can be monitored using [Micrometer](https://micrometer.io){rel=""nofollow""} which exposes metrics from our application, [Prometheus](https://prometheus.io){rel=""nofollow""} which stores the metric data and [Grafana](https://grafana.com){rel=""nofollow""} to visualize the data in graphs. This popular monitoring approach should help you to maintain your applications and make your customers happy. As always, the code for the demo used in this article can be found on [GitHub](https://github.com/Mokkapps/custom-metrics-spring-boot-demo){rel=""nofollow""}. # My Definition Of A Senior Developer I met and worked with many other developers as a software developer. Some just started their apprenticeship, some started their first job after university, some already had multiple years of work experience, and some even had 10+ years of experience working as a software developer. Early in my career, I asked myself what a "senior" developer is and how I could achieve this title? I thought it was related to the years of work experience and that I would automatically receive this title if I had 3+ years of work experience. After working with many other developers, I have a clear opinion about the title "senior" software developer. Let's first summarize my distinguishing marks as a senior software developer: 1. Have a passion for what you are doing 2. Be a "problem solver" 3. Learn the fundamental basics of your programming language and frameworks 4. Be a mentor and have a mentor 5. Keep yourself up-to-date 6. Leave your comfort zone 7. Fight for your opinion 8. Be social 9. Focus on soft skills as well Now let's dive deeper into these topics. ## Have a passion for what you are doing ![Passion Meme](https://mokkapps.de/blog/my-definition-of-a-senior-software-developer/SCR-20230405-nott.png) Most of the other marks will automatically be achieved if you have passion for your work. In my opinion, you can only be a good software developer if you love your work. This also means that you should choose a technical stack or specialty that you are (or will become) very good at. Of course, you should also learn other stuff outside your specialty. Your goal should be to become a [T-Shaped](http://en.wikipedia.org/wiki/T-shaped_skills){rel=""nofollow""} Software Engineer who knows his primary specialty very well. In this article, I will mainly focus on web development tech stacks as I have the most experience working with them and have a personal opinion. ## Be a "problem solver" You should love to solve challenging problems in an endless amount of time. You should have the power, ambition, skills, and passion for solving any possible situation during your career. ## Learn the fundamental basics of your programming language and frameworks ![Never Stop Learning](https://mokkapps.de/blog/my-definition-of-a-senior-software-developer/never-stop-learning.png) This is essential for a software developer. It is often not very complicated to learn the basics of a programming language or framework. Most of the time, you can quickly implement features or even smaller projects after a short time. But it gets tricky if you need to debug, adapt the framework, or fix bugs. For example, many people use the Angular CLI but are unfamiliar with all the steps behind the scenes. Or they use Angular with TypeScript but do not know how to read JavaScript code in the minified bundle code. Basically, you can follow these basic steps to learn the fundamentals: #### Read some of the fundamental books about software programming I would suggest reading some classic books about software development like [Clean Code: A Handbook of Agile Software Craftsmanship](https://lesen.amazon.de/kp/embed?asin=B001GSTOAM&preview=newtablinkCode=kperef_=cm_sw_r_kb_dp_VKevBbTK4P88Q){rel=""nofollow""} or [The Pragmatic Programer](https://lesen.amazon.de/kp/embed?asin=B003GCTQAE&preview=newtab&linkCode=kpe&ref_=cm_sw_r_kb_dp_ZRxwBb86F48F4){rel=""nofollow""}. These books will provide you the basic patterns, guidelines, and best practices to write good software. #### Deep dive into your programming language In web development, JavaScript is the language you should master. Your browser will run JavaScript code (even if it was written using frameworks like Angular with a programming language like TypeScript), and you need to understand this code that is executed. This is also important if you need to analyze how a particular functionality is implemented in your framework, so you should be able to read low-level JavaScript source code. For JavaScript, I would recommend you to read [JavaScript: The Good Parts](https://lesen.amazon.de/kp/embedasin=B0026OR2ZY&preview=newtab&linkCode=kpe&ref_=cm_sw_r_kb_dp_0RevBbP68KXYS){rel=""nofollow""}. #### Master your framework Same as for the programming language: Deep dive into the advanced mechanics used in your framework. For example, for Angular, I can recommend the blog [Angular In Depth](https://blog.angularindepth.com/){rel=""nofollow""}. #### Learn your IDE / editor / command line Be as efficient as possible by using keyboard shortcuts, plugins, and commands for your IDE, text editor, and command line. If you are using Visual Code, check out my article [How I Increased My Productivity With Visual Studio Code](https://mokkapps.de/blog/how-i-increased-my-productivity-with-visual-code). #### Learn version control I mainly worked with Git and can recommend you the free online ebook [Pro git](http://git-scm.com/book){rel=""nofollow""}. ## Be a mentor and have a mentor ![Mentor Meme](https://mokkapps.de/blog/my-definition-of-a-senior-software-developer/i-am-your-mentor.jpg) In my opinion, you can only call yourself a "senior" developer if you mentor others and also have a mentor yourself. It would help if you had someone at your company, in your project, or even on the internet who you could learn from and improve. So you can also have a "remote" mentor where you read a specific blog, watch presentations, hear a podcast, or read tweets. > Don't be afraid that you are not the best at everything. There is almost always somebody better than you. (Read also about the [Imposter Syndrome](https://en.wikipedia.org/wiki/Impostor_syndrome){rel=""nofollow""}) How you can mentor others: 1. Be patient and do not judge others because of their lack of knowledge 2. Let the other person talk and listen actively 3. Show the path of success that can be achieved as a senior developer 4. Spend enough time and offer help when it is needed ## Keep yourself up-to-date My suggestion is to use these channels to keep yourself up-to-date: - Twitter - YouTube - Podcasts - Conferences - Blogs - Meetups - (Online) Courses ## Leave your comfort zone ![Comfort Zone](https://mokkapps.de/blog/my-definition-of-a-senior-software-developer/comfort-zone.jpg) Many developers try to avoid leaving their comfort zone, and a "senior" developer should not be afraid of leaving his comfort zone. Here are some examples: - You are afraid of talking about technical stuff for many people? --> Give a talk at a conference or Meetup and get comfortable with it. - You don't like writing backend code and are only interested in frontend? --> Go ahead and learn backend technologies. You will benefit if you understand the "other" side. - You avoid touching your CI/CD pipeline as you do not understand it, and some other developers are more experienced with it? --> Take your time and learn the basics so that you can help yourself, and you are not dependent on other developers. ## Fight for your opinion In my opinion, a "senior" developer should have a clear statement and be able to fight for it in front of clients or other developers. It is not satisfying for me to "dictate" technical decisions to my team, and everyone accepts it without saying their meanings and starts implementing them. For both sides, it is more satisfying if there is a vivid discussion about the technical proposal. It can help the architect get new impressions, and the team can actively impact decisions. ## Be social Do not hide behind your monitors. Go out there and talk to other developers, and you will profit from it. Additionally, use the social platforms mentioned above to contact other developers. I would also recommend building up your brand and letting others be able to follow you: - Have a website where you present your projects - Use channels like Twitter, Facebook, YouTube, or Instagram and inform your followers about interesting topics - Start a blog where you start writing technical articles - Try to hold talks at conferences ## Focus on soft skills as well Writing good code is essential, but it is also crucial to describe technical stuff to "non-techies" like clients. You should be able to draw architecture understandably or describe it in words. Additionally, you should be able to have working time management where you can prioritize tasks and work on them most efficiently. ## Conclusion As you can see, the journey of becoming a senior software developer is not very easy and cannot be achieved in a short amount of time. This is where years of experience are essential, but you have to spend your time focusing on the aspects mentioned above in these years. If you only have many years of work experience but did not grow yourself as a developer, you cannot be a "senior," in my opinion. Of course, this is only my humble opinion so let me know what your definition of a "senior" developer is and what experiences you have had working with them? # My First NPM Package: github-traffic-cli Since I published my first projects on [GitHub](https://github.com/Mokkapps){rel=""nofollow""} I've enjoyed viewing the traffic on my repositories. It is exciting to see how many people visit or clone my repositories. Unfortunately, it costs a lot of time to click through all available repositories, and I was looking for a more elegant way. I stumbled upon the npm package [github-traffic](https://www.npmjs.com/package/github-traffic){rel=""nofollow""}, which already provides an API to fetch the GitHub traffic. So I decided to write a command-line interface (CLI) npm package which uses this API. As a result, I can check the traffic on all of my repositories with one CLI command: !["github-traffic-cli" Screenshot](https://mokkapps.de/blog/my-first-npm-package/github-traffic-cli_akmxsx.jpg) ## Develop & publish npm package The process is straightforward and documented in the [npm docs](https://docs.npmjs.com/getting-started/publishing-npm-packages){rel=""nofollow""}. ## Used npm packages - [chalk](https://www.npmjs.com/package/chalk){rel=""nofollow""}: Terminal string styling done right - [clui](https://www.npmjs.com/package/clui){rel=""nofollow""}: Node.js toolkit for quickly building nice looking command line interfaces - [commander](https://www.npmjs.com/package/commander){rel=""nofollow""}: The complete solution for node.js command-line interfaces - [figlet](https://www.npmjs.com/package/figlet){rel=""nofollow""}: Terminal ASCII art from text - [inquirer](https://www.npmjs.com/package/inquirer){rel=""nofollow""}: A collection of common interactive command line user interfaces. ## Links - [github-traffic-cli](https://www.npmjs.com/package/github-traffic-cli){rel=""nofollow""} - [Source Code](https://github.com/Mokkapps/github-traffic-cli){rel=""nofollow""} # My First Visual Code Extension I am a big fan of [Visual Code](https://code.visualstudio.com){rel=""nofollow""} and use it as my main IDE for software development. The available selection of extensions (see the [Extension Marketplace](https://marketplace.visualstudio.com/VSCode){rel=""nofollow""}) is amazing. As I started using Visual Code I found every extension I was looking for. Last week I stumbled upon a feature for which I could not find an extension. So I decided to write my first VS code extension and let you know about my experiences during the development. ## The problem I wanted to solve Currently, I am doing a lot of [Angular](https://angular.io/){rel=""nofollow""} development and therefore use [Jasmine](https://jasmine.github.io/){rel=""nofollow""} for unit tests. My first IDE, which I used for web development was [WebStorm](https://www.jetbrains.com/webstorm/){rel=""nofollow""} which is based on [IntelliJ IDEA](https://www.jetbrains.com/idea/){rel=""nofollow""}. In WebStorm, I often used and liked the plugin [ddescriber](https://github.com/andresdominguez/ddescriber){rel=""nofollow""} for Jasmine tests: > Intellij plugin to quickly transform a JavaScript test block from describe() to ddescribe() and a test it() into iit() This is a nice feature, but I often used the plugin to list all available specs and then jump to a certain `describe()` or `it()` block: > Just type Ctrl + Shift + D (Command + Shift + D on a Mac) to launch a dialog that lets you choose which suites or unit tests you want to include or exclude. This is useful in large unit tests which includes many `describe()` or `it()` blocks. As I could not find a VS code extension that solves this problem, I decided to write my first own VS code extension. ## What the extension should handle The first version of the extension should be able to: - List all `describe()` or `it()` blocks as dropdown in an opened file in the editor - If a block is selected, move the cursor to this block ### How to start? Big applause to the VS code team for the amazing [documentation](https://code.visualstudio.com/docs/extensions/overview){rel=""nofollow""} on how to build your own VS code extension. It is straightforward to grab one of the example projects or create a new one using [Extension Generator](https://code.visualstudio.com/docs/extensions/yocode){rel=""nofollow""} and get started. Additionally, it is very easy to [run and debug your new extension](https://code.visualstudio.com/docs/extensions/developing-extensions#_running-and-debugging-your-extension){rel=""nofollow""}. Searching through the [Extension API documentation](https://code.visualstudio.com/docs/extensionAPI/overview){rel=""nofollow""} I found this method ```ts showQuickPick(items: T[] | Thenable, options?: QuickPickOptions, token?: CancellationToken): Thenable ``` which functionality is described as: > Shows a selection list allowing multiple selections. It looks this way in VS code if it is called: ![VS Code Quick Pick Screenshot](https://mokkapps.de/blog/my-first-vs-code-extension/jasmine-test-selector_iquirk.png) So this sounded like an excellent opportunity to list all test blocks and provide a way to receive the selected value. So basically, I had to implement these steps: - Grab all strings in the opened editor, which include `it(` or `describe(` and the corresponding line number of this string. - Pass them to `showQuickPick` method. - Receive the selection and move the cursor to the corresponding line number. The final output for a Jasmine test file looks like this: ![VS Code Jasmine Test Selector Screenshot](https://mokkapps.de/blog/my-first-vs-code-extension/vs-code-quick-pick_mxoayz.jpg) ### Publishing the extension Another lovely experience was the straightforward publishing process for VS code extensions. Basically, I followed the [official documentation](https://code.visualstudio.com/docs/extensions/publish-extension){rel=""nofollow""}, which requires a [Visual Studio Team Services](https://docs.microsoft.com/vsts/accounts/create-account-msa-or-work-student){rel=""nofollow""} account. The published extension is available in the [Visual Studio Code Marketplace](https://marketplace.visualstudio.com/items?itemName=Mokkapps.jasmine-test-selector#overview){rel=""nofollow""}. ## Conclusion In summary, it made a lot of fun to develop a VS code experience. The documentation and examples are excellent, and I am thrilled that I added a functionality to my favorite code editor, which I've been missing. ## Links - [Download the extension from the marketplace](https://marketplace.visualstudio.com/items?itemName=Mokkapps.jasmine-test-selector#overview){rel=""nofollow""} - [Source code on GitHub](https://github.com/Mokkapps/jasmine-test-selector){rel=""nofollow""} # My Top Angular Interview Questions This article summarizes a list of Angular interview questions that I would ask candidates and that I get often asked in interviews. ## 1. What is Angular? What is the difference between Angular and Vue.js / React? [Angular](https://angular.io){rel=""nofollow""} is an application design framework and development platform for creating efficient and sophisticated single-page apps. Angular is built entirely in TypeScript and uses it as a primary language. As it is a framework it has many useful built-in features like routing, forms, HTTP client, Internationalization (i18n), animations, and many more. [Vue.js](https://vuejs.org/){rel=""nofollow""} and [React](https://reactjs.org/){rel=""nofollow""} are no application frameworks but JavaScript libraries to build user interfaces. Vue.js describe itself as `an incrementally adoptable ecosystem that scales between a library and a full-featured framework` and React as `a JavaScript library for building user interfaces`. ## 2. What's new in Angular? Check the [Angular blog](https://blog.angular.io){rel=""nofollow""} for latest release notes, for example, the [Angular 11 release](https://blog.angular.io/version-11-of-angular-now-available-74721b7952f7){rel=""nofollow""}. ## 3. What are Angular's main concepts? - **Component**: The basic building block of an Angular application and is used to control HTML views. - **Modules**: An Angular module contains basic building blocks like components, services, directives, etc. Using modules you can split your application into logical pieces where each piece performs a single task and is called a "module". - **Templates**: A template represents the view of an Angular application. - **Services**: Services are used to create components that can be shared across the entire application. - **Metadata**: Metadata is used to add more data to an Angular class. ![Angular architecture](https://mokkapps.de/blog/my-top-angular-interview-questions/angular-architecture_dqhiqx.jpg) ## 4. What is Dependency Injection? Dependency Injection (DI) is an important design pattern in which a class does not create dependencies itself but requests them from external sources. Dependencies are services or objects that a class needs to perform its function. Angular uses its own DI framework for resolving dependencies. The DI framework provides declared dependencies to a class when that class is instantiated. ## 5. What are Observables? Angular heavily relies on [RxJS](https://rxjs.dev/){rel=""nofollow""}, a library for composing asynchronous and callback-based code in a functional, reactive style using Observables. RxJS introduces Observables, a new Push system for JavaScript where an Observable is a producer of multiple values, "pushing" them to Observers (Consumers). ## 6. What is the difference between Promise and Observable? | Observable | Promise | | :------------------------------------------------------------------------------------------------- | :-------------------------------- | | They can be run whenever the result is needed as they do not start until subscription | Execute immediately on creation | | Provides multiple values over time | Provides only one value | | Subscribe method is used for error handling which makes centralized and predictable error handling | Push errors to the child promises | | Provides chaining and subscription to handle complex applications | Uses only .then() clause | ## 7. Can you explain various ways of component communication in Angular? 1. Data sharing between parent and one or more child components using the `@Input()` and `@Output()` directives. 2. Data sharing using an Angular service 3. Using state management, like [NgRx](https://ngrx.io/){rel=""nofollow""} 4. Read and write data to local storage 5. Pass data via URL parameters ## 8. How can you bind data to templates? - **Property binding**: Property binding in Angular helps you set values for properties of HTML elements or directives ```html ``` - **Event binding**: Event binding allows you to listen for and respond to user actions such as keystrokes, mouse movements, clicks, and touches. ```html ``` - **Two-way binding**: Two-way binding gives components in your application a way to share data. Use two-way binding binding to listen for events and update values simultaneously between parent and child components. ```html ``` ## 9. What do you understand by services? > Service is a broad category encompassing any value, function, or feature that an app needs. A service is typically a class with a narrow, well-defined purpose. It should do something specific and do it well. An Angular component should focus on presenting data and enabling the user experience. It should mediate between the application logic (data model) and the view (rendered by the template). Angular services help us to separate non-view-related functionality to keep component classes lean and efficient. ### How do you provide a service? You must register at least one provider of any service you are going to use. A service can be provided for specific modules or components or it can be made available everywhere in your application. #### Provide at root level ```ts @Injectable({ providedIn: 'root', }) ``` Angular creates a single, shared instance if a service is provided at root level. This shared instance is injected into any class that asks for it. By using the `@Injectable()` metadata, Angular can remove the service from the compiled app if it isn't used. ### Provide with a specific NgModule Registering a provider with a specific NgModule will return the same instance of a service to all components in that NgModule if they ask for it. ```ts @NgModule({ providers: [ BackendService, Logger ], ... }) ``` #### Provide at component level A new instance of a service is generated for each new instance of the component if you register the provider at component level. ```ts @Component({ selector: 'app-hero-list', templateUrl: './hero-list.component.html', providers: [ HeroService ] }) ``` ## 10. What do you understand by directives? Directives add behavior to an existing DOM element or an existing component instance. The basic difference between a component and a directive is that a component has a template, whereas an attribute or structural directive does not have a template and only one component can be instantiated per an element in a template. We can differentiate between three types of directives: - **Components**: These directives have a template. - **Structural directives**: These directives change the DOM layout by adding and removing DOM elements. - **Attribute directives**: These directives change the appearance or behavior of an element, component, or another directive. ## 11. JIT vs AOT Angular provides two ways to compile your app. The compilation step is needed as Angular templates and components cannot be understood by the browser therefore the HTML and TypeScript code is converted into efficient JavaScript code. When you run the `ng serve` or `ng build` CLI commands, the type of compilation (JIT or AOT) depends on the value of the `aot` property in your build configuration specified in `angular.json`. By default, `aot` is set to true for new CLI apps. ### Just-in-Time (JIT) JIT compiles your app in the browser at runtime. This was the default until Angular 8. ### Ahead-of-Time (AOT) AOT compiles your app at build time. This is the default since Angular 9. #### What are the advantages of AOT? - The application can be rendered without compiling the app because the browser downloads a pre-compiled version of the application. - External CSS style sheets and HTML templates are included within the application JavaScript code. This way, a lot of AJAX requests can be saved. - It is not necessary to download the Angular compiler which reduces the application payload. - Template binding errors can be detected and reported during the build step itself - No injection attacks as HTML templates and components are compiled into JavaScript. ## 12. What do you understand by lazy loading? By default, NgModules are eagerly loaded, which means that as soon as the app loads, so do all the NgModules, whether or not they are immediately necessary. For large apps with lots of routes, consider lazy loading—a design pattern that loads NgModules as needed. Lazy loading helps keep initial bundle sizes smaller, which in turn helps decrease load times. ## 13. Can you explain Angular Components Lifecycle Hooks? After your application instantiates a component or directive by calling its constructor, Angular calls the hook methods you have implemented at the appropriate point in the lifecycle of that instance. ![Lifecycle Hooks](https://mokkapps.de/blog/my-top-angular-interview-questions/hooks-in-sequence_a5ics8.png) Angular calls these hook methods in the following order: 1. **ngOnChanges**: Is called, when an input/output binding value changes. 2. **ngOnInit**: Is called after the first ngOnChanges. 3. **ngDoCheck**: Is called, if we as developer triggered a custom change detection. 4. **ngAfterContentInit**: Is called after the content of a component is initialized. 5. **ngAfterContentChecked**: Is called after every check of the component's content. 6. **ngAfterViewInit**: Is called after a component's views are initialized. 7. **ngAfterViewChecked**: Is called after every check of a component's views. 8. **ngOnDestroy**: Is called just before the directive is destroyed. ## 14. What is the difference between ViewChild and ContentChild? ViewChild and ContentChild are used for component communication in Angular, for example, if a parent component wants access to one or multiple child components. - A ViewChild is any component, directive, or element which is part of a template. - A ContentChild is any component or element which is projected in the template. In Angular exist two different DOMs: - **Content DOM** which has only knowledge of the template provided by the component at hand or content injected via ``. - **View DOM** which has only knowledge of the encapsulated and the descending components. ## 15. What is the difference between an Angular module and a JavaScript module? Both types of modules can help to modularize code and Angular relies on both kinds of modules but they are very different. A JavaScript module is an individual file with JavaScript code, usually containing a class or a library of functions for a specific purpose within your app. NgModules are specific to Angular and a NgModule is a class marked by the `@NgModule` decorator with a metadata object. ## 16. What are @HostBinding and @HostListener? - `@HostListener()` function decorator allows you to handle events of the host element in the directive class. For example, it can be used to change the color of the host element if you hover over the host element with the mouse. - `@HostBinding()` function decorator allows you to set the properties of the host element from the directive class. In this directive class, we can change any style property like height, width, color, margin, border, etc. ## 17. What is the difference between OnPush and default change detection? Please read my article [The Last Guide For Angular Change Detection You'll Ever Need](https://www.mokkapps.de/blog/the-last-guide-for-angular-change-detection-you-will-ever-need/){rel=""nofollow""} for a detailed explanation.  ## 18. What is ViewEncapsulation? Component CSS styles are encapsulated into the component's view to avoid styling side effects in the rest of the Angular application. The type of encapsulation can be controlled per component via the `encapsulation` property in the component metadata: ```ts // warning: few browsers support shadow DOM encapsulation at this time encapsulation: ViewEncapsulation.ShadowDom ``` You can choose between the following modes: - `ViewEncapsulation.Emulated` which is the default mode and emulates the shadow DOM behavior. It renames and preprocesses the CSS code to effectively scope the CSS to the component's view. Each DOM element gets attached some additional attributes like `_nghost` or `_ngcontent`. An element that would be a shadow DOM host in native encapsulation has a generated `_nghost` attribute. This is typically the case for component host elements. An element within a component's view has a `_ngcontent` attribute that identifies to which host's emulated shadow DOM this element belongs. - `ViewEncapsulation.None` which tells Angular to not use view encapsulation and adds CSS to the global styles. Essentially, this is the same behavior as pasing the component's styles into the HTML. - `ViewEncapsulation.ShadowDom` which uses the browser's native [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM){rel=""nofollow""} implementation. It attaches a shadow DOM to the component's host element and then puts the component view inside that shadow DOM. The component's styles are included within the shadow DOM. ## Conclusion I hope this list of Angular interview questions will help you to get your next Angular position. Leave me a comment if you know any other important Angular interview questions. ## Links - [Angular Docs](https://angular.io/docs){rel=""nofollow""} - [250+ Angular Interview Questions & Answers](https://github.com/sudheerj/angular-interview-questions){rel=""nofollow""} # My Top React Interview Questions This article summarizes a list of React interview questions that I would ask candidates and that I get often asked in interviews. ## 1. What is React? [React](https://reactjs.org/){rel=""nofollow""} is a "JavaScript library for building user interfaces" which was developed by Facebook in 2011. It’s the V in the MVC (Model - View -Controller), so it is rather an open-source UI library than a framework. ## 2. What are the advantages of React? - Good performance: due to VDOM, see [#17](https://mokkapps.de/blog/my-top-react-interview-questions#7-what-is-the-virtual-dom). - Easy to learn: with basic JavaScript knowledge you can start building applications. Frameworks like Angular require more knowledge about other technologies and patterns like RxJS, TypeScript, and Dependency Injection. - One-way data flow: this flow is also called "parent to child" or "top to bottom" and prevents errors and facilitates debugging. - Reusable components: Re-using React components in other parts of the code or even in different projects can be done with little or no changes. - Huge community: The community supplies a ton of libraries that can be used to build React applications. - It is very popular among developers. ## 3. What are the disadvantages of React? - As React provides only the View part of the MVC model you mostly will rely on other technologies in your projects as well. Therefore, every React project might look quite different. - Some people think that JSX is too difficult to grasp and too complex. - Often poor documentation for React and its libraries. ## 4. What is JSX? JSX (JavaScript XML) allows us to write HTML inside JavaScript. The [official docs](https://reactjs.org/docs/introducing-jsx.html){rel=""nofollow""} describe it as "syntax extension to JavaScript". React recommends using JSX, but it is also possible to create applications [without using JSX](https://reactjs.org/docs/react-without-jsx.html){rel=""nofollow""} at all. A simple JSX example: ```javascript const element =

Hello, world!

``` ## 5. How to pass data between components? 1. Use props to pass data from parent to child. 2. Use callbacks to pass data from child to parent. 3. Use any of the following methods to pass data among siblings: - Integrating the methods mentioned above. - Using [Redux](https://redux.js.org/){rel=""nofollow""}. - Utilizing [React's Context API](https://reactjs.org/docs/context.html#api){rel=""nofollow""}. ## 6. What are the differences between functional and class components? [Hooks](https://reactjs.org/docs/hooks-intro.html){rel=""nofollow""} were introduced in React 16.8. In previous versions, functional components were called stateless components and did not provide the same features as class components (e.g., accessing state). Hooks enable functional components to have the same features as class components. There are no plans to remove class components from React. So let's take a look at the differences: ### Declaration & Props #### Functional Component Functional components are JavaScript functions and therefore can be declared using an arrow function or the `function` keyword. Props are simply function arguments and can be directly used inside JSX: ```javascript const Card = (props) => { return

Title: {props.title}

} function Card(props) { return

Title: {props.title}

} ``` #### Class component Class components are declared using the ES6 `class` keyword. Props need to be accessed using the `this` keyword: ```javascript class Card extends React.Component { constructor(props) { super(props) } render() { return

Title: {this.props.title}

} } ``` ### Handling state #### Functional components In functional components we need to use the `useState` hook to be able to handle state: ```javascript const Counter = (props) => { const [counter, setCounter] = useState(0) const increment = () => { setCounter(++counter) } return (

Count: {counter}

) } ``` #### Class components It's not possible to use React Hooks inside class components, therefore state handling is done differently in a class component: ```javascript class Counter extends React.Component { constructor(props) { super(props) this.state = { counter: 0 } this.increment = this.increment.bind(this) } increment() { this.setState((prevState) => { return { counter: prevState.counter + 1 } }) } render() { return (

Count: {this.state.counter}

) } } ``` ## 7. What is the Virtual DOM? The [Virtual DOM (VDOM)](https://reactjs.org/docs/faq-internals.html#what-is-the-virtual-dom){rel=""nofollow""} is a lightweight JavaScript object and it contains a copy of the real DOM. | Real DOM | Virtual DOM | | --------------------------------- | :---------------------------------------: | | Slow & expensive DOM manipulation | Fast & inexpensive DOM manipulation | | Allows direct updates from HTML | It cannot be used to update HTML directly | | Wastes too much memory | Less memory consumption | ## 8. Is the Shadow DOM the same as the Virtual DOM? No, they are different. The [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM){rel=""nofollow""} is a browser technology designed primarily for scoping variables and CSS in web components. The virtual DOM is a concept implemented by libraries in JavaScript on top of browser APIs. ## 9. What is "React Fiber"? Fiber is the new reconciliation engine in React 16. Its headline feature is incremental rendering: the ability to split rendering work into chunks and spread it out over multiple frames. [Read more](https://github.com/acdlite/react-fiber-architecture){rel=""nofollow""}. ## 10. How does state differ from props? Both props and state are plain JavaScript objects. Props (short for "properties") is an object of arbitrary inputs that are passed to a component by its parent component. State are variables that are initialized and managed by the component and change over the lifetime of a specific instance of this component. [This article from Kent C. Dodds](https://kentcdodds.com/blog/props-vs-state){rel=""nofollow""} provides a more detailed explanation. ## 11. What are the differences between controlled and uncontrolled components? The value of an input element in a controlled React component is controlled by React. The value of an input element in an uncontrolled React component is controlled by the DOM. ## 12. What are the different lifecycle methods in React? React class components provide these lifecycle methods: - `componentDidMount()`: Runs after the component output has been rendered to the DOM. - `componentDidUpdate()`: Runs immediately after updating occurs. - `componentWillUnmount()`: Runs before the component is unmounted from the DOM and is used to clear up the memory space. There exist some other [rarely used](https://reactjs.org/docs/react-component.html#rarely-used-lifecycle-methods){rel=""nofollow""} and [legacy](https://reactjs.org/docs/react-component.html#legacy-lifecycle-methods){rel=""nofollow""} lifecycle methods. Hooks are used in functional components instead of the above-mentioned lifecycle methods. The Effect Hook `useEffect` adds, for example, the ability to perform side effects and provides the same functionality as `componentDidMount`, `componentDidUpdate`, and `componentWillUnmount`. ## 13. How can you improve your React app's performance? - Use [React.PureComponent](https://reactjs.org/docs/react-api.html#reactpurecomponent){rel=""nofollow""} which is a base class like `React.Component` but it provides in some cases a performance boost if its `render()` function renders the same result given the same props and state. - Use [useMemo Hook](https://reactjs.org/docs/hooks-reference.html#usememo){rel=""nofollow""} to memoize functions that perform expensive calculations on every render. It will only recompute the memoized value if one of the dependencies (that are passed to the Hook) has changed. - State colocation is a process that moves the state as close to where you need it. Some React applications have a lot of unnecessary state in their parent component which makes the code harder to maintain and leads to a lot of unnecessary re-renders. [This article](https://kentcdodds.com/blog/state-colocation-will-make-your-react-app-faster){rel=""nofollow""} provides a detailed explanation about state colocation. - Lazy load your components to reduce the load time of your application. React [Suspense](https://reactjs.org/docs/react-api.html#suspense){rel=""nofollow""} can be used to lazy load components. ## 14. What are keys in React? React needs keys to be able to identify which elements were changed, added, or removed. Each item in an array needs to have a key that provides a stable identity. It's not recommended to use indexes for keys if the order of items may change as it can have a negative impact on the performance and may cause state issues. React will use indexes as keys if you do not assign an explicit key to list items. Check out Robin Pokorny’s article for an [in-depth explanation of the negative impacts of using an index as a key](https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318){rel=""nofollow""}. Here is another [in-depth explanation about why keys are necessary](https://reactjs.org/docs/reconciliation.html#recursing-on-children){rel=""nofollow""} if you’re interested in learning more. ## 15. What are Higher Order Components? A [higher-order component (HOC)](https://reactjs.org/docs/higher-order-components.html#use-hocs-for-cross-cutting-concerns){rel=""nofollow""} is a function that takes a component and returns a new component. They are an advanced technique in React for reusing component logic and they are not part of the React API, per se. They are a pattern that emerges from React’s compositional nature: ```javascript const EnhancedComponent = higherOrderComponent(WrappedComponent) ``` Whereas a component transforms props into UI, a higher-order component transforms a component into another component. ## 16. What are error boundaries? React 16 introduced a new concept of an “error boundary”. [Error boundaries](https://reactjs.org/docs/error-boundaries.html#gatsby-focus-wrapper){rel=""nofollow""} are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them. ## 17. Why Hooks were introduced? Hooks solve a wide variety of seemingly unconnected problems in React that were encountered by Facebook over five years of writing and maintaining tens of thousands of components: - Hooks allow you to reuse stateful logic without changing your component hierarchy. - Hooks let you split one component into smaller functions based on what pieces are related (such as setting up a subscription or fetching data). - Hooks let you use more of React’s features without classes. - It removed the complexity of dealing with the `this` keyword inside class components. [Read more](https://reactjs.org/docs/hooks-intro.html#motivation){rel=""nofollow""} ## 18. What is the purpose of useEffect hook? The [Effect hook](https://reactjs.org/docs/hooks-reference.html#useeffect){rel=""nofollow""} lets us perform side effects in functional components. It helps us to avoid redundant code in different lifecycle methods of a class component. It helps to group related code. ## 19. What are synthetic events in React? [SyntheticEvent](https://reactjs.org/docs/events.html){rel=""nofollow""} is a cross-browser wrapper around the browser's native event. It has the same API as the browser's native event, including `stopPropagation()` and \`preventDefault(), except the events work identically across all browsers. ## 20. What is the use of refs? A [Ref](https://reactjs.org/docs/glossary.html#refs){rel=""nofollow""} is a special attribute that can be attached to any component. It can be an object created by `React.createRef()`, a callback function or a string (in legacy API). To get direct access to a DOM element or component instance you can use ref attribute as a callback function. The function receives the underlying DOM element or class instance (depending on the type of element) as its argument. In most cases, refs should be used sparingly. ## Conclusion I hope this list of React interview questions will help you to get your next React position. Leave me a comment if you know any other important React interview questions. If you liked this article, follow me on [Twitter](https://twitter.com/mokkapps){rel=""nofollow""} to get notified about new blog posts and more content from me. If you are looking for more interview questions you should take a look at this [list of top 500 React interview questions & answers](https://github.com/sudheerj/reactjs-interview-questions){rel=""nofollow""}. # My Top Vue.js Interview Questions This article summarizes a list of Vue.js interview questions that I would ask candidates and that I get often asked in interviews. ## 1. What is Vue.js? [Vue](https://vuejs.org){rel=""nofollow""} is a progressive framework for building user interfaces that was designed to be incrementally adoptable. Its core library is focused exclusively on the view layer so that it can easily be integrated with other projects or libraries. But in contrast to [React](https://reactjs.org/){rel=""nofollow""}, Vue provides companion libraries for routing and state management which are all officially supported and kept up-to-date with the core library. ## 2. What are some of the main features of Vue.js? - Virtual DOM: Vue uses a [Virtual DOM](https://vuejs.org/v2/guide/render-function.html#The-Virtual-DOM){rel=""nofollow""}, similar to other frameworks such as React, Ember, etc. - Components: Components are the basic building block for reusable elements in Vue applications. - Templates: Vue uses HTML-based templates. - Routing: Vue provide it's [own router](https://router.vuejs.org/){rel=""nofollow""}. - Built-in [directives](https://v3.vuejs.org/api/directives.html){rel=""nofollow""}: For example, v-if or v-for - Lightweight: Vue is a lightweight library compared to other frameworks. ## 3. Why would you choose Vue instead of React or Angular? Vue.js combines the best parts of Angular and React. Vue.js is a more flexible, less opinionated solution than Angular but it's still a framework and not a UI library like React I recently decided to focus my freelancer career on [Vue.js](https://vuejs.org){rel=""nofollow""}, you can read more about this decision in the [corresponding blog post](https://www.mokkapps.de/blog/why-i-picked-vue-js-as-my-freelancer-niche/){rel=""nofollow""}. ## 4. What is an SFC? Vue [Single File Components](https://v3.vuejs.org/guide/single-file-component.html){rel=""nofollow""} (aka `*.vue` files, abbreviated as SFC) is a special file format that allows us to encapsulate the template (`