# 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}
Loading...Todos: {{ data }}Error: {{ error }}
```
`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}
Loading ...
{{ todo.name }}
```
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 `
{{ todo.name }}{{ todo.id }}
```
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}
{{ data }}
```
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:

::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.

- 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

- 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)
- [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:

### 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:

### Use Minimal Theme
Choose *Minimal* theme to have a cleaner UI with smaller tabs as shown in the screenshot above:

### 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:

## 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
- [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""}
- [htop](https://hisham.hm/htop/){rel=""nofollow""}: "an interactive process viewer for Unix systems", which I use instead of the macOS `Activity Monitor.app`
- [Midnight Commander](https://midnight-commander.org/){rel=""nofollow""}: a visual file manager

- [tree](https://github.com/MrRaindrop/tree-cli){rel=""nofollow""}: List contents of directories in tree-like format

- [bat](https://github.com/sharkdp/bat){rel=""nofollow""}: a `cat` clone with syntax highlighting and Git integration

- [lnav](https://lnav.org/){rel=""nofollow""}: an advanced log file viewer

- [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:

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
```

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:

### 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:

### 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?

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}
May I show you something cool?
```
::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:

### 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}
Back home
```
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
Counter: {{ store.counter }}
```
Now we can run the Vue application using the Quasar CLI:
```bash
quasar dev
```
The Vue application is served at `http://localhost:8080`:

## 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:

[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
Open Electron File Dialog
```
Clicking on the button should now open the native OS file dialog:

## 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:

Alternatively, you can open it from [Command Menu](https://developer.chrome.com/docs/devtools/command-menu/){rel=""nofollow""}:

## 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:

::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.

The following GIF visualizes this process:

It's also possible to manually edit the recorded steps. For example, you can manually change selectors:

Additionally, you can manually add or remove steps:

## Replay
After recording a user flow, you can replay it by clicking on the "Replay" button.

::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:

## 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.

## 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
```

## 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
Blog
{{ title }}
```
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 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
Blog slug ({{ $route.params.slug }}) not found
```
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:

## 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}
Table of Contents
```
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}
Table of Contents
```
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
https://mokkapps.de
RSS for NodeSun, 14 Aug 2022 18:14:16 GMT
https://mokkapps.de/blog/article-5
https://mokkapps.de/blog/article-5Thu, 05 May 2022 00:00:00 GMT
https://mokkapps.de/blog/article-4
https://mokkapps.de/blog/article-4Mon, 04 Apr 2022 00:00:00 GMT
https://mokkapps.de/blog/article-3
https://mokkapps.de/blog/article-3Thu, 03 Mar 2022 00:00:00 GMT
https://mokkapps.de/blog/article-2
https://mokkapps.de/blog/article-2Wed, 02 Feb 2022 00:00:00 GMT
https://mokkapps.de/blog/article-1
https://mokkapps.de/blog/article-1Sat, 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
Nuxt 3 Tailwind Dark Mode Demo
```
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
Nuxt 3 Tailwind Dark Mode Demo
```
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`:

## 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.

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.

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`:

## 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:

If we now start the profiling, trigger a state change, and stop the profiling we can see that information:

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:

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`:

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
```

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}
{{ label }}
{{ count }}
```
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:

As already mentioned, Storybook converts the JSDoc comments from our code snippet above into documentation, shown in the following picture:

## 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:

The next picture shows the Swagger UI with the test route:

## 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]
{{ code }}
```
Let's extend that component by using our `useShikiHighlighter` composable to highlight the code:
```vue [components/content/ProseCode.vue] {4-5,7-34,39}
{{ code }}
```
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:

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:

### Components
> Unstyled components with useful functionality
The following image shows the list of components provided by the CDK:

### 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:

Now you will see a new section at the top of your profile page which renders the content of this new README file:

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 `
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:

The serverless function should then send an email with a list of tweets if someone mentioned the monitored keyword in the last 24 hours:

## 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://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.

- `CMD + D`: Finds and selects the next match for the currently selected word.

- `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.

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.

### 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 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.

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""}

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""}.

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:

## 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:
`
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:
`
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}
{{ doc.title }}
Not Found
Browse issues
```
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]
Admin Controls
Schedule
```
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`:

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:

> 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.

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:

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.

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:

- **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:

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:

### 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:

## Show Language
Next, we want to show the name of the language on the top right, if it is available.
```vue {3-9}
{{ languageText }}
```
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:

## Show File Name
Next, we want to show the file's name on the top left, if it is available:
```vue
{{ filename }}
```
The result looks like this:

## 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
Copied code!
```
Let's take a look at the final result with language & file name, copy code button, and line highlighting:

## 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:

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:

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:

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:

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:

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:

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:

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}}
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:

## 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:

Next, we click "Other type of secret" and enter key and value of our secret in the corresponding "Secret key/value" inputs:

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:

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:

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

with the following selections:

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://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:

### Start Frontend
Run `npm start` to serve the Angular application on `http://localhost:9000/`:

Finally, we can log in and see some of the out-of-the-box features like the possibility to see and edit our entities,

view metrics of the application

and a user management

## 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:

## 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.

## 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:

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.

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:

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:

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:

## 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""}:

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`:

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 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:

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:

### 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""}:

After loading the URL we can see the imported dashboard:

### 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:

Now we see a new dashboard where we can create a new panel:

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:

Additionally, a new panel for the `custom_counter` metric is added to our dashboard:

In the end, the dashboard looks like this:

## 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

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

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

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

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:

## 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:

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:

### 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.

## 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.

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 (``), logic (`
{{ count }}
```
::note
This only works if the `ref` is a top-level property in the template.
::
#### Watcher
We can directly pass a `ref` as a watcher dependency:
```js {3,5-6}
import { watch, ref } from 'vue'
const count = ref(0)
// Vue automatically unwraps this ref for us
watch(count, (newCount) => console.log(newCount))
```
#### Volar
If you are using VS Code, you can configure the [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar){rel=""nofollow""} extension to automatically add `.value` to refs. You can enable it in the settings under `Volar: Auto Complete Refs`:

The corresponding JSON setting:
```json
"volar.autoCompleteRefs": true
```
::note
To reduce CPU usage, this feature is disabled by default.
::
## Summarizing comparison between reactive() and ref()
Let's take a summarizing look at the differences between `reactive` and `ref`:
| `reactive` | `ref` |
| ------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| 👎 **only** works on object types | 👍 works with **any** value |
| 👍 no difference in accessing values in `
```
You can simply copy everything from `data` into `reactive` to migrate this component to Composition API:
```js [CompositionApiComponent.vue] {4-8}
```
### Composing ref and reactive
A recommended pattern is to group refs inside a `reactive` object:
```js {1-2,4-7}
const loading = ref(true)
const error = ref(null)
const state = reactive({
loading,
error,
})
// You can watch the reactive object...
watchEffect(() => console.log(state.loading))
// ...and the ref directly
watch(loading, () => console.log('loading has changed'))
setTimeout(() => {
loading.value = false
// Triggers both watchers
}, 500)
```
If you don't need the reactivity of the `state` object itself you could instead group the refs in a plain JavaScript object.
Grouping refs results in a single object that is easier to handle and keeps your code organized. At a glance, you can see that the grouped refs belong together and are related.
::note
This pattern is also used in libraries like [Vuelidate](https://vuelidate.js.org/){rel=""nofollow""} where they [use reactive() for setting up state for validations](https://blog.logrocket.com/form-validation-in-vue-with-vuelidate/){rel=""nofollow""}.
::
## Opinions from Vue Community
The amazing [Michael Thiessen](https://twitter.com/MichaelThiessen){rel=""nofollow""} wrote a [brilliant in-depth article](https://michaelnthiessen.com/ref-vs-reactive/#act-3-why-i-prefer-ref){rel=""nofollow""} about this topic and collected the opinions of famous people in the Vue community.
Summarized, **they all use `ref` by default** and use `reactive` when they need to group things.
## Conclusion
So, should you use `ref` or `reactive`?
My recommendation is to use `ref` by default and `reactive` when you need to group things. The Vue community has the same opinion but it's totally fine if you decide to use `reactive` by default.
Both `ref` and `reactive` are powerful tools to create reactive variables in Vue 3. You can even use both of them without any technical drawbacks. Just pick the one you like and try to stay **consistent** in how you write your code!
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""}.
# Rendering Dynamic Markdown in Nuxt 3+
In my current freelance project, I had to render dynamic Markdown content in a Nuxt 3+ application. The Markdown content was written by redactors in a CMS, provided via an API, and needed to be rendered on the client-side. In this article, I'll explain how we solved the problem of rendering dynamic Markdown content in a Nuxt 3+ application.
## The Problem
The Markdown content was provided by the CMS via an API and needed to be rendered on the client-side. The content was dynamic and could change at any time, so we couldn't hard-code it into the application. We needed a way to fetch the Markdown content from the API and render it as HTML in the Nuxt 3+ application.
Previously, the redactors wrote that content in HTML, and we used the `v-html` directive to render it. However, we wanted to switch to Markdown to make it easier for the redactors to write and format the content. Additionally, this allowed us to easily reuse our existing Vue components.
## The Solution
::note
If you are only working with static Markdown files, you can use the [`@nuxt/content` module](https://content.nuxt.com/){rel=""nofollow""} to render Markdown content in your Nuxt 3+ application.
::
Luckily, Nuxt 3+ provides a solution for rendering Markdown content using the [`@nuxtjs/mdc` module](https://github.com/nuxt-modules/mdc){rel=""nofollow""}. This module allows you to render Markdown content as HTML in your Nuxt 3+ application.
You can add it to your project using the following command:
```bash
npx nuxi@latest module add mdc
```
This command will install the `@nuxtjs/mdc` module and add it to the modules section of your `nuxt.config.ts` file.
Now you can use the `` component to render Markdown content in your Vue components. Here's an example of how you can use it:
```vue [Component.vue] {16}
```
That's it! The Markdown content will be rendered as HTML in your Nuxt app. Using the [MDC](https://content.nuxt.com/usage/markdown){rel=""nofollow""} syntax, you can also include custom Vue components in your Markdown content. In my example, I referenced the `my-button` component, which will be rendered as a button in the Markdown content.
::note
You have to globally register your Vue components if you want to use them in the Markdown content. You can do this by placing them in a `~/components/global` directory or by using a `.global.vue` suffix in the filename.
::
## Stackblitz Demo
Try it yourself in the following Stackblitz demo:
:stackblitz{project-id="nuxt-markdown-to-vue-converter"}
## Conclusion
Rendering dynamic Markdown content in a Nuxt 3+ application is easy using the `@nuxtjs/mdc` module. By using Markdown, you can make it easier for redactors to write and format content and reuse your existing Vue components.
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""}:
# Run Automated Electron App Tests Using Travis CI
Last year I developed the [Standup Picker](https://mokkapps.de/standup-picker), which is an [Angular](https://angular.io){rel=""nofollow""} application running in an [Electron](https://electronjs.org){rel=""nofollow""} shell.
As I released new versions while older versions were already in use, I wanted to gain more confidence while releasing newer versions of my application.
As the [source code is available at GitHub](https://github.com/Mokkapps/scrum-daily-standup-picker){rel=""nofollow""}, I researched for free alternatives to [Jenkins](https://jenkins-ci.org/){rel=""nofollow""} which we used at work for Continuous Integration (CI).
I found [Travis](https://travis-ci.org){rel=""nofollow""}, a free continuous integration platform for GitHub projects.
## My Expectations

I wanted to integrate automated E2E and unit tests before each release of the Electron application. In my case, a release should be triggered if something has been merged to master. So the CI should perform these steps:
1. Run unit tests
2. Run E2E tests
3. Create Electron releases for OS X, Linux, Windows
This way, I can ensure that my releases work as expected (at least all the stuff I have covered by tests).
## Integrate Travis CI in your project
To use Travis, you need to make sure that you have a GitHub account and owner permissions for this project hosted on GitHub.
The next step is to visit [the Travis homepage](https://travis-ci.com/){rel=""nofollow""}, [sign up with GitHub](https://travis-ci.com/signin){rel=""nofollow""} and follow the instructions until you can select your project.
To tell Travis CI what automated steps should be executed, you need to add a `.travis.yml` file to the root directory of your repository.
Finally, you must add the `.travis.yml` file to git. If you then commit and push, a Travis CI build is triggered. Be aware that Travis can only run builds on commits that were pushed after the `.travis.yml` file has been pushed to git.
## Configure Travis CI
I will explain how I configured the `.travis.yml` file for my Electron application.

### Select Operating System
I start with a quote from [the electron-builder website](https://www.electron.build/multi-platform-build){rel=""nofollow""}, which is an NPM package I used to create my Electron releases:
> Don’t expect that you can build app for all platforms on one platform.
As I wanted to create releases for OS X, Windows, Linux I had to define multiple operating systems. The main reason was that it is impossible to create a Linux release from OS X or Windows.
So I ran my Travis setup on Linux and OS X in parallel. My scripts check the current operating system and run
only in the correct environment.
Check the [official documentation](https://docs.travis-ci.com/user/multi-os/){rel=""nofollow""} for more details.
These are the relevant parts of my `.travis.yml` file:
```yaml
osx_image: xcode8.4 # define OS X image which will be mounted
dist: trusty # use Ubuntu Trusty for Linux operation system
# Note: if you switch to sudo: false, you'll need to launch chrome with --no-sandbox.
# See https://github.com/travis-ci/travis-ci/issues/8836
sudo: required
# Define Node.js as the programming language as we have a web application
language: node_js
node_js: '8'
addons:
chrome: stable # Install chrome stable on operating systems
# A list of operating systems that are used for tests
os:
- linux
- osx
```
## Electron Builder Configurations

For the [electron-builder](https://www.electron.build/){rel=""nofollow""} I added some additional cache and variable configuration based on the [official documentation](https://www.electron.build/multi-platform-build){rel=""nofollow""}:
```yaml
env:
global:
- ELECTRON_CACHE=$HOME/.cache/electron
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
cache:
yarn: true
directories:
- $HOME/.cache/electron
- $HOME/.cache/electron-builder
- $HOME/.npm/_prebuilds
before_cache:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then rm -rf $HOME/.cache/electron-builder/wine; fi
```
## Define Scripts
Now we define the scripts which Travis should execute:
```yaml
# These commands are executed before the scripts are executed
install:
# On OS X we first need to install Yarn via Homebrew
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install yarn; fi
# Install all dependencies listed in your package.json file
- yarn
script:
- echo "Unit Tests"
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then xvfb-run yarn test; else yarn test; fi
- echo "E2E Tests"
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then xvfb-run yarn test:electron; else yarn test:electron; fi
- echo "Deploy linux version to GitHub"
- if [[ "$TRAVIS_BRANCH" == "master" ]] && [[ "$TRAVIS_OS_NAME" == "linux" ]]; then yarn release:linux; fi
- echo "Deploy windows version to GitHub"
- if [[ "$TRAVIS_BRANCH" == "master" ]] && [[ "$TRAVIS_OS_NAME" == "osx" ]]; then yarn release:win; fi
- echo "Deploy mac version to GitHub"
- if [[ "$TRAVIS_BRANCH" == "master" ]] && [[ "$TRAVIS_OS_NAME" == "osx" ]]; then yarn release:mac; fi
```
### Unit & E2E tests
Electron needs a display driver as it is based on Chromium. You cannot execute any of your tests (and Electron will fail to launch) if Chromium cannot find a display driver. To fix this issue, we need to use a virtual display driver like [Xvfb](https://en.wikipedia.org/wiki/Xvfb){rel=""nofollow""}.
Xvfb is a virtual framebuffer that enables our tests to run in memory without showing an actual screen.
On Linux, we need to run the NPM test script via `xvfb-run yarn ` on OS X and Windows Chromium is already correctly configured.
### GitHub Release
By running `yarn release:` from my [package.json](https://github.com/Mokkapps/scrum-daily-standup-picker/blob/master/package.json){rel=""nofollow""} via electron-builder, I could automatically create a new release draft on the GitHub release page if the unit & E2E tests have passed:

## Conclusion
I had to invest multiple hours in configuring Travis for my application. In the end, the time and effort were worth it.
New releases have passed my tests, and I can be sure that the application's basic functionality is working.
# Run, Build & Deploy Stencil and Storybook From One Repository
I recently joined a project where the team used two separate Git repositories for their web components based on [Stencil](https://stenciljs.com/){rel=""nofollow""} and [Storybook](https://storybook.js.org/){rel=""nofollow""}. But the idea of Storybook is that the so-called "stories" live next to the components source code. Therefore, it made no sense to me to have those two tools in different repositories, and I combined them both in one repository.
My goal was that developers can also use Storybook stories via hot reload during development. Additionally, it should still be possible to separately deploy the web components to a [npm](https://www.npmjs.com/){rel=""nofollow""} registry and Storybook to a public URL.
This article describes the necessary steps to combine Storybook and Stencil in one repository. I wrote this article as there is currently no official documentation available on how to use Storybook with Stencil. Let's start with some basics.
## Stencil
> Stencil is a toolchain for building reusable, scalable Design Systems. Generate small, blazing fast, and 100% standards based Web Components that run in every browser.
Stencil combines the "best concepts of the most popular frameworks into a simple build-time tool" that provides features like:
- TypeScript support
- JSX support
- One way data-binding
As you can see from these picked concepts, Stencil is a [React](https://reactjs.org/){rel=""nofollow""}-inspired web component library. I previously worked with [lit-element](https://lit-element.polymer-project.org/){rel=""nofollow""} but due to the above-mentioned features, I prefer working with Stencil, especially in React projects.
### Init Stencil
Let's create a new Stencil project which will be the base for the demo project of this article which is available at [GitHub](https://github.com/Mokkapps/stencil-storybook-demo){rel=""nofollow""}:
```bash
npm init stencil
```
We choose the `component` starter as we want to build a web component library that can be shared via npm:
```bash
? Pick a starter › - Use arrow-keys. Return to submit.
ionic-pwa Everything you need to build fast, production ready PWAs
app Minimal starter for building a Stencil app or website
❯ component Collection of web components that can be used anywhere
```
Now we modify the automatically created `my-component.tsx` to be a bit more complex:
```ts
export interface CompOption {
value: string
displayText: string
}
@Component({
tag: 'my-component',
styleUrl: 'my-component.css',
shadow: true,
})
export class MyComponent {
/**
* The text which is shown as label
*/
@Prop() label: string
/**
* Is needed to reference the form data after the form is submitted
*/
@Prop({ reflect: true }) name: string
/**
* If true, the button is displayed as disabled
*/
@Prop({ reflect: true }) disabled = false
/**
* Define the available options in the drop-down list
*/
@Prop() options: CompOption[] = []
render() {
return (
)
}
}
```
Our demo component is a native HTML select component that gets its options passed via property. Some values like the label text, the component name, and if the component is disabled are also passed via props to the web component.
### Run Stencil web components
To be able to locally test our demo component we need to adjust `src/index.html` which is used if we start Stencil:
```html
Stencil Component Starter
```
Now we can locally test our demo component by running `npm run start-stencil` which is an auto-generated npm script from Stencil. The component should now be visible at `http://localhost:3333`:

### Build & deploy to npm registry
The next step is to deploy our component to an npm registry. For this demo, I use [Verdaccio](https://verdaccio.org){rel=""nofollow""} which is a "lightweight open source private npm proxy registry". First, it needs to be installed globally
```bash
npm install -g verdaccio
```
and then it can be started locally:
```bash
▶ verdaccio
warn --- config file - /Users/mhoffman/.config/verdaccio/config.yaml
warn --- Verdaccio started
warn --- Plugin successfully loaded: verdaccio-htpasswd
warn --- Plugin successfully loaded: verdaccio-audit
warn --- http address - http://localhost:4873/ - verdaccio/4.12.0
```
Now we have a local npm registry available at `http://localhost:4873/` so we need to tell npm to use that registry, for example, by modifying `.npmrc`:
```text
registry=http://localhost:4873
```
Additionally, we need to create a user in our registry:
```bash
npm adduser --registry http://localhost:4873
```
Finally, we can pack the package and publish it to the npm registry:
```bash
npm pack
npm publish
```
It should now be visible in our private registry at `http://localhost:4873/`:

At this point, we have a working Stencil web component library that can be deployed to any npm registry. The next step is to integrate Storybook into our repository.
## Storybook
> Storybook is an open source tool for developing UI components in isolation for React, Vue, Angular, and more
A typical use case for [Storybook](https://storybook.js.org/){rel=""nofollow""} is to have a visual representation of a web component library. This allows
any developer or designer to see which components are currently available and how they look and behave.
### Init Storybook
As Stencil components are compiled to web components we can use the [Storybook for HTML](https://storybook.js.org/docs/guides/guide-html/){rel=""nofollow""} project type:
```bash
npx -p @storybook/cli sb init -t html
```
### Run & build Storybook
If we now run `npm run storybook` it opens a browser window at `http://localhost:6006` which shows some automatically generated components & stories:

Now let's write a story for our `` demo web component:
```js
export default {
title: 'Demo/MyComponent',
argTypes: {
label: { type: 'text', description: 'The text which is shown as label' },
name: {
type: 'text',
description: 'Is needed to reference the form data after the form is submitted',
},
disabled: {
type: 'boolean',
description: 'If true, the button is displayed as disabled',
defaultValue: { summary: false },
},
},
}
const defaultArgs = {
disabled: false,
}
const Template = (args) => {
return
}
export const MyComponent = Template.bind({})
Default.MyComponent = { ...defaultArgs }
```
In our story, we defined [Controls](https://storybook.js.org/docs/react/essentials/controls#gatsby-focus-wrapper){rel=""nofollow""} to be able to manipulate
our component properties inside Storybook. We also added some default values and descriptions.
But unfortunately, we cannot see our component inside Storybook and need to do some further adjustments to the project setup.
First, we need to load and register our web components in `.storybook/preview.js` to include them in webpack's dependency graph. This JavaScript code is added to the preview canvas of every Storybook story and is therefore available for the webpack build:
```js {1,3}
import { defineCustomElements } from '../dist/esm/loader'
defineCustomElements()
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
}
```
Now we need to add [@storybook/react](https://www.npmjs.com/package/@storybook/react){rel=""nofollow""} to be able to use our component in the story:
```bash
npm add -D @storybook/react
```
Next step is to modify our `my-component.stories.js`:
```js {1-2,6}
import React from 'react'
import MyComponent from '../../../dist/collection/components/my-component/my-component'
export default {
title: 'Demo/MyComponent',
component: MyComponent,
argTypes: {
label: { type: 'text', description: 'The text which is shown as label' },
name: {
type: 'text',
description: 'Is needed to reference the form data after the form is submitted',
},
disabled: {
type: 'boolean',
description: 'If true, the button is displayed as disabled',
defaultValue: { summary: false },
},
},
}
const defaultArgs = {
disabled: false,
}
const Template = (args) => {
return
}
export const Default = Template.bind({})
Default.args = { ...defaultArgs }
```
Finally, we need to add two new npm scripts:
```json
"scripts": {
"build-stencil:watch": "stencil build --docs-readme --watch --serve",
"start-storybook": "start-storybook -p 6006 -s dist"
},
```
By running Stencil's build process with the `--watch` flag it generates the correct output with the `esm/loader.mjs` file we reference in the `preview.js` file. We then just need to tell Storybook to use the `dist` folder generated by the Stencil build command and disable its caching mechanism.
If we now run `build-stencil:watch` and then `start-storybook` in a separate terminal we can see our component in Storybook:

You can now also modify your Stencil web component and due to the hot reload you can see immediately your changes in Storybook.
You might also wonder how we can set options via property? It is possible by using `setTimeout` inside the Template function in `my-component.stories.js` to ensure that the component has been loaded:
```js
const Template = (args) => {
args.id = args.id ? args.id : 'my-component'
setTimeout(() => {
document.getElementById(args.id).options = [
{
value: 'Item 1',
displayText: 'Item 1',
},
{
value: 'Item 2',
displayText: 'Item 2',
},
{
value: 'Item 3',
displayText: 'Item 3',
},
]
})
return
}
```
### Deploy Storybook
Finally, we want to deploy Storybook to a public URL and therefore we use [storybook-deployer](https://github.com/storybookjs/storybook-deployer){rel=""nofollow""} which provides a nice way to deploy it to GitHub Pages or AWS S3. We will deploy it to AWS S3 by installing the tool
```bash
npm i @storybook/storybook-deployer --save-dev
```
and adding some new scripts to `package.json`:
```json
"scripts": {
"build-storybook": "build-storybook -o ./distStorybook",
"predeploy-storybook": "npm run build-storybook",
"deploy-storybook": "storybook-to-aws-s3 --existing-output-dir ./distStorybook --bucket-path ",
},
```
Before we deploy Storybook we trigger a build, this is done by using `build-storybook` as [pre script](https://docs.npmjs.com/cli/v7/using-npm/scripts#pre--post-scripts){rel=""nofollow""}. You also need to ensure that your [AWS S3 has public access allowed](https://havecamerawilltravel.com/photographer/how-allow-public-access-amazon-bucket/){rel=""nofollow""}.
For example, my demo project is hosted at {rel=""nofollow""}.
## Conclusion
It is a bit tricky to combine Stencil and Storybook and it would be nice to have official documentation for this topic.
But I think it is worth the effort, and it can also improve the local component development due to Storybook's features.
The code for the demo project is available at [GitHub](https://github.com/Mokkapps/stencil-storybook-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.
# Self-Host Your Nuxt App With Coolify
In this article, I want to share my experiences of how I self-hosted my Nuxt apps with Coolify on Hetzner servers.
## A brief history of my hosting provider journey
Let me tell you a quick history of my hosting provider journey: it all started with [Netlify](https://www.netlify.com/){rel=""nofollow""} back in 2018 when I looked for an easy way to host this portfolio website. You don’t get disappointed by providers like [Netlify](https://www.netlify.com/){rel=""nofollow""} or [Vercel](https://vercel.com/){rel=""nofollow""}: your app gets deployed with only a few clicks, and it’s completely free. An amazing user experience, and I used it to host all my other apps like [weekly-vue.news](https://weekly-vue.news){rel=""nofollow""} and [CodeSnap.dev](https://codesnap.dev){rel=""nofollow""}.
Things get tricky when your apps become more traffic as you only get a limited amount of bandwidth, build minutes, serverless function calls, etc., so I had to switch to the Pro team plan, which is at $19/month. I had to pay $25/month for edge function calls as my function calls exceeded the free limit. A quick win was to migrate some of my Nuxt server routes out to AWS Lambda functions. But this way, my code was split between the Nuxt app and the AWS Lambda functions which made the codebase harder to maintain.

To solve those problems, I moved my apps to [Render](https://render.com){rel=""nofollow""}. There, you pay a server for each of your web apps but you don’t have any function call limitations. You get a server with 0.5 CPU and 512MB RAM for $7/month. Soon, I had to switch to the Team plan for $19/month as I exceeded the free bandwidth of 100GB. The next problem was a traffic spike at one of my apps, which killed the server because it exceeded its server memory limit and wasn’t accessible anymore during that time. Upgrading to the next higher server would cost $25/month for 1 CPU and 2GB RAM...
I was shocked and decided to move my apps to [Coolify](https://coolify.io){rel=""nofollow""}, which I already used to host my analytics database and some monitoring tools like Grafana. I knew that the cheapest server rented from [Hetzner](https://hetzner.com){rel=""nofollow""} at \~$4/month would provide me with 2 CPUs and 4GB RAM. This solution is the most cost-effective one for me, and I can scale my servers as needed. Additionally, I don't have to worry about any limitations like bandwidth, build minutes, or function calls and any [serverless horror stories](https://serverlesshorrors.com/){rel=""nofollow""}.
So, let's do a short cost comparison of hosting my three Nuxt apps with the following providers:
- Netlify: $44/month (could potentially grow if I exceed more limits)
- Render: $40/month
- Coolify & Hetzner: $23/month
## What is Coolify?
> [Coolify](https://coolify.io){rel=""nofollow""} is an all-in one PaaS that helps you to self-host your own applications, databases or services (like Wordpress, Plausible Analytics, Ghost) without managing your servers, also known as an open-source & self-hostable Heroku / Netlify / Vercel alternative.
Some of its key features are:
- You can deploy your resources to any server, including your own servers.
- Compatible with a wide range of programming languages and frameworks.
- Deploy your resources to a single server, multiple servers, or Docker Swarm clusters.
- Git integration with both hosted and self-hosted platforms like GitHub, GitLab, Bitbucket, Gitea, and others.
- Pull Request Deployments
- Free SSL certificates
- and more...
## Setup Coolify
I used this amazing video by [Syntax](https://syntax.fm/){rel=""nofollow""} to set up Coolify with my Hetzner servers:
:you-tube-embed{url="https://www.youtube.com/embed/taJlPG82Ucw?si=yeN1CTyaqNqqFup0"}
By the way, I decided to use [Coolify Cloud](https://coolify.io/cloud){rel=""nofollow""} to get a fully managed Coolify instance, which I use to connect my Hetzner servers. It provides these advantages:
- Highly available
- Less maintenance
- Free email notifications
- Priority support via email or chat
## Deploy Your Nuxt App
Let’s assume you have a server that runs Coolify and an additional server that should host your Nuxt app(s).
To get started, you need to connect your Git repository to Coolify. Check [the official documentation](https://coolify.io/docs/knowledge-base/git/github/integration){rel=""nofollow""} for more details.
If you create a new application in Coolify you need to select [Nixpacks](https://nixpacks.com/){rel=""nofollow""} with the port of your Nuxt app (default is 3000):

Next, you need to change the `Start Command` to `node .output/server/index.mjs`:

Alternatively, you can change the `start` script inside `package.json` to `node .output/server/index.mjs`. Nixpacks will automatically use it as the start command.
::note{title="Static Site"}
If your Nuxt app is built as a static site, you need to check `Is it a static site?` and set `Publish Directory` to `/.output/public`
::
::note{title="pnpm"}
If you are using `pnpm 9+` as your package manager, you might get `ERR_PNPM_NO_LOCKFILE Cannot install with "frozen-lockfile" because pnpm-lock.yaml is absent` as build error.
Check [this GitHub issue](https://github.com/railwayapp/nixpacks/issues/1091){rel=""nofollow""} for more details.
To solve the problem you need to add `nixpacks.toml` to your repository with the following content:
```toml [nixpacks.toml]
providers = ["node"]
[phases.install]
cmds = ["npm install -g corepack", "corepack enable", "corepack prepare pnpm@9.1.4 --activate", "pnpm install"]
```
Additionally, you need to modify your `package.json`:
```json [package.json]
{
...
"packageManager": "pnpm@9.1.4",
"engines": {
"node": "20.12.2",
"pnpm": "9.1.4"
},
...
}
```
Of course, you need to adjust the versions to your needs.
::
And that’s it! You should be able to deploy your Nuxt app with Coolify on your servers.
## Conclusion
So far, I am very happy with my decision to move my apps to Coolify and Hetzner servers. I can scale my servers as needed and don’t have to worry about any limitations. I can host my apps for a fraction of the costs compared to other providers.
Of course, there are some downsides like more maintenance and less automation compared to providers like Netlify or Render. But I think the cost savings are worth it.
I hope this article helps you to decide on how to host your Nuxt apps. If you have any questions or feedback, feel free to reach out to me.
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""}:
# Sending Message To Specific Anonymous User On Spring WebSocket
In my current, I had the opportunity to develop a new application based on [Vue.js](https://vuejs.org/){rel=""nofollow""} in the frontend and [Spring Boot](https://spring.io/projects/spring-boot){rel=""nofollow""} in the backend. The backend should send updates to the frontend via a WebSocket connection so that users do not need to refresh the website to see the latest information.
The view should show a list of transfers where the user can modify the results by using filters and pagination. As a requirement, each user should receive specific results based on his filters and pagination without broadcasting this to all other connected users. The user does not need to authenticate itself at the backend.
Most tutorials cover either the case of broadcasting messages to all connected users or to send messages to certain authenticated users. In this article, I will demonstrate how to send messages to anonymous users without broadcasting the messages.
## The Demo Project
I have a created a [demo project](https://github.com/Mokkapps/spring-boot-websocket-anonymous-messages-demo){rel=""nofollow""} to be able to demonstrate the functionality. It is a simple monorepo which contains a backend and a frontend folder.
### Backend
The Spring Boot backend was bootstrapped using [Spring Initializr](https://start.spring.io/){rel=""nofollow""} where I chose `WebSocket` as the only dependency:

#### Configure Websocket
The next step is to configure the application to use a WebSocket connection. To configure the Spring Boot application I followed [this tutorial](https://spring.io/guides/gs/messaging-stomp-websocket/){rel=""nofollow""} without the frontend part.
After this tutorial we have a working WebSocket controller that receives and sends messages via a WebSocket connection:
```java
@Slf4j
@Controller
public class GreetingController {
@MessageMapping("/hello")
@SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
log.info("Received greeting message {}", message);
greetingService.addUserName(principal.getName());
Thread.sleep(1000); // simulated delay
return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
}
}
```
The `greeting()` method is called if a message is sent to the `/hello` destination. This is ensured by using the `@MessageMapping` annotation. The received message is then sent to `/ topic/greetings`. I have added a simulated delay to simulate any asynchronous operation that could be executed on the server-side in between receiving and sending messages.
In this implementation, all messages are broadcasted to all connected users by using the `@SendTo` annotation.
`Greeting.java` and `HelloMessage.java` are simple Java classes which represent the transferred data objects:
```java
public class Greeting {
private String content;
public Greeting() {
}
public Greeting(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
```
```java
public class HelloMessage {
private String name;
public HelloMessage() {
}
public HelloMessage(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
```
The WebSocket is configured in `WebSocketConfig.java`:
```java
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").setAllowedOrigins("*");
registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
}
}
```
In my project, we send updates to the clients via a scheduled interval so I also added this functionality to this demo
project. The first step is to enable scheduling in the Spring Boot application using the `@EnableScheduling` annotation:
```java
@SpringBootApplication
@EnableScheduling // this annotation enables scheduling
public class WebsocketAnonymousMessagesDemoApplication {
public static void main(String[] args) {
SpringApplication.run(WebsocketAnonymousMessagesDemoApplication.class, args);
}
}
```
Next, a `Scheduler.java` class handles the scheduled tasks and triggers `GreetingService` to send a new message each second:
```java
@Slf4j
@Component
public class Scheduler {
private final GreetingService greetingService;
Scheduler(GreetingService greetingService) {
this.greetingService = greetingService;
}
@Scheduled(fixedRateString = "6000", initialDelayString = "0")
public void schedulingTask() {
log.info("Send messages due to schedule");
greetingService.sendMessages();
}
}
```
`GreetingsService.java` injects `SimpMessagingTemplate` which provides methods to programmatically send WebSocket messages:
```java
@Data
@Slf4j
@Service
public class GreetingService {
private final SimpMessagingTemplate simpMessagingTemplate;
private static final String WS_MESSAGE_TRANSFER_DESTINATION = "/topic/greetings";
GreetingService(SimpMessagingTemplate simpMessagingTemplate) {
this.simpMessagingTemplate = simpMessagingTemplate;
}
public void sendMessages() {
simpMessagingTemplate.convertAndSend(WS_MESSAGE_TRANSFER_DESTINATION,
"Hallo " + " at " + new Date().toString());
}
}
```
`convertAndSend` is equivalent to the `@SendTo` annotation which was used in the controller to broadcast messages.
### Frontend
The frontend was bootstrapped using the [Vue CLI](https://cli.vuejs.org){rel=""nofollow""}:
```bash
# Install Vue CLI globally
npm install -g @vue/cli
# Create a new Vue project called "frontend"
vue create frontend
```
Next step is to add [STOMP.js](https://github.com/stomp-js/stompjs){rel=""nofollow""} as npm library which allows us to connect to our backend STOMP broker over WebSocket:
```bash
npm add @stomp/stompjs
```
I have created a `websocket-service.ts` as Singleton which handles the interaction with this library:
```ts
import { Client, messageCallbackType } from '@stomp/stompjs'
export class WebsocketService {
private readonly webSocketUrl =
process.env.NODE_ENV === 'development' ? 'ws://localhost:8080/ws' : `wss://${window.location.hostname}/ws`
private client: Client
private onConnectCb?: Function
private onDisconnectCb?: Function
private onErrorCb?: Function
private _isConnected = false
private static instance: WebsocketService
private constructor() {
console.log(`${process.env.NODE_ENV === 'development' ? 'DEV' : 'PROD'} mode`)
this.client = new Client({
brokerURL: this.webSocketUrl,
debug: function (str: string) {
console.log('WS debug: ', str)
},
reconnectDelay: 5000,
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000,
})
this.client.onConnect = () => {
this._isConnected = true
this.onConnectCb && this.onConnectCb()
}
this.client.onDisconnect = () => {
this._isConnected = false
this.onDisconnectCb && this.onDisconnectCb()
}
this.client.onStompError = (frame: any) => {
console.error('WS: Broker reported error: ' + frame.headers['message'])
console.error('WS: Additional details: ' + frame.body)
this.onErrorCb && this.onErrorCb()
}
}
static getInstance(): WebsocketService {
if (!WebsocketService.instance) {
return new WebsocketService()
}
return WebsocketService.instance
}
get isConnected(): boolean {
return this._isConnected
}
connect(onConnectCb: Function, onDisconnectCb: Function, onErrorCb: Function): void {
this.onConnectCb = onConnectCb
this.onDisconnectCb = onDisconnectCb
this.onErrorCb = onErrorCb
this.client.activate()
}
disconnect(): void {
this.client.deactivate()
}
subscribe(destination: string, cb: messageCallbackType): void {
this.client.subscribe(destination, cb)
}
sendMessage(destination: string, body: string): void {
this.client.publish({ destination, body })
}
}
```
In the constructor, the client configuration is done. If we run the backend locally the WebSocket connection is available at `localhost:8080/ws` that's why `ws://localhost:8080/ws` is used as broker URL in Vue development mode.
The service provides this public API:
```ts
interface IWebSocketService {
connect(onConnectCb: Function, onDisconnectCb: Function, onErrorCb: Function): void
disconnect(): void
subscribe(destination: string, cb: messageCallbackType): void
sendMessage(destination: string, body: string): void
}
```
Inside the `mounted()` method in `App.vue` the service is instantiated:
```vue
```
Received messages are rendered in the template:
```vue
Received WS messages
{{ message }}
```
At this point we have a running application that can send & broadcast messages via a WebSocket connection:

## Prevent Message Broadcasting
As you can see in the video above, each connected user receives the same broadcasted message as we cannot identify certain users. In this chapter, I want to demonstrate how to prevent broadcasting messages to all users without a need for authentication.
The idea is to use UUIDs for each connected client and instead of broadcasting to all users messages are only sent to specific UUIDs.
These steps need to be performed:
1. Generate a Spring Security `Principal` name by UUID for each newly connected client by using `DefaultHandshakeHandler`
2. Store the UUID if a new message is received
3. Use `@SendToUser` instead of `@SendTo` annotation in the WebSocket controller
4. Change endpoint in frontend to have the `user`, so `/user/topic/greetings` instead of `/topic/greetings`;
Let's start by creating a `CustomHandshakeHandler.java`
```java
/**
* Set anonymous user (Principal) in WebSocket messages by using UUID
* This is necessary to avoid broadcasting messages but sending them to specific user sessions
*/
public class CustomHandshakeHandler extends DefaultHandshakeHandler {
@Override
protected Principal determineUser(ServerHttpRequest request,
WebSocketHandler wsHandler,
Map attributes) {
// generate user name by UUID
return new StompPrincipal(UUID.randomUUID().toString());
}
}
```
which needs to be registered in `WebSocketConfig.java`:
```java
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/ws")
.setAllowedOrigins("*")
// use our new handler
.setHandshakeHandler(new CustomHandshakeHandler());
registry
.addEndpoint("/ws")
.setAllowedOrigins("*")
// use our new handler
.setHandshakeHandler(new CustomHandshakeHandler())
.withSockJS();
}
}
```
Now `GreetingController.java` has to be adopted:
```java
@Slf4j
@Controller
public class GreetingController {
private final GreetingService greetingService;
GreetingController(GreetingService greetingService) {
this.greetingService = greetingService;
}
@MessageMapping("/hello")
@SendToUser("/topic/greetings") // use @SendToUser instead of @SendTo
public Greeting greeting(HelloMessage message, Principal principal) throws Exception {
log.info("Received greeting message {} from {}", message, principal.getName());
greetingService.addUserName(principal.getName()); // store UUID
Thread.sleep(1000); // simulated delay
return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
}
}
```
`GreetingService` needs to be adjusted to be able to store the UUIDs in a list and we change `convertAndSend` to `convertAndSendToUser` where we iterate over all user names and send message to them:
```java
@Data
@Slf4j
@Service
public class GreetingService {
private final SimpMessagingTemplate simpMessagingTemplate;
private static final String WS_MESSAGE_TRANSFER_DESTINATION = "/topic/greetings";
private List userNames = new ArrayList<>();
GreetingService(SimpMessagingTemplate simpMessagingTemplate) {
this.simpMessagingTemplate = simpMessagingTemplate;
}
public void sendMessages() {
for (String userName : userNames) {
simpMessagingTemplate.convertAndSendToUser(userName, WS_MESSAGE_TRANSFER_DESTINATION,
"Hallo " + userName + " at " + new Date().toString());
}
}
public void addUserName(String username) {
userNames.add(username);
}
}
```
Finally, the topic endpoint in `App.vue` in the frontend code needs to be changed:
```ts
private readonly webSocketGreetingsSubscribeEndpoint = '/user/topic/greetings';
```
Let's see this in action:

## Conclusion
Sending WebSocket messages to specific anonymous users is not hard using Spring. You can also extend this mechanism by adding
another destination for broadcasted messages. This way, you can send certain messages to specific users and also broadcast messages to every connected user.
# Simpler Two-Way Binding in Vue With defineModel
`v-model` is a powerful feature in Vue that allows you to create two-way data bindings on your components. However, defining the props and emits in every component can be a bit verbose.
In this article, I'll show you how to simplify two-way binding in Vue with the `defineModel` compiler-macro, which is now the recommended way to define `v-model` bindings in Vue 3.4 and later.
::note
`defineModel()` is a new feature in Vue 3.4. Make sure you are using Vue 3.4 or later to use this feature.
::
## The "Problem"
When you create a component that uses `v-model`, you need to define a prop and an emit for the value. For example, if you have a component that uses `v-model` to bind to a `value` prop, you would need to define the following:
```vue [Child.vue] {2-3}
```
## The Solution
`defineModel` is a new `
```
I love this simple and clean syntax. It makes the code much easier to read and write.
`defineModel()` returns a `ref`, which is automatically bound to the `modelValue` prop and emits the `update:modelValue` event when the value changes. The `.value` is synced with the value bound by the parent `v-model`. When the `ref` is updated, the value bound by the parent is automatically updated.
This allows us to use `v-model` directly on the native input element without additional code.
## Options
`defineModel` also accepts an optional options object to configure the behavior of the model:
```vue [Child.vue]
```
## Multiple `v-model` bindings
If you have multiple `v-model` bindings in your component, you can use `defineModels` to define multiple models at once:
```vue [Parent.vue]
```
```vue [Child.vue]
```
If prop options are also needed, you can pass them after the model name:
```vue [Child.vue]
```
## Modifiers
`defineModel` also supports modifiers. You can use modifiers to customize the behavior of the model. Let's take a look at a simple modifier that modifies every character of the model value and makes it uppercase:
```vue [Parent.vue]
```
```vue [Child.vue]
```
## Typing
You can define the type of the model value inside the options object:
```vue [Child.vue]
```
If you are using TypeScript, you can also define the type of the model value and modifiers in the following way:
```vue [Child.vue]
```
## StackBlitz
Try it yourself in the following StackBlitz project:
:stackblitz{project-id="simpler-two-way-binding-in-vue-with-define-model"}
## Conclusion
I love the new `defineModel` compiler macro. It makes two-way binding in Vue much simpler and cleaner. I hope you find this feature as helpful as I do.
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""}:
# Sticky Footer in GatsbyJS using Flexbox
I recently developed some static websites based on [GatsbyJS](https://gatsbyjs.org){rel=""nofollow""} with a sticky footer. A sticky footer is always positioned on the bottom of the page, even for sparse content.
Unfortunately, I had some struggles solving this, and I want to share my learnings with you.
## Non-GatsbyJS solution
In a traditional HTML + CSS + JavaScript application, we can use [different ways](https://css-tricks.com/couple-takes-sticky-footer/){rel=""nofollow""} to implement such a fixed footer, but I prefer the [Flexbox solution of Philip Walton](https://philipwalton.github.io/solved-by-flexbox/demos/sticky-footer/){rel=""nofollow""}.
Flexbox provides a friendly solution for the sticky footer problem. It can be used to layout content in a horizontal and vertical direction. So we need to wrap the vertical sections (header, content, footer) in a flex container and choose which one should expand. In our case, we want the content to take up all the available space in the container automatically.
Following, you can see his solution:
```html
……
```
The corresponding CSS classes:
```css
.site {
display: flex;
min-height: 100vh;
flex-direction: column;
}
.site-content {
flex: 1;
}
```
Take a look at the [live demo](https://philipwalton.github.io/solved-by-flexbox/demos/sticky-footer/){rel=""nofollow""}.
## GatsbyJS solution
GatsbyJS is based on React; therefore, we have to think differently.
The basic `layout.js` file from the [official GatsbyJS default starter](https://github.com/gatsbyjs/gatsby-starter-default){rel=""nofollow""} has a similar structure like the following example:
```js
const Layout = ({ children }) => (
(
<>
{children}
>
)}
/>
);
export default Layout;
```
So if we would style `` and the `
{children}
` as proposed in [Philip Walton's solution](https://philipwalton.github.io/solved-by-flexbox/demos/sticky-footer/){rel=""nofollow""} it would not work.
But why? Because it would mean that the `` component has to be a direct child of the `` tag, which it isn't due to GatsbyJS's and React's way of building the HTML document.
To solve the problem, I added a new `` tag, which should represent the `` tag of the example mentioned above.
So my `layout.js` looks this way:
```js
const Layout = ({ children }) => (
(
<>
{children}
>
)}
/>
);
export default Layout;
```
The CSS:
```css
.site {
display: flex;
min-height: 100vh;
flex-direction: column;
}
.site-content {
flex-grow: 1;
}
```
You can see a working example on my [GitHub Traffic Viewer website](https://github-traffic-viewer.netlify.app/){rel=""nofollow""}. The first page shows spare content, but the footer is stuck to the bottom. If you sign in and see the result list, the footer is also shown at the bottom of the page.

I hope this post is helpful if you try implementing a sticky footer on a GatsbyJS website.
Happy Coding!
# The 10 Favorite Features of My Developer Portfolio Website
Inspired by [Braydon Coyer's new blogfolio](https://braydoncoyer.dev/blog/introducing-my-new-blogfolio){rel=""nofollow""}, I've added some excellent new features to my portfolio website.
In this article, I want to demonstrate the ten favorite features of my blogfolio.
## 1. Stats page
Inspired by [SLD](https://sld.codes/){rel=""nofollow""} and [Braydon Coyer](http://braydoncoyer.dev/stats){rel=""nofollow""}, I've added a [Stats page on my site](https://mokkapps.de/stats).
It shows statistics about the site itself, for example, how many active visitors are currently on the site and how many have visited it in total.
Additionally, it shows some information about my social media channels like the follower count of [GitHub](https://github.com/mokkapps){rel=""nofollow""}, [Twitter](https://twitter.com/mokkapps){rel=""nofollow""}, [Dev.to](https://dev.to/mokkapps){rel=""nofollow""}, and more.
I use [AWS Amplify Serverless Functions](https://mokkapps.de/categories/aws) to access a variety of APIs to provide the necessary data for this site.

## 2. Article Reactions
Built with [Supabase](https://supabase.com/){rel=""nofollow""} and AWS Amplify Serverless Functions, readers of my articles can now react to the article with the clap emoji.
Additionally, I use the same database table to store the number of page views.

## 3. Automated Open Graph Images
I use [Braydon's approach](https://braydoncoyer.dev/blog/how-to-dynamically-create-open-graph-images-with-cloudinary-and-next.js){rel=""nofollow""} to automatically generate [Open Graph](https://ogp.me/){rel=""nofollow""} images for certain pages using the [Cloudinary API](https://cloudinary.com/documentation/cloudinary_references){rel=""nofollow""}.
The code grabs the site's title and generates an Open Graph image using Cloudinary API.
The following image shows such an automatically generated image that I use on my website:

## 4. Mark Article as Read
Visitors of my website can see at a glimpse which articles they've already read. It's a nice little feature for recurring readers of my blog.

## 5. Intelligent Article Suggestions
If a reader of a blog article reaches the end of the article, he will see four similar articles. They are selected by checking how many categories match between the articles.

## 6. Article Search Options
I provide multiple ways to search for blog articles:
1. All articles are available on the [blog page](https://mokkapps.de/blog), and you can scroll or use the browser search to find an article.
2. Use the [minimal list](https://mokkapps.de/minimal-blog-list), which shows all blog posts grouped in years by title.
3. Use [Google](https://www.google.com/search?q=site%3Amokkapps.de%2Fblog){rel=""nofollow""}.

## 7. Prism Code Highlighting
I invested some time to create beautiful code snippets on my blog posts, as they are an essential part of my articles.
I use [Prism](https://prismjs.com/){rel=""nofollow""} with the [Gatsby Prism Remark plugin](https://www.gatsbyjs.com/plugins/gatsby-remark-prismjs/){rel=""nofollow""} to show code blocks in my markdown files:
```js {2-4}
export const getCategoryDisplayText = (category) => {
if (category === 'aws') {
return category.toUpperCase()
}
if (category.includes('-js')) {
const name = category.split('-')[0]
return `${capitalize(name)}.js`
}
return capitalize(category)
}
```
I can highlight certain lines of code, and I show the programming language as a nice badge on the top right.
## 8. MDX
[MDX](https://mdxjs.com/){rel=""nofollow""} is very powerful, and I use it for my tips page, where I inject the following React component into my Markdown files to create a beautiful comparison of two code blocks:
::code-card{type="bad"}
```html
```
::
::code-card{type="good"}
```html
```
::
## 9. Generate Scripts
Inspired by [Kent C. Dodds](https://github.com/kentcdodds){rel=""nofollow""}, I use [multiple JS scripts](https://github.com/Mokkapps/website/tree/master/scripts/generate){rel=""nofollow""} to generate boilerplate files for new blog posts and tips.
For example, the `blogpost.js` script will generate a similar output in the console:
```bash
? Title this is a test to see if my script is awesome
? Categories development, career, tools
? Release Date (format: yyyy-mm-dd) 2022-01-08
? Dry run without creating files? (default: false) Yes
Date:
2022-01-08
Slug:
this-is-a-test-to-see-if-my-script-is-awesome
Markdown data:
---
title: "This Is a Test to See if My Script Is Awesome"
categories:
- "development"
- "career"
- "tools"
cover: "images/cover.jpg"
---
```
The script asks for some mandatory information, converts the entered title to title caps, and finally generates the markdown file with the slug name at the correct directory.
Additionally, I have a script to generate a Table of Content (ToC) for a finished article and an image optimization script.
## 10. Open Source Analytics
I use [Umami](https://github.com/mikecao/umami){rel=""nofollow""} with a database hosted on [Digital Ocean](https://www.digitalocean.com/){rel=""nofollow""}. I send custom events if a visitor, for example, clicks a social link, subscribes to the newsletter or,
edits an article on GitHub. These events provide some valuable insights into how many visitors are using the features on my portfolio website.

## Conclusion
My portfolio website is my favorite digital playground. I love to experiment with different new features and try to provide the best possible
experience for visitors.
The source code of my website is [available on GitHub](https://github.com/Mokkapps/website){rel=""nofollow""}, so feel free to take a closer look if you are interested in
implementation details. Leave a comment if you want more information about a specific topic.
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""}.
# The Engineering Behind My Portfolio Website
I created my first personal website in 2017 when I launched [my first smartphone game](https://www.mokkapps.de/blog/lessons-learned-my-first-smartphone-game/){rel=""nofollow""}. I use Google Analytics in this game, and it is, therefore [necessary](https://privacypolicies.com/blog/privacy-policy-google-analytics/){rel=""nofollow""} to provide a link to a privacy policy website from inside the game. I used [WordPress](https://wordpress.org/){rel=""nofollow""} and a free theme as I had nearly no frontend knowledge at that time:
[See the first version in the web archive](https://web.archive.org/web/20170701233843/http://mokkapps.de:80/){rel=""nofollow""}
End of 2017 I then released a [new version](https://dev.to/mokkapps/how-i-built-my-website-with-hugo-and-netlify-3n49){rel=""nofollow""} based on the open-source static site generator [Hugo](https://gohugo.io/){rel=""nofollow""} using the [HTML5 Up Prologue theme](https://html5up.net/prologue){rel=""nofollow""}. The idea was to have a unique design and better control over the layout:
[See the second version in the web archive](https://web.archive.org/web/20180322183119/http://mokkapps.de/){rel=""nofollow""}
As I specialized more as a frontend developer I wanted to create my own portfolio website with its own styling. The inspiration came from Ali Spittel's blog post ["Building a Kickass Portfolio"](https://dev.to/aspittel/building-a-kickass-portfolio-28ph){rel=""nofollow""}.
Some of the main reasons which encouraged me to do the refactoring:
- Show my creativity and make a website that is a true expression of myself
- Design as much as possible myself without using pre-designed templates
- Make the site as fast and accessible as possible
- Provide a foundation to make the website easily extendable and adjustable
- Make the website fully responsive
The result can be seen at [www.mokkapps.de](https://www.mokkapps.de){rel=""nofollow""}:

The basic implementation took about 40 hours of work. I did not draft my sites before moving to code but just started coding and experimented with different designs.
## Website Generator
I decided to use [Gatsby.js](https://www.gatsbyjs.org/){rel=""nofollow""} due to several reasons:
- I love using [React](https://reactjs.org/){rel=""nofollow""} and its rich ecosystem of libraries, components, etc.
- It uses [GraphQL](https://graphql.org/){rel=""nofollow""}, which I wanted to gain some more practical experience with.
- Very good [documentation](https://www.gatsbyjs.org/docs/){rel=""nofollow""}, [plugins](https://www.gatsbyjs.org/plugins/){rel=""nofollow""} & [starters](https://www.gatsbyjs.org/starters/){rel=""nofollow""}.
- Can be easily combined with several APIs, CMS, and more.
The following graphic from the official website showcases how Gatsby works:

As a starter, I used the fantastic [Gatsby Starter Kit](https://greglobinski.github.io/gatsby-starter-kit-docs/){rel=""nofollow""}, which provided an ideal bare-bone application for my website.
## Hosting
I use [Netlify](https://www.netlify.com/){rel=""nofollow""} to host my website, an all-in-one platform for automating modern web projects.
It can be used for free if you have a public GitHub project. I decided to [provide my website code open-source on GitHub](https://github.com/mokkapps/website){rel=""nofollow""} as I wanted to demonstrate my skills to everybody interested in it.
## Styled Components
I like the idea of [Styled Components](https://www.styled-components.com/){rel=""nofollow""} and how it nicely integrates into a React component.
Styled Components utilizes tagged template literals to style your components. It removes the mapping between components and styles. This means that when you're defining your styles, you're actually creating a normal React component that has your styles attached to it.
Take a look at the following component of my website:
```javascript
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
const StyledArticle = styled.article`
max-width: 600px;
margin: 0 auto 30px;
background: white;
border-radius: 10px;
padding: 2rem;
min-width: ${(props) => (props.narrow ? '50%' : '100%')};
`
const Article = ({ children }) => {children}
Article.propTypes = {
children: PropTypes.node.isRequired,
}
export default Article
```
In this example I use the `` HTML tag but use it as `StyledArticle` which attaches my CSS styles. It is even possible to apply styles based on props which are passed to the component as you can see in this line:
```javascript
min-width: ${props => (props.narrow ? '50%' : '100%')};
```
## Responsive Images
Delivering images in the optimal size for the correct devices is crucial for a good website. It ensures that your site loads quickly and does not slow down when you use many pictures on the website.
[The "gatsby-image" plugin](https://www.gatsbyjs.org/packages/gatsby-image/#gatsby-image){rel=""nofollow""} is a fantastic solution for this requirement. It automatically resizes your images so your site won't load huge images on a mobile device. Additionally, it lazy loads the images and provides a nice blur effect while the images are loaded:

## Typography
I wanted a typography design and used [Typography.js](https://kyleamathews.github.io/typography.js/){rel=""nofollow""}, which is also recommended by the Gatsby documentation.
My configuration file looks this way:
```javascript
import Typography from 'typography'
import CodePlugin from 'typography-plugin-code'
import theme from 'typography-theme-alton'
theme.overrideThemeStyles = ({ rhythm }, options) => ({
a: {
color: '#FC1A20',
textDecoration: 'none',
},
'a:hover': {
color: '#FC1A20',
textDecoration: 'underline',
},
html: {
boxSizing: 'border-box',
background: '#424242',
},
})
theme.plugins = [new CodePlugin()]
const typography = new Typography(theme)
export default typography
```
In the next image, you can see the difference between my landing page with (upper image) and without (lower image) Typography.js:

## Blog
I enjoy writing articles in [Markdown](https://en.wikipedia.org/wiki/Markdown){rel=""nofollow""} and wanted to use Markdown files as a source for my blog.
The Gatsby Starter Kit already includes some excellent features for this requirement:
- Posts pages are automatically created from markdown files
- Categories are automatically created for blog posts
- Web pages are automatically created from markdown pages files
The relevant folder structure in the code looks this way:
```text
root
└── src
├── content
│ ├── posts
│ │ ├── 2018-05-11___my-first-vs-code-extension
│ │ | ├── jasmine-test-selector.png
│ │ │ └── index.md
│ │ ├── 2018-05-12___my-first-npm-package
│ │ | ├── github-traffic-cli.png
│ │ │ └── index.md
| |
| | ...
```
Using my Gatsby configuration, it automatically creates a blog post page for each of the folders, e.g
`https://www.mokkapps.de/blog/my-first-vs-code-extension/`
## Continuous Integration
I use [Travis CI](https://travis-ci.org/){rel=""nofollow""} to deploy & test my website each time I push to my git master branch.
Here is an excerpt of my `.travis.yml` file:
```yaml
script:
- npm run lint
- npm run test:e2e:ci
- npm run build
deploy:
provider: script
script: "curl -X POST -d '' https://api.netlify.com/build_hooks/5ba3c8da1f12b70cbbcaa1a3"
skip_cleanup: true
on:
branch: master
```
So on each push to master, it runs the TS linter, E2E test and builds the application. If all scripts succeed, a deployment on Netlify is triggered via webhook.
I therefore disabled auto-publishing in Netlify, which usually triggers a deployment each time a git push was detected on the configured branch.
In my case, I want to trigger a deployment if the tests and the build were successful.

Netlify also automatically builds a preview with a unique URL. Previews are perfect for testing and collaboration as a staging environment for every PR or branch. So I can even preview my new build before I manually deploy it.
## E2E Tests
For my E2E tests, I use [Cypress.io](https://www.cypress.io/){rel=""nofollow""} as I heard a lot of good stuff about it.
I created a set of tests that test the most critical function of my application.
For example, the E2E test of my home page:
```javascript
import config from '../../src/content/meta/config'
describe('Home Page Test', () => {
beforeEach(() => {
cy.visit('/')
})
it('includes a heading and a quote', () => {
cy.get('[data-cy=hero-heading]')
cy.get('[data-cy=hero-quote]')
})
it('shows characteristics section', () => {
cy.get('[data-cy=hero-characteristics-section]').children().should('have.length', 4)
cy.get('[data-cy=hero-characteristics-more-button]').click()
cy.url().should('include', '/about')
})
it('shows featured projects', () => {
const countFeaturedProjects = config.projects.filter((p) => p.featured)
cy.get('[data-cy=hero-projects-section]').children().should('have.length', countFeaturedProjects.length)
cy.get('[data-cy=hero-projects-more-button]').click()
cy.url().should('include', '/projects')
})
it('shows latest blog post', () => {
cy.get('[data-cy=blog-post-0]')
cy.get('[data-cy=hero-blog-more-button]').click()
cy.url().should('include', '/blog')
})
})
```
This video shows a Cypress test run on my website:
[](https://youtu.be/HgbFzH5-YrQ "Cypress.io E2E test"){rel=""nofollow""}
## Lighthouse
For me, it was important to have a good [Google Lighthouse score](https://developers.google.com/web/tools/lighthouse/){rel=""nofollow""}, and with Gatsby.js you achieve great results nearly out of the box:

## Sentry
To track errors on my website, I use [Sentry](https://www.sentry.io/){rel=""nofollow""}, an open-source error tracking software. It can be easily integrated into Gatsby using [gatsby-plugin-sentry](https://www.gatsbyjs.org/packages/gatsby-plugin-sentry/?=sentry#gatsby-plugin-sentry){rel=""nofollow""}.
## Conclusion
I am proud of my website, and I enjoyed engineering it. This website represents me on the world wide web, and I am very interested in its design, quality, accessibility, and page views.
I use it for marketing myself in different aspects:
- Show specific skills to employers
- Tell people to read my blog
- Promote my private projects
In my opinion, each web developer should have a custom website. A portfolio website is a true expression of yourself. We are programmers, and it is a creative process, so use and demonstrate your creativity.
## Links
- [www.mokkapps.de](https://www.mokkapps.de){rel=""nofollow""}
- [Website code on GitHub](https://github.com/mokkapps/website){rel=""nofollow""}
- [Presentation Slides](https://mokkapps-website-lightning-talk.netlify.com/){rel=""nofollow""}
# The Last Guide For Angular Change Detection You'll Ever Need
Angular's Change Detection is a core mechanic of the framework but (at least from my experience) it is very hard to understand. Unfortunately, there exists no official guide on the [official website](https://angular.io/){rel=""nofollow""} about this topic.
In this blog post, I will provide you all the necessary information you need to know about change detection. I will explain the mechanics by using a [demo project](https://github.com/Mokkapps/angular-change-detection-demo){rel=""nofollow""} I built for this blog post.
## What Is Change Detection
Two of Angular's main goals are to be predictable and performant. The framework needs to replicate the state of our application on the UI by combining the state and the template:

It is also necessary to update the view if any changes happen to the state. This mechanism of syncing the HTML with our data is called "Change Detection". Each frontend framework uses its implementation, e.g. React uses Virtual DOM, Angular uses change detection and so on. I can recommend the article [Change And Its Detection In JavaScript Frameworks ](https://teropa.info/blog/2015/03/02/change-and-its-detection-in-javascript-frameworks.html){rel=""nofollow""}which gives a good general overview of this topic.
> Change Detection: The process of updating the view (DOM) when the data has changed
As developers, most of the time we do not need to care about change detection until we need to optimize the performance of our application. Change detection can decrease performance in larger applications if it is not handled correctly.
## How Change Detection Works
A change detection cycle can be split into two parts:
- **Developer** updates the application model
- **Angular** syncs the updated model in the view by re-rendering it
Let us take a more detailed look at this process:
1. Developer updates the data model, e.g. by updating a component binding
2. Angular detects the change
3. Change detection checks **every** component in the component tree from top to bottom to see if the corresponding model has changed
4. If there is a new value, it will update the component’s view (DOM)
The following GIF demonstrates this process in a simplified way:

The picture shows an Angular component tree and its change detector (CD) for each component which is created during the application bootstrap process. This detector compares the current value with the previous value of the property. If the value has changed it will set `isChanged` to true. Check out [the implementation in the framework code](https://github.com/angular/angular/blob/885f1af509eb7d9ee049349a2fe5565282fbfefb/packages/core/src/util/comparison.ts#L13){rel=""nofollow""} which is just a `===` comparison with special handling for `NaN`.
> Change Detection does not perform a deep object comparison, it only compares the previous and current value of properties used by the template
### Zone.js
In general, a zone can keep track and intercept any asynchronous tasks.
A zone normally has these phases:
- it starts stable
- it becomes unstable if tasks run in the zone
- it becomes stable again if the tasks completed
Angular patches several low-level browser APIs at startup to be able to detect changes in the application. This is done using [zone.js](https://github.com/angular/angular/tree/master/packages/zone.js){rel=""nofollow""} which patches APIs such as `EventEmitter`, DOM event listeners, `XMLHttpRequest`, `fs` API in Node.js [and more](https://github.com/angular/angular/blob/master/packages/zone.js/STANDARD-APIS.md){rel=""nofollow""}.
In short, the framework will trigger a change detection if one of the following events occurs:
- any browser event (click, keyup, etc.)
- `setInterval()` and `setTimeout()`
- HTTP requests via `XMLHttpRequest`
Angular uses its zone called `NgZone`. There exists only one `NgZone` and change detection is only triggered for async operations triggered in this zone.
## Performance
> By default, Angular Change Detection checks for **all components from top to bottom** if a template value has changed.
Angular is very fast doing change detection for every single component as it can perform thousands of checks during milliseconds using [inline-caching](http://mrale.ph/blog/2012/06/03/explaining-js-vms-in-js-inline-caches.html){rel=""nofollow""} which produces VM-optimized code.
If you want to have a deeper explanation of this topic I would recommend to watch [Victor Savkin’s](https://twitter.com/victorsavkin){rel=""nofollow""} talk on [Change Detection Reinvented](https://www.youtube.com/watch?v=jvKGQSFQf10){rel=""nofollow""}.
Although Angular does a lot of optimizations behind the scenes the performance can still drop on larger applications. In the next chapter, you will learn how to actively improve Angular performance by using a different change detection strategy.
### Change Detection Strategies
Angular provides two strategies to run change detections:
- `Default`
- `OnPush`
Let's look at each of these change detection strategies.
#### Default Change Detection Strategy
By default, Angular uses the `ChangeDetectionStrategy.Default` change detection strategy. This default strategy checks every component in the component tree from top to bottom every time an event triggers change detection (like user event, timer, XHR, promise and so on). This conservative way of checking without making any assumption on the component's dependencies is called **dirty checking**. It can negatively influence your application's performance in large applications which consists of many components.

#### OnPush Change Detection Strategy
We can switch to the `ChangeDetectionStrategy.OnPush` change detection strategy by adding the `changeDetection` property to the component decorator metadata:
```ts
@Component({
selector: 'hero-card',
changeDetection: ChangeDetectionStrategy.OnPush,
template: ...
})
export class HeroCard {
...
}
```
This change detection strategy provides the possibility to skip unnecessary checks for this component and all it's child components.
The next GIF demonstrates skipping parts of the component tree by using the `OnPush` change detection strategy:

Using this strategy, Angular knows that the component only needs to be updated if:
- the input reference has changed
- the component or one of its children triggers an event handler
- change detection is triggered manually
- an observable linked to the template via the async pipe emits a new value
Let's take a closer look at these types of events.
#### Input Reference Changes
In the default change detection strategy, Angular will run the change detector any time `@Input()` data is changed or modified. Using the `OnPush` strategy, the change detector is only triggered if a **new reference** is passed as `@Input()` value.
Primitive types like numbers, string, booleans, null and undefined are passed by value. Object and arrays are also passed by value but modifying object properties or array entries does not create a new reference and therefore does not trigger change detection on an `OnPush` component. To trigger the change detector you need to pass a new object or array reference instead.
You can test this behavior using the [simple demo](https://angular-change-detection-demo.netlify.com/simple-demo){rel=""nofollow""}:
1. Modify the age of the `HeroCardComponent` with `ChangeDetectionStrategy.Default`
2. Verify that the `HeroCardOnPushComponent` with `ChangeDetectionStrategy.OnPush` does not reflect the changed age (visualized by a red border around the components)
3. Click on "Create new object reference" in "Modify Heroes" panel
4. Verify that the `HeroCardOnPushComponent` with `ChangeDetectionStrategy.OnPush` gets checked by change detection

To prevent change detection bugs it can be useful to build the application using `OnPush` change detection everywhere by using only immutable objects and lists. Immutable objects can only be modified by creating a new object reference so we can guarantee that:
- `OnPush` change detection is triggered for each change
- we do not forget to create a new object reference which could cause bugs
[Immutable.js](https://facebook.github.io/immutable-js/){rel=""nofollow""} is a good choice and the library provides persistent immutable data structures for objects (`Map`) and lists (`List`). Installing the library via [npm](https://www.npmjs.com/package/immutable){rel=""nofollow""} provides type definitions so that we can take advantage of type generics, error detection, and auto-complete in our IDE.
#### Event Handler Is Triggered
Change detection (for all components in the component tree) will be triggered if the `OnPush` component or one of its child components triggers an event handler, like clicking on a button.
Be careful, the following actions do not trigger change detection using the `OnPush` change detection strategy:
- `setTimeout`
- `setInterval`
- `Promise.resolve().then()`, (of course, the same for `Promise.reject().then()`)
- `this.http.get('...').subscribe()` (in general, any RxJS observable subscription)
You can test this behavior using the [simple demo](https://angular-change-detection-demo.netlify.com/simple-demo){rel=""nofollow""}:
1. Click on "Change Age" button in `HeroCardOnPushComponent` which uses `ChangeDetectionStrategy.OnPush`
2. Verify that change detection is triggered and checks all components

#### Trigger Change Detection Manually
There exist three methods to manually trigger change detections:
- `detectChanges()` on `ChangeDetectorRef` which runs change detection on this view and its children by keeping the change detection strategy in mind. It can be used in combination with `detach()` to implement local change detection checks.
- `ApplicationRef.tick()` which triggers change detection for the whole application by respecting the change detection strategy of a component
- `markForCheck()` on `ChangeDetectorRef` which does **not** trigger change detection but marks all `OnPush` ancestors as to be checked once, either as part of the current or next change detection cycle. It will run change detection on marked components even though they are using the `OnPush` strategy.
> Running change detection manually is not a hack but you should only use it in reasonable cases
The following illustrations shows the different `ChangeDetectorRef` methods in a visual representation:

You can test some of these actions using the "DC" (`detectChanges()`) and "MFC" (`markForCheck()`) buttons in the [simple demo](https://angular-change-detection-demo.netlify.com/simple-demo){rel=""nofollow""}.
#### Async Pipe
The built-in [AsyncPipe](https://angular.io/api/common/AsyncPipe){rel=""nofollow""} subscribes to an observable and returns the latest value it has emitted.
Internally the `AsyncPipe` calls `markForCheck` each time a new value is emitted, see [its source code](https://github.com/angular/angular/blob/5.2.10/packages/common/src/pipes/async_pipe.ts#L139){rel=""nofollow""}:
```ts
private _updateLatestValue(async: any, value: Object): void {
if (async === this._obj) {
this._latestValue = value;
this._ref.markForCheck();
}
}
```
As shown, the `AsyncPipe` automatically works using `OnPush` change detection strategy. So it is recommended to use it as much as possible to easier perform a later switch from default change detection strategy to `OnPush`.
You can see this behavior in action in the [async demo](https://angular-change-detection-demo.netlify.com/async-pipe-demo){rel=""nofollow""}.

The first component directly binds an observable via `AsyncPipe` to the template
```html
{{ (hero$ | async).name }}
```
```ts
hero$: Observable;
ngOnInit(): void {
this.hero$ = interval(1000).pipe(
startWith(createHero()),
map(() => createHero())
);
}
```
while the second component subscribes to the observable and updates a data binding value:
```html
{{ hero.name }}
```
```ts
hero: Hero = createHero();
ngOnInit(): void {
interval(1000)
.pipe(map(() => createHero()))
.subscribe(() => {
this.hero = createHero();
console.log(
'HeroCardAsyncPipeComponent new hero without AsyncPipe: ',
this.hero
);
});
}
```
As you can see the implementation without the `AsyncPipe` does not trigger change detection, so we would need to manually call `detectChanges()` for each new event that is emitted from the observable.
### Avoiding Change Detection Loops and ExpressionChangedAfterCheckedError
Angular includes a mechanism that detects change detection loops. In development mode, the framework runs change detection twice to check if the value has changed since the first run. In production mode change detection is only run once to have a better performance.
I force the error in my [ExpressionChangedAfterCheckedError demo](https://angular-change-detection-demo.netlify.com/expression-changed-demo){rel=""nofollow""} and you can see it if you open the browser console:

In this demo I forced the error by updating the `hero` property in the `ngAfterViewInit` lifecycle hook:
```ts
ngAfterViewInit(): void {
this.hero.name = 'Another name which triggers ExpressionChangedAfterItHasBeenCheckedError';
}
```
To understand why this causes the error we need to take a look at the different steps during a change detection run:

As we can see, the `AfterViewInit` lifecycle hook is called after the DOM updates of the current view have been rendered. If we change the value in this hook it will have a different value in the second change detection run (which is triggered automatically in development mode as described above) and therefore Angular will throw the `ExpressionChangedAfterCheckedError`.
I can highly recommend the article [Everything you need to know about change detection in Angular](https://blog.angularindepth.com/everything-you-need-to-know-about-change-detection-in-angular-8006c51d206f){rel=""nofollow""} from [Max Koretskyi](https://twitter.com/maxkoretskyi){rel=""nofollow""} which explores the underlying implementation and use cases of the famous `ExpressionChangedAfterCheckedError` in more detail.
### Run Code Without Change Detection
It is possible to run certain code blocks outside `NgZone` so that it does not trigger change detection.
```ts
constructor(private ngZone: NgZone) {}
runWithoutChangeDetection() {
this.ngZone.runOutsideAngular(() => {
// the following setTimeout will not trigger change detection
setTimeout(() => doStuff(), 1000);
});
}
```
The simple demo provides a button to trigger an action outside Angular zone:

You should see that the action is logged in the console but the `HeroCard` components get no checked which means their border does not turn red.
This mechanism can be useful for E2E tests run by [Protractor](https://www.protractortest.org/#/){rel=""nofollow""}, especially if you are using `browser.waitForAngular` in your tests. After each command sent to the browser, Protractor will wait until the zone becomes stable. If you are using `setInterval` your zone will never become stable and your tests will probably timeout.
The same issue can occur for RxJS observables but therefore you need to add a patched version to `polyfill.ts` as described in [Zone.js's support for non-standard APIs](https://github.com/angular/angular/blob/master/packages/zone.js/NON-STANDARD-APIS.md#usage){rel=""nofollow""}:
```js
import 'zone.js/dist/zone' // Included with Angular CLI.
import 'zone.js/dist/zone-patch-rxjs' // Import RxJS patch to make sure RxJS runs in the correct zone
```
Without this patch, you could run observable code inside `ngZone.runOutsideAngular` but it would still be run as a task inside `NgZone`.
### Deactivate Change Detection
There are special use cases where it makes sense to deactivate change detection. For example, if you are using a WebSocket to push a lot of data from the backend to the frontend and the corresponding frontend components should only be updated every 10 seconds. In this case we can deactivate change detection by calling `detach()` and trigger it manually using `detectChanges()`:
```ts
constructor(private ref: ChangeDetectorRef) {
ref.detach(); // deactivate change detection
setInterval(() => {
this.ref.detectChanges(); // manually trigger change detection
}, 10 * 1000);
}
```
It is also possible to completely deactivate Zone.js during bootstrapping of an Angular application. This means that automatic change detection is completely deactivated and we need to manually trigger UI changes, e.g. by calling `ChangeDetectorRef.detectChanges()`.
First, we need to comment out the Zone.js import from `polyfills.ts`:
```ts
import 'zone.js/dist/zone' // Included with Angular CLI.
```
Next, we need to pass the noop zone in `main.ts`:
```ts
platformBrowserDynamic().bootstrapModule(AppModule, {
ngZone: 'noop';
}).catch(err => console.log(err));
```
More details about deactivating Zone.js can be found in the article [Angular Elements without Zone.Js](https://www.softwarearchitekt.at/aktuelles/angular-elements-part-iii/){rel=""nofollow""}.
### Ivy
Angular 9 will use [Ivy, Angular's next-generation compilation and rendering pipeline](https://blog.angularindepth.com/all-you-need-to-know-about-ivy-the-new-angular-engine-9cde471f42cf){rel=""nofollow""} per default. Starting with Angular version 8, you [can choose to opt in to start using a preview version of Ivy](https://angular.io/guide/ivy){rel=""nofollow""} and help in its continuing development and tuning.
The Angular team will ensure that the new render engine still handles all framework lifecycle hooks in the correct order so that change detection works as before. So you will still see the same `ExpressionChangedAfterCheckedError` in your applications.
[Max Koretskyi](https://twitter.com/maxkoretskyi){rel=""nofollow""} wrote [in the article](https://blog.angularindepth.com/ivy-engine-in-angular-first-in-depth-look-at-compilation-runtime-and-change-detection-876751edd9fd){rel=""nofollow""}:
> As you can see, all the familiar operations are still here. But the order of operations appears to have changed. For example, it seems that now Angular first checks the child components and only then the embedded views. Since at the moment there’s no compiler to produce output suitable to test my assumptions, I can’t know for sure.
You can find two more interesting Ivy related articles in the "Recommend Articles" section at the end of this blog post.
### Conclusion
Angular Change Detection is a powerful framework mechanism that ensures that our UI represents our data in a predictable and performant way. It is safe to say that change detection just works for most applications, especially if they do not consist of 50+ components.
As a developer, you usually need to deep dive into this topic for two reasons:
- You receive an `ExpressionChangedAfterCheckedError` and need to solve it
- You need to improve your application performance
I hope this article could help you to have a better understanding of Angular's Change Detection. Feel free to use my [demo project](https://github.com/Mokkapps/angular-change-detection-demo){rel=""nofollow""} to play around with the different change detection strategies.
### Recommended Articles
- [Angular Change Detection - How Does It Really Work?](https://blog.angular-university.io/how-does-angular-2-change-detection-really-work/){rel=""nofollow""}
- [Angular OnPush Change Detection and Component Design - Avoid Common Pitfalls](https://blog.angular-university.io/onpush-change-detection-how-it-works/){rel=""nofollow""}
- [A Comprehensive Guide to Angular onPush Change Detection Strategy](https://netbasal.com/a-comprehensive-guide-to-angular-onpush-change-detection-strategy-5bac493074a4){rel=""nofollow""}
- [Angular Change Detection Explained](https://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html){rel=""nofollow""}
- [Angular Ivy change detection execution: are you prepared?](https://blog.angularindepth.com/angular-ivy-change-detection-execution-are-you-prepared-ab68d4231f2c){rel=""nofollow""}
- [Understanding Angular Ivy: Incremental DOM and Virtual DOM](https://blog.nrwl.io/understanding-angular-ivy-incremental-dom-and-virtual-dom-243be844bf36){rel=""nofollow""}
# The Mistakes I Made In My First Software Project
Before starting my professional career as a developer, I mainly developed Android apps using Java as the programming language. I got hired by a software service company, and we had to develop JavaScript-based applications for cars in my first project. So the first time in my life, I had to work with JavaScript, and I made many mistakes during this time which I now want to share with you.
> The only man who never makes a mistake is the man who never does anything.
>
> Theodore Roosevelt
## Project setup
As I joined the project, there was one project manager and a senior developer. Both left the project after some weeks, and I combined the role of a developer and project manager. In my office room, two other senior developers also worked on similar JavaScript projects. The team developed about ten relatively small JavaScript apps, and my goal was to fix bugs and implement new features until the release. The release went well, and I got the opportunity to work on a more extensive app based on the same tech stack.
The project's tech stack consisted of JavaScript (ECMAScript 3), Apache Maven for building the application, and Karma + jasmine as the test runner. There was no HTML and CSS involved as the JavaScript code talked to a proprietary UI developed internally by the automotive company.
## Learn the basics
One of my biggest mistakes was that I did not learn JavaScript properly. I just took a short online tutorial, which looked easy. But after this short tutorial, I had no idea about:
- Closures
- Scopes
- `this` references
- `==` vs `===`
- why to use `"strict mode"`
- `undefined is not a function`
- and all the other interesting aspects of JavaScript
If you are in a larger team with a code review process, this might not be a big problem because you will learn that lousy code should not find its way into the repository during the review process. But I was alone, and no one reviewed my code. I thought my basic JavaScript knowledge would be enough to do the job, but today I know that it was a terrible code, and I would do it today in a different way.
## Technical mistakes
I want to talk about some of the most significant technical mistakes I made in this project.
> A failure is not always a mistake, it may simply be the best one can do under the circumstances. The real mistake is to stop trying.
>
> B. F. Skinner
### Did not separate view from business code
I created a separate JavaScript file for each of the application's views. The problem was that I did not separate the view from the business logic. So these files contained nearly all the logic necessary to fill the screen with life.
It would have been a much better approach to use the MVC (Model-View-Controller) or some similar pattern to avoid the tight coupling of logic and view.
### Documented all methods
I added JSDoc to **every** method in the application even if the method already had a declarative name.
Reading recommendation: [Don’t comment your code!](http://apdevblog.com/comments-in-code/){rel=""nofollow""}
### Used window object as global state
This was probably my biggest mistake: I used the window object for global state and stored dozens of properties there. A summary of possible problems using the window object for the global state:
- anyone can change the state at any time (it is mutable)
- bad readability of the code
- testing can be tricky
- many more...
Reading recommendation: [Why is global state so evil](https://softwareengineering.stackexchange.com/questions/148108/why-is-global-state-so-evil){rel=""nofollow""}
### Generic backend handler
To avoid code duplication in every file, I created a generic class that was used in every view to make HTTP requests. This was a very, very, very stupid decision and led to a large and unmaintainable hell of code.
Each method in this backend handler class consisted of a large switch-case statement to determine which view class made the call. As multiple views could trigger a request simultaneously, we also implemented a simple queue mechanism, making it more complex and unmaintainable.
It would have been a much better approach to handle the request in each of the views (ideally in each controller but as mentioned above I did not implement such an abstraction).
### Bad unit tests
I wrote a lot of unit tests for the application, but in the end, they did not catch major bugs. I also did not write regression bugs after fixing a bug, so sometimes, I had to fix the same issue again. Additionally, coupling the view and business logic together in one class made it very hard to test as many dependencies had to be mocked.
Reading recommendation: [How to know what to test](https://kentcdodds.com/blog/how-to-know-what-to-test/){rel=""nofollow""}
### Result of the app
I finished the app on time and within budget, but the problems occurred after leaving the project. Of course, there were bugs, but the customer only provided a little budget to fix them. So my company assigned the tasks to working students or apprentices who just used quick dirty hacks to fix the bug. As you can imagine, this did not improve the already bad code base I left there.
Additionally, the app had to be rolled out in more regions with special business requirements. As the app was not scalable and flexible, this became quite a problem for the following developers of the project.
## Conclusion
As you can maybe imagine it is not easy for me as a developer (or in general as a person) to talk publicly about my mistakes. But I think it is important for my personal development and I also want to take you the fear to talk about the mistakes you made. Also, you should not be afraid of making mistakes in your first software project, this can happen and you will learn a lot from them.
Asking questions is one of the most important things you can do if you are new to a programming language or project. If you don't ask questions when necessary, you may get into trouble, as happened to me. Especially if it is about defining a software architecture for a business application, you should ask a senior developer for advice. Get help from experienced developers as early and often as you can. If you are the only developer in the team, try establishing a code review process with a more experienced developer.
Cover Image by [mohamed Hassan](https://pixabay.com/users/mohamed_hassan-5229782/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=3085712) from [Pixabay](https://pixabay.com/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=3085712)
# Track Twitter Follower Growth Over Time Using A Serverless Node.js API on AWS Amplify
In March 2021 I started to use [FeedHive](https://feedhive.io){rel=""nofollow""} to help me grow an audience on Twitter.
Recently, I wanted to check how my Twitter followers have grown over time. Unfortunately, [Twitter Analytics](https://analytics.twitter.com){rel=""nofollow""} only provides data from the last 30 days. So I decided to develop a simple serverless API to fetch and store my follower count each month.
## Tech Stack
As I already use [AWS Amplify](https://aws.amazon.com/amplify/){rel=""nofollow""} for some private APIs, I wanted to reuse this framework for this new project.

For this new project I need the following components:
- [React](https://reactjs.org/){rel=""nofollow""} for the frontend web application which will fetch the data from my serverless API
- [AWS API Gateway](https://aws.amazon.com/api-gateway/){rel=""nofollow""} which provides traffic management, CORS support, authorization and access control, throttling, monitoring, and API version management for the new API
- [AWS Lambda](https://aws.amazon.com/lambda/){rel=""nofollow""} with [Node.js](https://nodejs.org/){rel=""nofollow""} that fetches the follower count from [Twitter API](https://developer.twitter.com/en/docs/twitter-api){rel=""nofollow""}
- [AWS DynamoDB](https://aws.amazon.com/de/dynamodb/){rel=""nofollow""} which is a NoSQL database and which will store the follower count
## Fetching follower count from backend
The first step is to add a new Node.js REST API to our Amplify application that provides a `/twitter` endpoint which is triggered on a recurring schedule. In my case, it will be on every 1st day of the month. The [official documentation](https://docs.amplify.aws/guides/api-rest/node-api/q/platform/js/){rel=""nofollow""} will help you to set up such a new REST API.
To be able to fetch the follower count from Twitter API I decided to use [FeedHive's Twitter Client](https://github.com/FeedHive/twitter-api-client){rel=""nofollow""}. This library
needs four secrets to be able to access Twitter API. We will store them 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.
After the API is created and pushed to the cloud, we can write the basic functionality to fetch the Twitter followers inside our AWS Lambda function:
```js
const twitterApiClient = require('twitter-api-client')
const AWS = require('aws-sdk')
const twitterUsername = 'yourTwitterUsername'
const secretsManager = new AWS.SecretsManager()
const responseHeaders = {
'Content-Type': 'application/json',
'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
'Access-Control-Allow-Methods': 'OPTIONS,POST',
'Access-Control-Allow-Credentials': true,
'Access-Control-Allow-Origin': '*',
'X-Requested-With': '*',
}
exports.handler = async (event) => {
const secretData = await secretsManager.getSecretValue({ SecretId: 'prod/twitterApi/twitter' }).promise()
const secretValues = JSON.parse(secretData.SecretString)
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,
})
try {
const response = await twitterClient.accountsAndUsers.usersSearch({
q: twitterUsername,
})
const followersCount = response[0].followers_count
return {
statusCode: 200,
headers: responseHeaders,
body: followersCount,
}
} catch (e) {
console.error('Error:', e)
return {
statusCode: 500,
headers: responseHeaders,
body: e.message ? e.message : JSON.stringify(e),
}
}
}
```
The next step is to add DynamoDB support to be able to store a new follower count and get a list of the stored data.
Therefore, we need to add a new storage to our AWS Amplify application, see ["Adding a NoSQL database"](https://docs.amplify.aws/cli/storage/overview/#adding-a-nosql-database){rel=""nofollow""} for detailed instructions.
We are adding a NoSQL table that has the following columns:
- `id`: A unique string identifier for each row as a string
- `follower_count`: the current follower count as number
- `data`: an ISO timestamp string that represents the time when the follower count was fetched
Now, we need to allow our Lambda function to access this storage:
```bash
▶ amplify update function
? Select the Lambda function you want to update twitterfunction
? Which setting do you want to update? Resource access permissions
? Select the categories you want this function to have access to. storage
? Storage has 3 resources in this project. Select the one you would like your Lambda to access twitterdynamo
? Select the operations you want to permit on twitterdynamo create, read, update, delete
```
Finally, we can use the [AWS SDK](https://github.com/aws/aws-sdk-js){rel=""nofollow""} to store and read from DynamoDB:
```js {13-41,67-68}
const twitterApiClient = require('twitter-api-client')
const AWS = require('aws-sdk')
const { v4: uuidv4 } = require('uuid')
const secretsManager = new AWS.SecretsManager()
const twitterUsername = 'yourTwitterUsername'
const responseHeaders = {
'Access-Control-Allow-Origin': '*',
// ...
}
const docClient = new AWS.DynamoDB.DocumentClient()
let tableName = 'twittertable'
if (process.env.ENV && process.env.ENV !== 'NONE') {
tableName = `${tableName}-${process.env.ENV}`
}
const tableParams = {
TableName: tableName,
}
async function getStoredFollowers() {
console.log(`👷 Start scanning stored follower data...`)
return docClient.scan({ ...tableParams }).promise()
}
async function storeFollowersCount(followerCount) {
console.log(`👷 Start storing follower count...`)
return docClient
.put({
...tableParams,
Item: {
id: uuidv4(),
follower_count: followerCount,
date: new Date().toISOString(),
},
})
.promise()
}
async function fetchFollowerCount(twitterClient) {
console.log(`👷 Start fetching follower count...`)
const data = await twitterClient.accountsAndUsers.usersSearch({
q: twitterUsername,
})
return data[0].followers_count
}
exports.handler = async (event) => {
const secretData = await secretsManager.getSecretValue({ SecretId: 'prod/twitterApi/twitter' }).promise()
const secretValues = JSON.parse(secretData.SecretString)
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,
})
try {
const followersCount = await fetchFollowerCount(twitterClient)
await storeFollowersCount(followersCount)
const storedFollowers = await getStoredFollowers()
return {
statusCode: 200,
headers: responseHeaders,
body: JSON.stringify(storedFollowers.Items),
}
} catch (e) {
console.error('Error:', e)
return {
statusCode: 500,
headers: responseHeaders,
body: e.message ? e.message : JSON.stringify(e),
}
}
}
```
A successful API response will have a similar JSON array in its body:
```json
[
{
"follower_count": 350,
"date": "2021-08-09T11:39:50.885Z",
"id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
},
{
"follower_count": 380,
"date": "2021-09-09T11:39:50.885Z",
"id": "a5a2a894-166b-4672-aefe-cea01c70a01a"
}
]
```
## Show data in frontend
To be able to show the data in the React frontend I use the [Recharts library](https://recharts.org/en-US){rel=""nofollow""} which is "a composable charting library built on React components".
The React component is quite simple and uses the [AWS Amplify REST API library](https://docs.amplify.aws/lib/restapi/fetch/q/platform/js/){rel=""nofollow""} to fetch the data from our API endpoint:
```jsx
import { API } from 'aws-amplify';
import { useState } from 'react';
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
} from 'recharts';
const TwitterPage = () => {
const [isLoading, setIsLoading] = useState(false);
const [apiError, setApiError] = useState();
const [followerData, setFollowerData] = useState();
const triggerEndpoint = async () => {
setIsLoading(true);
try {
const data = await API.get('twitterapi', '/twitter');
setFollowerData(
data.map((d) => {
d.followers = d.follower_count;
d.date = new Date(d.date).toLocaleDateString();
return d;
})
);
} catch (error) {
console.error('Failed to trigger Twitter endpoint', error);
setApiError(JSON.stringify(error));
} finally {
setIsLoading(false);
}
};
return (
Twitter API
{followerData ? (
) : null}
{apiError ?
{JSON.parse(apiError)}
: null}
);
};
export default TwitterPage;
```
which results in such a graph:

## Conclusion
Using serverless functions it is quite easy and cheap to build a custom solution to track Twitter follower growth over time.
What do you use to track your follower growth? Leave a comment and tell me about your solution.
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.
# Unlocking the Power of v-for Loops in Vue With These Useful Tips
Looping through arrays and objects is a common task in Vue applications. The `v-for` directive is the perfect tool for this job. It is mighty, and you can use it in many different ways. In this article, I will show you some valuable tips and tricks to get the most out of the `v-for` directive.
## Use correct delimiter
The `v-for` directive supports two different delimiters: `in` and `of`. The `in` delimiter is the default one.
I prefer to use the `of` delimiter for **arrays** because it is closer to JavaScript's syntax for iterators like in the `for...of` loop:
```vue [Component.vue] {13-15}
{{ item.name }}
```
If I want to loop through an **object**, I use the `in` delimiter because it is closer to JavaScript's syntax for iterating over object properties:
```vue [Component.vue] {13-15}
Key: "{{ key }}", Value: "{{ value }}"
```
## Destructuring objects
It's possible to destructure the current item in the loop, which is useful if you want to access the current item's properties directly:
```vue [Component.vue] {13-15}
Title: {{ name }}
```
## Iterating over numbers
You can also use the `v-for` directive to iterate over numbers. This is useful if you want to render a list of elements with a specific number of items. For example, you can use it to render a list of 10 items:
```vue [Component.vue] {3}
{{ number }}
```
## Accessing index
Sometimes, you need to access the current item's index in the loop. You can do this by using the second argument of the `v-for` directive:
```vue [Component.vue] {13-15}
#{{ index + 1 }} - {{ item.name }}
```
## Avoid v-if in v-for loops
Using `v-if` and `v-for` on the same element [is not recommended](https://vuejs.org/guide/essentials/list.html#v-for-with-v-if){rel=""nofollow""} due to implicit precedence.
In this case, you should wrap the `v-for` loop in a `` element and use `v-if` on the `` element instead, which is also more explicit:
::code-group
```vue [Bad.vue] {2}
{{ item.name }}
```
```vue [Good.vue] {2,6}
{{ item.name }}
```
::
## Use key attribute
[It is recommended](https://vuejs.org/style-guide/rules-essential.html#use-keyed-v-for){rel=""nofollow""} to provide a `key` attribute with `v-for` whenever possible. `key` is a special attribute that lets us give hints for Vue's rendering system to identify specific virtual nodes.
Let's assume we have a list of todos and want to add a new todo. We can use the `splice()` method to insert a new todo at a specific index. If we don't provide a `key` attribute, Vue will be unable to identify the new todo and will not update the UI correctly.
First, let's take a look at the `TodoItem` component:
```vue [TodoItem.vue] {13,18-20}
Local todo name:{{ todoName }}
```
This simple component renders a todo item passed as a prop and has a local state to store the todo name, which is updated if the component is mounted.
Let's take a look at an interactive example without a `key` attribute. We have a list of todos and want to insert a new todo at a specific index, try it yourself by clicking the `Insert new Todo` button:
::tabs
:::div{icon="i-heroicons-magnifying-glass-circle" label="Preview"}
:demo-for-loop
:::
::
::div{icon="i-heroicons-code-bracket-square" label="Code"}
```vue [ForLoopWithoutKey.vue]
Insert new Todo{{ todo.name }}
```
::
\::
The new `Learn Vue` todo is inserted at the correct index, but the local todo name is not updated. This is because Vue is not accurately tracking the index as being new. Thus, the component will never re-mount, so our `localTodoName` will never get updated. Instead, the value of `localTodoName` will be that of the previous todo at that index.
You might have guessed it, we can simply fix that problem by providing a `key` attribute to our `v-for` loop:
::tabs
:::div{icon="i-heroicons-magnifying-glass-circle" label="Preview"}
::::demo-for-loop{with-key}
::::
:::
::
::div{icon="i-heroicons-code-bracket-square" label="Code"}
```vue [ForLoopWithoutKey.vue]
Insert new Todo{{ todo.name }}
```
::
\::
## Array Change Detection Caveats
You must carefully use non-mutating methods on your array in `v-for` loops, such as `filter()` or `map()`. These methods return a new array, which means that Vue cannot detect changes to the array.
If you want to use these methods, you need to assign the new array to the original array:
```vue [Component.vue] {11}
{{ item.name }}
```
If you use mutation methods like `push()`, `pop()`, `shift()`, `unshift()`, `splice()`, `sort()`, or `reverse()`, you don't need to assign the new array to the original array because these methods mutate the original array.
## Conclusion
I hope you learned something new about the `v-for` directive in this article. It's one of the most powerful directives in Vue, and you can use it in many different ways. If you want to learn more about the `v-for` directive, check out the [official documentation](https://vuejs.org/guide/essentials/list.html){rel=""nofollow""}.
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 newsletter](https://weekly-vue.news){rel=""nofollow""}:
# Use Git Bisect to Find the Commit That Introduced a Bug
As a developer you know that situation: the code worked like a charm and suddenly there is a bug but you have no idea where and when it was introduced.
If you are working in a big team the chances may be quite high that many commits have been added in the meantime. So finding the commit where the bug was introduced can become quite nasty.
Luckily, [Git](https://git-scm.com/){rel=""nofollow""} offers a tool that helps to detect the first bad commit that introduces the bug. It is called "git bisect".
## How does it work?
We need to provide Git Bisect two information to be able to identify
1. A "good" commit where the bug **was not** present.
2. A "bad" commit where the bug **is** present.
This way Git "knows" that the bug has to between the "good" and the "bad" commit. Starting the bisect process will split the range of commits between the "good" and "bad " commit in half and check out a commit in the middle:

Our task is now to validate the code at this commit. This can be done by compiling, running the application or launching a test case for the given bug. Next, we need to tell Git if the test was "good" or "bad". Git will simply repeat this process until we've singled out the commit that contains the bug.
The used algorithm is called [binary search](https://en.wikipedia.org/wiki/Binary_search_algorithm){rel=""nofollow""}.

## Practical Example
Let's look at how we can run Git Bisect from the command line. First, we need to start the process
```bash
$ git bisect start
```
Next step is to provide Git a "good" and "bad" commit. The "bad" commit is often the current state which refers to "HEAD":
```bash
$ git bisect bad HEAD
```
To be able to find "good" commit you need to check out any older revision where you are quite sure that the bug did not exist. After you have checked it out and verified that the bug is not present there, we provide Git the corresponding commit hash :
```bash
$ git bisect good acd72832
```
Now we are ready to start the "bisecting" process. Git will check out a commit in the middle of the range between the "good" and "bad" commit we provided:
```bash
Bisecting: 6 revisions left to test after this (roughly 2 step)
[commit_ABC] Added controller
```
At this point we need to verify if the bug is still present or not. If it is still present we need to run
```bash
$ git bisect bad
```
otherwise we run
```bash
$ git bisect good
```
to mark it as "good".
Depending on the result, Git will again split the original commit range and select either the first or second half. It will again check out a commit in the middle and we need to verify if the bug is present there.
This process is repeated until we've successfully singled out the bad commit!
Once we've found the culprit, we can end the bisect process by running:
```bash
$ git bisect reset
```
Git will then finish the bisect process and take us back to our previous HEAD revision.
## Conclusion
Git Bisect can be a helpful tool to track down a bug. I only use `git bisect` when I absolutely have no idea where the bug was introduced and I need to search through a lot of potentially unrelated changes.
For more information about Git Bisect take a look at the [official docs](https://git-scm.com/docs/git-bisect){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.
# Use Nitro as Mock Server
[Nitro](https://nitro.unjs.io/){rel=""nofollow""} is a server toolkit that allows you to create web servers with everything you need and deploy them wherever you prefer. It's used in [Nuxt 3](https://nuxt.com/docs/getting-started/server){rel=""nofollow""} to power the server-side part of your Nuxt applications.
In this article, I will show how you can use Nitro as a mock server for your frontend E2E tests.
## Setup Nitro
First, you need to create a new Nitro project, you can use the official starter template:
```bash
npx giget@latest nitro mock-server --install
```
Then, you can start the Nitro server in the newly created `mock-server` directory:
```bash
npm run dev
```
Your Nitro server is now running on `http://localhost:3000`.
## Mock API
To mock API endpoints, you can use the full power of Nitro's [server routes](https://nitro.unjs.io/guide/routing){rel=""nofollow""}. Defining a route is as simple as creating a file inside the `api/` or `routes/` directory.
Here is an example of how you can create a simple mock API endpoint with a dynamic route parameter:
```ts
// /routes/users/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
// Do something with id
return `User profile!`
})
```
Nitro's server routes are very powerful and flexible, you can use them to create any kind of API endpoint you need.
## Integrate in E2E setup
Now that you have a mock server running, you can use it in your frontend E2E tests. Let's do it exemplary with [Playwright](https://playwright.dev/){rel=""nofollow""}.
::note
[This repository](https://github.com/Mokkapps/nuxt-nitro-e2e-mock-server-demo){rel=""nofollow""} contains the demo code which I describe below.
::
Let me first introduce the basic setup of the Nuxt 3 demo application that I use for this article:
The application uses one server API endpoint that fetches users from an API endpoint that is configured in the `.env` file:
```yml [.env]
NUXT_EXTERNAL_API_URL=https://jsonplaceholder.typicode.com
```
In this example, we would like to mock this API endpoint to avoid making real API requests during our E2E tests. Therefore, we want to replace it with our Nitro mock server:
```yml [.env]
NUXT_EXTERNAL_API_URL=http://localhost:5005
```
To use the Nitro server in your Playwright tests, you can start the Nitro server in the background before running the tests using the [start-server-and-test](https://www.npmjs.com/package/start-server-and-test){rel=""nofollow""} npm package:
```json [package.json]
{
"scripts": {
"start-mock-server": "npm run --prefix mock-server start",
"dev:e2e": "dotenv -e ./.env.e2e -- playwright test --ui",
"test:e2e:ui": "start-server-and-test start-mock-server 5005 dev:e2e"
},
}
```
The Playwright configuration file starts the web server of your frontend application before running the tests:
```ts [playwright.config.ts]
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
// ....
/* Run your local dev server before starting the tests */
webServer: {
command: 'pnpm run dev',
reuseExistingServer: !process.env.CI,
url: 'http://localhost:3000',
},
})
```
That's it! Now you can run your E2E tests with the Nitro mock server running in the background:

As you can see the Playwright tests are passing and the Nitro server is used as a mock server for the external API requests. You can verify this because the original API endpoint returns 10 results and our mock server returns 2 results.
## Conclusion
In this article, I showed you how you can use Nitro as a mock server for your frontend E2E tests. This can be very useful to avoid making real API requests during your tests and to speed up your development process. If you are already using Nuxt 3 as your frontend framework, you can easily integrate Nitro as a mock server in your setup and benefit from its powerful server routes.
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""}:
# Use Shiki to Style Code Blocks in HTML Emails
I recently developed [a custom newsletter service using Nuxt 3](https://mokkapps.de/blog/how-i-replaced-revue-with-a-custom-built-newsletter-service-using-nuxt-3-supabase-serverless-and-amazon-ses). One of the main reasons why I developed it on my own was that I wanted to use good-looking code blocks in my emails.
In this article, I'll explain how I use [Shiki](https://github.com/shikijs/shiki){rel=""nofollow""} to generate nicely styled code blocks in my newsletter emails.
## Nuxt 3 & Nuxt Content
::note
If you don't use Nuxt and are only interested in the necessary Shiki customization, you can skip the next sections, and head over to "Shiki Code" section.
::
On my [newsletter website](https://weekly-vue.news){rel=""nofollow""} I use [Nuxt 3](https://nuxt.com){rel=""nofollow""} with the [Nuxt Content](https://content.nuxtjs.org/){rel=""nofollow""} and [Nuxt Tailwind](https://tailwindcss.nuxt.dev/){rel=""nofollow""} modules.
Nuxt Content uses [Shiki](https://github.com/shikijs/shiki){rel=""nofollow""} that colors tokens with VSCode themes.
By default, it uses `code`, `pre`, `span` and `div` tags with CSS Flexbox to render the code block.
**Unfortunately, [flex-direction\:column](https://www.caniemail.com/features/css-flex-direction/){rel=""nofollow""} is badly supported in email clients.**
Instead, we need to write the code block in an **HTML table** which is [supported in all email clients](https://www.caniemail.com/features/html-table/){rel=""nofollow""}.
So we need to eject from the default styling and create a custom code component.
## Custom Prose Component
Nuxt Content uses [Prose components](https://content.nuxtjs.org/api/components/prose){rel=""nofollow""} to render markdown files in the DOM.
To overwrite a prose component, we can create a component with the same name in our project `components/content/` directory.
In our case, we want to create a custom `ProseCode` component:
```vue [components/content/ProseCode.vue]
```
Next, we need to install [shiki-es](https://www.npmjs.com/package/shiki-es){rel=""nofollow""}, a standalone build of Shiki fully compatible with all ESM environments:
```bash
# npm
npm i shiki-es
# yarn
yarn add shiki-es
```
We can eject from the default styling by removing the `` tag and adding an `html` reactive variable that will contain the highlighted which is rendered via the [v-html directive](https://vuejs.org/api/built-in-directives.html#v-html){rel=""nofollow""}:
```vue [components/content/ProseCode.vue] {7,11}
```
## Shiki Code
Now it's time to manually call Shiki to render our code as an HTML table. We, therefore, use [shiki-es](https://www.npmjs.com/package/shiki-es){rel=""nofollow""}, a standalone build of Shiki that is fully compatible with all ESM environments.
Three steps are necessary to generate the HTML code:
1. Use `getHighlighter` to get an instance of the Shiki highlighter.
2. Call `codeToThemeTokens` with the given code and language to get the tokens that should be rendered.
3. Use `renderToHtml` to generate the HTML which should be rendered in the DOM. Its `elements` object can be used to modify the DOM structure of the code block. Here we add our `
`, `
` and `
` tags which are necessary for the HTML table.
Let's take a look at the code:
```vue [components/content/ProseCode.vue] {11-40}
```
This code will result in this DOM structure:

## CSS Styles
As mentioned, I use Tailwind in my project which heavily uses `rgb` colors. Unfortunately, `rgb()` works partially in email clients but [alpha values and whitespace syntax are not supported](https://www.caniemail.com/search/?s=rgb){rel=""nofollow""}.
I use the [postcss-preset-env](https://www.npmjs.com/package/postcss-preset-env){rel=""nofollow""} PostCSS plugin to convert modern CSS into something most browsers can understand:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
// ...
postcss: {
plugins: {
'postcss-preset-env': {},
tailwindcss: {},
autoprefixer: {},
},
},
})
```
## Final Result
For example, this is how a code block in a newsletter email from [Weekly Vue News](https://weekly-vue.news){rel=""nofollow""} looks like in a Gmail web client:

## Conclusion
Styling HTML emails is a real pain but having good-looking code blocks in my newsletter emails was worth the effort. It's very sad that we still need to use HTML tables in HTML emails in the year 2023...
Thankfully, Nuxt Content is very customizable and allows developers to build custom solutions.
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""}.
# Vercel Acquires NuxtLabs: What This Means for the Future of Nuxt
Last week, big news were dropped: Vercel acquired NuxtLabs. This acquisition marks a transformational moment for the Nuxt community — a moment filled with exciting possibilities and thoughtful challenges that we need to explore together.
## Introduction to the Acquisition
Vercel's move to acquire NuxtLabs is more than just a business transaction; it’s a statement about the future of full-stack web development. Nuxt, which has become a trusted umbrella for developers worldwide with over 1 million downloads weekly, is now poised to benefit from the additional resources, global reach, and innovative spirit that Vercel is known for.
The [official announcement on Vercel’s blog](https://vercel.com/blog/nuxtlabs-joins-vercel){rel=""nofollow""} outlines a vision of heightened collaboration and resource allocation. This means accelerated development for the Nuxt framework, better support for its ecosystem, and potentially smoother integration with tools that many of us already rely on in our day-to-day projects.
## Implications for the Nuxt Framework
The Nuxt framework’s evolution is in the spotlight as it steps into this new phase. With Vercel’s acquisition, we can expect:
- **Enhanced Development Speed and Quality:** NuxtLab's team no longer needs to worry about funding and can focus on what they do best: building a powerful framework for developers.
- **Streamlined Integration:** Vercel’s expertise in deployment and performance optimization can complement Nuxt’s development features, thereby offering a more comprehensive experience for developers.
- **A Global Perspective:** As Nuxt continues to grow, its integration with Vercel might even help it secure more international contributions and resonate with an even broader audience.
In essence, the acquisition is a vote of confidence in Nuxt’s potential. The commitment to keeping the core framework robust and community-focused promises a future with fewer limitations and more creative freedom for building full-stack applications.
## The Transition to Open-Source Tools
One of the most exciting facets of this acquisition is the transition of previously paid NuxtLabs tools to open-source. As someone who has seen firsthand the impact of accessible, community-driven resources, I believe this decision is a game-changer. Here’s what it entails:
- [Nuxt UI Pro:](https://ui.nuxt.com/pro){rel=""nofollow""} Formerly a paid collection of Vue components and templates, it will become free and open-source with Nuxt UI v4. This update brings over 100 components along with a comprehensive Figma Kit.
- [Nuxt Studio:](https://nuxt.studio/){rel=""nofollow""} This self-hostable content editor is designed for direct integration with Nuxt Content sites. By moving to open-source, developers gain complete control and flexibility, empowering them to fully tailor their website-building experiences. More details are available on .
- [NuxtHub Admin:](https://hub.nuxt.com/){rel=""nofollow""} Initially developed for Cloudflare, this tool is transitioning to a provider-agnostic model. In the future, it will support seamless integration with a range of providers, including popular options featured in Vercel’s Marketplace like Postgres and Redis. This move underlines the focus on versatility and user-centric design.
This transition resonates with the broader open-source philosophy—empowering the community through accessible, adaptable, and free tools. For developers like us, these updates translate into opportunities to innovate without paying premium prices for essential tools.
::tip
This will broaden the scope of potential customers for my [Nuxt SaaS Starter Kit](https://nuxtstarterkit.com){rel=""nofollow""}, which is built on top of Nuxt UI Pro and NuxtHub.
::
## Opportunities for Developers and Businesses
Every transformative event in tech brings its share of opportunities, and this acquisition is no different. For both developers and businesses, the future looks vibrant and rich with promise:
- **Enhanced Resources for Innovation:** With Vercel's backing, the Nuxt team is set to channel more energy into development. This means faster rollouts of new features and more sophisticated integrations that can empower developers to build superior user experiences.
- **AI Integration Prospects:** There’s already buzz about the incorporation of AI into the Nuxt developer experience. Imagine smart code completions, predictive design suggestions, and advanced error detections—all contributing to speeding up the development cycle.
- **Broader Market Reach for Businesses:** For companies, the improved suite of Nuxt tools and the increased efficiency in development translate into better time-to-market and more competitive product offerings. Enhanced performance and scalability provided by the integrated Vercel platform could redefine industry standards for web applications.
- **Community-Driven Innovation:** As more of these tools become open-source, there is a high likelihood of community-led plugins, extensions, and integrations. This collaborative ecosystem is not only appealing but will drive a cycle of continuous improvement among developers worldwide.
For businesses looking to remain at the cutting edge of web technology, the acquisition is a signal that unprecedented resources will be available to refine, optimize, and scale their applications.
## Addressing Concerns and Apprehensions
While the news is overwhelmingly positive, it’s natural to have some concerns about such a significant change. Several critical issues have been voiced by community members:
- **Maintaining Independence and Governance:** A frequent worry is whether Nuxt can remain as community-driven as it has always been. The discussion around ensuring its independence is active among developers, and maintaining open governance under Vercel's umbrella is crucial. More insights can be found in thoughtful pieces like the one on [RedMonk](https://redmonk.com/blog/2025/07/10/rmc-daniel-roe-vercels-nuxtlabs-acquisition/){rel=""nofollow""}.
- **Avoiding Vendor Lock-In:** Despite assurances, there is apprehension that an acquisition of this scale might inadvertently favor Vercel’s products, leading to potential vendor lock-in. It is vital that Nuxt’s neutrality is preserved so that it remains a flexible and adaptable framework for all kinds of deployments. Analyses on this topic can be found in discussions on [Tailkits](https://tailkits.com/blog/nuxtlabs-vercel-acquisition/){rel=""nofollow""}.
By addressing these concerns head-on, the leadership teams involved are promising to safeguard the core values of the Nuxt community. Open communication and continual community engagement will be key to ensuring that the spirit of Nuxt remains intact even as it scales new heights with Vercel’s support.
## Comparisons to Vercel's Past Acquisitions
In the tech world, acquisitions often set precedents. Comparing this acquisition to Vercel’s past moves can offer valuable insight into what might lie ahead. Vercel has a strong track record of integrating innovative tools and nurturing emerging open-source projects. Here are a few takeaways:
- **Track Record of Seamless Integrations:** Vercel has previously acquired or partnered with projects and tools that later became central to enhancing developer workflows. This integration usually comes with an intention to maintain the core community ethos while providing sufficient resources to scale innovation.
- **Community-Centric Evolution:** Past acquisitions have shown that preserving a strong developer and community voice is essential. For Nuxt, this means that while operational and technical support might ramp up, the decision-making power will continue to lie, at least in substantial part, with the community’s collective input.
- **Innovation Without Compromise:** Vercel’s ability to balance commercial interests with open-source values is evident in its history. Their acquisition strategy typically focuses on unlocking additional opportunities for developers, which bodes well for the Nuxt framework’s continued growth and adaptability.
These comparisons are reassuring because they indicate that Vercel has a thoughtful, community-respectful approach to its acquisitions. It’s an approach that can help calm apprehensions while promising an exciting, innovative future.
## Conclusion: Embracing Change and Looking Forward
In wrapping up, the acquisition of NuxtLabs by Vercel ushers in not just change, but a new era of opportunity. For me, this is one of those moments that feels both invigorating and full of promise. The transition of paid tools to open-source, the infusion of fresh resources, and the potential for deeper integration with AI and global technical infrastructure are all signals of an exciting road ahead.
This is a time for the community to rally around, contribute, and explore new horizons. Whether you are a seasoned developer, an entrepreneur, or a tech enthusiast, there has never been a better time to engage with Nuxt and take advantage of the incredible tools and innovations coming our way. As we step forward, embracing change and nurturing the rich collaborative spirit that has always defined the Nuxt community, the future looks both bright and boundless.
Let’s continue to innovate, question, and grow—together.
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""}:
# Navigating State Management in Vue: Composables, Provide/Inject, and Pinia
State management in Vue is one of those topics that seems simple until your app grows a little - and then you’re suddenly juggling prop drilling, duplicated logic, and confusing reactivity bugs. Over the years I’ve tried different approaches and learned that the right answer usually isn’t "one tool to rule them all" but choosing the right approach for the problem at hand. In this article I’ll walk you through composables, provide/inject, and [Pinia](https://pinia.vuejs.org/){rel=""nofollow""} — when to use each, how to use them well, and practical examples you can copy into your projects.
## Introduction to State Management in Vue
When I start a new Vue project I always ask three questions before picking a state approach:
- How widely does this state need to be shared? (single component, subtree, global)
- Is the logic reusable across components or tied to a specific component?
- Do I need advanced features like SSR, persistence, or strong typing?
Vue gives us multiple, complementary tools to solve these needs: [Composables](https://vuejs.org/guide/reusability/composables){rel=""nofollow""} (Composition API logic encapsulation), [Provide/Inject](https://vuejs.org/guide/components/provide-inject){rel=""nofollow""} (scoped sharing in a subtree), and [Pinia](https://pinia.vuejs.org/){rel=""nofollow""} (centralized/global stores). Each has strengths and trade-offs. Below I’ll dig into each with patterns, code, and rules of thumb I actually use day-to-day.
## Understanding Composables: When and How to Use Them
Composables are reusable functions that encapsulate state and logic using the Composition API. Think of them as the “utility modules” for stateful behavior.
Why I reach for a composable:
- When logic is reusable across unrelated components (e.g., fetch logic, form handling, timers).
- For small pieces of local state that aren’t global - counters, visibility toggles, input validation, etc.
- When performance matters (they’re lightweight and don't force global reactivity).
A minimal counter composable example:
```ts [useCounter.ts]
import { ref } from 'vue'
export function useCounter(initial = 0) {
const count = ref(initial)
const increment = () => ++count.value
const decrement = () => --count.value
const reset = () => {
count.value = initial
}
return { count, increment, decrement, reset }
}
```
Best practices I follow:
- Name composables with a use prefix: `useAuth`, `useFetch`, `useCounter`. This makes intent clear.
- Group composables by feature or domain (e.g., `/composables/auth`, `/composables/ui`).
- Keep state encapsulated; expose only what callers need (avoid leaking internal `refs` unnecessarily).
- If many components need the same state instance rather than independent instances, consider switching to Provide/Inject or Pinia rather than making a composable that returns a shared object — otherwise you get implicit singletons that are harder to reason about.
When **not** to use a composable:
- When the state must be truly global and monolithic (Pinia is better).
- When you must share state only within a subtree but not across the whole app (use Provide/Inject instead).
## Leveraging Provide/Inject for Local State Sharing
Provide/Inject lets a parent component provide values (reactive data, functions) and descendant components inject them without prop drilling. I use this pattern when state belongs to a component subtree - e.g., a theming context, a form with nested children, or a modal manager.
Example: simple theming using Provide/Inject
```vue [Parent.vue]
```
```vue [Child.vue]
```
When to use Provide/Inject:
- The state is scoped to a subtree and not needed globally.
- You want to avoid prop drilling for deeply nested components.
- You’re implementing context-like things: theme, localization, per-widget configuration, or a modal stack.
Caveats and best practices:
- Provide/Inject bypasses the component interface, so document the provided keys carefully and prefer symbol keys to avoid collisions.
- Avoid overusing it; it can make component relationships implicit and harder to trace compared to props.
- Combine with composables: provide a single composable instance (e.g., `const modal = useModal(); provide('modal', modal)`) so you get the composable API and scoped sharing together.
## Harnessing the Power of Pinia for Centralized State Management
Pinia is the officially recommended state library for Vue 3. I use Pinia for global state that multiple, unrelated components or pages need access to - authentication, user preferences, shopping cart, complex domain models.
Key reasons to choose Pinia:
- Intuitive, modular API and great TypeScript support.
- Supports SSR hydration and plugin ecosystem (persistence, logger, etc.).
- Encourages splitting concerns into smaller stores rather than a single monolithic store.
Example: simple auth store with Pinia
```ts [useAuthStore.ts]
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useAuthStore = defineStore('auth', () => {
const user = ref(null)
const token = ref(null)
const isLoggedIn = computed(() => !!user.value)
function setUser(payload) {
user.value = payload.user
token.value = payload.token
}
function logout() {
user.value = null
token.value = null
}
return { user, token, isLoggedIn, setUser, logout }
})
```
Usage in a component:
```vue [Navbar.vue]
```
Pinia best practices I follow:
- Create small, focused stores (`authStore`, `cartStore`, `uiStore`) instead of one giant store.
- Use plugins for cross-cutting concerns: persistence plugin for localStorage, logger for dev debugging.
- Type your stores when using TypeScript for safer refactoring and autocompletion.
- Prefer actions for async logic and mutations inside actions - keep the state mutations explicit.
- For SSR, make sure to return fresh stores per request (Pinia supports SSR patterns).
## Comparative Analysis: Choosing the Right Tool for the Job
I like to compare the three on the dimensions that matter most in real projects:
- **Scope**
- Composables: local to component or independent instances per consumer (or implicitly shared if you intentionally export a single instance).
- Provide/Inject: subtree-scoped.
- Pinia: global/app-wide.
- **Use case**
- Composables: reusable logic (fetch, form handling, timers).
- Provide/Inject: contextual settings for a component subtree (theme, nested form context).
- Pinia: global app state, multi-page shared state, complex interdependent state.
- **Complexity & tooling**
- Composables: low overhead, great for simple logic.
- Provide/Inject: lightweight, but can make dependency relationships implicit.
- Pinia: more structure, supports plugins, SSR, type-safety; better for larger apps.
- **Reactivity sharing**
- Composables: returns isolated reactive state unless intentionally shared.
- Provide/Inject: can provide reactive objects to descendants.
- Pinia: stores are reactive by design and accessible anywhere.
Rules of thumb I use:
- Start with composables for small apps and features. If you find multiple components need the same instance of state, either lift it up (parent-provide) or move it to Pinia.
- Use Provide/Inject when a feature must be scoped to a subtree and you want to avoid prop drilling — e.g., a multi-field form with many nested inputs.
- Use Pinia when state must be accessible across the app, persisted, or when you want the developer ergonomics and plugin support Pinia provides.
## Conclusion and Best Practices
State management in Vue doesn’t have to be either/or. Composables, Provide/Inject, and Pinia are complementary tools - choose based on scope, reusability, and complexity.
My checklist before implementing state:
- Is the state subtree-scoped? Use Provide/Inject.
- Is it local/reusable logic but independent per consumer? Use a composable.
- Is it global or shared across pages or unrelated components? Use Pinia.
- Do I need SSR, persistence, or a plugin ecosystem? Pinia is the best fit.
- Keep interfaces explicit: name keys, use symbols for Provide/Inject, prefix composables with use, and keep stores modular.
# What's New in Vue 3.3
Vue 3.3 "Rurouni Kenshin" is [now available](https://blog.vuejs.org/posts/vue-3-3){rel=""nofollow""} and "is focused on developer experience improvements".
In this article, I give an overview of the highlighted features in Vue 3.3. [Read the changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md#330-2023-05-08){rel=""nofollow""} if you are interested in all changes of this new version.
## Props Destructuring
::warning
This feature is experimental and requires explicit opt-in.
::
I think this is one of the coolest features of the new release. You can now destructure props without losing reactivity and also set default values:
```vue {2}
```
In my opinion, this is a very clean and "natural" way to define your props. Previously you had to use `toRefs` in combination with `withDefaults` to achieve the same result:
```vue
```
## defineModel
::warning
This feature is experimental and requires explicit opt-in.
::
Vue 3.3 provides a very elegant way to support two-way binding with `v-model`. Before 3.3 you had to write a lot of boilerplate code to support it:
```vue {2-3,6,11}
```
With 3.3 we can achieve the same functionality with less code:
```vue {2,6}
```
In this example, the `defineModel` macro automatically registers a prop `modelValue` and returns a ref that can be directly mutated. Additionally. it registers the `update:modelValue` event.
## Improved TypeScript Type Support
In the past, only local types such as type literals and interfaces could be used in the type parameter position of the `defineProps` and `defineEmits` compiler macros.
The reason for this was that Vue needed to analyze the properties on the props interface to create runtime options. However, this limitation has been addressed in version 3.3. The Vue compiler can now handle imported types and a limited set of complex types:
```vue
```
## Generic Components
Your components can now accept generic type parameters via the `generic` attribute if you are using `
```
## More Ergonomic defineEmits
Typing `defineEmits` was a bit verbose before 3.3:
```vue
```
Vue 3.3 provides a "more ergonomic" way:
```vue
```
## Use console in the template
You can now use `console` in your template:
```vue {9}
{{ count }}
```
In previous versions of Vue, this caused an error: `TypeError: Cannot read properties of undefined (reading 'log')`
[Try it yourself](https://play.vuejs.org/#eNp9j81uwjAQhF/F8gUQxC7iFoUoPfYdfAFngZT4R/aGHiK/e9cJqiIqcZyZnf12R/7pvXgMwEteRR06jywCDr5WtjPeBWQjC3BhiV2CM2xFoytltbMRmXaDRXbM+fpjo2wl5w3UJYFgfH9CIMVYddvX4/hspFRJ0pN/HhCdZY3uO30/Kp43ux5E767r7Xaa3yhef1kdwIDFSs4NalfyD8F3fL62MCcvvqOz9M+YAeoZRMVLNjnZoy+yVvyG6GMp5WD9/Sq0M7KhTAbCdgaK1pnmIA5iL9su4tIWEE1xDu4nQiCg4rvFbknmA0IRwLYQILxlvcwueS/RP2ZGJmUTT78djp6C){rel=""nofollow""}
## Conclusion
Vue got so much better with this new version. The new features improve the developer experience and I'm very excited to see how the framework further evolves with the next upcoming releases.
For more information, read the [official announcement](https://blog.vuejs.org/posts/vue-3-3){rel=""nofollow""} and the [GitHub changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md#330-2023-05-08){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 [subscribe to my weekly Vue newsletter](https://weekly-vue.news){rel=""nofollow""}:
# When to Use useState in Nuxt
Nuxt provides the `useState` composable, which creates a reactive and SSR-friendly shared state. It's an SSR-friendly alternative to the `ref` function from Vue 3.
You might be confused when to use `useState` or `ref` in your Nuxt app. In this article, I want to answer this question.
## Problem with `ref` using SSR
Let's take a look at a simple example where we use the `ref` function to create a shared state in a Nuxt app:
```vue [Component.vue] {6-10}
{{ randomString }}
```
The problem with this code is that the `ref` function is not SSR-friendly. The code inside the `
{{ randomString }}
```
Try it yourself in the following StackBlitz project, and you will see that the random strings are the same on the server and the client. There are no hydration mismatch warnings in the console:
:stackblitz{index="1" open-file-path="pages/use-state.vue" project-id="when-to-use-use-state-in-nuxt"}
## Conclusion
In general, you should use the `useState` composable from Nuxt when you want to create a shared state that is reactive and SSR-friendly. The `useState` composable ensures that the state is only created once and is shared between the server and the client.
If your Nuxt app does not require SSR, you can use the `ref` function from Vue 3 to create a shared state.
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""}:
# Why A Good Frontend Developer Should Care About Web Accessibility
Cover image from [Poakpong](https://www.flickr.com/photos/poakpong/4681315789) licensed under [CC 2.0](https://creativecommons.org/licenses/by/2.0/)
Back in 2017, when I started frontend development, I heard [an interesting talk](https://isellsoap.github.io/talk-aesthetics-of-the-invisible/#/){rel=""nofollow""} with the title "Aesthetics of the invisible" from my former colleague [Francesco Schwarz](https://francescoschwarz.de/){rel=""nofollow""}. It was all about accessibility in websites and [the related blog post](https://francescoschwarz.de/en/blog/aesthetics-of-the-invisible/){rel=""nofollow""} starts with a remarkable statement:
> Sometimes one single hidden glyph in an HTML markup makes the difference between a good and an outstanding front-end.
As I learned soon, accessibility is a very polarizing topic. These are the typical statements I heard related to this topic:
- "We have no time for accessibility features."
- "There are only a few blind persons. We do not need to support this minority."
- "I hate these [ugly borders](http://www.outlinenone.com/){rel=""nofollow""} and I always remove them."
- "We can care about accessibility later if we have more users."
In this article, I want to tell you what accessibility is, why it is essential for websites, why I care about it, and why you should care about it too.
## What is Web Accessibility?
If I needed to describe it in my own words, I would define it as
> **Anyone** can fully access and interact with a website
The [official Wikipedia article](https://en.wikipedia.org/wiki/Web_accessibility){rel=""nofollow""} describes it as
> the inclusive practice of ensuring there are no barriers that prevent interaction with, or access to websites, by people with disabilities.
So a good website should enable access to its content to everybody, even people with disabilities.
By the way, accessibility is often abbreviated by **A11Y**.
A11Y is known as a [numeronym](https://a11yproject.com/posts/a11y-and-other-numeronyms/){rel=""nofollow""}, which is somewhat similar to an acronym. Unlike an acronym, numbers are used in place of letters to shorten the term. You may already be familiar with other numeronyms, such as “K-9” for “Canine” or “W3C” for “World Wide Web Consortium”.
## What are disabilities?
As I mentioned in the beginning, there is the misbelief that web accessibility is only relevant to blind users.
According to [a WHO report](http://www.who.int/news-room/fact-sheets/detail/blindness-and-visual-impairment){rel=""nofollow""}, approximately 1.3 billion people live with some form of vision impairment. Thereof, 36 million people are blind. But also, with mild and severe vision impairments, you can have trouble reading content on a website.
Of course, there exist not only visual disabilities. [Google's Accessibility Fundamentals](https://developers.google.com/web/fundamentals/accessibility/){rel=""nofollow""} demonstrate some access impairments in real-world examples:
| | Situational | Temporary | Permanent |
| --------- | ----------------- | ---------- | --------- |
| Visual | distracted driver | concussion | blindness |
| Motor | holding a baby | broken arm | |
| Hearing | noisy office | | deaf |
| Cognitive | | concussion | |
So all of us could get in a situation where we need to interact with websites but have some situational, temporary, or permanent disability.
I like the quote from the article ["Accessibility matters—and here's what we're doing about it"](https://product.voxmedia.com/2016/5/11/11612516/accessibility-matters-and-heres-what-were-doing-about-it){rel=""nofollow""}:
> **We should never make assumptions about our users**
> Making a product accessible does not mean targeting a specific subset of people. Rather, accessible design, or universal design, is about > > making products usable by the greatest number of people possible. We should not assume we know how our users are engaging with our content, > and should understand that it may be "seen" by a number of assisting technologies, including automated tools, keyboard-only navigation, and > screen readers.
You probably are now thinking: "But **my** customers are different".
Nope, I don't think so!
As you can see in the table above, the chances are high that one of your website users has a situational, temporary or permanent disability.
You should care about everyone and not care about a minority.
**Never forget: The website is the front door to your business!**
Of course, you want to have as many people as possible in your business, so you should care. So extend them a warm welcome!
## Tools that can assist in browsing a website
I want to introduce you to some tools which can help to browse a website if you have some disability:
- Speech recognition software which allows dictating words and commands to the computer. Helpful for people who cannot use a keyboard or mouse to interact with the computer.
- Subtitled or sign language versions for deaf people.
- Software that enlarges the content of your monitor, which can help people with visual impairments.
- Screen reader software which uses synthesized speech to read out elements on the computer display.
It would be best if you try them to get a feeling for them. I would especially recommend testing screen readers. You can read more about how to use them [here](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Screenreaders){rel=""nofollow""}.
This video shows the usage of a screen reader:
[](https://www.youtube.com/watch?v=xpP_Km5L46E){rel=""nofollow""}
## How can I make a site more accessible?
| User Constraint | Accessibility Solution |
| --------------------------------------- | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| Cannot use a mouse or standard keyboard | Code your page in a way that the navigation also works without a mouse. Therefore it is important to [not remove the outline property](http://www.outlinenone.com/){rel=""nofollow""}. |
| Visual impairment | Use larger texts and images as well as a good color contrast on the page. |
| Blindness | For screen readers, having a semantically meaningful HTML (more about that below the table) and textual description of images and links (e.g. using the alt-tag to describe an image: ``) is helpful |
| Deaf and hard-of-hearing | Add closed captioned videos or provide a sign language version. |
| Color blind | Underline and color links (or differentiate otherwise) to help color blind users notice them. |
These are just some of the examples, a good checklist with more information is available at [Web Content Accessibility Guidelines 2.0](http://romeo.elsevier.com/accessibility_checklist/){rel=""nofollow""}.
In general using `
` as HTML tag should be avoided if possible. Therefore you can always check this amazing graphic from [HTML5 Doctor](http://html5doctor.com/downloads/h5d-sectioning-flowchart.pdf){rel=""nofollow""}:

I would also advise using tooling that assists during development like [eslint-plugin-jsx-a11y](https://www.npmjs.com/package/eslint-plugin-jsx-a11y){rel=""nofollow""}, which is an npm package that provides a static AST checker for accessibility rules on JSX elements.
## How to test if my website is accessible
I mainly use [Google Lighthouse](https://developers.google.com/web/tools/lighthouse/){rel=""nofollow""} to check if my site is accessible.
You can use one of the many accessibility checklists available online, but I recommend using any of the tools described in [Accessibility Testing Tools ](https://css-tricks.com/accessibility-testing-tools/){rel=""nofollow""}.
## Is it time-consuming to implement accessibility?
Yes, if the project is already in a late-stage or you have a legacy code base with massive accessibility issues which you now need to fix.
No, if you can consider accessibility from the beginning of a project and care about it throughout the development.
## Why an accessible website is a good website
- A well-structured semantic HTML website helps to improve your SEO. A search engine bot is, for example, blind, can’t hear, and has the cognitive abilities of a young child. So he is one of your most crucial website visitors. If he cannot correctly access your website, you will get a lower rank in the search requests.
- Nearly everyone can access & interact with your website
- Caring about accessibility is a good attribute of a professional web developer
- An accessible website feels way more professional
- You will save money as you will not need to respond to support questions from users with disabilities.
## Summary
After this article, I hope you understand why web accessibility is important and why you should care about it.
It is not about providing support for a minority of people but to providing a good user experience for **every user** of your website.
Your website is the front door of your business: Let your users know that they are welcome, that you care about them and that your business cares about quality and professionalism.
Hopefully, you are now also a mentor for other developers who still believe that accessibility is not necessary.
## Important Links
- [MDN "What is accessibility?"](https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility){rel=""nofollow""}
- [Google Web Fundamentals Accessibility](https://developers.google.com/web/fundamentals/accessibility/){rel=""nofollow""}
- [Why Web Accessibility Is Important and How You Can Accomplish It](https://medium.com/fbdevclagos/why-web-accessibility-is-important-and-how-you-can-accomplish-it-4f59fda7859c){rel=""nofollow""}
- [Web Accessibility Checklist](https://a11yproject.com/checklist){rel=""nofollow""}
# Why I Developed My Own Nuxt Starter Kit for SaaS Products
Beginning in 2025, I had the idea for a micro SaaS product that I wanted to build. I didn't want to start from scratch, so I looked for existing Nuxt starter kits that could help me get up and running quickly. However, I found that most of the available options were either too generic or not tailored to my specific needs.
I decided to take matters into my own hands and develop [Nuxt Starter Kit](https://nuxtstarterkit.com){rel=""nofollow""}, a highly opinionated and custom solution that would serve as a solid foundation for my SaaS projects. This decision was not just about building a product; it was about creating a development environment that would enhance productivity, reduce maintenance costs, and align perfectly with the unique requirements of my applications.
## The Motivation for a Highly Opinionated Nuxt Starter Kit
I got access to [Supastarter](https://supastarter.dev/){rel=""nofollow""} and [Super SaaS](https://supersaas.dev/){rel=""nofollow""}, two very famous Nuxt starter kits, but I found that they didn't fully meet my needs. Unfortunately, both of them did not reflect my coding style and preferences, which made it difficult to work with them effectively.
These starter kits were created for maximum flexibility, which is great for some use cases, but I needed something more focused and opinionated. I wanted a solution that would not only provide a solid foundation but also enforce best practices and coding standards that I value.
To reduce the maintenance overhead, I wanted to limit my starter kit to three main components:
- [Nuxt Hub](https://hub.nuxt.com/){rel=""nofollow""}: a platform for deploying and scaling Nuxt applications globally, powered by Cloudflare.
- [Nuxt UI Pro](https://ui.nuxt.com/pro?aff=z1NAy){rel=""nofollow""}: a collection of premium Vue components, composables and utils built on top of Nuxt UI.
- [Polar](https://polar.sh){rel=""nofollow""}: an open source Merchant of Record (MoR) solution that simplifies payment processing, tax compliance, and subscription management.
These tools would provide the necessary functionality while keeping the codebase lean and efficient.
## Development Process and Features
I started developing the main features of the [Nuxt Starter Kit](https://nuxtstarterkit.com){rel=""nofollow""}, and rewrote my existing SaaS product [CodeSnap](https://codesnap.dev){rel=""nofollow""} using this new foundation. The development process was smooth, and I was able to quickly convert my existing codebase to the new structure.
This way I could ensure that the starter kit was not just theoretical but practical and battle-tested. Additionally, I used the starter kit to build the landing page for the [Nuxt Starter Kit](https://nuxtstarterkit.com){rel=""nofollow""} itself and published [Sigxel](https://sigxel.com){rel=""nofollow""}, a micro SaaS to manage email signatures.
## Conclusion
Ultimately, creating [Nuxt Starter Kit](https://nuxtstarterkit.com){rel=""nofollow""} for my SaaS products has proven to be a highly rewarding endeavor. Not only does it provide a foundation that is perfectly aligned with specific project requirements, but it also reduces the overhead of maintaining a bloated codebase. Leveraging modern tools like Nuxt Hub, Nuxt UI Pro, and Polar adds to the robustness of the final product, ensuring enhanced performance and scalability for long-term success.
By choosing to develop your own solution instead of adapting a generic starter kit, you invest in a system that evolves with your business, offering a competitive edge in the ever-innovative world of SaaS products.
::tip
With discount code `TN8JDLYO`, the 20 first users can get 30% off. [Buy now!](https://nuxtstarterkit.com){rel=""nofollow""}
::
# Why I Love Vue 3's Composition API
[Vue 3](https://v3.vuejs.org/){rel=""nofollow""} introduced the [Composition API](https://v3.vuejs.org/guide/composition-api-introduction.html){rel=""nofollow""} to provide a better way to collocate code related to the same logical concern. In this article, I want to tell you why I love this new way of writing Vue components.
First, I will show you how you can build components using Vue 2, and then I will show you the same component implemented using Composition API. I'll explain some of the Composition API basics and why I prefer Composition API for building components.
For this article, I created a [Stackblitz Vue 3 demo application](https://stackblitz.com/edit/vue-3-composition-api-demo?file=src/App.vue){rel=""nofollow""} which includes all the components that I'll showcase in this article:
:stackblitz{project-id="vue-3-composition-api-demo"}
The source code is also available on [GitHub](https://github.com/Mokkapps/vue-3-composition-api-demo/){rel=""nofollow""}.
## Options API
First, let's look at how we build components in Vue 2 without the Composition API.
In Vue 2 we build components using the Options API by filling (option) properties like methods, data, computed, etc. An example component could look like this:
```vue
...
```
As you can see, Options API has a significant drawback: The logical concerns (filtering, sorting, etc.) are not grouped but split between the different options of the Options API. Such fragmentation is what makes it challenging to understand and maintain complex Vue components.
Let's start by looking at [CounterOptionsApi.vue](https://github.com/Mokkapps/vue-3-composition-api-demo/blob/master/src/components/CounterOptionsApi.vue){rel=""nofollow""}, the Options API counter component:
```vue
Counter Options API
Count: {{ count }}
2^Count: {{ countPow }}
```
This simple counter component includes multiple essential Vue functionalities:
- We use a `count` data property that uses the `initialValue` property as its initial value.
- `countPow` as computed property which calculates the `count` value to the power of two.
- A watcher that emits the `counter-update` event if the `count` value has changed.
- Multiple methods to modify the `count` value.
- A `console.log` message that is written if the [mounted lifecycle hook](https://vuejs.org/v2/api/#mounted){rel=""nofollow""} was triggered.
If you are not familiar with the Vue 2 features mentioned above, you should first read the [official Vue 2 documentation](https://vuejs.org/v2/guide/){rel=""nofollow""} before you continue reading this article.
## Composition API
Since Vue 3 we can **additionally** use [Composition API](https://v3.vuejs.org/guide/composition-api-introduction.html#why-composition-api){rel=""nofollow""} to build Vue components.
::note
Composition API is fully optional, and we can still use Options API in Vue 3.
::
In my [demo application](https://stackblitz.com/edit/vue-3-composition-api-demo?file=src/App.vue){rel=""nofollow""} I use the same template for all Vue components, so let's focus on the `
```
Let's analyze this code:
The entry point for all Composition API components is the new `setup` method. It is executed **before** the component is created and once the props are resolved. The function returns an object, and all of its properties are exposed to the rest of the component.
::warning
We should avoid using `this` inside setup as it won't refer to the component instance. `setup` is called before data properties, computed properties, or methods are resolved, so that they won't be available within setup.
::
But we need to be careful: The variables we return from the setup method are, by default, not reactive.
We can use the `reactive` method to create a reactive state from a JavaScript object. Alternatively, we can use `ref` to make a standalone primitive value (for example, a string, number, or boolean) reactive:
```ts
import { reactive, ref } from 'vue'
const state = reactive({
count: 0,
})
console.log(state.count) // 0
const count = ref(0)
console.log(count.value) // 0
```
The `ref` object contains only one property named `value`, which can access the property value.
Vue 3 also provides different new methods like `computed`, `watch`, or `onMounted` that we can use in our `setup` method to implement the same logic we used in the Options API component.
### Extract Composition Function
But we can further improve our Vue component code by extracting the counter logic to a standalone **composition function** ([useCounter](https://github.com/Mokkapps/vue-3-composition-api-demo/blob/master/src/composables/useCounter.ts){rel=""nofollow""}):
```ts
import { ref, computed, onMounted } from 'vue'
export default function useCounter(initialValue: number) {
const count = ref(initialValue)
const increment = () => {
count.value += 1
}
const decrement = () => {
count.value -= 1
}
const incrementBy = (value: number) => {
count.value += value
}
const countPow = computed(() => count.value * count.value)
onMounted(() => console.log('useCounter mounted'))
return {
count,
countPow,
increment,
decrement,
incrementBy,
}
}
```
This drastically reduces the code in our [CounterCompositionApiv2.vue](https://github.com/Mokkapps/vue-3-composition-api-demo/blob/master/src/components/CounterCompositionApiv2.vue){rel=""nofollow""} component and additionally allows us to use the counter functionality in any other component:
```vue
```
In Vue 2, [Mixins](https://vuejs.org/v2/guide/mixins.html#Basics){rel=""nofollow""} were mainly used to share code between components. But they have a few issues:
- It's impossible to pass parameters to the mixin to change its logic which drastically reduces its flexibility.
- Property name conflicts can occur as properties from each mixin are merged into the same component.
- It isn't necessarily apparent which properties came from which mixin if a component uses multiple mixins.
Composition API addresses all of these issues.
### SFC Script Setup
[Vue 3.2](https://blog.vuejs.org/posts/vue-3.2.html){rel=""nofollow""} allows us to get rid of the `setup` method by providing the `
```
### Using the Composition API with Vue 2
If you can’t migrate to Vue 3 today, then you can still use the Composition API already. You can do this by installing [the official Composition API Vue 2 Plugin](https://github.com/vuejs/composition-api){rel=""nofollow""}.
## Conclusion
You've seen the same counter component created in Vue 2 using Options API and created in Vue 3 using Composition API.
Let's summarize all the things I love about Composition API:
- More readable and maintainable code with the feature-wise separation of concerns brought with the composition API.
- No more `this` keyword, so we can use arrow functions and use functional programming.
- We can only access the things we return from the `setup` method, making things more readable.
- Vue 3 is written in TypeScript and [fully supports Composition API](https://v3.vuejs.org/guide/typescript-support.html#using-with-composition-api){rel=""nofollow""}.
- Composition functions can easily be unit tested.
The following image shows a large component where colors group its logical concerns and compares Options API versus Composition API:

You can see that Composition API groups logical concerns, resulting in better maintainable code, especially for larger and complex components.
I can understand that many developers still prefer Options API as it is easier to teach people who are new to the framework and have JavaScript knowledge. But I would recommend that you use Composition API for complex applications that require a lot of domains and functionality. Additionally, Options API does not work very well with TypeScript, which is, in my opinion, also a must-have for complex applications.
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""}.
# Why I Picked Vue.js as My Freelancer Niche
I have professional experience with the three big players in web development: [Angular](https://angular.io){rel=""nofollow""}, [Vue.js](https://vuejs.org/){rel=""nofollow""} and [React](https://reactjs.org/){rel=""nofollow""}.
I've reached the point in my career where I need to choose one of the three frameworks/libraries that I will use for my future freelancing career.
As the title already reveals, I chose Vue and in this article, I will describe to you why I picked it instead of React or Angular.
::warning
This article will not provide a full comparison between the three technologies.
::
## Why Do I Need a Niche?

Finding your niche as a freelancer can have an extremely positive impact on your career. It took me some time to find mine, but finally, I found it and I can take my business to the next level. It has some advantages to be a jack of all but in the end, it's even better to be the master of one trade. Having a niche can boost your income, helps to find new projects easier, and is useful to advertise yourself as an expert.
I can also give you an example of how the niche saves me time every day:
My previous search queries for job agents on freelancer platforms looked like this: `React OR Angular OR TypeScript OR JavaScript OR React Native OR Vue`. This way, I got job agent emails with dozens of job offers that I had to manually scan for interesting projects.
With a niche in place, I modified these search queries to `Vue` and now the job agent emails contain only a few freelancer projects but they are tailored to my skills.
## My Freelancing History
When I started freelancing back in 2019 my tech focus was on web development using the [Angular](https://angular.io){rel=""nofollow""} framework.
But for my first freelancing project I choose a [Vue.js](https://vuejs.org/){rel=""nofollow""} project and I stayed there for about two years. I chose this project
because I already had professional experience with Angular and some experience with React as I used it for my [portfolio website](https://mokkapps.de) and two React Native apps that I developed and published. I wanted to see how it compares to Angular and React. After this project, beginning from January to September 2021 I worked in a [React](https://reactjs.org/){rel=""nofollow""} project as I wanted to gain some professional experience with the library.
I could easily further specialize in Angular, but I have no good belly feeling with this choice. Therefore, I had to choose between React and Vue.
## What I Love About Vue
> TL;DR: In my opinion, 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.

### Less Usage of JavaScript's "this" keyword
Angular components are full of the JavaScript keyword `this`. I don't like this and thankfully we can write React and Vue components without the `this` keyword by using [React Hooks](https://reactjs.org/docs/hooks-intro.html){rel=""nofollow""} and [Vue 3's Composition API](https://v3.vuejs.org/api/composition-api.html){rel=""nofollow""}.
### Outstanding Documentation
The [official Vue documentation](https://v3.vuejs.org/guide/introduction.html){rel=""nofollow""} is amazing and one of the best resources to learn Vue.
### Best Parts of React and Angular
In its early development phase, Vue took inspiration from the good things of [AngularJS](https://angularjs.org/){rel=""nofollow""} (the first version of Angular).
Vue also got inspired by React and they share some similarities:
- They have their focus in the core library. Concerns like global state management and routing are handled by separate companion libraries.
- Both provide reactive and composable view components.
- One and the other use a virtual DOM.
### Less Optimization Efforts
In Vue, I need to care less about optimization efforts in comparison to React. React triggers a re-rendering of the entire component tree when a component's state changes. Read my article ["Debug Why React (Re-)Renders a Component"](https://www.mokkapps.de/blog/debug-why-react-re-renders-a-component/){rel=""nofollow""} for further details.
There are multiple ways to avoid unnecessary re-rendering of child components in React:
- use [PureComponent](https://reactjs.org/docs/react-api.html#reactpurecomponent){rel=""nofollow""}
- implement `shouldComponentUpdate` if you are using class components
- use immutable data structures
Angular developers also need to take care of the change detection, you can 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""} if you want to deep-dive into that mechanism.
Vue automatically tracks a component's dependencies during its render. Therefore, it knows precisely which components need to be re-rendered when the state changes. As a Vue developer, I can more focus on building the app than on performance optimizations.
### Templates
Vue uses HTML templates, but there’s an option to write the render function in [JSX](https://reactjs.org/docs/introducing-jsx.html){rel=""nofollow""}. On the other hand, in React there's solely JSX. A Vue component is split into three parts: HTML (``), CSS (`
```
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
```

## 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
Blog
{{ title }}
```
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 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
Blog slug ({{ $route.params.slug }}) not found
```
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:

## 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}
Table of Contents
```
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}
Table of Contents
```
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
https://mokkapps.de
RSS for NodeSun, 14 Aug 2022 18:14:16 GMT
https://mokkapps.de/blog/article-5
https://mokkapps.de/blog/article-5Thu, 05 May 2022 00:00:00 GMT
https://mokkapps.de/blog/article-4
https://mokkapps.de/blog/article-4Mon, 04 Apr 2022 00:00:00 GMT
https://mokkapps.de/blog/article-3
https://mokkapps.de/blog/article-3Thu, 03 Mar 2022 00:00:00 GMT
https://mokkapps.de/blog/article-2
https://mokkapps.de/blog/article-2Wed, 02 Feb 2022 00:00:00 GMT
https://mokkapps.de/blog/article-1
https://mokkapps.de/blog/article-1Sat, 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
Nuxt 3 Tailwind Dark Mode Demo
```
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
Nuxt 3 Tailwind Dark Mode Demo
```
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`:

## 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.

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.

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`:

## 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:

If we now start the profiling, trigger a state change, and stop the profiling we can see that information:

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:

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`:

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
```

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}
{{ label }}
{{ count }}
```
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:

As already mentioned, Storybook converts the JSDoc comments from our code snippet above into documentation, shown in the following picture:

## 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:

The next picture shows the Swagger UI with the test route:

## 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]
{{ code }}
```
Let's extend that component by using our `useShikiHighlighter` composable to highlight the code:
```vue [components/content/ProseCode.vue] {4-5,7-34,39}
{{ code }}
```
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:

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:

### Components
> Unstyled components with useful functionality
The following image shows the list of components provided by the CDK:

### 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:

Now you will see a new section at the top of your profile page which renders the content of this new README file:

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 `
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:

The serverless function should then send an email with a list of tweets if someone mentioned the monitored keyword in the last 24 hours:

## 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://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.

- `CMD + D`: Finds and selects the next match for the currently selected word.

- `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.

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.

### 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 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.

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""}

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""}.

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:

## 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:
`
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:
`
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}
{{ doc.title }}
Not Found
Browse issues
```
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]
Admin Controls
Schedule
```
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`:

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:

> 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.

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:

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.

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:

- **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:

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:

### 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:

## Show Language
Next, we want to show the name of the language on the top right, if it is available.
```vue {3-9}
{{ languageText }}
```
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:

## Show File Name
Next, we want to show the file's name on the top left, if it is available:
```vue
{{ filename }}
```
The result looks like this:

## 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
Copied code!
```
Let's take a look at the final result with language & file name, copy code button, and line highlighting:

## 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:

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:

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:

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:

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:

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:

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:

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}}
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:

## 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:

Next, we click "Other type of secret" and enter key and value of our secret in the corresponding "Secret key/value" inputs:

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:

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:

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

with the following selections:

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://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:

### Start Frontend
Run `npm start` to serve the Angular application on `http://localhost:9000/`:

Finally, we can log in and see some of the out-of-the-box features like the possibility to see and edit our entities,

view metrics of the application

and a user management

## 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:

## 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.

## 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:

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.

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:

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:

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:

## 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""}:

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`:

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 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:

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:

### 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""}:

After loading the URL we can see the imported dashboard:

### 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:

Now we see a new dashboard where we can create a new panel:

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:

Additionally, a new panel for the `custom_counter` metric is added to our dashboard:

In the end, the dashboard looks like this:

## 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

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

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

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

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:

## 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:

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:

### 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.

## 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.

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 (``), logic (`
{{ count }}
```
::note
This only works if the `ref` is a top-level property in the template.
::
#### Watcher
We can directly pass a `ref` as a watcher dependency:
```js {3,5-6}
import { watch, ref } from 'vue'
const count = ref(0)
// Vue automatically unwraps this ref for us
watch(count, (newCount) => console.log(newCount))
```
#### Volar
If you are using VS Code, you can configure the [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar){rel=""nofollow""} extension to automatically add `.value` to refs. You can enable it in the settings under `Volar: Auto Complete Refs`:

The corresponding JSON setting:
```json
"volar.autoCompleteRefs": true
```
::note
To reduce CPU usage, this feature is disabled by default.
::
## Summarizing comparison between reactive() and ref()
Let's take a summarizing look at the differences between `reactive` and `ref`:
| `reactive` | `ref` |
| ------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| 👎 **only** works on object types | 👍 works with **any** value |
| 👍 no difference in accessing values in `
```
You can simply copy everything from `data` into `reactive` to migrate this component to Composition API:
```js [CompositionApiComponent.vue] {4-8}
```
### Composing ref and reactive
A recommended pattern is to group refs inside a `reactive` object:
```js {1-2,4-7}
const loading = ref(true)
const error = ref(null)
const state = reactive({
loading,
error,
})
// You can watch the reactive object...
watchEffect(() => console.log(state.loading))
// ...and the ref directly
watch(loading, () => console.log('loading has changed'))
setTimeout(() => {
loading.value = false
// Triggers both watchers
}, 500)
```
If you don't need the reactivity of the `state` object itself you could instead group the refs in a plain JavaScript object.
Grouping refs results in a single object that is easier to handle and keeps your code organized. At a glance, you can see that the grouped refs belong together and are related.
::note
This pattern is also used in libraries like [Vuelidate](https://vuelidate.js.org/){rel=""nofollow""} where they [use reactive() for setting up state for validations](https://blog.logrocket.com/form-validation-in-vue-with-vuelidate/){rel=""nofollow""}.
::
## Opinions from Vue Community
The amazing [Michael Thiessen](https://twitter.com/MichaelThiessen){rel=""nofollow""} wrote a [brilliant in-depth article](https://michaelnthiessen.com/ref-vs-reactive/#act-3-why-i-prefer-ref){rel=""nofollow""} about this topic and collected the opinions of famous people in the Vue community.
Summarized, **they all use `ref` by default** and use `reactive` when they need to group things.
## Conclusion
So, should you use `ref` or `reactive`?
My recommendation is to use `ref` by default and `reactive` when you need to group things. The Vue community has the same opinion but it's totally fine if you decide to use `reactive` by default.
Both `ref` and `reactive` are powerful tools to create reactive variables in Vue 3. You can even use both of them without any technical drawbacks. Just pick the one you like and try to stay **consistent** in how you write your code!
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""}.
# Rendering Dynamic Markdown in Nuxt 3+
In my current freelance project, I had to render dynamic Markdown content in a Nuxt 3+ application. The Markdown content was written by redactors in a CMS, provided via an API, and needed to be rendered on the client-side. In this article, I'll explain how we solved the problem of rendering dynamic Markdown content in a Nuxt 3+ application.
## The Problem
The Markdown content was provided by the CMS via an API and needed to be rendered on the client-side. The content was dynamic and could change at any time, so we couldn't hard-code it into the application. We needed a way to fetch the Markdown content from the API and render it as HTML in the Nuxt 3+ application.
Previously, the redactors wrote that content in HTML, and we used the `v-html` directive to render it. However, we wanted to switch to Markdown to make it easier for the redactors to write and format the content. Additionally, this allowed us to easily reuse our existing Vue components.
## The Solution
::note
If you are only working with static Markdown files, you can use the [`@nuxt/content` module](https://content.nuxt.com/){rel=""nofollow""} to render Markdown content in your Nuxt 3+ application.
::
Luckily, Nuxt 3+ provides a solution for rendering Markdown content using the [`@nuxtjs/mdc` module](https://github.com/nuxt-modules/mdc){rel=""nofollow""}. This module allows you to render Markdown content as HTML in your Nuxt 3+ application.
You can add it to your project using the following command:
```bash
npx nuxi@latest module add mdc
```
This command will install the `@nuxtjs/mdc` module and add it to the modules section of your `nuxt.config.ts` file.
Now you can use the `` component to render Markdown content in your Vue components. Here's an example of how you can use it:
```vue [Component.vue] {16}
```
That's it! The Markdown content will be rendered as HTML in your Nuxt app. Using the [MDC](https://content.nuxt.com/usage/markdown){rel=""nofollow""} syntax, you can also include custom Vue components in your Markdown content. In my example, I referenced the `my-button` component, which will be rendered as a button in the Markdown content.
::note
You have to globally register your Vue components if you want to use them in the Markdown content. You can do this by placing them in a `~/components/global` directory or by using a `.global.vue` suffix in the filename.
::
## Stackblitz Demo
Try it yourself in the following Stackblitz demo:
:stackblitz{project-id="nuxt-markdown-to-vue-converter"}
## Conclusion
Rendering dynamic Markdown content in a Nuxt 3+ application is easy using the `@nuxtjs/mdc` module. By using Markdown, you can make it easier for redactors to write and format content and reuse your existing Vue components.
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""}:
# Run Automated Electron App Tests Using Travis CI
Last year I developed the [Standup Picker](https://mokkapps.de/standup-picker), which is an [Angular](https://angular.io){rel=""nofollow""} application running in an [Electron](https://electronjs.org){rel=""nofollow""} shell.
As I released new versions while older versions were already in use, I wanted to gain more confidence while releasing newer versions of my application.
As the [source code is available at GitHub](https://github.com/Mokkapps/scrum-daily-standup-picker){rel=""nofollow""}, I researched for free alternatives to [Jenkins](https://jenkins-ci.org/){rel=""nofollow""} which we used at work for Continuous Integration (CI).
I found [Travis](https://travis-ci.org){rel=""nofollow""}, a free continuous integration platform for GitHub projects.
## My Expectations

I wanted to integrate automated E2E and unit tests before each release of the Electron application. In my case, a release should be triggered if something has been merged to master. So the CI should perform these steps:
1. Run unit tests
2. Run E2E tests
3. Create Electron releases for OS X, Linux, Windows
This way, I can ensure that my releases work as expected (at least all the stuff I have covered by tests).
## Integrate Travis CI in your project
To use Travis, you need to make sure that you have a GitHub account and owner permissions for this project hosted on GitHub.
The next step is to visit [the Travis homepage](https://travis-ci.com/){rel=""nofollow""}, [sign up with GitHub](https://travis-ci.com/signin){rel=""nofollow""} and follow the instructions until you can select your project.
To tell Travis CI what automated steps should be executed, you need to add a `.travis.yml` file to the root directory of your repository.
Finally, you must add the `.travis.yml` file to git. If you then commit and push, a Travis CI build is triggered. Be aware that Travis can only run builds on commits that were pushed after the `.travis.yml` file has been pushed to git.
## Configure Travis CI
I will explain how I configured the `.travis.yml` file for my Electron application.

### Select Operating System
I start with a quote from [the electron-builder website](https://www.electron.build/multi-platform-build){rel=""nofollow""}, which is an NPM package I used to create my Electron releases:
> Don’t expect that you can build app for all platforms on one platform.
As I wanted to create releases for OS X, Windows, Linux I had to define multiple operating systems. The main reason was that it is impossible to create a Linux release from OS X or Windows.
So I ran my Travis setup on Linux and OS X in parallel. My scripts check the current operating system and run
only in the correct environment.
Check the [official documentation](https://docs.travis-ci.com/user/multi-os/){rel=""nofollow""} for more details.
These are the relevant parts of my `.travis.yml` file:
```yaml
osx_image: xcode8.4 # define OS X image which will be mounted
dist: trusty # use Ubuntu Trusty for Linux operation system
# Note: if you switch to sudo: false, you'll need to launch chrome with --no-sandbox.
# See https://github.com/travis-ci/travis-ci/issues/8836
sudo: required
# Define Node.js as the programming language as we have a web application
language: node_js
node_js: '8'
addons:
chrome: stable # Install chrome stable on operating systems
# A list of operating systems that are used for tests
os:
- linux
- osx
```
## Electron Builder Configurations

For the [electron-builder](https://www.electron.build/){rel=""nofollow""} I added some additional cache and variable configuration based on the [official documentation](https://www.electron.build/multi-platform-build){rel=""nofollow""}:
```yaml
env:
global:
- ELECTRON_CACHE=$HOME/.cache/electron
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
cache:
yarn: true
directories:
- $HOME/.cache/electron
- $HOME/.cache/electron-builder
- $HOME/.npm/_prebuilds
before_cache:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then rm -rf $HOME/.cache/electron-builder/wine; fi
```
## Define Scripts
Now we define the scripts which Travis should execute:
```yaml
# These commands are executed before the scripts are executed
install:
# On OS X we first need to install Yarn via Homebrew
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install yarn; fi
# Install all dependencies listed in your package.json file
- yarn
script:
- echo "Unit Tests"
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then xvfb-run yarn test; else yarn test; fi
- echo "E2E Tests"
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then xvfb-run yarn test:electron; else yarn test:electron; fi
- echo "Deploy linux version to GitHub"
- if [[ "$TRAVIS_BRANCH" == "master" ]] && [[ "$TRAVIS_OS_NAME" == "linux" ]]; then yarn release:linux; fi
- echo "Deploy windows version to GitHub"
- if [[ "$TRAVIS_BRANCH" == "master" ]] && [[ "$TRAVIS_OS_NAME" == "osx" ]]; then yarn release:win; fi
- echo "Deploy mac version to GitHub"
- if [[ "$TRAVIS_BRANCH" == "master" ]] && [[ "$TRAVIS_OS_NAME" == "osx" ]]; then yarn release:mac; fi
```
### Unit & E2E tests
Electron needs a display driver as it is based on Chromium. You cannot execute any of your tests (and Electron will fail to launch) if Chromium cannot find a display driver. To fix this issue, we need to use a virtual display driver like [Xvfb](https://en.wikipedia.org/wiki/Xvfb){rel=""nofollow""}.
Xvfb is a virtual framebuffer that enables our tests to run in memory without showing an actual screen.
On Linux, we need to run the NPM test script via `xvfb-run yarn ` on OS X and Windows Chromium is already correctly configured.
### GitHub Release
By running `yarn release:` from my [package.json](https://github.com/Mokkapps/scrum-daily-standup-picker/blob/master/package.json){rel=""nofollow""} via electron-builder, I could automatically create a new release draft on the GitHub release page if the unit & E2E tests have passed:

## Conclusion
I had to invest multiple hours in configuring Travis for my application. In the end, the time and effort were worth it.
New releases have passed my tests, and I can be sure that the application's basic functionality is working.
# Run, Build & Deploy Stencil and Storybook From One Repository
I recently joined a project where the team used two separate Git repositories for their web components based on [Stencil](https://stenciljs.com/){rel=""nofollow""} and [Storybook](https://storybook.js.org/){rel=""nofollow""}. But the idea of Storybook is that the so-called "stories" live next to the components source code. Therefore, it made no sense to me to have those two tools in different repositories, and I combined them both in one repository.
My goal was that developers can also use Storybook stories via hot reload during development. Additionally, it should still be possible to separately deploy the web components to a [npm](https://www.npmjs.com/){rel=""nofollow""} registry and Storybook to a public URL.
This article describes the necessary steps to combine Storybook and Stencil in one repository. I wrote this article as there is currently no official documentation available on how to use Storybook with Stencil. Let's start with some basics.
## Stencil
> Stencil is a toolchain for building reusable, scalable Design Systems. Generate small, blazing fast, and 100% standards based Web Components that run in every browser.
Stencil combines the "best concepts of the most popular frameworks into a simple build-time tool" that provides features like:
- TypeScript support
- JSX support
- One way data-binding
As you can see from these picked concepts, Stencil is a [React](https://reactjs.org/){rel=""nofollow""}-inspired web component library. I previously worked with [lit-element](https://lit-element.polymer-project.org/){rel=""nofollow""} but due to the above-mentioned features, I prefer working with Stencil, especially in React projects.
### Init Stencil
Let's create a new Stencil project which will be the base for the demo project of this article which is available at [GitHub](https://github.com/Mokkapps/stencil-storybook-demo){rel=""nofollow""}:
```bash
npm init stencil
```
We choose the `component` starter as we want to build a web component library that can be shared via npm:
```bash
? Pick a starter › - Use arrow-keys. Return to submit.
ionic-pwa Everything you need to build fast, production ready PWAs
app Minimal starter for building a Stencil app or website
❯ component Collection of web components that can be used anywhere
```
Now we modify the automatically created `my-component.tsx` to be a bit more complex:
```ts
export interface CompOption {
value: string
displayText: string
}
@Component({
tag: 'my-component',
styleUrl: 'my-component.css',
shadow: true,
})
export class MyComponent {
/**
* The text which is shown as label
*/
@Prop() label: string
/**
* Is needed to reference the form data after the form is submitted
*/
@Prop({ reflect: true }) name: string
/**
* If true, the button is displayed as disabled
*/
@Prop({ reflect: true }) disabled = false
/**
* Define the available options in the drop-down list
*/
@Prop() options: CompOption[] = []
render() {
return (
)
}
}
```
Our demo component is a native HTML select component that gets its options passed via property. Some values like the label text, the component name, and if the component is disabled are also passed via props to the web component.
### Run Stencil web components
To be able to locally test our demo component we need to adjust `src/index.html` which is used if we start Stencil:
```html
Stencil Component Starter
```
Now we can locally test our demo component by running `npm run start-stencil` which is an auto-generated npm script from Stencil. The component should now be visible at `http://localhost:3333`:

### Build & deploy to npm registry
The next step is to deploy our component to an npm registry. For this demo, I use [Verdaccio](https://verdaccio.org){rel=""nofollow""} which is a "lightweight open source private npm proxy registry". First, it needs to be installed globally
```bash
npm install -g verdaccio
```
and then it can be started locally:
```bash
▶ verdaccio
warn --- config file - /Users/mhoffman/.config/verdaccio/config.yaml
warn --- Verdaccio started
warn --- Plugin successfully loaded: verdaccio-htpasswd
warn --- Plugin successfully loaded: verdaccio-audit
warn --- http address - http://localhost:4873/ - verdaccio/4.12.0
```
Now we have a local npm registry available at `http://localhost:4873/` so we need to tell npm to use that registry, for example, by modifying `.npmrc`:
```text
registry=http://localhost:4873
```
Additionally, we need to create a user in our registry:
```bash
npm adduser --registry http://localhost:4873
```
Finally, we can pack the package and publish it to the npm registry:
```bash
npm pack
npm publish
```
It should now be visible in our private registry at `http://localhost:4873/`:

At this point, we have a working Stencil web component library that can be deployed to any npm registry. The next step is to integrate Storybook into our repository.
## Storybook
> Storybook is an open source tool for developing UI components in isolation for React, Vue, Angular, and more
A typical use case for [Storybook](https://storybook.js.org/){rel=""nofollow""} is to have a visual representation of a web component library. This allows
any developer or designer to see which components are currently available and how they look and behave.
### Init Storybook
As Stencil components are compiled to web components we can use the [Storybook for HTML](https://storybook.js.org/docs/guides/guide-html/){rel=""nofollow""} project type:
```bash
npx -p @storybook/cli sb init -t html
```
### Run & build Storybook
If we now run `npm run storybook` it opens a browser window at `http://localhost:6006` which shows some automatically generated components & stories:

Now let's write a story for our `` demo web component:
```js
export default {
title: 'Demo/MyComponent',
argTypes: {
label: { type: 'text', description: 'The text which is shown as label' },
name: {
type: 'text',
description: 'Is needed to reference the form data after the form is submitted',
},
disabled: {
type: 'boolean',
description: 'If true, the button is displayed as disabled',
defaultValue: { summary: false },
},
},
}
const defaultArgs = {
disabled: false,
}
const Template = (args) => {
return
}
export const MyComponent = Template.bind({})
Default.MyComponent = { ...defaultArgs }
```
In our story, we defined [Controls](https://storybook.js.org/docs/react/essentials/controls#gatsby-focus-wrapper){rel=""nofollow""} to be able to manipulate
our component properties inside Storybook. We also added some default values and descriptions.
But unfortunately, we cannot see our component inside Storybook and need to do some further adjustments to the project setup.
First, we need to load and register our web components in `.storybook/preview.js` to include them in webpack's dependency graph. This JavaScript code is added to the preview canvas of every Storybook story and is therefore available for the webpack build:
```js {1,3}
import { defineCustomElements } from '../dist/esm/loader'
defineCustomElements()
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
}
```
Now we need to add [@storybook/react](https://www.npmjs.com/package/@storybook/react){rel=""nofollow""} to be able to use our component in the story:
```bash
npm add -D @storybook/react
```
Next step is to modify our `my-component.stories.js`:
```js {1-2,6}
import React from 'react'
import MyComponent from '../../../dist/collection/components/my-component/my-component'
export default {
title: 'Demo/MyComponent',
component: MyComponent,
argTypes: {
label: { type: 'text', description: 'The text which is shown as label' },
name: {
type: 'text',
description: 'Is needed to reference the form data after the form is submitted',
},
disabled: {
type: 'boolean',
description: 'If true, the button is displayed as disabled',
defaultValue: { summary: false },
},
},
}
const defaultArgs = {
disabled: false,
}
const Template = (args) => {
return
}
export const Default = Template.bind({})
Default.args = { ...defaultArgs }
```
Finally, we need to add two new npm scripts:
```json
"scripts": {
"build-stencil:watch": "stencil build --docs-readme --watch --serve",
"start-storybook": "start-storybook -p 6006 -s dist"
},
```
By running Stencil's build process with the `--watch` flag it generates the correct output with the `esm/loader.mjs` file we reference in the `preview.js` file. We then just need to tell Storybook to use the `dist` folder generated by the Stencil build command and disable its caching mechanism.
If we now run `build-stencil:watch` and then `start-storybook` in a separate terminal we can see our component in Storybook:

You can now also modify your Stencil web component and due to the hot reload you can see immediately your changes in Storybook.
You might also wonder how we can set options via property? It is possible by using `setTimeout` inside the Template function in `my-component.stories.js` to ensure that the component has been loaded:
```js
const Template = (args) => {
args.id = args.id ? args.id : 'my-component'
setTimeout(() => {
document.getElementById(args.id).options = [
{
value: 'Item 1',
displayText: 'Item 1',
},
{
value: 'Item 2',
displayText: 'Item 2',
},
{
value: 'Item 3',
displayText: 'Item 3',
},
]
})
return
}
```
### Deploy Storybook
Finally, we want to deploy Storybook to a public URL and therefore we use [storybook-deployer](https://github.com/storybookjs/storybook-deployer){rel=""nofollow""} which provides a nice way to deploy it to GitHub Pages or AWS S3. We will deploy it to AWS S3 by installing the tool
```bash
npm i @storybook/storybook-deployer --save-dev
```
and adding some new scripts to `package.json`:
```json
"scripts": {
"build-storybook": "build-storybook -o ./distStorybook",
"predeploy-storybook": "npm run build-storybook",
"deploy-storybook": "storybook-to-aws-s3 --existing-output-dir ./distStorybook --bucket-path ",
},
```
Before we deploy Storybook we trigger a build, this is done by using `build-storybook` as [pre script](https://docs.npmjs.com/cli/v7/using-npm/scripts#pre--post-scripts){rel=""nofollow""}. You also need to ensure that your [AWS S3 has public access allowed](https://havecamerawilltravel.com/photographer/how-allow-public-access-amazon-bucket/){rel=""nofollow""}.
For example, my demo project is hosted at {rel=""nofollow""}.
## Conclusion
It is a bit tricky to combine Stencil and Storybook and it would be nice to have official documentation for this topic.
But I think it is worth the effort, and it can also improve the local component development due to Storybook's features.
The code for the demo project is available at [GitHub](https://github.com/Mokkapps/stencil-storybook-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.
# Self-Host Your Nuxt App With Coolify
In this article, I want to share my experiences of how I self-hosted my Nuxt apps with Coolify on Hetzner servers.
## A brief history of my hosting provider journey
Let me tell you a quick history of my hosting provider journey: it all started with [Netlify](https://www.netlify.com/){rel=""nofollow""} back in 2018 when I looked for an easy way to host this portfolio website. You don’t get disappointed by providers like [Netlify](https://www.netlify.com/){rel=""nofollow""} or [Vercel](https://vercel.com/){rel=""nofollow""}: your app gets deployed with only a few clicks, and it’s completely free. An amazing user experience, and I used it to host all my other apps like [weekly-vue.news](https://weekly-vue.news){rel=""nofollow""} and [CodeSnap.dev](https://codesnap.dev){rel=""nofollow""}.
Things get tricky when your apps become more traffic as you only get a limited amount of bandwidth, build minutes, serverless function calls, etc., so I had to switch to the Pro team plan, which is at $19/month. I had to pay $25/month for edge function calls as my function calls exceeded the free limit. A quick win was to migrate some of my Nuxt server routes out to AWS Lambda functions. But this way, my code was split between the Nuxt app and the AWS Lambda functions which made the codebase harder to maintain.

To solve those problems, I moved my apps to [Render](https://render.com){rel=""nofollow""}. There, you pay a server for each of your web apps but you don’t have any function call limitations. You get a server with 0.5 CPU and 512MB RAM for $7/month. Soon, I had to switch to the Team plan for $19/month as I exceeded the free bandwidth of 100GB. The next problem was a traffic spike at one of my apps, which killed the server because it exceeded its server memory limit and wasn’t accessible anymore during that time. Upgrading to the next higher server would cost $25/month for 1 CPU and 2GB RAM...
I was shocked and decided to move my apps to [Coolify](https://coolify.io){rel=""nofollow""}, which I already used to host my analytics database and some monitoring tools like Grafana. I knew that the cheapest server rented from [Hetzner](https://hetzner.com){rel=""nofollow""} at \~$4/month would provide me with 2 CPUs and 4GB RAM. This solution is the most cost-effective one for me, and I can scale my servers as needed. Additionally, I don't have to worry about any limitations like bandwidth, build minutes, or function calls and any [serverless horror stories](https://serverlesshorrors.com/){rel=""nofollow""}.
So, let's do a short cost comparison of hosting my three Nuxt apps with the following providers:
- Netlify: $44/month (could potentially grow if I exceed more limits)
- Render: $40/month
- Coolify & Hetzner: $23/month
## What is Coolify?
> [Coolify](https://coolify.io){rel=""nofollow""} is an all-in one PaaS that helps you to self-host your own applications, databases or services (like Wordpress, Plausible Analytics, Ghost) without managing your servers, also known as an open-source & self-hostable Heroku / Netlify / Vercel alternative.
Some of its key features are:
- You can deploy your resources to any server, including your own servers.
- Compatible with a wide range of programming languages and frameworks.
- Deploy your resources to a single server, multiple servers, or Docker Swarm clusters.
- Git integration with both hosted and self-hosted platforms like GitHub, GitLab, Bitbucket, Gitea, and others.
- Pull Request Deployments
- Free SSL certificates
- and more...
## Setup Coolify
I used this amazing video by [Syntax](https://syntax.fm/){rel=""nofollow""} to set up Coolify with my Hetzner servers:
:you-tube-embed{url="https://www.youtube.com/embed/taJlPG82Ucw?si=yeN1CTyaqNqqFup0"}
By the way, I decided to use [Coolify Cloud](https://coolify.io/cloud){rel=""nofollow""} to get a fully managed Coolify instance, which I use to connect my Hetzner servers. It provides these advantages:
- Highly available
- Less maintenance
- Free email notifications
- Priority support via email or chat
## Deploy Your Nuxt App
Let’s assume you have a server that runs Coolify and an additional server that should host your Nuxt app(s).
To get started, you need to connect your Git repository to Coolify. Check [the official documentation](https://coolify.io/docs/knowledge-base/git/github/integration){rel=""nofollow""} for more details.
If you create a new application in Coolify you need to select [Nixpacks](https://nixpacks.com/){rel=""nofollow""} with the port of your Nuxt app (default is 3000):

Next, you need to change the `Start Command` to `node .output/server/index.mjs`:

Alternatively, you can change the `start` script inside `package.json` to `node .output/server/index.mjs`. Nixpacks will automatically use it as the start command.
::note{title="Static Site"}
If your Nuxt app is built as a static site, you need to check `Is it a static site?` and set `Publish Directory` to `/.output/public`
::
::note{title="pnpm"}
If you are using `pnpm 9+` as your package manager, you might get `ERR_PNPM_NO_LOCKFILE Cannot install with "frozen-lockfile" because pnpm-lock.yaml is absent` as build error.
Check [this GitHub issue](https://github.com/railwayapp/nixpacks/issues/1091){rel=""nofollow""} for more details.
To solve the problem you need to add `nixpacks.toml` to your repository with the following content:
```toml [nixpacks.toml]
providers = ["node"]
[phases.install]
cmds = ["npm install -g corepack", "corepack enable", "corepack prepare pnpm@9.1.4 --activate", "pnpm install"]
```
Additionally, you need to modify your `package.json`:
```json [package.json]
{
...
"packageManager": "pnpm@9.1.4",
"engines": {
"node": "20.12.2",
"pnpm": "9.1.4"
},
...
}
```
Of course, you need to adjust the versions to your needs.
::
And that’s it! You should be able to deploy your Nuxt app with Coolify on your servers.
## Conclusion
So far, I am very happy with my decision to move my apps to Coolify and Hetzner servers. I can scale my servers as needed and don’t have to worry about any limitations. I can host my apps for a fraction of the costs compared to other providers.
Of course, there are some downsides like more maintenance and less automation compared to providers like Netlify or Render. But I think the cost savings are worth it.
I hope this article helps you to decide on how to host your Nuxt apps. If you have any questions or feedback, feel free to reach out to me.
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""}:
# Sending Message To Specific Anonymous User On Spring WebSocket
In my current, I had the opportunity to develop a new application based on [Vue.js](https://vuejs.org/){rel=""nofollow""} in the frontend and [Spring Boot](https://spring.io/projects/spring-boot){rel=""nofollow""} in the backend. The backend should send updates to the frontend via a WebSocket connection so that users do not need to refresh the website to see the latest information.
The view should show a list of transfers where the user can modify the results by using filters and pagination. As a requirement, each user should receive specific results based on his filters and pagination without broadcasting this to all other connected users. The user does not need to authenticate itself at the backend.
Most tutorials cover either the case of broadcasting messages to all connected users or to send messages to certain authenticated users. In this article, I will demonstrate how to send messages to anonymous users without broadcasting the messages.
## The Demo Project
I have a created a [demo project](https://github.com/Mokkapps/spring-boot-websocket-anonymous-messages-demo){rel=""nofollow""} to be able to demonstrate the functionality. It is a simple monorepo which contains a backend and a frontend folder.
### Backend
The Spring Boot backend was bootstrapped using [Spring Initializr](https://start.spring.io/){rel=""nofollow""} where I chose `WebSocket` as the only dependency:

#### Configure Websocket
The next step is to configure the application to use a WebSocket connection. To configure the Spring Boot application I followed [this tutorial](https://spring.io/guides/gs/messaging-stomp-websocket/){rel=""nofollow""} without the frontend part.
After this tutorial we have a working WebSocket controller that receives and sends messages via a WebSocket connection:
```java
@Slf4j
@Controller
public class GreetingController {
@MessageMapping("/hello")
@SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
log.info("Received greeting message {}", message);
greetingService.addUserName(principal.getName());
Thread.sleep(1000); // simulated delay
return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
}
}
```
The `greeting()` method is called if a message is sent to the `/hello` destination. This is ensured by using the `@MessageMapping` annotation. The received message is then sent to `/ topic/greetings`. I have added a simulated delay to simulate any asynchronous operation that could be executed on the server-side in between receiving and sending messages.
In this implementation, all messages are broadcasted to all connected users by using the `@SendTo` annotation.
`Greeting.java` and `HelloMessage.java` are simple Java classes which represent the transferred data objects:
```java
public class Greeting {
private String content;
public Greeting() {
}
public Greeting(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
```
```java
public class HelloMessage {
private String name;
public HelloMessage() {
}
public HelloMessage(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
```
The WebSocket is configured in `WebSocketConfig.java`:
```java
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").setAllowedOrigins("*");
registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
}
}
```
In my project, we send updates to the clients via a scheduled interval so I also added this functionality to this demo
project. The first step is to enable scheduling in the Spring Boot application using the `@EnableScheduling` annotation:
```java
@SpringBootApplication
@EnableScheduling // this annotation enables scheduling
public class WebsocketAnonymousMessagesDemoApplication {
public static void main(String[] args) {
SpringApplication.run(WebsocketAnonymousMessagesDemoApplication.class, args);
}
}
```
Next, a `Scheduler.java` class handles the scheduled tasks and triggers `GreetingService` to send a new message each second:
```java
@Slf4j
@Component
public class Scheduler {
private final GreetingService greetingService;
Scheduler(GreetingService greetingService) {
this.greetingService = greetingService;
}
@Scheduled(fixedRateString = "6000", initialDelayString = "0")
public void schedulingTask() {
log.info("Send messages due to schedule");
greetingService.sendMessages();
}
}
```
`GreetingsService.java` injects `SimpMessagingTemplate` which provides methods to programmatically send WebSocket messages:
```java
@Data
@Slf4j
@Service
public class GreetingService {
private final SimpMessagingTemplate simpMessagingTemplate;
private static final String WS_MESSAGE_TRANSFER_DESTINATION = "/topic/greetings";
GreetingService(SimpMessagingTemplate simpMessagingTemplate) {
this.simpMessagingTemplate = simpMessagingTemplate;
}
public void sendMessages() {
simpMessagingTemplate.convertAndSend(WS_MESSAGE_TRANSFER_DESTINATION,
"Hallo " + " at " + new Date().toString());
}
}
```
`convertAndSend` is equivalent to the `@SendTo` annotation which was used in the controller to broadcast messages.
### Frontend
The frontend was bootstrapped using the [Vue CLI](https://cli.vuejs.org){rel=""nofollow""}:
```bash
# Install Vue CLI globally
npm install -g @vue/cli
# Create a new Vue project called "frontend"
vue create frontend
```
Next step is to add [STOMP.js](https://github.com/stomp-js/stompjs){rel=""nofollow""} as npm library which allows us to connect to our backend STOMP broker over WebSocket:
```bash
npm add @stomp/stompjs
```
I have created a `websocket-service.ts` as Singleton which handles the interaction with this library:
```ts
import { Client, messageCallbackType } from '@stomp/stompjs'
export class WebsocketService {
private readonly webSocketUrl =
process.env.NODE_ENV === 'development' ? 'ws://localhost:8080/ws' : `wss://${window.location.hostname}/ws`
private client: Client
private onConnectCb?: Function
private onDisconnectCb?: Function
private onErrorCb?: Function
private _isConnected = false
private static instance: WebsocketService
private constructor() {
console.log(`${process.env.NODE_ENV === 'development' ? 'DEV' : 'PROD'} mode`)
this.client = new Client({
brokerURL: this.webSocketUrl,
debug: function (str: string) {
console.log('WS debug: ', str)
},
reconnectDelay: 5000,
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000,
})
this.client.onConnect = () => {
this._isConnected = true
this.onConnectCb && this.onConnectCb()
}
this.client.onDisconnect = () => {
this._isConnected = false
this.onDisconnectCb && this.onDisconnectCb()
}
this.client.onStompError = (frame: any) => {
console.error('WS: Broker reported error: ' + frame.headers['message'])
console.error('WS: Additional details: ' + frame.body)
this.onErrorCb && this.onErrorCb()
}
}
static getInstance(): WebsocketService {
if (!WebsocketService.instance) {
return new WebsocketService()
}
return WebsocketService.instance
}
get isConnected(): boolean {
return this._isConnected
}
connect(onConnectCb: Function, onDisconnectCb: Function, onErrorCb: Function): void {
this.onConnectCb = onConnectCb
this.onDisconnectCb = onDisconnectCb
this.onErrorCb = onErrorCb
this.client.activate()
}
disconnect(): void {
this.client.deactivate()
}
subscribe(destination: string, cb: messageCallbackType): void {
this.client.subscribe(destination, cb)
}
sendMessage(destination: string, body: string): void {
this.client.publish({ destination, body })
}
}
```
In the constructor, the client configuration is done. If we run the backend locally the WebSocket connection is available at `localhost:8080/ws` that's why `ws://localhost:8080/ws` is used as broker URL in Vue development mode.
The service provides this public API:
```ts
interface IWebSocketService {
connect(onConnectCb: Function, onDisconnectCb: Function, onErrorCb: Function): void
disconnect(): void
subscribe(destination: string, cb: messageCallbackType): void
sendMessage(destination: string, body: string): void
}
```
Inside the `mounted()` method in `App.vue` the service is instantiated:
```vue
```
Received messages are rendered in the template:
```vue
Received WS messages
{{ message }}
```
At this point we have a running application that can send & broadcast messages via a WebSocket connection:

## Prevent Message Broadcasting
As you can see in the video above, each connected user receives the same broadcasted message as we cannot identify certain users. In this chapter, I want to demonstrate how to prevent broadcasting messages to all users without a need for authentication.
The idea is to use UUIDs for each connected client and instead of broadcasting to all users messages are only sent to specific UUIDs.
These steps need to be performed:
1. Generate a Spring Security `Principal` name by UUID for each newly connected client by using `DefaultHandshakeHandler`
2. Store the UUID if a new message is received
3. Use `@SendToUser` instead of `@SendTo` annotation in the WebSocket controller
4. Change endpoint in frontend to have the `user`, so `/user/topic/greetings` instead of `/topic/greetings`;
Let's start by creating a `CustomHandshakeHandler.java`
```java
/**
* Set anonymous user (Principal) in WebSocket messages by using UUID
* This is necessary to avoid broadcasting messages but sending them to specific user sessions
*/
public class CustomHandshakeHandler extends DefaultHandshakeHandler {
@Override
protected Principal determineUser(ServerHttpRequest request,
WebSocketHandler wsHandler,
Map attributes) {
// generate user name by UUID
return new StompPrincipal(UUID.randomUUID().toString());
}
}
```
which needs to be registered in `WebSocketConfig.java`:
```java
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/ws")
.setAllowedOrigins("*")
// use our new handler
.setHandshakeHandler(new CustomHandshakeHandler());
registry
.addEndpoint("/ws")
.setAllowedOrigins("*")
// use our new handler
.setHandshakeHandler(new CustomHandshakeHandler())
.withSockJS();
}
}
```
Now `GreetingController.java` has to be adopted:
```java
@Slf4j
@Controller
public class GreetingController {
private final GreetingService greetingService;
GreetingController(GreetingService greetingService) {
this.greetingService = greetingService;
}
@MessageMapping("/hello")
@SendToUser("/topic/greetings") // use @SendToUser instead of @SendTo
public Greeting greeting(HelloMessage message, Principal principal) throws Exception {
log.info("Received greeting message {} from {}", message, principal.getName());
greetingService.addUserName(principal.getName()); // store UUID
Thread.sleep(1000); // simulated delay
return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
}
}
```
`GreetingService` needs to be adjusted to be able to store the UUIDs in a list and we change `convertAndSend` to `convertAndSendToUser` where we iterate over all user names and send message to them:
```java
@Data
@Slf4j
@Service
public class GreetingService {
private final SimpMessagingTemplate simpMessagingTemplate;
private static final String WS_MESSAGE_TRANSFER_DESTINATION = "/topic/greetings";
private List userNames = new ArrayList<>();
GreetingService(SimpMessagingTemplate simpMessagingTemplate) {
this.simpMessagingTemplate = simpMessagingTemplate;
}
public void sendMessages() {
for (String userName : userNames) {
simpMessagingTemplate.convertAndSendToUser(userName, WS_MESSAGE_TRANSFER_DESTINATION,
"Hallo " + userName + " at " + new Date().toString());
}
}
public void addUserName(String username) {
userNames.add(username);
}
}
```
Finally, the topic endpoint in `App.vue` in the frontend code needs to be changed:
```ts
private readonly webSocketGreetingsSubscribeEndpoint = '/user/topic/greetings';
```
Let's see this in action:

## Conclusion
Sending WebSocket messages to specific anonymous users is not hard using Spring. You can also extend this mechanism by adding
another destination for broadcasted messages. This way, you can send certain messages to specific users and also broadcast messages to every connected user.
# Simpler Two-Way Binding in Vue With defineModel
`v-model` is a powerful feature in Vue that allows you to create two-way data bindings on your components. However, defining the props and emits in every component can be a bit verbose.
In this article, I'll show you how to simplify two-way binding in Vue with the `defineModel` compiler-macro, which is now the recommended way to define `v-model` bindings in Vue 3.4 and later.
::note
`defineModel()` is a new feature in Vue 3.4. Make sure you are using Vue 3.4 or later to use this feature.
::
## The "Problem"
When you create a component that uses `v-model`, you need to define a prop and an emit for the value. For example, if you have a component that uses `v-model` to bind to a `value` prop, you would need to define the following:
```vue [Child.vue] {2-3}
```
## The Solution
`defineModel` is a new `
```
I love this simple and clean syntax. It makes the code much easier to read and write.
`defineModel()` returns a `ref`, which is automatically bound to the `modelValue` prop and emits the `update:modelValue` event when the value changes. The `.value` is synced with the value bound by the parent `v-model`. When the `ref` is updated, the value bound by the parent is automatically updated.
This allows us to use `v-model` directly on the native input element without additional code.
## Options
`defineModel` also accepts an optional options object to configure the behavior of the model:
```vue [Child.vue]
```
## Multiple `v-model` bindings
If you have multiple `v-model` bindings in your component, you can use `defineModels` to define multiple models at once:
```vue [Parent.vue]
```
```vue [Child.vue]
```
If prop options are also needed, you can pass them after the model name:
```vue [Child.vue]
```
## Modifiers
`defineModel` also supports modifiers. You can use modifiers to customize the behavior of the model. Let's take a look at a simple modifier that modifies every character of the model value and makes it uppercase:
```vue [Parent.vue]
```
```vue [Child.vue]
```
## Typing
You can define the type of the model value inside the options object:
```vue [Child.vue]
```
If you are using TypeScript, you can also define the type of the model value and modifiers in the following way:
```vue [Child.vue]
```
## StackBlitz
Try it yourself in the following StackBlitz project:
:stackblitz{project-id="simpler-two-way-binding-in-vue-with-define-model"}
## Conclusion
I love the new `defineModel` compiler macro. It makes two-way binding in Vue much simpler and cleaner. I hope you find this feature as helpful as I do.
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""}:
# Sticky Footer in GatsbyJS using Flexbox
I recently developed some static websites based on [GatsbyJS](https://gatsbyjs.org){rel=""nofollow""} with a sticky footer. A sticky footer is always positioned on the bottom of the page, even for sparse content.
Unfortunately, I had some struggles solving this, and I want to share my learnings with you.
## Non-GatsbyJS solution
In a traditional HTML + CSS + JavaScript application, we can use [different ways](https://css-tricks.com/couple-takes-sticky-footer/){rel=""nofollow""} to implement such a fixed footer, but I prefer the [Flexbox solution of Philip Walton](https://philipwalton.github.io/solved-by-flexbox/demos/sticky-footer/){rel=""nofollow""}.
Flexbox provides a friendly solution for the sticky footer problem. It can be used to layout content in a horizontal and vertical direction. So we need to wrap the vertical sections (header, content, footer) in a flex container and choose which one should expand. In our case, we want the content to take up all the available space in the container automatically.
Following, you can see his solution:
```html
……
```
The corresponding CSS classes:
```css
.site {
display: flex;
min-height: 100vh;
flex-direction: column;
}
.site-content {
flex: 1;
}
```
Take a look at the [live demo](https://philipwalton.github.io/solved-by-flexbox/demos/sticky-footer/){rel=""nofollow""}.
## GatsbyJS solution
GatsbyJS is based on React; therefore, we have to think differently.
The basic `layout.js` file from the [official GatsbyJS default starter](https://github.com/gatsbyjs/gatsby-starter-default){rel=""nofollow""} has a similar structure like the following example:
```js
const Layout = ({ children }) => (
(
<>
{children}
>
)}
/>
);
export default Layout;
```
So if we would style `` and the `
{children}
` as proposed in [Philip Walton's solution](https://philipwalton.github.io/solved-by-flexbox/demos/sticky-footer/){rel=""nofollow""} it would not work.
But why? Because it would mean that the `` component has to be a direct child of the `` tag, which it isn't due to GatsbyJS's and React's way of building the HTML document.
To solve the problem, I added a new `` tag, which should represent the `` tag of the example mentioned above.
So my `layout.js` looks this way:
```js
const Layout = ({ children }) => (
(
<>
{children}
>
)}
/>
);
export default Layout;
```
The CSS:
```css
.site {
display: flex;
min-height: 100vh;
flex-direction: column;
}
.site-content {
flex-grow: 1;
}
```
You can see a working example on my [GitHub Traffic Viewer website](https://github-traffic-viewer.netlify.app/){rel=""nofollow""}. The first page shows spare content, but the footer is stuck to the bottom. If you sign in and see the result list, the footer is also shown at the bottom of the page.

I hope this post is helpful if you try implementing a sticky footer on a GatsbyJS website.
Happy Coding!
# The 10 Favorite Features of My Developer Portfolio Website
Inspired by [Braydon Coyer's new blogfolio](https://braydoncoyer.dev/blog/introducing-my-new-blogfolio){rel=""nofollow""}, I've added some excellent new features to my portfolio website.
In this article, I want to demonstrate the ten favorite features of my blogfolio.
## 1. Stats page
Inspired by [SLD](https://sld.codes/){rel=""nofollow""} and [Braydon Coyer](http://braydoncoyer.dev/stats){rel=""nofollow""}, I've added a [Stats page on my site](https://mokkapps.de/stats).
It shows statistics about the site itself, for example, how many active visitors are currently on the site and how many have visited it in total.
Additionally, it shows some information about my social media channels like the follower count of [GitHub](https://github.com/mokkapps){rel=""nofollow""}, [Twitter](https://twitter.com/mokkapps){rel=""nofollow""}, [Dev.to](https://dev.to/mokkapps){rel=""nofollow""}, and more.
I use [AWS Amplify Serverless Functions](https://mokkapps.de/categories/aws) to access a variety of APIs to provide the necessary data for this site.

## 2. Article Reactions
Built with [Supabase](https://supabase.com/){rel=""nofollow""} and AWS Amplify Serverless Functions, readers of my articles can now react to the article with the clap emoji.
Additionally, I use the same database table to store the number of page views.

## 3. Automated Open Graph Images
I use [Braydon's approach](https://braydoncoyer.dev/blog/how-to-dynamically-create-open-graph-images-with-cloudinary-and-next.js){rel=""nofollow""} to automatically generate [Open Graph](https://ogp.me/){rel=""nofollow""} images for certain pages using the [Cloudinary API](https://cloudinary.com/documentation/cloudinary_references){rel=""nofollow""}.
The code grabs the site's title and generates an Open Graph image using Cloudinary API.
The following image shows such an automatically generated image that I use on my website:

## 4. Mark Article as Read
Visitors of my website can see at a glimpse which articles they've already read. It's a nice little feature for recurring readers of my blog.

## 5. Intelligent Article Suggestions
If a reader of a blog article reaches the end of the article, he will see four similar articles. They are selected by checking how many categories match between the articles.

## 6. Article Search Options
I provide multiple ways to search for blog articles:
1. All articles are available on the [blog page](https://mokkapps.de/blog), and you can scroll or use the browser search to find an article.
2. Use the [minimal list](https://mokkapps.de/minimal-blog-list), which shows all blog posts grouped in years by title.
3. Use [Google](https://www.google.com/search?q=site%3Amokkapps.de%2Fblog){rel=""nofollow""}.

## 7. Prism Code Highlighting
I invested some time to create beautiful code snippets on my blog posts, as they are an essential part of my articles.
I use [Prism](https://prismjs.com/){rel=""nofollow""} with the [Gatsby Prism Remark plugin](https://www.gatsbyjs.com/plugins/gatsby-remark-prismjs/){rel=""nofollow""} to show code blocks in my markdown files:
```js {2-4}
export const getCategoryDisplayText = (category) => {
if (category === 'aws') {
return category.toUpperCase()
}
if (category.includes('-js')) {
const name = category.split('-')[0]
return `${capitalize(name)}.js`
}
return capitalize(category)
}
```
I can highlight certain lines of code, and I show the programming language as a nice badge on the top right.
## 8. MDX
[MDX](https://mdxjs.com/){rel=""nofollow""} is very powerful, and I use it for my tips page, where I inject the following React component into my Markdown files to create a beautiful comparison of two code blocks:
::code-card{type="bad"}
```html
```
::
::code-card{type="good"}
```html
```
::
## 9. Generate Scripts
Inspired by [Kent C. Dodds](https://github.com/kentcdodds){rel=""nofollow""}, I use [multiple JS scripts](https://github.com/Mokkapps/website/tree/master/scripts/generate){rel=""nofollow""} to generate boilerplate files for new blog posts and tips.
For example, the `blogpost.js` script will generate a similar output in the console:
```bash
? Title this is a test to see if my script is awesome
? Categories development, career, tools
? Release Date (format: yyyy-mm-dd) 2022-01-08
? Dry run without creating files? (default: false) Yes
Date:
2022-01-08
Slug:
this-is-a-test-to-see-if-my-script-is-awesome
Markdown data:
---
title: "This Is a Test to See if My Script Is Awesome"
categories:
- "development"
- "career"
- "tools"
cover: "images/cover.jpg"
---
```
The script asks for some mandatory information, converts the entered title to title caps, and finally generates the markdown file with the slug name at the correct directory.
Additionally, I have a script to generate a Table of Content (ToC) for a finished article and an image optimization script.
## 10. Open Source Analytics
I use [Umami](https://github.com/mikecao/umami){rel=""nofollow""} with a database hosted on [Digital Ocean](https://www.digitalocean.com/){rel=""nofollow""}. I send custom events if a visitor, for example, clicks a social link, subscribes to the newsletter or,
edits an article on GitHub. These events provide some valuable insights into how many visitors are using the features on my portfolio website.

## Conclusion
My portfolio website is my favorite digital playground. I love to experiment with different new features and try to provide the best possible
experience for visitors.
The source code of my website is [available on GitHub](https://github.com/Mokkapps/website){rel=""nofollow""}, so feel free to take a closer look if you are interested in
implementation details. Leave a comment if you want more information about a specific topic.
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""}.
# The Engineering Behind My Portfolio Website
I created my first personal website in 2017 when I launched [my first smartphone game](https://www.mokkapps.de/blog/lessons-learned-my-first-smartphone-game/){rel=""nofollow""}. I use Google Analytics in this game, and it is, therefore [necessary](https://privacypolicies.com/blog/privacy-policy-google-analytics/){rel=""nofollow""} to provide a link to a privacy policy website from inside the game. I used [WordPress](https://wordpress.org/){rel=""nofollow""} and a free theme as I had nearly no frontend knowledge at that time:
[See the first version in the web archive](https://web.archive.org/web/20170701233843/http://mokkapps.de:80/){rel=""nofollow""}
End of 2017 I then released a [new version](https://dev.to/mokkapps/how-i-built-my-website-with-hugo-and-netlify-3n49){rel=""nofollow""} based on the open-source static site generator [Hugo](https://gohugo.io/){rel=""nofollow""} using the [HTML5 Up Prologue theme](https://html5up.net/prologue){rel=""nofollow""}. The idea was to have a unique design and better control over the layout:
[See the second version in the web archive](https://web.archive.org/web/20180322183119/http://mokkapps.de/){rel=""nofollow""}
As I specialized more as a frontend developer I wanted to create my own portfolio website with its own styling. The inspiration came from Ali Spittel's blog post ["Building a Kickass Portfolio"](https://dev.to/aspittel/building-a-kickass-portfolio-28ph){rel=""nofollow""}.
Some of the main reasons which encouraged me to do the refactoring:
- Show my creativity and make a website that is a true expression of myself
- Design as much as possible myself without using pre-designed templates
- Make the site as fast and accessible as possible
- Provide a foundation to make the website easily extendable and adjustable
- Make the website fully responsive
The result can be seen at [www.mokkapps.de](https://www.mokkapps.de){rel=""nofollow""}:

The basic implementation took about 40 hours of work. I did not draft my sites before moving to code but just started coding and experimented with different designs.
## Website Generator
I decided to use [Gatsby.js](https://www.gatsbyjs.org/){rel=""nofollow""} due to several reasons:
- I love using [React](https://reactjs.org/){rel=""nofollow""} and its rich ecosystem of libraries, components, etc.
- It uses [GraphQL](https://graphql.org/){rel=""nofollow""}, which I wanted to gain some more practical experience with.
- Very good [documentation](https://www.gatsbyjs.org/docs/){rel=""nofollow""}, [plugins](https://www.gatsbyjs.org/plugins/){rel=""nofollow""} & [starters](https://www.gatsbyjs.org/starters/){rel=""nofollow""}.
- Can be easily combined with several APIs, CMS, and more.
The following graphic from the official website showcases how Gatsby works:

As a starter, I used the fantastic [Gatsby Starter Kit](https://greglobinski.github.io/gatsby-starter-kit-docs/){rel=""nofollow""}, which provided an ideal bare-bone application for my website.
## Hosting
I use [Netlify](https://www.netlify.com/){rel=""nofollow""} to host my website, an all-in-one platform for automating modern web projects.
It can be used for free if you have a public GitHub project. I decided to [provide my website code open-source on GitHub](https://github.com/mokkapps/website){rel=""nofollow""} as I wanted to demonstrate my skills to everybody interested in it.
## Styled Components
I like the idea of [Styled Components](https://www.styled-components.com/){rel=""nofollow""} and how it nicely integrates into a React component.
Styled Components utilizes tagged template literals to style your components. It removes the mapping between components and styles. This means that when you're defining your styles, you're actually creating a normal React component that has your styles attached to it.
Take a look at the following component of my website:
```javascript
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
const StyledArticle = styled.article`
max-width: 600px;
margin: 0 auto 30px;
background: white;
border-radius: 10px;
padding: 2rem;
min-width: ${(props) => (props.narrow ? '50%' : '100%')};
`
const Article = ({ children }) => {children}
Article.propTypes = {
children: PropTypes.node.isRequired,
}
export default Article
```
In this example I use the `` HTML tag but use it as `StyledArticle` which attaches my CSS styles. It is even possible to apply styles based on props which are passed to the component as you can see in this line:
```javascript
min-width: ${props => (props.narrow ? '50%' : '100%')};
```
## Responsive Images
Delivering images in the optimal size for the correct devices is crucial for a good website. It ensures that your site loads quickly and does not slow down when you use many pictures on the website.
[The "gatsby-image" plugin](https://www.gatsbyjs.org/packages/gatsby-image/#gatsby-image){rel=""nofollow""} is a fantastic solution for this requirement. It automatically resizes your images so your site won't load huge images on a mobile device. Additionally, it lazy loads the images and provides a nice blur effect while the images are loaded:

## Typography
I wanted a typography design and used [Typography.js](https://kyleamathews.github.io/typography.js/){rel=""nofollow""}, which is also recommended by the Gatsby documentation.
My configuration file looks this way:
```javascript
import Typography from 'typography'
import CodePlugin from 'typography-plugin-code'
import theme from 'typography-theme-alton'
theme.overrideThemeStyles = ({ rhythm }, options) => ({
a: {
color: '#FC1A20',
textDecoration: 'none',
},
'a:hover': {
color: '#FC1A20',
textDecoration: 'underline',
},
html: {
boxSizing: 'border-box',
background: '#424242',
},
})
theme.plugins = [new CodePlugin()]
const typography = new Typography(theme)
export default typography
```
In the next image, you can see the difference between my landing page with (upper image) and without (lower image) Typography.js:

## Blog
I enjoy writing articles in [Markdown](https://en.wikipedia.org/wiki/Markdown){rel=""nofollow""} and wanted to use Markdown files as a source for my blog.
The Gatsby Starter Kit already includes some excellent features for this requirement:
- Posts pages are automatically created from markdown files
- Categories are automatically created for blog posts
- Web pages are automatically created from markdown pages files
The relevant folder structure in the code looks this way:
```text
root
└── src
├── content
│ ├── posts
│ │ ├── 2018-05-11___my-first-vs-code-extension
│ │ | ├── jasmine-test-selector.png
│ │ │ └── index.md
│ │ ├── 2018-05-12___my-first-npm-package
│ │ | ├── github-traffic-cli.png
│ │ │ └── index.md
| |
| | ...
```
Using my Gatsby configuration, it automatically creates a blog post page for each of the folders, e.g
`https://www.mokkapps.de/blog/my-first-vs-code-extension/`
## Continuous Integration
I use [Travis CI](https://travis-ci.org/){rel=""nofollow""} to deploy & test my website each time I push to my git master branch.
Here is an excerpt of my `.travis.yml` file:
```yaml
script:
- npm run lint
- npm run test:e2e:ci
- npm run build
deploy:
provider: script
script: "curl -X POST -d '' https://api.netlify.com/build_hooks/5ba3c8da1f12b70cbbcaa1a3"
skip_cleanup: true
on:
branch: master
```
So on each push to master, it runs the TS linter, E2E test and builds the application. If all scripts succeed, a deployment on Netlify is triggered via webhook.
I therefore disabled auto-publishing in Netlify, which usually triggers a deployment each time a git push was detected on the configured branch.
In my case, I want to trigger a deployment if the tests and the build were successful.

Netlify also automatically builds a preview with a unique URL. Previews are perfect for testing and collaboration as a staging environment for every PR or branch. So I can even preview my new build before I manually deploy it.
## E2E Tests
For my E2E tests, I use [Cypress.io](https://www.cypress.io/){rel=""nofollow""} as I heard a lot of good stuff about it.
I created a set of tests that test the most critical function of my application.
For example, the E2E test of my home page:
```javascript
import config from '../../src/content/meta/config'
describe('Home Page Test', () => {
beforeEach(() => {
cy.visit('/')
})
it('includes a heading and a quote', () => {
cy.get('[data-cy=hero-heading]')
cy.get('[data-cy=hero-quote]')
})
it('shows characteristics section', () => {
cy.get('[data-cy=hero-characteristics-section]').children().should('have.length', 4)
cy.get('[data-cy=hero-characteristics-more-button]').click()
cy.url().should('include', '/about')
})
it('shows featured projects', () => {
const countFeaturedProjects = config.projects.filter((p) => p.featured)
cy.get('[data-cy=hero-projects-section]').children().should('have.length', countFeaturedProjects.length)
cy.get('[data-cy=hero-projects-more-button]').click()
cy.url().should('include', '/projects')
})
it('shows latest blog post', () => {
cy.get('[data-cy=blog-post-0]')
cy.get('[data-cy=hero-blog-more-button]').click()
cy.url().should('include', '/blog')
})
})
```
This video shows a Cypress test run on my website:
[](https://youtu.be/HgbFzH5-YrQ "Cypress.io E2E test"){rel=""nofollow""}
## Lighthouse
For me, it was important to have a good [Google Lighthouse score](https://developers.google.com/web/tools/lighthouse/){rel=""nofollow""}, and with Gatsby.js you achieve great results nearly out of the box:

## Sentry
To track errors on my website, I use [Sentry](https://www.sentry.io/){rel=""nofollow""}, an open-source error tracking software. It can be easily integrated into Gatsby using [gatsby-plugin-sentry](https://www.gatsbyjs.org/packages/gatsby-plugin-sentry/?=sentry#gatsby-plugin-sentry){rel=""nofollow""}.
## Conclusion
I am proud of my website, and I enjoyed engineering it. This website represents me on the world wide web, and I am very interested in its design, quality, accessibility, and page views.
I use it for marketing myself in different aspects:
- Show specific skills to employers
- Tell people to read my blog
- Promote my private projects
In my opinion, each web developer should have a custom website. A portfolio website is a true expression of yourself. We are programmers, and it is a creative process, so use and demonstrate your creativity.
## Links
- [www.mokkapps.de](https://www.mokkapps.de){rel=""nofollow""}
- [Website code on GitHub](https://github.com/mokkapps/website){rel=""nofollow""}
- [Presentation Slides](https://mokkapps-website-lightning-talk.netlify.com/){rel=""nofollow""}
# The Last Guide For Angular Change Detection You'll Ever Need
Angular's Change Detection is a core mechanic of the framework but (at least from my experience) it is very hard to understand. Unfortunately, there exists no official guide on the [official website](https://angular.io/){rel=""nofollow""} about this topic.
In this blog post, I will provide you all the necessary information you need to know about change detection. I will explain the mechanics by using a [demo project](https://github.com/Mokkapps/angular-change-detection-demo){rel=""nofollow""} I built for this blog post.
## What Is Change Detection
Two of Angular's main goals are to be predictable and performant. The framework needs to replicate the state of our application on the UI by combining the state and the template:

It is also necessary to update the view if any changes happen to the state. This mechanism of syncing the HTML with our data is called "Change Detection". Each frontend framework uses its implementation, e.g. React uses Virtual DOM, Angular uses change detection and so on. I can recommend the article [Change And Its Detection In JavaScript Frameworks ](https://teropa.info/blog/2015/03/02/change-and-its-detection-in-javascript-frameworks.html){rel=""nofollow""}which gives a good general overview of this topic.
> Change Detection: The process of updating the view (DOM) when the data has changed
As developers, most of the time we do not need to care about change detection until we need to optimize the performance of our application. Change detection can decrease performance in larger applications if it is not handled correctly.
## How Change Detection Works
A change detection cycle can be split into two parts:
- **Developer** updates the application model
- **Angular** syncs the updated model in the view by re-rendering it
Let us take a more detailed look at this process:
1. Developer updates the data model, e.g. by updating a component binding
2. Angular detects the change
3. Change detection checks **every** component in the component tree from top to bottom to see if the corresponding model has changed
4. If there is a new value, it will update the component’s view (DOM)
The following GIF demonstrates this process in a simplified way:

The picture shows an Angular component tree and its change detector (CD) for each component which is created during the application bootstrap process. This detector compares the current value with the previous value of the property. If the value has changed it will set `isChanged` to true. Check out [the implementation in the framework code](https://github.com/angular/angular/blob/885f1af509eb7d9ee049349a2fe5565282fbfefb/packages/core/src/util/comparison.ts#L13){rel=""nofollow""} which is just a `===` comparison with special handling for `NaN`.
> Change Detection does not perform a deep object comparison, it only compares the previous and current value of properties used by the template
### Zone.js
In general, a zone can keep track and intercept any asynchronous tasks.
A zone normally has these phases:
- it starts stable
- it becomes unstable if tasks run in the zone
- it becomes stable again if the tasks completed
Angular patches several low-level browser APIs at startup to be able to detect changes in the application. This is done using [zone.js](https://github.com/angular/angular/tree/master/packages/zone.js){rel=""nofollow""} which patches APIs such as `EventEmitter`, DOM event listeners, `XMLHttpRequest`, `fs` API in Node.js [and more](https://github.com/angular/angular/blob/master/packages/zone.js/STANDARD-APIS.md){rel=""nofollow""}.
In short, the framework will trigger a change detection if one of the following events occurs:
- any browser event (click, keyup, etc.)
- `setInterval()` and `setTimeout()`
- HTTP requests via `XMLHttpRequest`
Angular uses its zone called `NgZone`. There exists only one `NgZone` and change detection is only triggered for async operations triggered in this zone.
## Performance
> By default, Angular Change Detection checks for **all components from top to bottom** if a template value has changed.
Angular is very fast doing change detection for every single component as it can perform thousands of checks during milliseconds using [inline-caching](http://mrale.ph/blog/2012/06/03/explaining-js-vms-in-js-inline-caches.html){rel=""nofollow""} which produces VM-optimized code.
If you want to have a deeper explanation of this topic I would recommend to watch [Victor Savkin’s](https://twitter.com/victorsavkin){rel=""nofollow""} talk on [Change Detection Reinvented](https://www.youtube.com/watch?v=jvKGQSFQf10){rel=""nofollow""}.
Although Angular does a lot of optimizations behind the scenes the performance can still drop on larger applications. In the next chapter, you will learn how to actively improve Angular performance by using a different change detection strategy.
### Change Detection Strategies
Angular provides two strategies to run change detections:
- `Default`
- `OnPush`
Let's look at each of these change detection strategies.
#### Default Change Detection Strategy
By default, Angular uses the `ChangeDetectionStrategy.Default` change detection strategy. This default strategy checks every component in the component tree from top to bottom every time an event triggers change detection (like user event, timer, XHR, promise and so on). This conservative way of checking without making any assumption on the component's dependencies is called **dirty checking**. It can negatively influence your application's performance in large applications which consists of many components.

#### OnPush Change Detection Strategy
We can switch to the `ChangeDetectionStrategy.OnPush` change detection strategy by adding the `changeDetection` property to the component decorator metadata:
```ts
@Component({
selector: 'hero-card',
changeDetection: ChangeDetectionStrategy.OnPush,
template: ...
})
export class HeroCard {
...
}
```
This change detection strategy provides the possibility to skip unnecessary checks for this component and all it's child components.
The next GIF demonstrates skipping parts of the component tree by using the `OnPush` change detection strategy:

Using this strategy, Angular knows that the component only needs to be updated if:
- the input reference has changed
- the component or one of its children triggers an event handler
- change detection is triggered manually
- an observable linked to the template via the async pipe emits a new value
Let's take a closer look at these types of events.
#### Input Reference Changes
In the default change detection strategy, Angular will run the change detector any time `@Input()` data is changed or modified. Using the `OnPush` strategy, the change detector is only triggered if a **new reference** is passed as `@Input()` value.
Primitive types like numbers, string, booleans, null and undefined are passed by value. Object and arrays are also passed by value but modifying object properties or array entries does not create a new reference and therefore does not trigger change detection on an `OnPush` component. To trigger the change detector you need to pass a new object or array reference instead.
You can test this behavior using the [simple demo](https://angular-change-detection-demo.netlify.com/simple-demo){rel=""nofollow""}:
1. Modify the age of the `HeroCardComponent` with `ChangeDetectionStrategy.Default`
2. Verify that the `HeroCardOnPushComponent` with `ChangeDetectionStrategy.OnPush` does not reflect the changed age (visualized by a red border around the components)
3. Click on "Create new object reference" in "Modify Heroes" panel
4. Verify that the `HeroCardOnPushComponent` with `ChangeDetectionStrategy.OnPush` gets checked by change detection

To prevent change detection bugs it can be useful to build the application using `OnPush` change detection everywhere by using only immutable objects and lists. Immutable objects can only be modified by creating a new object reference so we can guarantee that:
- `OnPush` change detection is triggered for each change
- we do not forget to create a new object reference which could cause bugs
[Immutable.js](https://facebook.github.io/immutable-js/){rel=""nofollow""} is a good choice and the library provides persistent immutable data structures for objects (`Map`) and lists (`List`). Installing the library via [npm](https://www.npmjs.com/package/immutable){rel=""nofollow""} provides type definitions so that we can take advantage of type generics, error detection, and auto-complete in our IDE.
#### Event Handler Is Triggered
Change detection (for all components in the component tree) will be triggered if the `OnPush` component or one of its child components triggers an event handler, like clicking on a button.
Be careful, the following actions do not trigger change detection using the `OnPush` change detection strategy:
- `setTimeout`
- `setInterval`
- `Promise.resolve().then()`, (of course, the same for `Promise.reject().then()`)
- `this.http.get('...').subscribe()` (in general, any RxJS observable subscription)
You can test this behavior using the [simple demo](https://angular-change-detection-demo.netlify.com/simple-demo){rel=""nofollow""}:
1. Click on "Change Age" button in `HeroCardOnPushComponent` which uses `ChangeDetectionStrategy.OnPush`
2. Verify that change detection is triggered and checks all components

#### Trigger Change Detection Manually
There exist three methods to manually trigger change detections:
- `detectChanges()` on `ChangeDetectorRef` which runs change detection on this view and its children by keeping the change detection strategy in mind. It can be used in combination with `detach()` to implement local change detection checks.
- `ApplicationRef.tick()` which triggers change detection for the whole application by respecting the change detection strategy of a component
- `markForCheck()` on `ChangeDetectorRef` which does **not** trigger change detection but marks all `OnPush` ancestors as to be checked once, either as part of the current or next change detection cycle. It will run change detection on marked components even though they are using the `OnPush` strategy.
> Running change detection manually is not a hack but you should only use it in reasonable cases
The following illustrations shows the different `ChangeDetectorRef` methods in a visual representation:

You can test some of these actions using the "DC" (`detectChanges()`) and "MFC" (`markForCheck()`) buttons in the [simple demo](https://angular-change-detection-demo.netlify.com/simple-demo){rel=""nofollow""}.
#### Async Pipe
The built-in [AsyncPipe](https://angular.io/api/common/AsyncPipe){rel=""nofollow""} subscribes to an observable and returns the latest value it has emitted.
Internally the `AsyncPipe` calls `markForCheck` each time a new value is emitted, see [its source code](https://github.com/angular/angular/blob/5.2.10/packages/common/src/pipes/async_pipe.ts#L139){rel=""nofollow""}:
```ts
private _updateLatestValue(async: any, value: Object): void {
if (async === this._obj) {
this._latestValue = value;
this._ref.markForCheck();
}
}
```
As shown, the `AsyncPipe` automatically works using `OnPush` change detection strategy. So it is recommended to use it as much as possible to easier perform a later switch from default change detection strategy to `OnPush`.
You can see this behavior in action in the [async demo](https://angular-change-detection-demo.netlify.com/async-pipe-demo){rel=""nofollow""}.

The first component directly binds an observable via `AsyncPipe` to the template
```html
{{ (hero$ | async).name }}
```
```ts
hero$: Observable;
ngOnInit(): void {
this.hero$ = interval(1000).pipe(
startWith(createHero()),
map(() => createHero())
);
}
```
while the second component subscribes to the observable and updates a data binding value:
```html
{{ hero.name }}
```
```ts
hero: Hero = createHero();
ngOnInit(): void {
interval(1000)
.pipe(map(() => createHero()))
.subscribe(() => {
this.hero = createHero();
console.log(
'HeroCardAsyncPipeComponent new hero without AsyncPipe: ',
this.hero
);
});
}
```
As you can see the implementation without the `AsyncPipe` does not trigger change detection, so we would need to manually call `detectChanges()` for each new event that is emitted from the observable.
### Avoiding Change Detection Loops and ExpressionChangedAfterCheckedError
Angular includes a mechanism that detects change detection loops. In development mode, the framework runs change detection twice to check if the value has changed since the first run. In production mode change detection is only run once to have a better performance.
I force the error in my [ExpressionChangedAfterCheckedError demo](https://angular-change-detection-demo.netlify.com/expression-changed-demo){rel=""nofollow""} and you can see it if you open the browser console:

In this demo I forced the error by updating the `hero` property in the `ngAfterViewInit` lifecycle hook:
```ts
ngAfterViewInit(): void {
this.hero.name = 'Another name which triggers ExpressionChangedAfterItHasBeenCheckedError';
}
```
To understand why this causes the error we need to take a look at the different steps during a change detection run:

As we can see, the `AfterViewInit` lifecycle hook is called after the DOM updates of the current view have been rendered. If we change the value in this hook it will have a different value in the second change detection run (which is triggered automatically in development mode as described above) and therefore Angular will throw the `ExpressionChangedAfterCheckedError`.
I can highly recommend the article [Everything you need to know about change detection in Angular](https://blog.angularindepth.com/everything-you-need-to-know-about-change-detection-in-angular-8006c51d206f){rel=""nofollow""} from [Max Koretskyi](https://twitter.com/maxkoretskyi){rel=""nofollow""} which explores the underlying implementation and use cases of the famous `ExpressionChangedAfterCheckedError` in more detail.
### Run Code Without Change Detection
It is possible to run certain code blocks outside `NgZone` so that it does not trigger change detection.
```ts
constructor(private ngZone: NgZone) {}
runWithoutChangeDetection() {
this.ngZone.runOutsideAngular(() => {
// the following setTimeout will not trigger change detection
setTimeout(() => doStuff(), 1000);
});
}
```
The simple demo provides a button to trigger an action outside Angular zone:

You should see that the action is logged in the console but the `HeroCard` components get no checked which means their border does not turn red.
This mechanism can be useful for E2E tests run by [Protractor](https://www.protractortest.org/#/){rel=""nofollow""}, especially if you are using `browser.waitForAngular` in your tests. After each command sent to the browser, Protractor will wait until the zone becomes stable. If you are using `setInterval` your zone will never become stable and your tests will probably timeout.
The same issue can occur for RxJS observables but therefore you need to add a patched version to `polyfill.ts` as described in [Zone.js's support for non-standard APIs](https://github.com/angular/angular/blob/master/packages/zone.js/NON-STANDARD-APIS.md#usage){rel=""nofollow""}:
```js
import 'zone.js/dist/zone' // Included with Angular CLI.
import 'zone.js/dist/zone-patch-rxjs' // Import RxJS patch to make sure RxJS runs in the correct zone
```
Without this patch, you could run observable code inside `ngZone.runOutsideAngular` but it would still be run as a task inside `NgZone`.
### Deactivate Change Detection
There are special use cases where it makes sense to deactivate change detection. For example, if you are using a WebSocket to push a lot of data from the backend to the frontend and the corresponding frontend components should only be updated every 10 seconds. In this case we can deactivate change detection by calling `detach()` and trigger it manually using `detectChanges()`:
```ts
constructor(private ref: ChangeDetectorRef) {
ref.detach(); // deactivate change detection
setInterval(() => {
this.ref.detectChanges(); // manually trigger change detection
}, 10 * 1000);
}
```
It is also possible to completely deactivate Zone.js during bootstrapping of an Angular application. This means that automatic change detection is completely deactivated and we need to manually trigger UI changes, e.g. by calling `ChangeDetectorRef.detectChanges()`.
First, we need to comment out the Zone.js import from `polyfills.ts`:
```ts
import 'zone.js/dist/zone' // Included with Angular CLI.
```
Next, we need to pass the noop zone in `main.ts`:
```ts
platformBrowserDynamic().bootstrapModule(AppModule, {
ngZone: 'noop';
}).catch(err => console.log(err));
```
More details about deactivating Zone.js can be found in the article [Angular Elements without Zone.Js](https://www.softwarearchitekt.at/aktuelles/angular-elements-part-iii/){rel=""nofollow""}.
### Ivy
Angular 9 will use [Ivy, Angular's next-generation compilation and rendering pipeline](https://blog.angularindepth.com/all-you-need-to-know-about-ivy-the-new-angular-engine-9cde471f42cf){rel=""nofollow""} per default. Starting with Angular version 8, you [can choose to opt in to start using a preview version of Ivy](https://angular.io/guide/ivy){rel=""nofollow""} and help in its continuing development and tuning.
The Angular team will ensure that the new render engine still handles all framework lifecycle hooks in the correct order so that change detection works as before. So you will still see the same `ExpressionChangedAfterCheckedError` in your applications.
[Max Koretskyi](https://twitter.com/maxkoretskyi){rel=""nofollow""} wrote [in the article](https://blog.angularindepth.com/ivy-engine-in-angular-first-in-depth-look-at-compilation-runtime-and-change-detection-876751edd9fd){rel=""nofollow""}:
> As you can see, all the familiar operations are still here. But the order of operations appears to have changed. For example, it seems that now Angular first checks the child components and only then the embedded views. Since at the moment there’s no compiler to produce output suitable to test my assumptions, I can’t know for sure.
You can find two more interesting Ivy related articles in the "Recommend Articles" section at the end of this blog post.
### Conclusion
Angular Change Detection is a powerful framework mechanism that ensures that our UI represents our data in a predictable and performant way. It is safe to say that change detection just works for most applications, especially if they do not consist of 50+ components.
As a developer, you usually need to deep dive into this topic for two reasons:
- You receive an `ExpressionChangedAfterCheckedError` and need to solve it
- You need to improve your application performance
I hope this article could help you to have a better understanding of Angular's Change Detection. Feel free to use my [demo project](https://github.com/Mokkapps/angular-change-detection-demo){rel=""nofollow""} to play around with the different change detection strategies.
### Recommended Articles
- [Angular Change Detection - How Does It Really Work?](https://blog.angular-university.io/how-does-angular-2-change-detection-really-work/){rel=""nofollow""}
- [Angular OnPush Change Detection and Component Design - Avoid Common Pitfalls](https://blog.angular-university.io/onpush-change-detection-how-it-works/){rel=""nofollow""}
- [A Comprehensive Guide to Angular onPush Change Detection Strategy](https://netbasal.com/a-comprehensive-guide-to-angular-onpush-change-detection-strategy-5bac493074a4){rel=""nofollow""}
- [Angular Change Detection Explained](https://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html){rel=""nofollow""}
- [Angular Ivy change detection execution: are you prepared?](https://blog.angularindepth.com/angular-ivy-change-detection-execution-are-you-prepared-ab68d4231f2c){rel=""nofollow""}
- [Understanding Angular Ivy: Incremental DOM and Virtual DOM](https://blog.nrwl.io/understanding-angular-ivy-incremental-dom-and-virtual-dom-243be844bf36){rel=""nofollow""}
# The Mistakes I Made In My First Software Project
Before starting my professional career as a developer, I mainly developed Android apps using Java as the programming language. I got hired by a software service company, and we had to develop JavaScript-based applications for cars in my first project. So the first time in my life, I had to work with JavaScript, and I made many mistakes during this time which I now want to share with you.
> The only man who never makes a mistake is the man who never does anything.
>
> Theodore Roosevelt
## Project setup
As I joined the project, there was one project manager and a senior developer. Both left the project after some weeks, and I combined the role of a developer and project manager. In my office room, two other senior developers also worked on similar JavaScript projects. The team developed about ten relatively small JavaScript apps, and my goal was to fix bugs and implement new features until the release. The release went well, and I got the opportunity to work on a more extensive app based on the same tech stack.
The project's tech stack consisted of JavaScript (ECMAScript 3), Apache Maven for building the application, and Karma + jasmine as the test runner. There was no HTML and CSS involved as the JavaScript code talked to a proprietary UI developed internally by the automotive company.
## Learn the basics
One of my biggest mistakes was that I did not learn JavaScript properly. I just took a short online tutorial, which looked easy. But after this short tutorial, I had no idea about:
- Closures
- Scopes
- `this` references
- `==` vs `===`
- why to use `"strict mode"`
- `undefined is not a function`
- and all the other interesting aspects of JavaScript
If you are in a larger team with a code review process, this might not be a big problem because you will learn that lousy code should not find its way into the repository during the review process. But I was alone, and no one reviewed my code. I thought my basic JavaScript knowledge would be enough to do the job, but today I know that it was a terrible code, and I would do it today in a different way.
## Technical mistakes
I want to talk about some of the most significant technical mistakes I made in this project.
> A failure is not always a mistake, it may simply be the best one can do under the circumstances. The real mistake is to stop trying.
>
> B. F. Skinner
### Did not separate view from business code
I created a separate JavaScript file for each of the application's views. The problem was that I did not separate the view from the business logic. So these files contained nearly all the logic necessary to fill the screen with life.
It would have been a much better approach to use the MVC (Model-View-Controller) or some similar pattern to avoid the tight coupling of logic and view.
### Documented all methods
I added JSDoc to **every** method in the application even if the method already had a declarative name.
Reading recommendation: [Don’t comment your code!](http://apdevblog.com/comments-in-code/){rel=""nofollow""}
### Used window object as global state
This was probably my biggest mistake: I used the window object for global state and stored dozens of properties there. A summary of possible problems using the window object for the global state:
- anyone can change the state at any time (it is mutable)
- bad readability of the code
- testing can be tricky
- many more...
Reading recommendation: [Why is global state so evil](https://softwareengineering.stackexchange.com/questions/148108/why-is-global-state-so-evil){rel=""nofollow""}
### Generic backend handler
To avoid code duplication in every file, I created a generic class that was used in every view to make HTTP requests. This was a very, very, very stupid decision and led to a large and unmaintainable hell of code.
Each method in this backend handler class consisted of a large switch-case statement to determine which view class made the call. As multiple views could trigger a request simultaneously, we also implemented a simple queue mechanism, making it more complex and unmaintainable.
It would have been a much better approach to handle the request in each of the views (ideally in each controller but as mentioned above I did not implement such an abstraction).
### Bad unit tests
I wrote a lot of unit tests for the application, but in the end, they did not catch major bugs. I also did not write regression bugs after fixing a bug, so sometimes, I had to fix the same issue again. Additionally, coupling the view and business logic together in one class made it very hard to test as many dependencies had to be mocked.
Reading recommendation: [How to know what to test](https://kentcdodds.com/blog/how-to-know-what-to-test/){rel=""nofollow""}
### Result of the app
I finished the app on time and within budget, but the problems occurred after leaving the project. Of course, there were bugs, but the customer only provided a little budget to fix them. So my company assigned the tasks to working students or apprentices who just used quick dirty hacks to fix the bug. As you can imagine, this did not improve the already bad code base I left there.
Additionally, the app had to be rolled out in more regions with special business requirements. As the app was not scalable and flexible, this became quite a problem for the following developers of the project.
## Conclusion
As you can maybe imagine it is not easy for me as a developer (or in general as a person) to talk publicly about my mistakes. But I think it is important for my personal development and I also want to take you the fear to talk about the mistakes you made. Also, you should not be afraid of making mistakes in your first software project, this can happen and you will learn a lot from them.
Asking questions is one of the most important things you can do if you are new to a programming language or project. If you don't ask questions when necessary, you may get into trouble, as happened to me. Especially if it is about defining a software architecture for a business application, you should ask a senior developer for advice. Get help from experienced developers as early and often as you can. If you are the only developer in the team, try establishing a code review process with a more experienced developer.
Cover Image by [mohamed Hassan](https://pixabay.com/users/mohamed_hassan-5229782/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=3085712) from [Pixabay](https://pixabay.com/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=3085712)
# Track Twitter Follower Growth Over Time Using A Serverless Node.js API on AWS Amplify
In March 2021 I started to use [FeedHive](https://feedhive.io){rel=""nofollow""} to help me grow an audience on Twitter.
Recently, I wanted to check how my Twitter followers have grown over time. Unfortunately, [Twitter Analytics](https://analytics.twitter.com){rel=""nofollow""} only provides data from the last 30 days. So I decided to develop a simple serverless API to fetch and store my follower count each month.
## Tech Stack
As I already use [AWS Amplify](https://aws.amazon.com/amplify/){rel=""nofollow""} for some private APIs, I wanted to reuse this framework for this new project.

For this new project I need the following components:
- [React](https://reactjs.org/){rel=""nofollow""} for the frontend web application which will fetch the data from my serverless API
- [AWS API Gateway](https://aws.amazon.com/api-gateway/){rel=""nofollow""} which provides traffic management, CORS support, authorization and access control, throttling, monitoring, and API version management for the new API
- [AWS Lambda](https://aws.amazon.com/lambda/){rel=""nofollow""} with [Node.js](https://nodejs.org/){rel=""nofollow""} that fetches the follower count from [Twitter API](https://developer.twitter.com/en/docs/twitter-api){rel=""nofollow""}
- [AWS DynamoDB](https://aws.amazon.com/de/dynamodb/){rel=""nofollow""} which is a NoSQL database and which will store the follower count
## Fetching follower count from backend
The first step is to add a new Node.js REST API to our Amplify application that provides a `/twitter` endpoint which is triggered on a recurring schedule. In my case, it will be on every 1st day of the month. The [official documentation](https://docs.amplify.aws/guides/api-rest/node-api/q/platform/js/){rel=""nofollow""} will help you to set up such a new REST API.
To be able to fetch the follower count from Twitter API I decided to use [FeedHive's Twitter Client](https://github.com/FeedHive/twitter-api-client){rel=""nofollow""}. This library
needs four secrets to be able to access Twitter API. We will store them 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.
After the API is created and pushed to the cloud, we can write the basic functionality to fetch the Twitter followers inside our AWS Lambda function:
```js
const twitterApiClient = require('twitter-api-client')
const AWS = require('aws-sdk')
const twitterUsername = 'yourTwitterUsername'
const secretsManager = new AWS.SecretsManager()
const responseHeaders = {
'Content-Type': 'application/json',
'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
'Access-Control-Allow-Methods': 'OPTIONS,POST',
'Access-Control-Allow-Credentials': true,
'Access-Control-Allow-Origin': '*',
'X-Requested-With': '*',
}
exports.handler = async (event) => {
const secretData = await secretsManager.getSecretValue({ SecretId: 'prod/twitterApi/twitter' }).promise()
const secretValues = JSON.parse(secretData.SecretString)
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,
})
try {
const response = await twitterClient.accountsAndUsers.usersSearch({
q: twitterUsername,
})
const followersCount = response[0].followers_count
return {
statusCode: 200,
headers: responseHeaders,
body: followersCount,
}
} catch (e) {
console.error('Error:', e)
return {
statusCode: 500,
headers: responseHeaders,
body: e.message ? e.message : JSON.stringify(e),
}
}
}
```
The next step is to add DynamoDB support to be able to store a new follower count and get a list of the stored data.
Therefore, we need to add a new storage to our AWS Amplify application, see ["Adding a NoSQL database"](https://docs.amplify.aws/cli/storage/overview/#adding-a-nosql-database){rel=""nofollow""} for detailed instructions.
We are adding a NoSQL table that has the following columns:
- `id`: A unique string identifier for each row as a string
- `follower_count`: the current follower count as number
- `data`: an ISO timestamp string that represents the time when the follower count was fetched
Now, we need to allow our Lambda function to access this storage:
```bash
▶ amplify update function
? Select the Lambda function you want to update twitterfunction
? Which setting do you want to update? Resource access permissions
? Select the categories you want this function to have access to. storage
? Storage has 3 resources in this project. Select the one you would like your Lambda to access twitterdynamo
? Select the operations you want to permit on twitterdynamo create, read, update, delete
```
Finally, we can use the [AWS SDK](https://github.com/aws/aws-sdk-js){rel=""nofollow""} to store and read from DynamoDB:
```js {13-41,67-68}
const twitterApiClient = require('twitter-api-client')
const AWS = require('aws-sdk')
const { v4: uuidv4 } = require('uuid')
const secretsManager = new AWS.SecretsManager()
const twitterUsername = 'yourTwitterUsername'
const responseHeaders = {
'Access-Control-Allow-Origin': '*',
// ...
}
const docClient = new AWS.DynamoDB.DocumentClient()
let tableName = 'twittertable'
if (process.env.ENV && process.env.ENV !== 'NONE') {
tableName = `${tableName}-${process.env.ENV}`
}
const tableParams = {
TableName: tableName,
}
async function getStoredFollowers() {
console.log(`👷 Start scanning stored follower data...`)
return docClient.scan({ ...tableParams }).promise()
}
async function storeFollowersCount(followerCount) {
console.log(`👷 Start storing follower count...`)
return docClient
.put({
...tableParams,
Item: {
id: uuidv4(),
follower_count: followerCount,
date: new Date().toISOString(),
},
})
.promise()
}
async function fetchFollowerCount(twitterClient) {
console.log(`👷 Start fetching follower count...`)
const data = await twitterClient.accountsAndUsers.usersSearch({
q: twitterUsername,
})
return data[0].followers_count
}
exports.handler = async (event) => {
const secretData = await secretsManager.getSecretValue({ SecretId: 'prod/twitterApi/twitter' }).promise()
const secretValues = JSON.parse(secretData.SecretString)
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,
})
try {
const followersCount = await fetchFollowerCount(twitterClient)
await storeFollowersCount(followersCount)
const storedFollowers = await getStoredFollowers()
return {
statusCode: 200,
headers: responseHeaders,
body: JSON.stringify(storedFollowers.Items),
}
} catch (e) {
console.error('Error:', e)
return {
statusCode: 500,
headers: responseHeaders,
body: e.message ? e.message : JSON.stringify(e),
}
}
}
```
A successful API response will have a similar JSON array in its body:
```json
[
{
"follower_count": 350,
"date": "2021-08-09T11:39:50.885Z",
"id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
},
{
"follower_count": 380,
"date": "2021-09-09T11:39:50.885Z",
"id": "a5a2a894-166b-4672-aefe-cea01c70a01a"
}
]
```
## Show data in frontend
To be able to show the data in the React frontend I use the [Recharts library](https://recharts.org/en-US){rel=""nofollow""} which is "a composable charting library built on React components".
The React component is quite simple and uses the [AWS Amplify REST API library](https://docs.amplify.aws/lib/restapi/fetch/q/platform/js/){rel=""nofollow""} to fetch the data from our API endpoint:
```jsx
import { API } from 'aws-amplify';
import { useState } from 'react';
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
} from 'recharts';
const TwitterPage = () => {
const [isLoading, setIsLoading] = useState(false);
const [apiError, setApiError] = useState();
const [followerData, setFollowerData] = useState();
const triggerEndpoint = async () => {
setIsLoading(true);
try {
const data = await API.get('twitterapi', '/twitter');
setFollowerData(
data.map((d) => {
d.followers = d.follower_count;
d.date = new Date(d.date).toLocaleDateString();
return d;
})
);
} catch (error) {
console.error('Failed to trigger Twitter endpoint', error);
setApiError(JSON.stringify(error));
} finally {
setIsLoading(false);
}
};
return (
Twitter API
{followerData ? (
) : null}
{apiError ?
{JSON.parse(apiError)}
: null}
);
};
export default TwitterPage;
```
which results in such a graph:

## Conclusion
Using serverless functions it is quite easy and cheap to build a custom solution to track Twitter follower growth over time.
What do you use to track your follower growth? Leave a comment and tell me about your solution.
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.
# Unlocking the Power of v-for Loops in Vue With These Useful Tips
Looping through arrays and objects is a common task in Vue applications. The `v-for` directive is the perfect tool for this job. It is mighty, and you can use it in many different ways. In this article, I will show you some valuable tips and tricks to get the most out of the `v-for` directive.
## Use correct delimiter
The `v-for` directive supports two different delimiters: `in` and `of`. The `in` delimiter is the default one.
I prefer to use the `of` delimiter for **arrays** because it is closer to JavaScript's syntax for iterators like in the `for...of` loop:
```vue [Component.vue] {13-15}
{{ item.name }}
```
If I want to loop through an **object**, I use the `in` delimiter because it is closer to JavaScript's syntax for iterating over object properties:
```vue [Component.vue] {13-15}
Key: "{{ key }}", Value: "{{ value }}"
```
## Destructuring objects
It's possible to destructure the current item in the loop, which is useful if you want to access the current item's properties directly:
```vue [Component.vue] {13-15}
Title: {{ name }}
```
## Iterating over numbers
You can also use the `v-for` directive to iterate over numbers. This is useful if you want to render a list of elements with a specific number of items. For example, you can use it to render a list of 10 items:
```vue [Component.vue] {3}
{{ number }}
```
## Accessing index
Sometimes, you need to access the current item's index in the loop. You can do this by using the second argument of the `v-for` directive:
```vue [Component.vue] {13-15}
#{{ index + 1 }} - {{ item.name }}
```
## Avoid v-if in v-for loops
Using `v-if` and `v-for` on the same element [is not recommended](https://vuejs.org/guide/essentials/list.html#v-for-with-v-if){rel=""nofollow""} due to implicit precedence.
In this case, you should wrap the `v-for` loop in a `` element and use `v-if` on the `` element instead, which is also more explicit:
::code-group
```vue [Bad.vue] {2}
{{ item.name }}
```
```vue [Good.vue] {2,6}
{{ item.name }}
```
::
## Use key attribute
[It is recommended](https://vuejs.org/style-guide/rules-essential.html#use-keyed-v-for){rel=""nofollow""} to provide a `key` attribute with `v-for` whenever possible. `key` is a special attribute that lets us give hints for Vue's rendering system to identify specific virtual nodes.
Let's assume we have a list of todos and want to add a new todo. We can use the `splice()` method to insert a new todo at a specific index. If we don't provide a `key` attribute, Vue will be unable to identify the new todo and will not update the UI correctly.
First, let's take a look at the `TodoItem` component:
```vue [TodoItem.vue] {13,18-20}
Local todo name:{{ todoName }}
```
This simple component renders a todo item passed as a prop and has a local state to store the todo name, which is updated if the component is mounted.
Let's take a look at an interactive example without a `key` attribute. We have a list of todos and want to insert a new todo at a specific index, try it yourself by clicking the `Insert new Todo` button:
::tabs
:::div{icon="i-heroicons-magnifying-glass-circle" label="Preview"}
:demo-for-loop
:::
::
::div{icon="i-heroicons-code-bracket-square" label="Code"}
```vue [ForLoopWithoutKey.vue]
Insert new Todo{{ todo.name }}
```
::
\::
The new `Learn Vue` todo is inserted at the correct index, but the local todo name is not updated. This is because Vue is not accurately tracking the index as being new. Thus, the component will never re-mount, so our `localTodoName` will never get updated. Instead, the value of `localTodoName` will be that of the previous todo at that index.
You might have guessed it, we can simply fix that problem by providing a `key` attribute to our `v-for` loop:
::tabs
:::div{icon="i-heroicons-magnifying-glass-circle" label="Preview"}
::::demo-for-loop{with-key}
::::
:::
::
::div{icon="i-heroicons-code-bracket-square" label="Code"}
```vue [ForLoopWithoutKey.vue]
Insert new Todo{{ todo.name }}
```
::
\::
## Array Change Detection Caveats
You must carefully use non-mutating methods on your array in `v-for` loops, such as `filter()` or `map()`. These methods return a new array, which means that Vue cannot detect changes to the array.
If you want to use these methods, you need to assign the new array to the original array:
```vue [Component.vue] {11}
{{ item.name }}
```
If you use mutation methods like `push()`, `pop()`, `shift()`, `unshift()`, `splice()`, `sort()`, or `reverse()`, you don't need to assign the new array to the original array because these methods mutate the original array.
## Conclusion
I hope you learned something new about the `v-for` directive in this article. It's one of the most powerful directives in Vue, and you can use it in many different ways. If you want to learn more about the `v-for` directive, check out the [official documentation](https://vuejs.org/guide/essentials/list.html){rel=""nofollow""}.
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 newsletter](https://weekly-vue.news){rel=""nofollow""}:
# Use Git Bisect to Find the Commit That Introduced a Bug
As a developer you know that situation: the code worked like a charm and suddenly there is a bug but you have no idea where and when it was introduced.
If you are working in a big team the chances may be quite high that many commits have been added in the meantime. So finding the commit where the bug was introduced can become quite nasty.
Luckily, [Git](https://git-scm.com/){rel=""nofollow""} offers a tool that helps to detect the first bad commit that introduces the bug. It is called "git bisect".
## How does it work?
We need to provide Git Bisect two information to be able to identify
1. A "good" commit where the bug **was not** present.
2. A "bad" commit where the bug **is** present.
This way Git "knows" that the bug has to between the "good" and the "bad" commit. Starting the bisect process will split the range of commits between the "good" and "bad " commit in half and check out a commit in the middle:

Our task is now to validate the code at this commit. This can be done by compiling, running the application or launching a test case for the given bug. Next, we need to tell Git if the test was "good" or "bad". Git will simply repeat this process until we've singled out the commit that contains the bug.
The used algorithm is called [binary search](https://en.wikipedia.org/wiki/Binary_search_algorithm){rel=""nofollow""}.

## Practical Example
Let's look at how we can run Git Bisect from the command line. First, we need to start the process
```bash
$ git bisect start
```
Next step is to provide Git a "good" and "bad" commit. The "bad" commit is often the current state which refers to "HEAD":
```bash
$ git bisect bad HEAD
```
To be able to find "good" commit you need to check out any older revision where you are quite sure that the bug did not exist. After you have checked it out and verified that the bug is not present there, we provide Git the corresponding commit hash :
```bash
$ git bisect good acd72832
```
Now we are ready to start the "bisecting" process. Git will check out a commit in the middle of the range between the "good" and "bad" commit we provided:
```bash
Bisecting: 6 revisions left to test after this (roughly 2 step)
[commit_ABC] Added controller
```
At this point we need to verify if the bug is still present or not. If it is still present we need to run
```bash
$ git bisect bad
```
otherwise we run
```bash
$ git bisect good
```
to mark it as "good".
Depending on the result, Git will again split the original commit range and select either the first or second half. It will again check out a commit in the middle and we need to verify if the bug is present there.
This process is repeated until we've successfully singled out the bad commit!
Once we've found the culprit, we can end the bisect process by running:
```bash
$ git bisect reset
```
Git will then finish the bisect process and take us back to our previous HEAD revision.
## Conclusion
Git Bisect can be a helpful tool to track down a bug. I only use `git bisect` when I absolutely have no idea where the bug was introduced and I need to search through a lot of potentially unrelated changes.
For more information about Git Bisect take a look at the [official docs](https://git-scm.com/docs/git-bisect){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.
# Use Nitro as Mock Server
[Nitro](https://nitro.unjs.io/){rel=""nofollow""} is a server toolkit that allows you to create web servers with everything you need and deploy them wherever you prefer. It's used in [Nuxt 3](https://nuxt.com/docs/getting-started/server){rel=""nofollow""} to power the server-side part of your Nuxt applications.
In this article, I will show how you can use Nitro as a mock server for your frontend E2E tests.
## Setup Nitro
First, you need to create a new Nitro project, you can use the official starter template:
```bash
npx giget@latest nitro mock-server --install
```
Then, you can start the Nitro server in the newly created `mock-server` directory:
```bash
npm run dev
```
Your Nitro server is now running on `http://localhost:3000`.
## Mock API
To mock API endpoints, you can use the full power of Nitro's [server routes](https://nitro.unjs.io/guide/routing){rel=""nofollow""}. Defining a route is as simple as creating a file inside the `api/` or `routes/` directory.
Here is an example of how you can create a simple mock API endpoint with a dynamic route parameter:
```ts
// /routes/users/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
// Do something with id
return `User profile!`
})
```
Nitro's server routes are very powerful and flexible, you can use them to create any kind of API endpoint you need.
## Integrate in E2E setup
Now that you have a mock server running, you can use it in your frontend E2E tests. Let's do it exemplary with [Playwright](https://playwright.dev/){rel=""nofollow""}.
::note
[This repository](https://github.com/Mokkapps/nuxt-nitro-e2e-mock-server-demo){rel=""nofollow""} contains the demo code which I describe below.
::
Let me first introduce the basic setup of the Nuxt 3 demo application that I use for this article:
The application uses one server API endpoint that fetches users from an API endpoint that is configured in the `.env` file:
```yml [.env]
NUXT_EXTERNAL_API_URL=https://jsonplaceholder.typicode.com
```
In this example, we would like to mock this API endpoint to avoid making real API requests during our E2E tests. Therefore, we want to replace it with our Nitro mock server:
```yml [.env]
NUXT_EXTERNAL_API_URL=http://localhost:5005
```
To use the Nitro server in your Playwright tests, you can start the Nitro server in the background before running the tests using the [start-server-and-test](https://www.npmjs.com/package/start-server-and-test){rel=""nofollow""} npm package:
```json [package.json]
{
"scripts": {
"start-mock-server": "npm run --prefix mock-server start",
"dev:e2e": "dotenv -e ./.env.e2e -- playwright test --ui",
"test:e2e:ui": "start-server-and-test start-mock-server 5005 dev:e2e"
},
}
```
The Playwright configuration file starts the web server of your frontend application before running the tests:
```ts [playwright.config.ts]
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
// ....
/* Run your local dev server before starting the tests */
webServer: {
command: 'pnpm run dev',
reuseExistingServer: !process.env.CI,
url: 'http://localhost:3000',
},
})
```
That's it! Now you can run your E2E tests with the Nitro mock server running in the background:

As you can see the Playwright tests are passing and the Nitro server is used as a mock server for the external API requests. You can verify this because the original API endpoint returns 10 results and our mock server returns 2 results.
## Conclusion
In this article, I showed you how you can use Nitro as a mock server for your frontend E2E tests. This can be very useful to avoid making real API requests during your tests and to speed up your development process. If you are already using Nuxt 3 as your frontend framework, you can easily integrate Nitro as a mock server in your setup and benefit from its powerful server routes.
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""}:
# Use Shiki to Style Code Blocks in HTML Emails
I recently developed [a custom newsletter service using Nuxt 3](https://mokkapps.de/blog/how-i-replaced-revue-with-a-custom-built-newsletter-service-using-nuxt-3-supabase-serverless-and-amazon-ses). One of the main reasons why I developed it on my own was that I wanted to use good-looking code blocks in my emails.
In this article, I'll explain how I use [Shiki](https://github.com/shikijs/shiki){rel=""nofollow""} to generate nicely styled code blocks in my newsletter emails.
## Nuxt 3 & Nuxt Content
::note
If you don't use Nuxt and are only interested in the necessary Shiki customization, you can skip the next sections, and head over to "Shiki Code" section.
::
On my [newsletter website](https://weekly-vue.news){rel=""nofollow""} I use [Nuxt 3](https://nuxt.com){rel=""nofollow""} with the [Nuxt Content](https://content.nuxtjs.org/){rel=""nofollow""} and [Nuxt Tailwind](https://tailwindcss.nuxt.dev/){rel=""nofollow""} modules.
Nuxt Content uses [Shiki](https://github.com/shikijs/shiki){rel=""nofollow""} that colors tokens with VSCode themes.
By default, it uses `code`, `pre`, `span` and `div` tags with CSS Flexbox to render the code block.
**Unfortunately, [flex-direction\:column](https://www.caniemail.com/features/css-flex-direction/){rel=""nofollow""} is badly supported in email clients.**
Instead, we need to write the code block in an **HTML table** which is [supported in all email clients](https://www.caniemail.com/features/html-table/){rel=""nofollow""}.
So we need to eject from the default styling and create a custom code component.
## Custom Prose Component
Nuxt Content uses [Prose components](https://content.nuxtjs.org/api/components/prose){rel=""nofollow""} to render markdown files in the DOM.
To overwrite a prose component, we can create a component with the same name in our project `components/content/` directory.
In our case, we want to create a custom `ProseCode` component:
```vue [components/content/ProseCode.vue]
```
Next, we need to install [shiki-es](https://www.npmjs.com/package/shiki-es){rel=""nofollow""}, a standalone build of Shiki fully compatible with all ESM environments:
```bash
# npm
npm i shiki-es
# yarn
yarn add shiki-es
```
We can eject from the default styling by removing the `` tag and adding an `html` reactive variable that will contain the highlighted which is rendered via the [v-html directive](https://vuejs.org/api/built-in-directives.html#v-html){rel=""nofollow""}:
```vue [components/content/ProseCode.vue] {7,11}
```
## Shiki Code
Now it's time to manually call Shiki to render our code as an HTML table. We, therefore, use [shiki-es](https://www.npmjs.com/package/shiki-es){rel=""nofollow""}, a standalone build of Shiki that is fully compatible with all ESM environments.
Three steps are necessary to generate the HTML code:
1. Use `getHighlighter` to get an instance of the Shiki highlighter.
2. Call `codeToThemeTokens` with the given code and language to get the tokens that should be rendered.
3. Use `renderToHtml` to generate the HTML which should be rendered in the DOM. Its `elements` object can be used to modify the DOM structure of the code block. Here we add our `
`, `
` and `
` tags which are necessary for the HTML table.
Let's take a look at the code:
```vue [components/content/ProseCode.vue] {11-40}
```
This code will result in this DOM structure:

## CSS Styles
As mentioned, I use Tailwind in my project which heavily uses `rgb` colors. Unfortunately, `rgb()` works partially in email clients but [alpha values and whitespace syntax are not supported](https://www.caniemail.com/search/?s=rgb){rel=""nofollow""}.
I use the [postcss-preset-env](https://www.npmjs.com/package/postcss-preset-env){rel=""nofollow""} PostCSS plugin to convert modern CSS into something most browsers can understand:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
// ...
postcss: {
plugins: {
'postcss-preset-env': {},
tailwindcss: {},
autoprefixer: {},
},
},
})
```
## Final Result
For example, this is how a code block in a newsletter email from [Weekly Vue News](https://weekly-vue.news){rel=""nofollow""} looks like in a Gmail web client:

## Conclusion
Styling HTML emails is a real pain but having good-looking code blocks in my newsletter emails was worth the effort. It's very sad that we still need to use HTML tables in HTML emails in the year 2023...
Thankfully, Nuxt Content is very customizable and allows developers to build custom solutions.
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""}.
# Vercel Acquires NuxtLabs: What This Means for the Future of Nuxt
Last week, big news were dropped: Vercel acquired NuxtLabs. This acquisition marks a transformational moment for the Nuxt community — a moment filled with exciting possibilities and thoughtful challenges that we need to explore together.
## Introduction to the Acquisition
Vercel's move to acquire NuxtLabs is more than just a business transaction; it’s a statement about the future of full-stack web development. Nuxt, which has become a trusted umbrella for developers worldwide with over 1 million downloads weekly, is now poised to benefit from the additional resources, global reach, and innovative spirit that Vercel is known for.
The [official announcement on Vercel’s blog](https://vercel.com/blog/nuxtlabs-joins-vercel){rel=""nofollow""} outlines a vision of heightened collaboration and resource allocation. This means accelerated development for the Nuxt framework, better support for its ecosystem, and potentially smoother integration with tools that many of us already rely on in our day-to-day projects.
## Implications for the Nuxt Framework
The Nuxt framework’s evolution is in the spotlight as it steps into this new phase. With Vercel’s acquisition, we can expect:
- **Enhanced Development Speed and Quality:** NuxtLab's team no longer needs to worry about funding and can focus on what they do best: building a powerful framework for developers.
- **Streamlined Integration:** Vercel’s expertise in deployment and performance optimization can complement Nuxt’s development features, thereby offering a more comprehensive experience for developers.
- **A Global Perspective:** As Nuxt continues to grow, its integration with Vercel might even help it secure more international contributions and resonate with an even broader audience.
In essence, the acquisition is a vote of confidence in Nuxt’s potential. The commitment to keeping the core framework robust and community-focused promises a future with fewer limitations and more creative freedom for building full-stack applications.
## The Transition to Open-Source Tools
One of the most exciting facets of this acquisition is the transition of previously paid NuxtLabs tools to open-source. As someone who has seen firsthand the impact of accessible, community-driven resources, I believe this decision is a game-changer. Here’s what it entails:
- [Nuxt UI Pro:](https://ui.nuxt.com/pro){rel=""nofollow""} Formerly a paid collection of Vue components and templates, it will become free and open-source with Nuxt UI v4. This update brings over 100 components along with a comprehensive Figma Kit.
- [Nuxt Studio:](https://nuxt.studio/){rel=""nofollow""} This self-hostable content editor is designed for direct integration with Nuxt Content sites. By moving to open-source, developers gain complete control and flexibility, empowering them to fully tailor their website-building experiences. More details are available on .
- [NuxtHub Admin:](https://hub.nuxt.com/){rel=""nofollow""} Initially developed for Cloudflare, this tool is transitioning to a provider-agnostic model. In the future, it will support seamless integration with a range of providers, including popular options featured in Vercel’s Marketplace like Postgres and Redis. This move underlines the focus on versatility and user-centric design.
This transition resonates with the broader open-source philosophy—empowering the community through accessible, adaptable, and free tools. For developers like us, these updates translate into opportunities to innovate without paying premium prices for essential tools.
::tip
This will broaden the scope of potential customers for my [Nuxt SaaS Starter Kit](https://nuxtstarterkit.com){rel=""nofollow""}, which is built on top of Nuxt UI Pro and NuxtHub.
::
## Opportunities for Developers and Businesses
Every transformative event in tech brings its share of opportunities, and this acquisition is no different. For both developers and businesses, the future looks vibrant and rich with promise:
- **Enhanced Resources for Innovation:** With Vercel's backing, the Nuxt team is set to channel more energy into development. This means faster rollouts of new features and more sophisticated integrations that can empower developers to build superior user experiences.
- **AI Integration Prospects:** There’s already buzz about the incorporation of AI into the Nuxt developer experience. Imagine smart code completions, predictive design suggestions, and advanced error detections—all contributing to speeding up the development cycle.
- **Broader Market Reach for Businesses:** For companies, the improved suite of Nuxt tools and the increased efficiency in development translate into better time-to-market and more competitive product offerings. Enhanced performance and scalability provided by the integrated Vercel platform could redefine industry standards for web applications.
- **Community-Driven Innovation:** As more of these tools become open-source, there is a high likelihood of community-led plugins, extensions, and integrations. This collaborative ecosystem is not only appealing but will drive a cycle of continuous improvement among developers worldwide.
For businesses looking to remain at the cutting edge of web technology, the acquisition is a signal that unprecedented resources will be available to refine, optimize, and scale their applications.
## Addressing Concerns and Apprehensions
While the news is overwhelmingly positive, it’s natural to have some concerns about such a significant change. Several critical issues have been voiced by community members:
- **Maintaining Independence and Governance:** A frequent worry is whether Nuxt can remain as community-driven as it has always been. The discussion around ensuring its independence is active among developers, and maintaining open governance under Vercel's umbrella is crucial. More insights can be found in thoughtful pieces like the one on [RedMonk](https://redmonk.com/blog/2025/07/10/rmc-daniel-roe-vercels-nuxtlabs-acquisition/){rel=""nofollow""}.
- **Avoiding Vendor Lock-In:** Despite assurances, there is apprehension that an acquisition of this scale might inadvertently favor Vercel’s products, leading to potential vendor lock-in. It is vital that Nuxt’s neutrality is preserved so that it remains a flexible and adaptable framework for all kinds of deployments. Analyses on this topic can be found in discussions on [Tailkits](https://tailkits.com/blog/nuxtlabs-vercel-acquisition/){rel=""nofollow""}.
By addressing these concerns head-on, the leadership teams involved are promising to safeguard the core values of the Nuxt community. Open communication and continual community engagement will be key to ensuring that the spirit of Nuxt remains intact even as it scales new heights with Vercel’s support.
## Comparisons to Vercel's Past Acquisitions
In the tech world, acquisitions often set precedents. Comparing this acquisition to Vercel’s past moves can offer valuable insight into what might lie ahead. Vercel has a strong track record of integrating innovative tools and nurturing emerging open-source projects. Here are a few takeaways:
- **Track Record of Seamless Integrations:** Vercel has previously acquired or partnered with projects and tools that later became central to enhancing developer workflows. This integration usually comes with an intention to maintain the core community ethos while providing sufficient resources to scale innovation.
- **Community-Centric Evolution:** Past acquisitions have shown that preserving a strong developer and community voice is essential. For Nuxt, this means that while operational and technical support might ramp up, the decision-making power will continue to lie, at least in substantial part, with the community’s collective input.
- **Innovation Without Compromise:** Vercel’s ability to balance commercial interests with open-source values is evident in its history. Their acquisition strategy typically focuses on unlocking additional opportunities for developers, which bodes well for the Nuxt framework’s continued growth and adaptability.
These comparisons are reassuring because they indicate that Vercel has a thoughtful, community-respectful approach to its acquisitions. It’s an approach that can help calm apprehensions while promising an exciting, innovative future.
## Conclusion: Embracing Change and Looking Forward
In wrapping up, the acquisition of NuxtLabs by Vercel ushers in not just change, but a new era of opportunity. For me, this is one of those moments that feels both invigorating and full of promise. The transition of paid tools to open-source, the infusion of fresh resources, and the potential for deeper integration with AI and global technical infrastructure are all signals of an exciting road ahead.
This is a time for the community to rally around, contribute, and explore new horizons. Whether you are a seasoned developer, an entrepreneur, or a tech enthusiast, there has never been a better time to engage with Nuxt and take advantage of the incredible tools and innovations coming our way. As we step forward, embracing change and nurturing the rich collaborative spirit that has always defined the Nuxt community, the future looks both bright and boundless.
Let’s continue to innovate, question, and grow—together.
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""}:
# Navigating State Management in Vue: Composables, Provide/Inject, and Pinia
State management in Vue is one of those topics that seems simple until your app grows a little - and then you’re suddenly juggling prop drilling, duplicated logic, and confusing reactivity bugs. Over the years I’ve tried different approaches and learned that the right answer usually isn’t "one tool to rule them all" but choosing the right approach for the problem at hand. In this article I’ll walk you through composables, provide/inject, and [Pinia](https://pinia.vuejs.org/){rel=""nofollow""} — when to use each, how to use them well, and practical examples you can copy into your projects.
## Introduction to State Management in Vue
When I start a new Vue project I always ask three questions before picking a state approach:
- How widely does this state need to be shared? (single component, subtree, global)
- Is the logic reusable across components or tied to a specific component?
- Do I need advanced features like SSR, persistence, or strong typing?
Vue gives us multiple, complementary tools to solve these needs: [Composables](https://vuejs.org/guide/reusability/composables){rel=""nofollow""} (Composition API logic encapsulation), [Provide/Inject](https://vuejs.org/guide/components/provide-inject){rel=""nofollow""} (scoped sharing in a subtree), and [Pinia](https://pinia.vuejs.org/){rel=""nofollow""} (centralized/global stores). Each has strengths and trade-offs. Below I’ll dig into each with patterns, code, and rules of thumb I actually use day-to-day.
## Understanding Composables: When and How to Use Them
Composables are reusable functions that encapsulate state and logic using the Composition API. Think of them as the “utility modules” for stateful behavior.
Why I reach for a composable:
- When logic is reusable across unrelated components (e.g., fetch logic, form handling, timers).
- For small pieces of local state that aren’t global - counters, visibility toggles, input validation, etc.
- When performance matters (they’re lightweight and don't force global reactivity).
A minimal counter composable example:
```ts [useCounter.ts]
import { ref } from 'vue'
export function useCounter(initial = 0) {
const count = ref(initial)
const increment = () => ++count.value
const decrement = () => --count.value
const reset = () => {
count.value = initial
}
return { count, increment, decrement, reset }
}
```
Best practices I follow:
- Name composables with a use prefix: `useAuth`, `useFetch`, `useCounter`. This makes intent clear.
- Group composables by feature or domain (e.g., `/composables/auth`, `/composables/ui`).
- Keep state encapsulated; expose only what callers need (avoid leaking internal `refs` unnecessarily).
- If many components need the same state instance rather than independent instances, consider switching to Provide/Inject or Pinia rather than making a composable that returns a shared object — otherwise you get implicit singletons that are harder to reason about.
When **not** to use a composable:
- When the state must be truly global and monolithic (Pinia is better).
- When you must share state only within a subtree but not across the whole app (use Provide/Inject instead).
## Leveraging Provide/Inject for Local State Sharing
Provide/Inject lets a parent component provide values (reactive data, functions) and descendant components inject them without prop drilling. I use this pattern when state belongs to a component subtree - e.g., a theming context, a form with nested children, or a modal manager.
Example: simple theming using Provide/Inject
```vue [Parent.vue]
```
```vue [Child.vue]
```
When to use Provide/Inject:
- The state is scoped to a subtree and not needed globally.
- You want to avoid prop drilling for deeply nested components.
- You’re implementing context-like things: theme, localization, per-widget configuration, or a modal stack.
Caveats and best practices:
- Provide/Inject bypasses the component interface, so document the provided keys carefully and prefer symbol keys to avoid collisions.
- Avoid overusing it; it can make component relationships implicit and harder to trace compared to props.
- Combine with composables: provide a single composable instance (e.g., `const modal = useModal(); provide('modal', modal)`) so you get the composable API and scoped sharing together.
## Harnessing the Power of Pinia for Centralized State Management
Pinia is the officially recommended state library for Vue 3. I use Pinia for global state that multiple, unrelated components or pages need access to - authentication, user preferences, shopping cart, complex domain models.
Key reasons to choose Pinia:
- Intuitive, modular API and great TypeScript support.
- Supports SSR hydration and plugin ecosystem (persistence, logger, etc.).
- Encourages splitting concerns into smaller stores rather than a single monolithic store.
Example: simple auth store with Pinia
```ts [useAuthStore.ts]
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useAuthStore = defineStore('auth', () => {
const user = ref(null)
const token = ref(null)
const isLoggedIn = computed(() => !!user.value)
function setUser(payload) {
user.value = payload.user
token.value = payload.token
}
function logout() {
user.value = null
token.value = null
}
return { user, token, isLoggedIn, setUser, logout }
})
```
Usage in a component:
```vue [Navbar.vue]
```
Pinia best practices I follow:
- Create small, focused stores (`authStore`, `cartStore`, `uiStore`) instead of one giant store.
- Use plugins for cross-cutting concerns: persistence plugin for localStorage, logger for dev debugging.
- Type your stores when using TypeScript for safer refactoring and autocompletion.
- Prefer actions for async logic and mutations inside actions - keep the state mutations explicit.
- For SSR, make sure to return fresh stores per request (Pinia supports SSR patterns).
## Comparative Analysis: Choosing the Right Tool for the Job
I like to compare the three on the dimensions that matter most in real projects:
- **Scope**
- Composables: local to component or independent instances per consumer (or implicitly shared if you intentionally export a single instance).
- Provide/Inject: subtree-scoped.
- Pinia: global/app-wide.
- **Use case**
- Composables: reusable logic (fetch, form handling, timers).
- Provide/Inject: contextual settings for a component subtree (theme, nested form context).
- Pinia: global app state, multi-page shared state, complex interdependent state.
- **Complexity & tooling**
- Composables: low overhead, great for simple logic.
- Provide/Inject: lightweight, but can make dependency relationships implicit.
- Pinia: more structure, supports plugins, SSR, type-safety; better for larger apps.
- **Reactivity sharing**
- Composables: returns isolated reactive state unless intentionally shared.
- Provide/Inject: can provide reactive objects to descendants.
- Pinia: stores are reactive by design and accessible anywhere.
Rules of thumb I use:
- Start with composables for small apps and features. If you find multiple components need the same instance of state, either lift it up (parent-provide) or move it to Pinia.
- Use Provide/Inject when a feature must be scoped to a subtree and you want to avoid prop drilling — e.g., a multi-field form with many nested inputs.
- Use Pinia when state must be accessible across the app, persisted, or when you want the developer ergonomics and plugin support Pinia provides.
## Conclusion and Best Practices
State management in Vue doesn’t have to be either/or. Composables, Provide/Inject, and Pinia are complementary tools - choose based on scope, reusability, and complexity.
My checklist before implementing state:
- Is the state subtree-scoped? Use Provide/Inject.
- Is it local/reusable logic but independent per consumer? Use a composable.
- Is it global or shared across pages or unrelated components? Use Pinia.
- Do I need SSR, persistence, or a plugin ecosystem? Pinia is the best fit.
- Keep interfaces explicit: name keys, use symbols for Provide/Inject, prefix composables with use, and keep stores modular.
# What's New in Vue 3.3
Vue 3.3 "Rurouni Kenshin" is [now available](https://blog.vuejs.org/posts/vue-3-3){rel=""nofollow""} and "is focused on developer experience improvements".
In this article, I give an overview of the highlighted features in Vue 3.3. [Read the changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md#330-2023-05-08){rel=""nofollow""} if you are interested in all changes of this new version.
## Props Destructuring
::warning
This feature is experimental and requires explicit opt-in.
::
I think this is one of the coolest features of the new release. You can now destructure props without losing reactivity and also set default values:
```vue {2}
```
In my opinion, this is a very clean and "natural" way to define your props. Previously you had to use `toRefs` in combination with `withDefaults` to achieve the same result:
```vue
```
## defineModel
::warning
This feature is experimental and requires explicit opt-in.
::
Vue 3.3 provides a very elegant way to support two-way binding with `v-model`. Before 3.3 you had to write a lot of boilerplate code to support it:
```vue {2-3,6,11}
```
With 3.3 we can achieve the same functionality with less code:
```vue {2,6}
```
In this example, the `defineModel` macro automatically registers a prop `modelValue` and returns a ref that can be directly mutated. Additionally. it registers the `update:modelValue` event.
## Improved TypeScript Type Support
In the past, only local types such as type literals and interfaces could be used in the type parameter position of the `defineProps` and `defineEmits` compiler macros.
The reason for this was that Vue needed to analyze the properties on the props interface to create runtime options. However, this limitation has been addressed in version 3.3. The Vue compiler can now handle imported types and a limited set of complex types:
```vue
```
## Generic Components
Your components can now accept generic type parameters via the `generic` attribute if you are using `
```
## More Ergonomic defineEmits
Typing `defineEmits` was a bit verbose before 3.3:
```vue
```
Vue 3.3 provides a "more ergonomic" way:
```vue
```
## Use console in the template
You can now use `console` in your template:
```vue {9}
{{ count }}
```
In previous versions of Vue, this caused an error: `TypeError: Cannot read properties of undefined (reading 'log')`
[Try it yourself](https://play.vuejs.org/#eNp9j81uwjAQhF/F8gUQxC7iFoUoPfYdfAFngZT4R/aGHiK/e9cJqiIqcZyZnf12R/7pvXgMwEteRR06jywCDr5WtjPeBWQjC3BhiV2CM2xFoytltbMRmXaDRXbM+fpjo2wl5w3UJYFgfH9CIMVYddvX4/hspFRJ0pN/HhCdZY3uO30/Kp43ux5E767r7Xaa3yhef1kdwIDFSs4NalfyD8F3fL62MCcvvqOz9M+YAeoZRMVLNjnZoy+yVvyG6GMp5WD9/Sq0M7KhTAbCdgaK1pnmIA5iL9su4tIWEE1xDu4nQiCg4rvFbknmA0IRwLYQILxlvcwueS/RP2ZGJmUTT78djp6C){rel=""nofollow""}
## Conclusion
Vue got so much better with this new version. The new features improve the developer experience and I'm very excited to see how the framework further evolves with the next upcoming releases.
For more information, read the [official announcement](https://blog.vuejs.org/posts/vue-3-3){rel=""nofollow""} and the [GitHub changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md#330-2023-05-08){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 [subscribe to my weekly Vue newsletter](https://weekly-vue.news){rel=""nofollow""}:
# When to Use useState in Nuxt
Nuxt provides the `useState` composable, which creates a reactive and SSR-friendly shared state. It's an SSR-friendly alternative to the `ref` function from Vue 3.
You might be confused when to use `useState` or `ref` in your Nuxt app. In this article, I want to answer this question.
## Problem with `ref` using SSR
Let's take a look at a simple example where we use the `ref` function to create a shared state in a Nuxt app:
```vue [Component.vue] {6-10}
{{ randomString }}
```
The problem with this code is that the `ref` function is not SSR-friendly. The code inside the `
{{ randomString }}
```
Try it yourself in the following StackBlitz project, and you will see that the random strings are the same on the server and the client. There are no hydration mismatch warnings in the console:
:stackblitz{index="1" open-file-path="pages/use-state.vue" project-id="when-to-use-use-state-in-nuxt"}
## Conclusion
In general, you should use the `useState` composable from Nuxt when you want to create a shared state that is reactive and SSR-friendly. The `useState` composable ensures that the state is only created once and is shared between the server and the client.
If your Nuxt app does not require SSR, you can use the `ref` function from Vue 3 to create a shared state.
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""}:
# Why A Good Frontend Developer Should Care About Web Accessibility
Cover image from [Poakpong](https://www.flickr.com/photos/poakpong/4681315789) licensed under [CC 2.0](https://creativecommons.org/licenses/by/2.0/)
Back in 2017, when I started frontend development, I heard [an interesting talk](https://isellsoap.github.io/talk-aesthetics-of-the-invisible/#/){rel=""nofollow""} with the title "Aesthetics of the invisible" from my former colleague [Francesco Schwarz](https://francescoschwarz.de/){rel=""nofollow""}. It was all about accessibility in websites and [the related blog post](https://francescoschwarz.de/en/blog/aesthetics-of-the-invisible/){rel=""nofollow""} starts with a remarkable statement:
> Sometimes one single hidden glyph in an HTML markup makes the difference between a good and an outstanding front-end.
As I learned soon, accessibility is a very polarizing topic. These are the typical statements I heard related to this topic:
- "We have no time for accessibility features."
- "There are only a few blind persons. We do not need to support this minority."
- "I hate these [ugly borders](http://www.outlinenone.com/){rel=""nofollow""} and I always remove them."
- "We can care about accessibility later if we have more users."
In this article, I want to tell you what accessibility is, why it is essential for websites, why I care about it, and why you should care about it too.
## What is Web Accessibility?
If I needed to describe it in my own words, I would define it as
> **Anyone** can fully access and interact with a website
The [official Wikipedia article](https://en.wikipedia.org/wiki/Web_accessibility){rel=""nofollow""} describes it as
> the inclusive practice of ensuring there are no barriers that prevent interaction with, or access to websites, by people with disabilities.
So a good website should enable access to its content to everybody, even people with disabilities.
By the way, accessibility is often abbreviated by **A11Y**.
A11Y is known as a [numeronym](https://a11yproject.com/posts/a11y-and-other-numeronyms/){rel=""nofollow""}, which is somewhat similar to an acronym. Unlike an acronym, numbers are used in place of letters to shorten the term. You may already be familiar with other numeronyms, such as “K-9” for “Canine” or “W3C” for “World Wide Web Consortium”.
## What are disabilities?
As I mentioned in the beginning, there is the misbelief that web accessibility is only relevant to blind users.
According to [a WHO report](http://www.who.int/news-room/fact-sheets/detail/blindness-and-visual-impairment){rel=""nofollow""}, approximately 1.3 billion people live with some form of vision impairment. Thereof, 36 million people are blind. But also, with mild and severe vision impairments, you can have trouble reading content on a website.
Of course, there exist not only visual disabilities. [Google's Accessibility Fundamentals](https://developers.google.com/web/fundamentals/accessibility/){rel=""nofollow""} demonstrate some access impairments in real-world examples:
| | Situational | Temporary | Permanent |
| --------- | ----------------- | ---------- | --------- |
| Visual | distracted driver | concussion | blindness |
| Motor | holding a baby | broken arm | |
| Hearing | noisy office | | deaf |
| Cognitive | | concussion | |
So all of us could get in a situation where we need to interact with websites but have some situational, temporary, or permanent disability.
I like the quote from the article ["Accessibility matters—and here's what we're doing about it"](https://product.voxmedia.com/2016/5/11/11612516/accessibility-matters-and-heres-what-were-doing-about-it){rel=""nofollow""}:
> **We should never make assumptions about our users**
> Making a product accessible does not mean targeting a specific subset of people. Rather, accessible design, or universal design, is about > > making products usable by the greatest number of people possible. We should not assume we know how our users are engaging with our content, > and should understand that it may be "seen" by a number of assisting technologies, including automated tools, keyboard-only navigation, and > screen readers.
You probably are now thinking: "But **my** customers are different".
Nope, I don't think so!
As you can see in the table above, the chances are high that one of your website users has a situational, temporary or permanent disability.
You should care about everyone and not care about a minority.
**Never forget: The website is the front door to your business!**
Of course, you want to have as many people as possible in your business, so you should care. So extend them a warm welcome!
## Tools that can assist in browsing a website
I want to introduce you to some tools which can help to browse a website if you have some disability:
- Speech recognition software which allows dictating words and commands to the computer. Helpful for people who cannot use a keyboard or mouse to interact with the computer.
- Subtitled or sign language versions for deaf people.
- Software that enlarges the content of your monitor, which can help people with visual impairments.
- Screen reader software which uses synthesized speech to read out elements on the computer display.
It would be best if you try them to get a feeling for them. I would especially recommend testing screen readers. You can read more about how to use them [here](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Screenreaders){rel=""nofollow""}.
This video shows the usage of a screen reader:
[](https://www.youtube.com/watch?v=xpP_Km5L46E){rel=""nofollow""}
## How can I make a site more accessible?
| User Constraint | Accessibility Solution |
| --------------------------------------- | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| Cannot use a mouse or standard keyboard | Code your page in a way that the navigation also works without a mouse. Therefore it is important to [not remove the outline property](http://www.outlinenone.com/){rel=""nofollow""}. |
| Visual impairment | Use larger texts and images as well as a good color contrast on the page. |
| Blindness | For screen readers, having a semantically meaningful HTML (more about that below the table) and textual description of images and links (e.g. using the alt-tag to describe an image: ``) is helpful |
| Deaf and hard-of-hearing | Add closed captioned videos or provide a sign language version. |
| Color blind | Underline and color links (or differentiate otherwise) to help color blind users notice them. |
These are just some of the examples, a good checklist with more information is available at [Web Content Accessibility Guidelines 2.0](http://romeo.elsevier.com/accessibility_checklist/){rel=""nofollow""}.
In general using `
` as HTML tag should be avoided if possible. Therefore you can always check this amazing graphic from [HTML5 Doctor](http://html5doctor.com/downloads/h5d-sectioning-flowchart.pdf){rel=""nofollow""}:

I would also advise using tooling that assists during development like [eslint-plugin-jsx-a11y](https://www.npmjs.com/package/eslint-plugin-jsx-a11y){rel=""nofollow""}, which is an npm package that provides a static AST checker for accessibility rules on JSX elements.
## How to test if my website is accessible
I mainly use [Google Lighthouse](https://developers.google.com/web/tools/lighthouse/){rel=""nofollow""} to check if my site is accessible.
You can use one of the many accessibility checklists available online, but I recommend using any of the tools described in [Accessibility Testing Tools ](https://css-tricks.com/accessibility-testing-tools/){rel=""nofollow""}.
## Is it time-consuming to implement accessibility?
Yes, if the project is already in a late-stage or you have a legacy code base with massive accessibility issues which you now need to fix.
No, if you can consider accessibility from the beginning of a project and care about it throughout the development.
## Why an accessible website is a good website
- A well-structured semantic HTML website helps to improve your SEO. A search engine bot is, for example, blind, can’t hear, and has the cognitive abilities of a young child. So he is one of your most crucial website visitors. If he cannot correctly access your website, you will get a lower rank in the search requests.
- Nearly everyone can access & interact with your website
- Caring about accessibility is a good attribute of a professional web developer
- An accessible website feels way more professional
- You will save money as you will not need to respond to support questions from users with disabilities.
## Summary
After this article, I hope you understand why web accessibility is important and why you should care about it.
It is not about providing support for a minority of people but to providing a good user experience for **every user** of your website.
Your website is the front door of your business: Let your users know that they are welcome, that you care about them and that your business cares about quality and professionalism.
Hopefully, you are now also a mentor for other developers who still believe that accessibility is not necessary.
## Important Links
- [MDN "What is accessibility?"](https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility){rel=""nofollow""}
- [Google Web Fundamentals Accessibility](https://developers.google.com/web/fundamentals/accessibility/){rel=""nofollow""}
- [Why Web Accessibility Is Important and How You Can Accomplish It](https://medium.com/fbdevclagos/why-web-accessibility-is-important-and-how-you-can-accomplish-it-4f59fda7859c){rel=""nofollow""}
- [Web Accessibility Checklist](https://a11yproject.com/checklist){rel=""nofollow""}
# Why I Developed My Own Nuxt Starter Kit for SaaS Products
Beginning in 2025, I had the idea for a micro SaaS product that I wanted to build. I didn't want to start from scratch, so I looked for existing Nuxt starter kits that could help me get up and running quickly. However, I found that most of the available options were either too generic or not tailored to my specific needs.
I decided to take matters into my own hands and develop [Nuxt Starter Kit](https://nuxtstarterkit.com){rel=""nofollow""}, a highly opinionated and custom solution that would serve as a solid foundation for my SaaS projects. This decision was not just about building a product; it was about creating a development environment that would enhance productivity, reduce maintenance costs, and align perfectly with the unique requirements of my applications.
## The Motivation for a Highly Opinionated Nuxt Starter Kit
I got access to [Supastarter](https://supastarter.dev/){rel=""nofollow""} and [Super SaaS](https://supersaas.dev/){rel=""nofollow""}, two very famous Nuxt starter kits, but I found that they didn't fully meet my needs. Unfortunately, both of them did not reflect my coding style and preferences, which made it difficult to work with them effectively.
These starter kits were created for maximum flexibility, which is great for some use cases, but I needed something more focused and opinionated. I wanted a solution that would not only provide a solid foundation but also enforce best practices and coding standards that I value.
To reduce the maintenance overhead, I wanted to limit my starter kit to three main components:
- [Nuxt Hub](https://hub.nuxt.com/){rel=""nofollow""}: a platform for deploying and scaling Nuxt applications globally, powered by Cloudflare.
- [Nuxt UI Pro](https://ui.nuxt.com/pro?aff=z1NAy){rel=""nofollow""}: a collection of premium Vue components, composables and utils built on top of Nuxt UI.
- [Polar](https://polar.sh){rel=""nofollow""}: an open source Merchant of Record (MoR) solution that simplifies payment processing, tax compliance, and subscription management.
These tools would provide the necessary functionality while keeping the codebase lean and efficient.
## Development Process and Features
I started developing the main features of the [Nuxt Starter Kit](https://nuxtstarterkit.com){rel=""nofollow""}, and rewrote my existing SaaS product [CodeSnap](https://codesnap.dev){rel=""nofollow""} using this new foundation. The development process was smooth, and I was able to quickly convert my existing codebase to the new structure.
This way I could ensure that the starter kit was not just theoretical but practical and battle-tested. Additionally, I used the starter kit to build the landing page for the [Nuxt Starter Kit](https://nuxtstarterkit.com){rel=""nofollow""} itself and published [Sigxel](https://sigxel.com){rel=""nofollow""}, a micro SaaS to manage email signatures.
## Conclusion
Ultimately, creating [Nuxt Starter Kit](https://nuxtstarterkit.com){rel=""nofollow""} for my SaaS products has proven to be a highly rewarding endeavor. Not only does it provide a foundation that is perfectly aligned with specific project requirements, but it also reduces the overhead of maintaining a bloated codebase. Leveraging modern tools like Nuxt Hub, Nuxt UI Pro, and Polar adds to the robustness of the final product, ensuring enhanced performance and scalability for long-term success.
By choosing to develop your own solution instead of adapting a generic starter kit, you invest in a system that evolves with your business, offering a competitive edge in the ever-innovative world of SaaS products.
::tip
With discount code `TN8JDLYO`, the 20 first users can get 30% off. [Buy now!](https://nuxtstarterkit.com){rel=""nofollow""}
::
# Why I Love Vue 3's Composition API
[Vue 3](https://v3.vuejs.org/){rel=""nofollow""} introduced the [Composition API](https://v3.vuejs.org/guide/composition-api-introduction.html){rel=""nofollow""} to provide a better way to collocate code related to the same logical concern. In this article, I want to tell you why I love this new way of writing Vue components.
First, I will show you how you can build components using Vue 2, and then I will show you the same component implemented using Composition API. I'll explain some of the Composition API basics and why I prefer Composition API for building components.
For this article, I created a [Stackblitz Vue 3 demo application](https://stackblitz.com/edit/vue-3-composition-api-demo?file=src/App.vue){rel=""nofollow""} which includes all the components that I'll showcase in this article:
:stackblitz{project-id="vue-3-composition-api-demo"}
The source code is also available on [GitHub](https://github.com/Mokkapps/vue-3-composition-api-demo/){rel=""nofollow""}.
## Options API
First, let's look at how we build components in Vue 2 without the Composition API.
In Vue 2 we build components using the Options API by filling (option) properties like methods, data, computed, etc. An example component could look like this:
```vue
...
```
As you can see, Options API has a significant drawback: The logical concerns (filtering, sorting, etc.) are not grouped but split between the different options of the Options API. Such fragmentation is what makes it challenging to understand and maintain complex Vue components.
Let's start by looking at [CounterOptionsApi.vue](https://github.com/Mokkapps/vue-3-composition-api-demo/blob/master/src/components/CounterOptionsApi.vue){rel=""nofollow""}, the Options API counter component:
```vue
Counter Options API
Count: {{ count }}
2^Count: {{ countPow }}
```
This simple counter component includes multiple essential Vue functionalities:
- We use a `count` data property that uses the `initialValue` property as its initial value.
- `countPow` as computed property which calculates the `count` value to the power of two.
- A watcher that emits the `counter-update` event if the `count` value has changed.
- Multiple methods to modify the `count` value.
- A `console.log` message that is written if the [mounted lifecycle hook](https://vuejs.org/v2/api/#mounted){rel=""nofollow""} was triggered.
If you are not familiar with the Vue 2 features mentioned above, you should first read the [official Vue 2 documentation](https://vuejs.org/v2/guide/){rel=""nofollow""} before you continue reading this article.
## Composition API
Since Vue 3 we can **additionally** use [Composition API](https://v3.vuejs.org/guide/composition-api-introduction.html#why-composition-api){rel=""nofollow""} to build Vue components.
::note
Composition API is fully optional, and we can still use Options API in Vue 3.
::
In my [demo application](https://stackblitz.com/edit/vue-3-composition-api-demo?file=src/App.vue){rel=""nofollow""} I use the same template for all Vue components, so let's focus on the `
```
Let's analyze this code:
The entry point for all Composition API components is the new `setup` method. It is executed **before** the component is created and once the props are resolved. The function returns an object, and all of its properties are exposed to the rest of the component.
::warning
We should avoid using `this` inside setup as it won't refer to the component instance. `setup` is called before data properties, computed properties, or methods are resolved, so that they won't be available within setup.
::
But we need to be careful: The variables we return from the setup method are, by default, not reactive.
We can use the `reactive` method to create a reactive state from a JavaScript object. Alternatively, we can use `ref` to make a standalone primitive value (for example, a string, number, or boolean) reactive:
```ts
import { reactive, ref } from 'vue'
const state = reactive({
count: 0,
})
console.log(state.count) // 0
const count = ref(0)
console.log(count.value) // 0
```
The `ref` object contains only one property named `value`, which can access the property value.
Vue 3 also provides different new methods like `computed`, `watch`, or `onMounted` that we can use in our `setup` method to implement the same logic we used in the Options API component.
### Extract Composition Function
But we can further improve our Vue component code by extracting the counter logic to a standalone **composition function** ([useCounter](https://github.com/Mokkapps/vue-3-composition-api-demo/blob/master/src/composables/useCounter.ts){rel=""nofollow""}):
```ts
import { ref, computed, onMounted } from 'vue'
export default function useCounter(initialValue: number) {
const count = ref(initialValue)
const increment = () => {
count.value += 1
}
const decrement = () => {
count.value -= 1
}
const incrementBy = (value: number) => {
count.value += value
}
const countPow = computed(() => count.value * count.value)
onMounted(() => console.log('useCounter mounted'))
return {
count,
countPow,
increment,
decrement,
incrementBy,
}
}
```
This drastically reduces the code in our [CounterCompositionApiv2.vue](https://github.com/Mokkapps/vue-3-composition-api-demo/blob/master/src/components/CounterCompositionApiv2.vue){rel=""nofollow""} component and additionally allows us to use the counter functionality in any other component:
```vue
```
In Vue 2, [Mixins](https://vuejs.org/v2/guide/mixins.html#Basics){rel=""nofollow""} were mainly used to share code between components. But they have a few issues:
- It's impossible to pass parameters to the mixin to change its logic which drastically reduces its flexibility.
- Property name conflicts can occur as properties from each mixin are merged into the same component.
- It isn't necessarily apparent which properties came from which mixin if a component uses multiple mixins.
Composition API addresses all of these issues.
### SFC Script Setup
[Vue 3.2](https://blog.vuejs.org/posts/vue-3.2.html){rel=""nofollow""} allows us to get rid of the `setup` method by providing the `
```
### Using the Composition API with Vue 2
If you can’t migrate to Vue 3 today, then you can still use the Composition API already. You can do this by installing [the official Composition API Vue 2 Plugin](https://github.com/vuejs/composition-api){rel=""nofollow""}.
## Conclusion
You've seen the same counter component created in Vue 2 using Options API and created in Vue 3 using Composition API.
Let's summarize all the things I love about Composition API:
- More readable and maintainable code with the feature-wise separation of concerns brought with the composition API.
- No more `this` keyword, so we can use arrow functions and use functional programming.
- We can only access the things we return from the `setup` method, making things more readable.
- Vue 3 is written in TypeScript and [fully supports Composition API](https://v3.vuejs.org/guide/typescript-support.html#using-with-composition-api){rel=""nofollow""}.
- Composition functions can easily be unit tested.
The following image shows a large component where colors group its logical concerns and compares Options API versus Composition API:

You can see that Composition API groups logical concerns, resulting in better maintainable code, especially for larger and complex components.
I can understand that many developers still prefer Options API as it is easier to teach people who are new to the framework and have JavaScript knowledge. But I would recommend that you use Composition API for complex applications that require a lot of domains and functionality. Additionally, Options API does not work very well with TypeScript, which is, in my opinion, also a must-have for complex applications.
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""}.
# Why I Picked Vue.js as My Freelancer Niche
I have professional experience with the three big players in web development: [Angular](https://angular.io){rel=""nofollow""}, [Vue.js](https://vuejs.org/){rel=""nofollow""} and [React](https://reactjs.org/){rel=""nofollow""}.
I've reached the point in my career where I need to choose one of the three frameworks/libraries that I will use for my future freelancing career.
As the title already reveals, I chose Vue and in this article, I will describe to you why I picked it instead of React or Angular.
::warning
This article will not provide a full comparison between the three technologies.
::
## Why Do I Need a Niche?

Finding your niche as a freelancer can have an extremely positive impact on your career. It took me some time to find mine, but finally, I found it and I can take my business to the next level. It has some advantages to be a jack of all but in the end, it's even better to be the master of one trade. Having a niche can boost your income, helps to find new projects easier, and is useful to advertise yourself as an expert.
I can also give you an example of how the niche saves me time every day:
My previous search queries for job agents on freelancer platforms looked like this: `React OR Angular OR TypeScript OR JavaScript OR React Native OR Vue`. This way, I got job agent emails with dozens of job offers that I had to manually scan for interesting projects.
With a niche in place, I modified these search queries to `Vue` and now the job agent emails contain only a few freelancer projects but they are tailored to my skills.
## My Freelancing History
When I started freelancing back in 2019 my tech focus was on web development using the [Angular](https://angular.io){rel=""nofollow""} framework.
But for my first freelancing project I choose a [Vue.js](https://vuejs.org/){rel=""nofollow""} project and I stayed there for about two years. I chose this project
because I already had professional experience with Angular and some experience with React as I used it for my [portfolio website](https://mokkapps.de) and two React Native apps that I developed and published. I wanted to see how it compares to Angular and React. After this project, beginning from January to September 2021 I worked in a [React](https://reactjs.org/){rel=""nofollow""} project as I wanted to gain some professional experience with the library.
I could easily further specialize in Angular, but I have no good belly feeling with this choice. Therefore, I had to choose between React and Vue.
## What I Love About Vue
> TL;DR: In my opinion, 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.

### Less Usage of JavaScript's "this" keyword
Angular components are full of the JavaScript keyword `this`. I don't like this and thankfully we can write React and Vue components without the `this` keyword by using [React Hooks](https://reactjs.org/docs/hooks-intro.html){rel=""nofollow""} and [Vue 3's Composition API](https://v3.vuejs.org/api/composition-api.html){rel=""nofollow""}.
### Outstanding Documentation
The [official Vue documentation](https://v3.vuejs.org/guide/introduction.html){rel=""nofollow""} is amazing and one of the best resources to learn Vue.
### Best Parts of React and Angular
In its early development phase, Vue took inspiration from the good things of [AngularJS](https://angularjs.org/){rel=""nofollow""} (the first version of Angular).
Vue also got inspired by React and they share some similarities:
- They have their focus in the core library. Concerns like global state management and routing are handled by separate companion libraries.
- Both provide reactive and composable view components.
- One and the other use a virtual DOM.
### Less Optimization Efforts
In Vue, I need to care less about optimization efforts in comparison to React. React triggers a re-rendering of the entire component tree when a component's state changes. Read my article ["Debug Why React (Re-)Renders a Component"](https://www.mokkapps.de/blog/debug-why-react-re-renders-a-component/){rel=""nofollow""} for further details.
There are multiple ways to avoid unnecessary re-rendering of child components in React:
- use [PureComponent](https://reactjs.org/docs/react-api.html#reactpurecomponent){rel=""nofollow""}
- implement `shouldComponentUpdate` if you are using class components
- use immutable data structures
Angular developers also need to take care of the change detection, you can 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""} if you want to deep-dive into that mechanism.
Vue automatically tracks a component's dependencies during its render. Therefore, it knows precisely which components need to be re-rendered when the state changes. As a Vue developer, I can more focus on building the app than on performance optimizations.
### Templates
Vue uses HTML templates, but there’s an option to write the render function in [JSX](https://reactjs.org/docs/introducing-jsx.html){rel=""nofollow""}. On the other hand, in React there's solely JSX. A Vue component is split into three parts: HTML (``), CSS (`
```
```json
{
"a": 10
}
```
```js
var a = 10
```
```ts
var a = 10
```
```css
div {
color: red;
}
```
```html
{{ msg }}
```
```js
export default {
data() {
return {
msg: 'Focused!', // [!code focus]
}
},
}
```
```js
export default {
data () {
return {
msg: 'Removed' // [!code --]
msg: 'Added' // [!code ++]
}
}
}
```
::code-group
```js [config.js] {1-3}
/**
* @type {import('vitepress').UserConfig}
*/
const config = {
// ...
}
export default config
```
```ts [config.ts] {2}
import type { UserConfig } from 'vitepress'
const config: UserConfig = {
// ...
}
export default config
```
::
::note
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
:nuxt-link[Home]{inactive-class="text-[var(--ui-primary)]" to="https://mokkapps.de"}
::
::warning
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
:nuxt-link[Home]{inactive-class="text-[var(--ui-primary)]" to="https://mokkapps.de"}
::
::caution
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
:nuxt-link[Home]{inactive-class="text-[var(--ui-primary)]" to="https://mokkapps.de"}
::
:stackblitz{project-id="nuxt-content-v2-custom-code-blocks"}
# Über mich
## Über mich
Mein Name ist Michael Hoffmann, ich bin freiberuflicher Software-Entwickler und wohne in Rattenberg in Deutschland. Ich habe an der Technischen Universität in München meinen Master in Elektrotechnik absolviert und arbeite seit mehr als :years-professional-experience Jahren als professioneller Softwareentwickler.
Ich arbeite für Firmen unterschiedlichster Größen und konnte bereits Erfahrung in der Zusammenarbeit mit einigen großen Konzernen und kleineren Firmen sammeln. In meiner beruflichen Karriere habe ich bisher mehrere Projekte von Grund auf entwickelt, gewartet und veröffentlicht. Außerdem konnte ich auch wertvolle Erfahrung in der Weiterentwicklung bestehender Anwendungen sammeln.
Programmieren ist meine Leidenschaft, insbesondere die Entwicklung komplexer Business-Applikationen sowohl im Frontend als auch im Backend ist für mich spannend. Ich genieße meine tägliche Arbeit als freiberuflicher Entwickler und auch die Entwicklung meiner [privaten Projekte](https://mokkapps.de/projects). Ich schreibe [Blog Artikel](https://mokkapps.de/blog) und [halte Vorträge](https://mokkapps.de/publications), weil ich gerne mein Wissen mit anderen Leuten teilen will. Deswegen veröffentliche ich auch viele meiner Projekte auf [GitHub](https://github.com/mokkapps){rel=""nofollow""}.
Mein Schwerpunkt liegt auf der Vue.js- und Nuxt.js-Entwicklung als Freelancer. Zusätzlich arbeite ich als KI-gestützter Entwickler und integriere KI-Tools in meinen täglichen Workflow, um schneller zu liefern — ohne Kompromisse bei der Code-Qualität, immer mit solidem technischem Urteilsvermögen im Mittelpunkt.
Du kennst vielleicht als **Mokkapps** auf verschiedenen Plattformen.
# Arbeite mit mir zusammen
## Arbeite mit mir zusammen
### Warum ich die perfekte Wahl für Ihre Vue.js- & Nuxt.js-Projekte bin
Wenn Sie einen Vue.js/Nuxt.js-Freelancer suchen, der technisches Know-how mit Professionalität verbindet, dann sind Sie bei mir genau richtig:
- **Tiefgehende Expertise in Vue & Nuxt**: :br
Ich habe mich seit Jahren ausschließlich auf die Entwicklung mit Vue.js und Nuxt.js spezialisiert. Egal ob es um anspruchsvolle Kundenprojekte oder private Experimente geht – diese Technologien sind meine Leidenschaft.
- **Vordenker in der Community**: :br
Ich teile regelmäßig meine Erkenntnisse über Vue und Nuxt auf [meiner Portfolio-Website](https://mokkapps.de){rel=""nofollow""} und betreibe einen [wöchentlichen Vue- & Nuxt-Newsletter](https://weekly-vue.news){rel=""nofollow""}, um Entwickler über die neuesten Trends und Updates auf dem Laufenden zu halten.
- **Starke Verbindungen in der Branche**: :br
Durch meine aktive Präsenz in den sozialen Medien habe ich enge Kontakte zu den Core-Maintainern von Vue und Nuxt aufgebaut. So bleibe ich immer auf dem neuesten Stand und bin mit den Roadmaps der Frameworks bestens vertraut.
- **KI-gestützte Entwicklung**: :br
Erfahren in der Integration von KI-Tools (z. B. Cursor, GitHub Copilot, LLM-gestütztes Coding) in professionelle Vue.js/Nuxt.js-Workflows — für schnellere Lieferung ohne Qualitätseinbußen.
- **Bewährte Remote-Arbeitsfähigkeiten**: :br
Ich nehme Remote-Arbeit sehr ernst:
- Bei Meetings ist meine Kamera immer eingeschaltet, um eine klare Kommunikation zu gewährleisten.
- Mein Verfügbarkeitsstatus ist immer korrekt, und ich antworte schnell, wenn ich verfügbar bin.
- Kunden vertrauen darauf, dass ich qualitativ hochwertige Arbeit mit minimaler Aufsicht liefere.
- **Hervorragende Kundenzufriedenheit**: :br
Meine bisherigen Kunden betonen immer wieder meine Zuverlässigkeit, mein technisches Können und meine Fähigkeit, Ergebnisse zu liefern, die ihre Erwartungen übertreffen. Ihre positiven Bewertungen bestätigen, dass ich die Investition wert bin.
Wenn Sie einen Vue.js/Nuxt.js-Spezialisten suchen, der sowohl außergewöhnliche Ergebnisse als auch professionelle Kommunikation bietet, lassen Sie uns besprechen, wie ich Ihr Projekt zum Erfolg führen kann!
### Tech Stack
Wenn ich mir meinen Traum-Tech-Stack aussuchen dürfte, würde ich folgende Technologien wählen:
- [Nuxt.js](https://nuxt.com){rel=""nofollow""}
- [Vue.js](https://vuejs.org){rel=""nofollow""}
- [TypeScript](https://www.typescriptlang.org/){rel=""nofollow""}
- [Tailwind](https://tailwindcss.com/){rel=""nofollow""}
### Skills
:skills
### Grundanforderungen
- Vollständig remote oder remote-first (ich bin in Deutschland ansässig, UTC +2 / CEST)
- Vollzeit (40 Std./Woche)
- Arbeit an einem Softwareprodukt (keine projektbezogene Arbeit)
### Was ich nicht mache
Normalerweise entwickle ich keine einfachen Websites, sondern bin eher an komplexen Webanwendungen oder mobilen Apps interessiert.
Ich kann einfache Logos erstellen oder bei der Auswahl von Typografie und Farben helfen. Wenn du weitere Hilfe für deine Marke benötigst, vermittle ich dich gerne an einen Grafik- und Markendesigner meines Vertrauens.
### Was mir bei meinem nächsten Job wichtig ist
### Menschen sind mir wichtig
- Ich möchte über einen längeren Zeitraum in einem festen Team arbeiten, um Verbindungen aufzubauen, gute Wege der Kommunikation und Zusammenarbeit zu finden und Empathie füreinander zu entwickeln. Meiner Erfahrung nach macht dies die Menschen in einem Team produktiv und glücklich.
- Ich möchte an einem Ort arbeiten, der den Menschen bewusst Raum gibt, um diese Art von Verbindung aufzubauen, und der die Zeit, die für den Aufbau und die Verbesserung von Teams aufgewendet wird, wertschätzt.
- Ich bevorzuge die Arbeit in einem cross-funktionalen Team. Die Zusammenarbeit mit jedem und jeder Rolle in meinem Team ist für mich wichtig.
#### Code und Qualität sind mir wichtig
- Ich bin spezialisiert auf die Entwicklung komplexer Anwendungen auf Basis von Vue.js & Nuxt.js.
- Ich entwickle sie immer mit Blick auf den Endbenutzer, da dies auch Ihnen hilft, Ihre Geschäftsziele zu erreichen.
- Ich bin interessiert an der Entwicklung von Backend- und Frontend-Anwendungen.
- Ich bin zuverlässig und ehrlich zu meinen Kunden und Auftraggebern.
- Ich produziere hochqualitativen, sauberen, gut dokumentierten und leicht zu wartenden Code und respektiere dabei Fristen und Budgets.
- Ich verwende hohe Code-Qualitätsstandards und statische Code-Analysatoren.
- Ich schreibe gerne Tests, egal ob Unit-, Integrations- oder End-to-End-Tests (E2E).
- Ich mag strikte Typisierung in JavaScript, insbesondere mit TypeScript.
- Ich verwende modernes CSS mit Grid, Flexbox und Animationen.
- Ich lege Wert auf zugängliches, valides und semantisches HTML.
- Ich erstelle schöne responsive Produkte, die auf verschiedenen Geräten und Browsern funktionieren.
### Ich interessiere mich für das Produkt
- Ich möchte an den Diskussionen darüber teilnehmen, wie wir unseren Kunden einen Mehrwert bieten können und wie wir Funktionen weiterentwickeln können.
- Ich wünsche mir eine vertrauensvolle, respektvolle Beziehung zum Produktmanagement, in der jede Seite die Expertise der anderen respektiert und wir über Bedürfnisse kommunizieren - für das Produkt, für den Code und auch für uns als Menschen in unseren Rollen.
- Es macht mir großen Spaß, neue Branchen kennenzulernen, vor allem wenn das Produkt einen positiven Einfluss hat.
#### Mir ist die Unternehmenskultur wichtig
- Ich möchte in einer offenen Kultur arbeiten, in der häufige Rückmeldungen geschätzt werden, sicher und ehrlich sind.
- Die Werte und die Kultur eines Unternehmens sollten Tag für Tag aktiv gelebt werden - vor allem in stressigen Zeiten und wenn schwierige Entscheidungen getroffen werden müssen.
- Transparenz ist wichtig. Sie sollte nie aufhören, wenn sich die mitgeteilten Informationen unangenehm anfühlen oder schlechte Nachrichten sind.
- Das Management sollte die Arbeit an Vielfalt und Integration aktiv und sichtbar vorantreiben.
# 27 Helpful Tips for Vue Developers

- Free eBook, 1th edition
- 40+ pages
- 4400+ words
- Includes code examples & playground links
- Available as PDF, ePub (Android and iOS) and Mobi (Kindle)
::note
Subscribe at [Weekly Vue News](https://weekly-vue.news){rel=""nofollow""} to get your free copy!
::
## Content
- Tip 1: Prefer Slots Over Props
- Tip 2: Reactive Values in CSS
- Tip 3: Detailed Prop Definitions
- Tip 4: Props and Context in Setup Method
- Tip 5: Avoid Empty Class Attributes
- Tip 6: Destructure in v-for
- Tip 7: Avoid Unwanted Re-Renders of an Element Using v-once
- Tip 8: Use Multiple v-model Bindings
- Tip 9: Trigger Watcher Immediately
- Tip 10: Simple Expressions in Templates
- Tip 11: Assign Handler for Uncaught Errors
- Tip 12: Use Teleport to Render a Component in a Different Place
- Tip 13: Automatic Global Registration of Base Components
- Tip 14: Display Raw HTML
- Tip 15: Scroll to Top When Navigating to a New Route
- Tip 16: Speed Up Initial Load Using Async Components
- Tip 17: Use Two Script Blocks
- Tip 18: Use Optional Chaining in Templates
- Tip 19: Special CSS Selectors
- Tip 20: Use Vuex in Vue Router Navigation Guards
- Tip 21: Watch Nested Values
- Tip 22: Use v-bind to Pass Multiple Props to Components
- Tip 23: Animate Child Component Before Route Leave
- Tip 24: Measure Performance
- Tip 25: Check Version at Runtime
- Tip 26: Query Inner Elements in Third-Party Components
- Tip 27: Create Custom v-model Modifier
## Preview

# About me
## About me
My name is Michael Hoffmann. I'm a freelance software engineer based in Rattenberg/Germany. I hold a Master's in Electrical Engineering from the renowned Technical University of Munich (TUM) and have :years-professional-experience years of professional experience.
I work for businesses of all sizes and have experience with small and medium enterprises and corporates. In my career, I have developed, maintained, and launched multiple projects from scratch or improved an existing code base.
Programming is my passion, especially developing complex business applications both in the frontend and backend. I enjoy working as a freelance developer and developing [private projects](https://mokkapps.de/projects). I write [blog posts](https://mokkapps.de/blog) and [do talks](https://mokkapps.de/publications) because I like to share my knowledge with others. Therefore, I also try to share most of my projects on [GitHub](https://github.com/mokkapps){rel=""nofollow""}.
My primary expertise is Vue.js and Nuxt.js freelance development. I also work as an AI-assisted developer, integrating AI tools into my daily workflow to ship faster while maintaining high code quality — always with solid engineering judgment at the core.
You might know me as **Mokkapps** on various platforms.
# Work with me
## Work with me
### Why I’m the Perfect Fit for Your Vue.js & Nuxt.js Projects
If you’re looking for a Vue.js/Nuxt.js freelancer who combines technical expertise with professionalism, here’s why I’m the ideal choice:
- **Deep Expertise in Vue & Nuxt**: :br
I’ve specialized exclusively in Vue.js and Nuxt.js development for years. Whether it’s building cutting-edge client projects or experimenting in private endeavors, I live and breathe these technologies.
- **Thought Leader in the Community**: :br
I regularly share my insights about Vue and Nuxt on my [portfolio website](https://mokkapps.de){rel=""nofollow""} and run a [weekly Vue & Nuxt newsletter](https://weekly-vue.news){rel=""nofollow""} to help developers stay informed about the latest trends and updates.
- **Strong Industry Connections**: :br
Through active engagement on social media, I’ve built strong relationships with Vue and Nuxt core maintainers, keeping me ahead of the curve and in tune with the frameworks' roadmaps.
- **AI-Assisted Development**: :br
Experienced in integrating AI tools (e.g. Cursor, GitHub Copilot, LLM-assisted coding) into professional Vue.js/Nuxt.js workflows for faster delivery without sacrificing quality.
- **Proven Remote Work Skills**: :br
I take remote work seriously:
- My meeting setup always includes my camera on, ensuring clear communication.
- My availability status is always accurate, and I respond quickly when online.
- Clients trust me to deliver high-quality work with minimal oversight.
- **Exceptional Client Satisfaction**: :br
My previous clients consistently highlight my reliability, technical skill, and ability to deliver results that exceed expectations. Their glowing testimonials confirm that I’m worth the investment.
If you’re looking for a Vue.js/Nuxt.js specialist who delivers both exceptional results and professional communication, let’s discuss how I can help bring your project to life!
### Tech Stack
If I had to choose my dream tech stack, I'd pick the following:
- [Nuxt.js](https://nuxt.com){rel=""nofollow""}
- [Vue.js](https://vuejs.org){rel=""nofollow""}
- [TypeScript](https://www.typescriptlang.org/){rel=""nofollow""}
- [Tailwind](https://tailwindcss.com/){rel=""nofollow""}
### Skills
:skills
### Base requirements
- Fully remote or remote-first (I’m based in Germany, UTC +2 / CEST)
- Full-time (40hrs/week)
- Working on a software product (no project-based work)
### What I Don't Do
Usually, I do not develop simple websites, but I am more interested in complex web applications or mobile apps.
I can provide simple logos or help you pick typography or colors. If you need further help with your brand, I am happy to connect you with a trusted graphic and brand designer.
### What is important to me in my next job
#### I care about people
- I want to work in a fixed team for longer to build connections, find good ways to communicate and work together and build empathy for each other. In my experience, this makes team members productive–and happy.
- I want to work in a place that consciously gives explicit room to enable people to build this kind of connection and values the time spent on team building and improvement.
- I prefer working in a cross-functional team. Collaborating with everybody and every role in my team is crucial to me.
#### I care about code and quality
- I specialize in building complex applications based on Vue.js & Nuxt.js.
- I always develop them with your end-user in mind, as this will help you meet your business goals.
- I am interested in doing development work in backend & frontend applications.
- I am reliable and honest with my customers and clients.
- I produce high-quality, clean, well-documented, and easily maintainable code while respecting deadlines and budgets.
- I use high code quality standards and static code analyzers.
- I like to write tests, whether unit, integration, or end-to-end (E2E).
- I like strict typing in JavaScript, especially using TypeScript.
- I use modern CSS using Grid, Flexbox, and Animations.
- I value accessible, valid, and semantic HTML.
- I create beautifully responsive products that work across various devices and browsers.
#### I care about the product
- I want to be part of the discussions on bringing value to our customers and how to iterate over features.
- I wish for a trustful, respectful relationship with Product Management, where every side respects the expertise of the other, and we communicate about needs–for the product, the code, and also for us as humans in our roles.
- It is great fun to learn about new industries, especially if the product has a positive impact.
#### I care about company culture
- I want to work in an open culture where frequent feedback is appreciated, safe and honest.
- A company’s values and culture should be actively lived daily–especially during stressful times and when hard decisions have to be made.
- Transparency is essential. It should never end when the shared information feels uncomfortable or is terrible news.
- Management actively and visibly drive work on diversity and inclusion forward.
# Legal Disclosure
## Contact Information
Michael Hoffmann
Siegersdorf 18
94371 Rattenberg
Telephone: +4915141257551
E-Mail:
Internet address: {rel=""nofollow""}
## Disclaimer
#### Accountability for content
The contents of our pages have been created with the utmost care. However, we cannot guarantee the contents' accuracy, completeness or topicality. According to statutory provisions, we are furthermore responsible for our own content on these web pages. In this matter, please note that we are not obliged to monitor the transmitted or saved information of third parties, or investigate circumstances pointing to illegal activity. Our obligations to remove or block the use of information under generally applicable laws remain unaffected by this as per §§ 8 to 10 of the Telemedia Act (TMG).
#### Accountability for links
Responsibility for the content of external links (to web pages of third parties) lies solely with the operators of the linked pages. No violations were evident to us at the time of linking. Should any legal infringement become known to us, we will remove the respective link immediately.
#### Copyright
Our web pages and their contents are subject to German copyright law. Unless expressly permitted by law, every form of utilizing, reproducing or processing works subject to copyright protection on our web pages requires the prior consent of the respective owner of the rights. Individual reproductions of a work are only allowed for private use. The materials from these pages are copyrighted and any unauthorized use may violate copyright laws.
Source: [Impressum Generator](http://www.translate-24h.de/){rel=""nofollow""}
# Parents Soundboard
::callout{color="warning" icon="i-heroicons-exclamation-triangle-solid"}
The app is no longer available in the app stores because I don't have the time to maintain it anymore.
::
Used Technology: [React Native](https://facebook.github.io/react-native/){rel=""nofollow""}
Source Code: [GitHub](https://github.com/Mokkapps/parents-soundboard){rel=""nofollow""}
You are annoyed speaking the same sentences over and over again to your children?
If yes, this soundboard will be your new best friend.
You can define custom sentences and let the smartphone speak them for you.
Your kids will hate it ;-)

# Privacy Policy
## TL;DR
- We do not snoop, sell or have any intention to monetize your data.
- We use [Umami](https://umami.is/){rel=""nofollow""} for website analytics.
- We use [Sentry](https://sentry.io/){rel=""nofollow""} for error reporting and debugging.
- We use [BuySellAds](https://www.buysellads.com/){rel=""nofollow""} to show ads on the website.
- The on-site AI chat sends your messages to [OpenAI](https://openai.com/){rel=""nofollow""} to generate replies, and may forward a copy of the conversation to [Slack](https://slack.com/){rel=""nofollow""} so the site operator can operate and improve the chat.
---
We are very delighted that you have shown interest in our enterprise. Data protection is of a particularly high priority for the management of the Mokkapps. The use of the Internet pages of the Mokkapps is possible without any indication of personal data; however, if a data subject wants to use special enterprise services via our website, processing of personal data could become necessary. If the processing of personal data is necessary and there is no statutory basis for such processing, we generally obtain consent from the data subject.
The processing of personal data, such as the name, address, e-mail address, or telephone number of a data subject shall always be in line with the General Data Protection Regulation (GDPR), and in accordance with the country-specific data protection regulations applicable to the Mokkapps. By means of this data protection declaration, our enterprise would like to inform the general public of the nature, scope, and purpose of the personal data we collect, use and process. Furthermore, data subjects are informed, by means of this data protection declaration, of the rights to which they are entitled.
As the controller, the Mokkapps has implemented numerous technical and organizational measures to ensure the most complete protection of personal data processed through this website. However, Internet-based data transmissions may in principle have security gaps, so absolute protection may not be guaranteed. For this reason, every data subject is free to transfer personal data to us via alternative means, e.g. by telephone.
### Definitions
The data protection declaration of the Mokkapps is based on the terms used by the European legislator for the adoption of the General Data Protection Regulation (GDPR). Our data protection declaration should be legible and understandable for the general public, as well as our customers and business partners. To ensure this, we would like to first explain the terminology used.
In this data protection declaration, we use, inter alia, the following terms:
a) Personal data
Personal data means any information relating to an identified or identifiable natural person (“data subject”). An identifiable natural person is one who can be identified, directly or indirectly, in particular by reference to an identifier such as a name, an identification number, location data, an online identifier or to one or more factors specific to thephysical, physiological, genetic, mental, economic, cultural or social identity of that natural person.
b) Data subject
Data subject is any identified or identifiable natural person, whose personal data is processed by the controller responsible for the processing.
c) Processing
Processing is any operation or set of operations which is performed on personal data or on sets of personal data, whether or not by automated means, such as collection, recording, organisation, structuring, storage, adaptation or alteration, retrieval, consultation, use, disclosure by transmission, dissemination or otherwise making available, alignment or combination, restriction, erasure or destruction.
d) Restriction of processing
Restriction of processing is the marking of stored personal data with the aim of limiting their processing in the future.
e) Profiling
Profiling means any form of automated processing of personal data consisting of the use of personal data to evaluate certain personal aspects relating to a natural person, in particular to analyse or predict aspects concerning that natural person's performance at work, economic situation, health, personal preferences, interests, reliability, behaviour, location or movements.
f) Pseudonymisation
Pseudonymisation is the processing of personal data in such a manner that the personal data can no longer be attributed to a specific data subject without the use of additional information, provided that such additional information is kept separately and is subject to technical and organisational measures to ensure that the personal data are not attributed to an identified or identifiable natural person.
g) Controller or controller responsible for the processing
Controller or controller responsible for the processing is the natural or legal person, public authority, agency or other body which, alone or jointly with others, determines the purposes and means of the processing of personal data; where the purposes and means of such processing are determined by Union or Member State law, the controller or the specific criteria for its nomination may be provided for by Union or Member State law.
h) Processor
Processor is a natural or legal person, public authority, agency or other body which processes personal data on behalf of the controller.
i) Recipient
Recipient is a natural or legal person, public authority, agency or another body, to which the personal data are disclosed, whether a third party or not. However, public authorities which may receive personal data in the framework of a particular inquiry in accordance with Union or Member State law shall not be regarded as recipients; the processing of those data by those public authorities shall be in compliance with the applicable data protection rules according to the purposes of the processing.
j) Third party
Third party is a natural or legal person, public authority, agency or body other than the data subject, controller, processor and persons who, under the direct authority of the controller or processor, are authorised to process personal data.
k) Consent
Consent of the data subject is any freely given, specific, informed and unambiguous indication of the data subject's wishes by which he or she, by a statement or by a clear affirmative action, signifies agreement to the processing of personal data relating to him or her.
### Name and Address of the controller
Controller for the purposes of the General Data Protection Regulation (GDPR), other data protection laws applicable in Member states of the European Union and other provisions related to data protection is:
Michael Hoffmann
Siegersdorf 18
94371 Rattenberg
Germany
Phone: +4915141257551
Email:
Website: [www.mokkapps.de](http://www.mokkapps.de){rel=""nofollow""}
### Collection of general data and information
The website of the Mokkapps collects a series of general data and information when a data subject or automated system calls up the website. This general data and information are stored in the server log files. Collected may be (1) the browser types and versions used, (2) the operating system used by the accessing system, (3) the website from which an accessing system reaches our website (so-called referrers), (4) the sub-websites, (5) the date and time of access to the Internet site and information that may be used in the event of attacks on our information technology systems.
When using these general data and information, the Mokkapps does not draw any conclusions about the data subject. Rather, this information is needed to (1) deliver the content of our website correctly, (2) optimize the content of our website as well as its advertisement, (3) ensure the long-term viability of our information technology systems and website technology, and (4) provide law enforcement authorities with the information necessary for criminal prosecution in case of a cyber-attack. Therefore, the Mokkapps analyzes anonymously collected data and information statistically, with the aim of increasing the data protection and data security of our enterprise, and to ensure an optimal level of protection for the personal data we process. The anonymous data of the server log files are stored separately from all personal data provided by a data subject.
:do-not-track-button
Mokkapps uses [BuySellAds](https://www.buysellads.com/){rel=""nofollow""} to show ads on the website. They don't collect any sensitive information, more information can be found [here](https://content.buysellads.com/publishers/what-data-does-buysellads-collect){rel=""nofollow""}. They will use the IP address and useragent to set the campaign targeting, but they're not storing them for fingerprinting/retargeting.
### Contact possibility via the website
The website of the Mokkapps contains information that enables a quick electronic contact to our enterprise, as well as direct communication with us, which also includes a general address of the so-called electronic mail (e-mail address). If a data subject contacts the controller by e-mail or via a contact form, the personal data transmitted by the data subject are automatically stored. Such personal data transmitted on a voluntary basis by a data subject to the data controller are stored for the purpose of processing or contacting the data subject. There is no transfer of this personal data to third parties.
### AI chat on the website
The Mokkapps website offers an optional AI chat. If a data subject uses the chat, the messages entered in the chat are transmitted to OpenAI to generate a reply. For operating the chat (including quality control and abuse monitoring), a copy of the chat turn (user message, assistant reply, locale, detected intent, and a random conversation identifier) may also be forwarded to Slack. Do not submit sensitive personal data via the chat. Processing is based on Art. 6(1) lit. f GDPR (legitimate interest in providing and operating the chat service).
### Comments function in the blog on the website
The Mokkapps offers users the possibility to leave individual comments on individual blog contributions on a blog, which is on the website of the controller. A blog is a web-based, publicly-accessible portal, through which one or more people called bloggers or web-bloggers may post articles or write down thoughts in so-called blogposts. Blogposts may usually be commented by third parties.
If a data subject leaves a comment on the blog published on this website, the comments made by the data subject are also stored and published, as well as information on the date of the commentary and on the user's (pseudonym) chosen by the data subject. In addition, the IP address assigned by the Internet service provider (ISP) to the data subject is also logged. This storage of the IP address takes place for security reasons, and in case the data subject violates the rights of third parties, or posts illegal content through a given comment. The storage of these personal data is, therefore, in the own interest of the data controller, so that he can exculpate in the event of an infringement. This collected personal data will not be passed to third parties, unless such a transfer is required by law or serves the aim of the defense of the data controller.
### Subscription to comments in the blog on the website
The comments made in the blog of the Mokkapps may be subscribed to by third parties. In particular, there is the possibility that a commenter subscribes to the comments following his comments on a particular blog post.
If a data subject decides to subscribe to the option, the controller will send an automatic confirmation e-mail to check the double opt-in procedure as to whether the owner of the specified e-mail address decided in favor of this option. The option to subscribe to comments may be terminated at any time.
### Routine erasure and blocking of personal data
The data controller shall process and store the personal data of the data subject only for the period necessary to achieve the purpose of storage, or as far as this is granted by the European legislator or other legislators in laws or regulations to which the controller is subject to.
If the storage purpose is not applicable, or if a storage period prescribed by the European legislator or another competent legislator expires, the personal data are routinely blocked or erased in accordance with legal requirements.
### Rights of the data subject
a) Right of confirmation
Each data subject shall have the right granted by the European legislator to obtain from the controller the confirmation as to whether or not personal data concerning him or her are being processed. If a data subject wishes to avail himself of this right of confirmation, he or she may, at any time, contact any employee of the controller.
b) Right of access
Each data subject shall have the right granted by the European legislator to obtain from the controller free information about his or her personal data stored at any time and a copy of this information. Furthermore, the European directives and regulations grant the data subject access to the following information:
the purposes of the processing;
the categories of personal data concerned;
the recipients or categories of recipients to whom the personal data have been or will be disclosed, in particular recipients in third countries or international organisations;
where possible, the envisaged period for which the personal data will be stored, or, if not possible, the criteria used to determine that period;
the existence of the right to request from the controller rectification or erasure of personal data, or restriction of processing of personal data concerning the data subject, or to object to such processing;
the existence of the right to lodge a complaint with a supervisory authority;
where the personal data are not collected from the data subject, any available information as to their source;
the existence of automated decision-making, including profiling, referred to in Article 22(1) and (4) of the GDPR and, at least in those cases, meaningful information about the logic involved, as well as the significance and envisaged consequences of such processing for the data subject.
Furthermore, the data subject shall have a right to obtain information as to whether personal data are transferred to a third country or to an international organisation. Where this is the case, the data subject shall have the right to be informed of the appropriate safeguards relating to the transfer.
If a data subject wishes to avail himself of this right of access, he or she may, at any time, contact any employee of the controller.
c) Right to rectification
Each data subject shall have the right granted by the European legislator to obtain from the controller without undue delay the rectification of inaccurate personal data concerning him or her. Taking into account the purposes of the processing, the data subject shall have the right to have incomplete personal data completed, including by means of providing a supplementary statement.
If a data subject wishes to exercise this right to rectification, he or she may, at any time, contact any employee of the controller.
d) Right to erasure (Right to be forgotten)
Each data subject shall have the right granted by the European legislator to obtain from the controller the erasure of personal data concerning him or her without undue delay, and the controller shall have the obligation to erase personal data without undue delay where one of the following grounds applies, as long as the processing is not necessary:
The personal data are no longer necessary in relation to the purposes for which they were collected or otherwise processed.
The data subject withdraws consent to which the processing is based according to point (a) of Article 6(1) of the GDPR, or point (a) of Article 9(2) of the GDPR, and where there is no other legal ground for the processing.
The data subject objects to the processing pursuant to Article 21(1) of the GDPR and there are no overriding legitimate grounds for the processing, or the data subject objects to the processing pursuant to Article 21(2) of the GDPR.
The personal data have been unlawfully processed.
The personal data must be erased for compliance with a legal obligation in Union or Member State law to which the controller is subject.
The personal data have been collected in relation to the offer of information society services referred to in Article 8(1) of the GDPR.
If one of the aforementioned reasons applies, and a data subject wishes to request the erasure of personal data stored by the Mokkapps, he or she may, at any time, contact any employee of the controller. An employee of Mokkapps shall promptly ensure that the erasure request is complied with immediately.
Where the controller has made personal data public and is obliged pursuant to Article 17(1) to erase the personal data, the controller, taking account of available technology and the cost of implementation, shall take reasonable steps, including technical measures, to inform other controllers processing the personal data that the data subject has requested erasure by such controllers of any links to, or copy or replication of, those personal data, as far as processing is not required. An employees of the Mokkapps will arrange the necessary measures in individual cases.
e) Right of restriction of processing
Each data subject shall have the right granted by the European legislator to obtain from the controller restriction of processing where one of the following applies:
The accuracy of the personal data is contested by the data subject, for a period enabling the controller to verify the accuracy of the personal data.
The processing is unlawful and the data subject opposes the erasure of the personal data and requests instead the restriction of their use instead.
The controller no longer needs the personal data for the purposes of the processing, but they are required by the data subject for the establishment, exercise or defence of legal claims.
The data subject has objected to processing pursuant to Article 21(1) of the GDPR pending the verification whether the legitimate grounds of the controller override those of the data subject.
If one of the aforementioned conditions is met, and a data subject wishes to request the restriction of the processing of personal data stored by the Mokkapps, he or she may at any time contact any employee of the controller. The employee of the Mokkapps will arrange the restriction of the processing.
f) Right to data portability
Each data subject shall have the right granted by the European legislator, to receive the personal data concerning him or her, which was provided to a controller, in a structured, commonly used and machine-readable format. He or she shall have the right to transmit those data to another controller without hindrance from the controller to which the personal data have been provided, as long as the processing is based on consent pursuant to point (a) of Article 6(1) of the GDPR or point (a) of Article 9(2) of the GDPR, or on a contract pursuant to point (b) of Article 6(1) of the GDPR, and the processing is carried out by automated means, as long as the processing is not necessary for the performance of a task carried out in the public interest or in the exercise of official authority vested in the controller.
Furthermore, in exercising his or her right to data portability pursuant to Article 20(1) of the GDPR, the data subject shall have the right to have personal data transmitted directly from one controller to another, where technically feasible and when doing so does not adversely affect the rights and freedoms of others.
In order to assert the right to data portability, the data subject may at any time contact any employee of the Mokkapps.
g) Right to object
Each data subject shall have the right granted by the European legislator to object, on grounds relating to his or her particular situation, at any time, to processing of personal data concerning him or her, which is based on point (e) or (f) of Article 6(1) of the GDPR. This also applies to profiling based on these provisions.
The Mokkapps shall no longer process the personal data in the event of the objection, unless we can demonstrate compelling legitimate grounds for the processing which override the interests, rights and freedoms of the data subject, or for the establishment, exercise or defence of legal claims.
If the Mokkapps processes personal data for direct marketing purposes, the data subject shall have the right to object at any time to processing of personal data concerning him or her for such marketing. This applies to profiling to the extent that it is related to such direct marketing. If the data subject objects to the Mokkapps to the processing for direct marketing purposes, the Mokkapps will no longer process the personal data for these purposes.
In addition, the data subject has the right, on grounds relating to his or her particular situation, to object to processing of personal data concerning him or her by the Mokkapps for scientific or historical research purposes, or for statistical purposes pursuant to Article 89(1) of the GDPR, unless the processing is necessary for the performance of a task carried out for reasons of public interest.
In order to exercise the right to object, the data subject may contact any employee of the Mokkapps. In addition, the data subject is free in the context of the use of information society services, and notwithstanding Directive 2002/58/EC, to use his or her right to object by automated means using technical specifications.
h) Automated individual decision-making, including profiling
Each data subject shall have the right granted by the European legislator not to be subject to a decision based solely on automated processing, including profiling, which produces legal effects concerning him or her, or similarly significantly affects him or her, as long as the decision (1) is not is necessary for entering into, or the performance of, a contract between the data subject and a data controller, or (2) is not authorised by Union or Member State law to which the controller is subject and which also lays down suitable measures to safeguard the data subject's rights and freedoms and legitimate interests, or (3) is not based on the data subject's explicit consent.
If the decision (1) is necessary for entering into, or the performance of, a contract between the data subject and a data controller, or (2) it is based on the data subject's explicit consent, the Mokkapps shall implement suitable measures to safeguard the data subject's rights and freedoms and legitimate interests, at least the right to obtain human intervention on the part of the controller, to express his or her point of view and contest the decision.
If the data subject wishes to exercise the rights concerning automated individual decision-making, he or she may, at any time, contact any employee of the Mokkapps.
i) Right to withdraw data protection consent
Each data subject shall have the right granted by the European legislator to withdraw his or her consent to processing of his or her personal data at any time.
If the data subject wishes to exercise the right to withdraw the consent, he or she may, at any time, contact any employee of the Mokkapps.
### 1ß. Legal basis for the processing
Art. 6(1) lit. a GDPR serves as the legal basis for processing operations for which we obtain consent for a specific processing purpose. If the processing of personal data is necessary for the performance of a contract to which the data subject is party, as is the case, for example, when processing operations are necessary for the supply of goods or to provide any other service, the processing is based on Article 6(1) lit. b GDPR. The same applies to such processing operations which are necessary for carrying out pre-contractual measures, for example in the case of inquiries concerning our products or services. Is our company subject to a legal obligation by which processing of personal data is required, such as for the fulfillment of tax obligations, the processing is based on Art. 6(1) lit. c GDPR. In rare cases, the processing of personal data may be necessary to protect the vital interests of the data subject or of another natural person. This would be the case, for example, if a visitor were injured in our company and his name, age, health insurance data or other vital information would have to be passed on to a doctor, hospital or other third party. Then the processing would be based on Art. 6(1) lit. d GDPR. Finally, processing operations could be based on Article 6(1) lit. f GDPR. This legal basis is used for processing operations which are not covered by any of the abovementioned legal grounds, if processing is necessary for the purposes of the legitimate interests pursued by our company or by a third party, except where such interests are overridden by the interests or fundamental rights and freedoms of the data subject which require protection of personal data. Such processing operations are particularly permissible because they have been specifically mentioned by the European legislator. He considered that a legitimate interest could be assumed if the data subject is a client of the controller (Recital 47 Sentence 2 GDPR).
### The legitimate interests pursued by the controller or by a third party
Where the processing of personal data is based on Article 6(1) lit. f GDPR our legitimate interest is to carry out our business in favor of the well-being of all our employees and the shareholders.
### Period for which the personal data will be stored
The criteria used to determine the period of storage of personal data is the respective statutory retention period. After expiration of that period, the corresponding data is routinely deleted, as long as it is no longer necessary for the fulfillment of the contract or the initiation of a contract.
### Provision of personal data as statutory or contractual requirement; Requirement necessary to enter into a contract; Obligation of the data subject to provide the personal data; possible consequences of failure to provide such data
We clarify that the provision of personal data is partly required by law (e.g. tax regulations) or can also result from contractual provisions (e.g. information on the contractual partner). Sometimes it may be necessary to conclude a contract that the data subject provides us with personal data, which must subsequently be processed by us. The data subject is, for example, obliged to provide us with personal data when our company signs a contract with him or her. The non-provision of the personal data would have the consequence that the contract with the data subject could not be concluded. Before personal data is provided by the data subject, the data subject must contact any employee. The employee clarifies to the data subject whether the provision of the personal data is required by law or contract or is necessary for the conclusion of the contract, whether there is an obligation to provide the personal data and the consequences of non-provision of the personal data.
### Existence of automated decision-making
As a responsible company, we do not use automatic decision-making or profiling.
This Privacy Policy has been generated by the Privacy Policy Generator of the DGD - Your External DPO that was developed in cooperation with German Lawyers from WILDE BEUGER SOLMECKE, Cologne.
# RebelGamer
::callout{color="warning" icon="i-heroicons-exclamation-triangle-solid"}
The app is no longer available in the app stores because I don't have the time to maintain it anymore.
::
Used Technology: [React Native](https://facebook.github.io/react-native/){rel=""nofollow""}
Source Code: [GitHub](https://github.com/Mokkapps/rebelgamer-mobile-app){rel=""nofollow""}

# Standup Picker
Used Technologies: [Electron](https://electronjs.org/){rel=""nofollow""}, [Vue.js](https://vuejs.org/){rel=""nofollow""}
Source Code: [GitHub](https://github.com/Mokkapps/scrum-daily-standup-picker){rel=""nofollow""}
## Features
- Randomly select a team member. You can click on team member images to "ignore" them if they are not attending at the standup.
- Play standup music at a given time.
- Inform about ending standup time by a sound.
## Releases
All releases are available [here](https://github.com/Mokkapps/scrum-daily-standup-picker/releases){rel=""nofollow""}.

[](https://youtu.be/7MHk09N5APM "Standup Picker"){rel=""nofollow""}
# Supermarket Challenge
::callout{color="warning" icon="i-heroicons-exclamation-triangle-solid"}
The app is no longer available in the app stores because I don't have the time to maintain it anymore.
::
Used Technology: [Corona SDK](https://coronalabs.com/){rel=""nofollow""}

Supermarket Challenge delivers an addictive gameplay experience!
Challenge yourself and try to sustain at the supermarket cash register. Set a highscore and share it with your friends for a new challenge.
Alternatively you can try one of the 12 levels to get a better gameplay feeling.
You have to scan articles, enter the correct barcode for fruits and throw bombs away before they explode.
Surprise boxes can include useful extras or have a negative surprise for you.
Be careful! Unscanned articles in the shopping basket or articles which are thrown out of the screen, cost a life. You have three lifes per round.
:base-app-store-button{store="ios" url="https://itunes.apple.com/de/app/supermarket-challenge/id1207665675"}
:base-app-store-button{store="android" url="https://play.google.com/store/apps/details?id=de.mokkapps.supermarketchallenge"}
# JavaScript Tip: Throw an Error if a Required Parameter Is Missing
:you-tube-embed{url="https://www.youtube.com/embed/4tdRSYnybps"}
Default function parameters allow named parameters to be initialized with default values if no value or `undefined` is passed.
We can use this approach to write a function that throws an error if a required parameter is missing:
```js [index.js] {1-3,5}
const isRequired = () => {
throw new Error('Parameter is required!')
}
const foo = (bar = isRequired()) => {
console.log(bar)
}
foo() // throws the isRequired error
foo(undefined) // throws the isRequired error
foo(false) // logs "false"
foo(null) // logs "null"
foo('Test') // logs "true"
```
# JavaScript Tip: Get Valuable Info About Device Battery
The [Battery Status API](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API){rel=""nofollow""} provides information about the system's battery charge level and lets you be notified by events sent when the battery level or charging status changes.
Knowing a device's battery status can be helpful in several situations or use cases. Here are some examples:
- Save the application state before the battery runs out to prevent data loss.
- Pause heavy computations/animations when the battery is low.
- For example, an email client may check the server for new emails less frequently if the device is low on battery.
- Switch to dark mode when the battery is low, which can save energy.
Let's take a look at a simple example to get the device's battery status:
```js [battery.js]
navigator.getBattery().then((battery) => {
console.log(`Battery level: ${Math.round(battery.level * 100)}%`)
// Battery level: 57%
console.log(`Battery discharging time: ${battery.dischargingTime / 60} minutes`)
// Battery discharging time: 179 minutes
})
```
# JavaScript Tip: How to Sort an Array of Integers
In JavaScript, `Array.prototype.sort()` sorts the elements of an array in place and returns the reference to the same array, now sorted.
The default sort order is ascending, built upon converting the elements into strings and comparing their UTF-16 code unit value sequences.
`sort()` mutates the original array. Therefore we use `[...numbers]` to create a shallow copy of the array in the following example.
To sort an array of integers, we must provide a compare function that defines the sort order:
```js [sort.js] {3,6}
const numbers = [10, 3, 45, 999, 20]
const ascendingSortedNumbers = [...numbers].sort((a, b) => a - b)
console.log(ascendingSortedNumbers) // [ 3, 10, 20, 45, 999 ]
const descendingSortedNumbers = [...numbers].sort((a, b) => b - a)
console.log(descendingSortedNumbers) // [ 999, 45, 20, 10, 3 ]
```
# JavaScript Tip: Replace Switch Statements With Object Literals
:you-tube-embed{url="https://www.youtube.com/embed/odXyNaFsfFg"}
I'm not a big fan of JavaScript's `switch` statement. Its syntax is hard to remember and can cause tricky bugs if you forget to add a `break` statement for every case.
Let's take a look at an example:
```js [index.js]
const getRole = (id) => {
switch (id) {
case 11:
return 'ADMIN'
case 22:
return 'OPERATOR'
default:
return 'USER'
}
}
```
**I prefer using JavaScript's object literals over `switch` statements as the code is faster, more readable, and less verbose.**
For each case we would have in the `switch` statement, we need to define an object key for each case:
```js [index.js]
const getRole = (id) => {
const rolesMap = {
11: 'ADMIN',
22: 'OPERATOR',
33: 'USER',
}
return rolesMap[id]
}
```
Finally, let's handle the `default` case of the `switch` statement by adding a default key:
```js [index.js] {2,10}
const getRole = (id) => {
const defaultKey = 33
const rolesMap = {
11: 'ADMIN',
22: 'OPERATOR',
33: 'USER',
}
return rolesMap[id] ?? rolesMap[defaultKey]
}
```
We try to access the value using `rolesMap[id]` and use the nullish coalescing operator (`??`) to set the default value if the value is `undefined` or `null`.
# JavaScript Tip: Return Object Literal From an Arrow Function
ECMAScript 2015 introduced the use of arrow functions, which offer a concise syntax for defining functions without the need for the `function` keyword.
An example of this shorthand syntax can be seen in a function that doubles a given integer:
```js
const doubleCount = (value) => {
return {
doubleCount: value * 2,
}
}
```
You might be tempted to simplify the code by removing the `return` statement, like this:
```js
const doubleCount = (value) => {
doubleCount: value * 2
}
```
Upon calling the `doubleCount` function, it is observed that it is not functioning as intended. Regardless of the input value passed, the function returns `undefined`. Why is that?
The problem with the arrow function is that it's not returning the expected value because the parser is treating the braces as a **block statement** instead of an **object literal**. As a result, the parser is interpreting the label `doubleCount` as belonging to the expression statement `value * 2` and there is no return statement, resulting in the function returning `undefined`.
To correct this behavior, you must ensure that the parser interprets the object literal as an expression instead of a block statement. This can be achieved by adding parentheses around the entire body of the function:
```js
const doubleCount = (value) => ({
doubleCount: value * 2,
})
```
# Uses
In the past, I stumbled upon many exciting tools by investigating the development setups of other developers. Therefore, I thought I could also present you my setup.
I change up things sometimes, so this page will serve as a living document and a place to point to curious developers when I get asked.
## Software
### IDE + Editor + Terminal
- [Visual Studio Code](https://code.visualstudio.com/){rel=""nofollow""} - My text editor.
- [IntelliJ Ultimate](https://www.jetbrains.com/idea/){rel=""nofollow""} - My IDE.
- [Ghostty](https://ghostty.org/){rel=""nofollow""} as terminal including [Oh My Zsh](https://github.com/robbyrussell/oh-my-zsh){rel=""nofollow""} with [avit theme](https://github.com/robbyrussell/oh-my-zsh/wiki/themes#avit){rel=""nofollow""}.
- [lazygit](https://github.com/jesseduffield/lazygit){rel=""nofollow""} - A simple terminal UI for git commands.
- [Cascadia Code](https://github.com/microsoft/cascadia-code){rel=""nofollow""} - My monospaced font.
### Desktop Apps
- [Zen Browser](https://zen-browser.app/){rel=""nofollow""} - My main browser.
- [Insomnia](https://insomnia.rest/){rel=""nofollow""} - API development and testing.
- [Enpass](https://enpass.io/){rel=""nofollow""} - Password management.
- [Parallels](https://www.parallels.com/){rel=""nofollow""} - Windows virtualization.
- [FileZilla](https://filezilla-project.org){rel=""nofollow""} - FTP client.
- [Rambox](https://rambox.app/){rel=""nofollow""} - A messaging app.
- [Spotify](https://www.spotify.com/){rel=""nofollow""} - My music streaming service.
- [Raycast](https://www.raycast.com/){rel=""nofollow""} - A blazingly fast and totally extendable launcher.
- [Kimai](https://kimai.org){rel=""nofollow""} - My time tracking software.
- [Shottr](https://shottr.cc/){rel=""nofollow""} - Free screenshot tool for Mac with premium features.
- [Screenflow](https://www.telestream.net/screenflow/overview.htm){rel=""nofollow""} - My software to edit videos and screen recordings.
- [Screen Studio](https://www.screen.studio/?aff=z1NAy){rel=""nofollow""} - Beautiful screen recordings in minutes.
- [Obsidian](https://obsidian.md/){rel=""nofollow""} - As a markdown allrounder.
### AI Development Tools
- [Cursor](https://cursor.com/){rel=""nofollow""} - My AI-powered code editor for Vue.js and Nuxt.js development.
- [GitHub Copilot](https://github.com/features/copilot){rel=""nofollow""} - AI pair programming in the IDE.
- [ChatGPT](https://chatgpt.com/){rel=""nofollow""} - For research, architecture discussions, and problem solving.
I integrate these tools into my daily Vue.js development workflow to ship faster while keeping code quality high.
## Hardware
### Notebook
I use a MacBook M2 Pro (16-inch, 2023) for development.
### Monitor
I use the [Samsung C34H890WJU 32" 4k curved monitor](https://amzn.to/3CDHSoE){rel=""nofollow""}.
### Mechanical Keyboard
I use the [Varmillo VA88M](https://geekboards.de/products/varmilo-ink-rhyme-87-ansi){rel=""nofollow""} ISO-DE with clear MX switches:
### Mouse
I use the [Logitech MX Master 2 Wireless Mouse](https://amzn.to/3vWDjSO){rel=""nofollow""} as my wireless mouse.
### Headphones
I use the [Bose QuietComfort 35](https://www.bose.de/de_de/products/headphones/over_ear_headphones/quietcomfort-35-wireless-ii.html){rel=""nofollow""}
### Cameras and Lighting
- My webcam is a [Logitech HD Pro c920](https://amzn.to/3X6PuZa){rel=""nofollow""}
- My microphone is an [Elgato Wave:3](https://amzn.to/3CDEkTm){rel=""nofollow""} on a [InnoGear microphone arm](https://amzn.to/3ioTg16){rel=""nofollow""}
- I use a [simple ring light](https://amzn.to/3GWJwUW){rel=""nofollow""} in combination with a [benQ Screenbar Halo](https://amzn.to/3itftuT){rel=""nofollow""}
### Standing Desk
I use the [IKEA BEKANT standing desk](https://www.ikea.com/us/en/p/bekant-desk-sit-stand-white-stained-oak-veneer-white-s99282086/#content){rel=""nofollow""} and the [inwerk Masterlift 2](https://www.inwerk-bueromoebel.de/buerotische/hoehenverstellbare-schreibtische/hoehenverstellbarer-schreibtisch-masterlift-2-schwarz-weiss){rel=""nofollow""}
# Vue Tip: Access DOM in Watcher Callback After Vue Updated It
If you make changes to reactive state in a Vue component, it can trigger both component updates and any watcher callbacks that you have created.
By default, these callbacks are executed before the component updates, so if you try to access the DOM inside a callback, it will be in its pre-update state.
**To access the DOM after Vue has updated it in a watcher callback**, you can use the `flush: 'post'` option:
```vue [Component.vue] {3,7}
```
Example for Vue 2 with Options API:
```vue [Component.vue] {7}
```
Post-flush `watchEffect()` also has a convenience alias, `watchPostEffect()`:
```vue [Component.vue]
```
# Nuxt Tip: Accessing Pinia Store in Production Build
During local development, you can easily debug your Pinia store using the [Nuxt Devtools](https://devtools.nuxt.com/){rel=""nofollow""}. However, when you build your Nuxt application for production, the way you access your Pinia store changes slightly.
To access your Pinia store in a Nuxt production build, you can use the following command in your browser's console:
```bash
useNuxtApp().$pinia.state.value
```
# Vue Tip: Accessing Template Ref in Child Component
Sometimes you need to access a [template ref](https://v3.vuejs.org/guide/component-template-refs.html){rel=""nofollow""} of a nested component. For example, you want to focus an input field in a child component from the parent component.
Let's take a look at how you can do this in Vue 3:
```vue [Child.vue] {4,6-8,12}
```
In the child component, we define a template ref called `innerChildRef` and expose it to the parent component using `defineExpose()`.
Now you can access this template ref in the parent component:
```vue [Parent.vue] {6,10,16}
```
## StackBlitz Demo
Try it yourself in the following StackBlitz project:
:stackblitz{project-id="vue-tip-accessing-template-ref-in-child-component"}
# Nuxt Tip: Add Custom iframe Tab to Nuxt DevTools
[Nuxt DevTools](https://devtools.nuxtjs.org/){rel=""nofollow""} is designed to be extensible and you can add your own modules' integration to it.
If you want to add your module as a new tab in Nuxt DevTools you need to serve your module's view and integrate it via iframe.
The first option to register your custom tab is to use the utility kit provided by Nuxt DevTools:
```ts
import { addCustomTab } from '@nuxt/devtools-kit'
addCustomTab({
// unique identifier
name: 'my-module',
// title to display in the tab
title: 'My Module',
// any icon from Iconify, or a URL to an image
icon: 'carbon:apps',
// iframe view
view: {
type: 'iframe',
src: '/url-to-your-module-view',
},
})
```
Alternatively, you can use Nuxt hooks:
```ts
nuxt.hook('devtools:customTabs', (tabs) => {
tabs.push({
// unique identifier
name: 'my-module',
// title to display in the tab
title: 'My Module',
// any icon from Iconify, or a URL to an image
icon: 'carbon:apps',
// iframe view
view: {
type: 'iframe',
src: '/url-to-your-module-view',
},
})
})
```
Let's see it in action:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
dev: {
enabled: true,
},
modules: [
'@nuxt/devtools',
(inlineOptions, nuxt) => {
nuxt.hook('devtools:customTabs', (tabs) => {
tabs.push({
name: 'doom',
title: 'Doom',
icon: 'carbon:apps',
view: {
type: 'iframe',
src: 'https://silentspacemarine.com/',
},
})
})
},
],
})
```
If you now open the Nuxt DevTools you can find a new tab called "Doom":

# Nuxt Tip: An URL Object Working on Both Server-Side and Client-Side
Sometimes you need access to the current [URL object](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL){rel=""nofollow""} in your Nuxt application. For example, you might want to get the current path or query parameters.
If you are using SSR (Server Side Rendering) your code will be executed on the server and the client. This means that you need to make sure that your code works on both the server and the client. For example, you can't use the `window` object on the server.
A simple workaround is to add an `if` statement to check if the `window` object is available:
```vue [App.vue]
```
This works but it's not very elegant. It would be better if we could use the same code on both the server and the client.
Luckily, Nuxt provides a helper function called [useRequestURL](https://nuxt.com/docs/api/composables/use-request-url#userequesturl){rel=""nofollow""} that returns a URL object working on both the server side and client side:
```vue [App.vue]
URL is: {{ url }}
Path is: {{ url.pathname }}
```
Try it yourself in the following StackBlitz playground:
:stackblitz{project-id="nuxt-tip-use-request-url"}
# Vue Tip: Animate Child Component Before Route Leave
[Vue Router](https://router.vuejs.org){rel=""nofollow""} provides a way to use [transitions](https://router.vuejs.org/guide/advanced/transitions.html){rel=""nofollow""} on your route components and animate navigations.
But sometimes, you might want to animate a specific (sub-)component before a route is left.
Therefore, you can use the [onBeforeRouteLeave navigation guard](https://router.vuejs.org/guide/advanced/composition-api.html#navigation-guards){rel=""nofollow""} combined with a [ref](https://vuejs.org/api/built-in-special-attributes.html#ref){rel=""nofollow""} to toggle the visibility of the component:
::note
The `v-if` is necessary to trigger the animation. More at the [official docs](https://vuejs.org/guide/built-ins/transition.html#the-transition-component){rel=""nofollow""}.
::
```vue
```
Let's take a closer look at the code inside `onBeforeRouteLeave`:
```js
onBeforeRouteLeave((to, from, next) => {
if (to.name === 'Home') {
showUI.value = false
setTimeout(() => {
next()
}, 1000)
} else {
next()
}
})
```
If the following route is anything other than `Home`, we allow the navigation and call `next`.
If the following route is `Home`, we toggle the component's visibility by setting `showUI` to `false`. Next, we call `setTimeout` with 1 second to wait until the animation is done and then proceed with the route change by calling `next`.
The animation time is defined in our `
```
# Vue Tip: Assign Handler for Uncaught Errors
You should monitor your errors in production. Vue, therefore, provides the
[errorHandler](https://v3.vuejs.org/api/application-config.html#errorhandler){rel=""nofollow""} for uncaught errors during component render function and watchers.
The handler gets called with the error and the application instance.
```js
/**
* Handles Vue errors
* @param err - the error trace
* @param vm - VM component
* @param info - Vue-specific error info, e.g. which lifecycle hook the error was found in
*/
app.config.errorHandler = (err, vm, info) => {
// handle error
}
```
Error tracking services [Sentry](https://sentry.io/for/vue/){rel=""nofollow""} and [Bugsnag](https://docs.bugsnag.com/platforms/browsers/vue/){rel=""nofollow""} provide official integrations for the `errorHandler`.
It's also possible to add the [errorCaptured](https://v3.vuejs.org/api/options-lifecycle-hooks.html#errorcaptured){rel=""nofollow""} lifecycle hook to your component.
It is called when an error from any descendent component is captured.
# Vue Tip: Automatic Global Registration of Base Components
Base components are relatively generic components, like basic inputs or buttons. These components are frequently used across
our application inside other components.
A typical component import might look like this:
```js
import BaseButton from './BaseButton.vue'
import BaseIcon from './BaseIcon.vue'
import BaseInput from './BaseInput.vue'
export default {
components: {
BaseButton,
BaseIcon,
BaseInput,
},
}
```
These imports are necessary to be able to reference the components inside our template:
```html
```
Using [webpack](https://webpack.js.org/){rel=""nofollow""} or [Vue CLI](https://github.com/vuejs/vue-cli){rel=""nofollow""} we can use `require.context`
to globally register only these very common base components.
Here's a code example from [the official Vue docs](https://v3.vuejs.org/cookbook/automatic-global-registration-of-base-components.html#base-example){rel=""nofollow""}
to globally import base components in your app's entry file (e.g. `src/main.js`):
```js
import { createApp } from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
import App from './App.vue'
const app = createApp(App)
const requireComponent = require.context(
// The relative path of the components folder
'./components',
// Whether or not to look in subfolders
false,
// The regular expression used to match base component filenames
/Base[A-Z]\w+\.(vue|js)$/
)
requireComponent.keys().forEach((fileName) => {
// Get component config
const componentConfig = requireComponent(fileName)
// Get PascalCase name of component
const componentName = upperFirst(
camelCase(
// Gets the file name regardless of folder depth
fileName
.split('/')
.pop()
.replace(/\.\w+$/, '')
)
)
app.component(
componentName,
// Look for the component options on `.default`, which will
// exist if the component was exported with `export default`,
// otherwise fall back to module's root.
componentConfig.default || componentConfig
)
})
app.mount('#app')
```
# Vue Tip: Automatically Import Components
This topic is controversial, but I love it if components are automatically imported. You just have to reference them in your template and they are available. No need to import them manually. This feature is heavily used in [Nuxt 3](https://nuxt.com/docs/guide/concepts/auto-imports){rel=""nofollow""}.
I will show you how to implement this feature in Vue 3 using [unplugin-vue-components](https://github.com/unplugin/unplugin-vue-components){rel=""nofollow""}.
## Installation & Usage
First, install the plugin:
```bash
npm i unplugin-vue-components -D
```
Next, add it to your `vite.config.ts`:
```ts
import Components from 'unplugin-vue-components/vite'
export default defineConfig({
plugins: [
Components({ /* options */ }),
],
})
```
::note
The [GitHub README](https://github.com/unplugin/unplugin-vue-components#installation){rel=""nofollow""} contains installation guides for other bundlers such as Rollup or Webpack.
::
And that's it. Now, you can reference components in your template without importing them manually:
```vue
```
## Demo
Try it yourself in the following StackBlitz project:
:stackblitz{project-id="vue-tip-automatically-import-components"}
# Vue Tip: Avoid Directly DOM Manipulation
It is a no-go to manipulate the DOM in Vue.js directly:
```vue {7}
```
Instead, you want to make use of [refs](https://vuejs.org/guide/essentials/template-refs.html){rel=""nofollow""}:
```vue
```
`ref` is a special attribute that allows us to obtain a direct reference to a specific DOM element or child component instance after it's mounted.
::warning
Note that you can only access the ref **after the component is mounted** because the element doesn't exist until after the first render.
::
Let's take a look at the above example using a template ref:
```vue {2,8-9,12}
```
If you are trying to watch for changes of a template ref, make sure to check if the ref has a null value:
```vue {10-16}
```
# Vue Tip: Avoid Empty Class Attributes
If you want to conditionally add a class to an element in Vue, you might be tempted to use a ternary operator to check if a condition is met.
However, this can lead to an empty class attribute being rendered when the condition is false:
```vue [Component.vue]
```
Instead, use a falsy value like an empty string to avoid this issue:
```vue [Component.vue]
```
# Vue Tip: Avoid Empty Wrapper for Conditions
There are many situations when you need to conditionally display multiple Vue components. To avoid "empty" wrappers you should use the `` instead of other HTML Tags. `` will not generate any HTML tag and serves as an invisible wrapper. The final rendered result will not include the `` element:
::code-card{type="bad"}
```vue
```
::
::code-card{type="good"}
```vue
```
::
`v-else` and `v-else-if` can also be used on ``.
# Vue Tip: Avoid Mutating a Prop Directly
If you are using Vue 3 + [ESLint](https://eslint.vuejs.org/){rel=""nofollow""} and trying to mutate a prop in your Vue component, you should see the following error:
::caution
Unexpected mutation of "todo" prop. *eslintvue/no-mutating-props*
::
If you are using Vue 2, Vue will throw the following error/warning in your browser's console:
::caution
\[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value.
::
## Explanation
First, we look at the [official documentation](https://vuejs.org/guide/components/props.html#one-way-data-flow){rel=""nofollow""} to understand why Vue throws that error.
### Why do we see the error
> When objects and arrays are passed as props, while the child component cannot mutate the prop binding, it will be able to mutate the object or array's nested properties. This is because JavaScript objects and arrays are passed by reference, and it is unreasonably expensive for Vue to prevent such mutations.
### Why is it a problem
> The main drawback of such mutations is that it allows the child component to affect the parent state in a way that isn't obvious to the parent component, potentially making it more difficult to reason about the data flow in the future. **As a best practice, you should avoid such mutations unless the parent and child are tightly coupled by design.**
### Recommended solution
> In most cases, the child should [emit an event](https://vuejs.org/guide/components/events.html){rel=""nofollow""} to let the parent perform the mutation.
## Demo
Let's look at a code example. I'm using a Todo app to demonstrate the error. The source code is interactively available on StackBlitz:
:stackblitz{project-id="vue-tip-avoid-mutating-a-prop-directly"}
Let's start by taking a look at the `TodoList.vue` component:
```vue [TodoList.vue] {5-8,14-16}
Todo
{{ todos }}
Nothing todo
```
`TodoList.vue` contains a reactive variable `todos` that include an array of Todo items. In the template, each item is rendered via `TodoItem.vue` component:
```vue [TodoItem.vue] {11}
{{ todo.name }}
Completed?
```
Assuming ESLint is [correctly configured](https://eslint.vuejs.org/user-guide/#editor-integrations){rel=""nofollow""}, you should see the following ESLint error if you open `TodoItem.vue` in your editor:

## Mutating props is an anti-pattern
We want to write components that are easy to maintain.
In a maintainable component, **only the component itself should be able to change its own state**.
Additionally, **only the component's parent should be able to change the props**.
These two rules are essential to ensure [Vue's One-Way Data Flow](https://vuejs.org/guide/components/props.html#one-way-data-flow){rel=""nofollow""} to make our app's data flow easier to understand.
::warning
If we mutate the props, we violate both rules and break Vue's data flow!
::
In addition, every time the parent component is updated, all props in the child component will be refreshed with the latest value.
## Solution
::note
In most cases, the error can be solved using a [computed property](https://vuejs.org/guide/essentials/computed.html#computed-properties){rel=""nofollow""}.
::
In our example, instead of mutating the prop, we emit an event to the parent. The parent is then responsible for updating the Todo list correctly.
Let's use a writeable computed property in `TodoItem.vue`. Its getter accesses the prop's value, and the setter emits an event to the parent:
```vue [TodoItem.vue] {8-10,12-21,28}
{{ todo.name }}
Completed?
```
Finally, we need to react to the emitted event in `TodoList.vue` and update the `todos` accordingly:
```vue [TodoList.vue] {11-18,25}
Todo
{{ todos }}
Nothing todo
```
The prop is not mutated with this solution, and we correctly use the one-way data flow in our Vue application.
::note
The amazing [Michael Thiessen](https://twitter.com/MichaelThiessen){rel=""nofollow""} also wrote [a detailed article](https://michaelnthiessen.com/avoid-mutating-prop-directly/){rel=""nofollow""} about this topic.
::
## Video
:you-tube-embed{url="https://www.youtube.com/embed/ZaQZblRUnXs"}
# Vue Tip: Avoid Side Effects in Computed Properties
It is considered bad practice to introduce side effects inside computed properties and functions because it makes the code unpredictable and hard to understand.
What is a side effect? In the context of computed properties, a side effect is a modification of the state's global state or the internal component state.
Let's take a look at an example with side effects:
::code-card{title="Code with side effects" type="bad"}
```vue [Component.vue] {9,13}
```
::
Now let's change the code and remove the side effects:
::code-card{title="Code without side effects" type="good"}
```vue [Component.vue]
```
::
Now the code is predictable and easy to understand. The computed properties `fullName` and `reversedArray` only depend on the values of `firstName`, `lastName`, and `array`. They don't modify the global state or the internal component state.
::note{title="Recommended Reading"}
Read [this fantastic article from Michael Thiessen](https://michaelnthiessen.com/side-effect-surgery){rel=""nofollow""} for more details about side effects.
::
# Vue Tip: Best Way to Force Re-Render Vue Component
If you read this article, you're most likely experiencing issues with Vue's reactivity system. In my experience, you're probably doing something wrong and not using Vue's reactivity system correctly.
However, there are scenarios where it's necessary to force the re-rendering of a component. An example of this is when you're using a third-party library that doesn't play well with Vue's reactivity system. If the third-party library directly modifies the DOM and doesn't provide a way to notify Vue of changes, you'll have to force the re-rendering of the component.
Let me show you the correct way to force re-render a Vue component using the `key` attribute:
```vue [App.vue] {4,6-8,13}
```
Each invocation of `forceRender` changes the `componentKey` value, which is added as `key` attribute to `MyComponent`. Vue recognizes this change, destroys the old component instance, and creates a new one.
::note
Check the [official documentation](https://vuejs.org/api/built-in-special-attributes.html#key){rel=""nofollow""} for more information about the `key` attribute.
::
# Vue Tip: Cache Component Instances With the KeepAlive Component
`` is a built-in Vue component that allows you to conditionally cache component instances when dynamically switching between multiple components.
::note
If you are not familiar with the concept of dynamic concepts, you should read the [Dynamic Components](https://mokkapps.de/%5B/vue-tips/dynamic-component%5D\(https://vuejs.org/guide/essentials/component-basics#dynamic-components\)s) documentation first.
::
## Example
Let's take a look at a simple example to understand how `` works. We have two components, `ComponentA` and `ComponentB`, and we want to cache the instances of these components when switching between them.
```vue [App.vue] {18}
```
## Interactive Demo
Try it yourself in the following demo:
:demo-without-keep-alive
If you switch between the components using the radio buttons, you will notice that the component instances are destroyed and recreated every time you switch between them. Thus, the state of each component is lost.
If you want to cache the instances of the components, you can wrap the `` element with the `` component.
```vue [App.vue] {2,4}
```
Let's see how it works in the following demo:
:demo-with-keep-alive
Now, when you switch between the components, the instances are cached, and the state of each component is preserved.
## Additional Features
`` provides additional features to control the caching behavior of the components. You can use the `include` and `exclude` props to specify which components should be cached or excluded from caching.
Let's say you want to cache only the `ComponentA` instances. You can use the `include` prop to specify the name of the component:
```vue [App.vue] {2,4}
```
Try it yourself in the following demo:
:demo-with-keep-alive-include
For more information about the `` component, you can refer to the official documentation: [KeepAlive Component](https://vuejs.org/guide/built-ins/keep-alive.html){rel=""nofollow""}.
## StackBlitz
The following StackBlitz projects contains the source code for the examples shown in this article:
:stackblitz{project-id="cache-component-instances-with-the-keep-alive-component"}
# Vue Tip: Chaining Event Modifiers
Most likely you have already called `event.preventDefault()` or `event.stopPropagation()` inside event handlers:
```vue [Component.vue] {3,10}
```
As you can see, you can easily implement this functionality using methods. but as an alternative, Vue provides event modifiers.
Using these modifiers your **method contains only the data logic** and the DOM event details are part of the template:
```vue [Component.vue] {3,8}
```
Vue provides the following event modifiers:
- `.stop`
- `.prevent`
- `.self`
- `.capture`
- `.once`
- `.passive`
You can also chain modifiers:
```vue [Component.vue] {8}
```
::warning
The sequence of modifiers is significant as the corresponding code is generated in the exact order they appear.
An example: Using `@click.prevent.self` will prevent click's default action on the element itself and its children, while `@click.self.prevent` will only prevent click's default action on the element itself.
::
::warning
Do not use `.passive` and `.prevent` together.
By using the `.passive` modifier, you are effectively signaling to the browser that you have no intention of preventing the default behavior of the event.
::
# Nuxt Tip: Change Status Code of the Response
Nuxt provides the [setResponseStatus](https://nuxt.com/docs/api/utils/set-response-status){rel=""nofollow""} composable to set the status code (and optionally the status message) of the response.
This composable only works on the server and will have no effect on the client. Additionally, it can only be used in the [Nuxt Content](https://nuxt.com/docs/guide/going-further/nuxt-app#the-nuxt-context){rel=""nofollow""}. The Nuxt context is only accessible in plugins, Nuxt hooks, Nuxt middleware, and setup functions (in pages and components).
Let's take a look at how to use it:
```ts {1,4}
const event = useRequestEvent()
if (event) {
setResponseStatus(event, 404, 'Page Not Found')
}
```
`event` will be `undefined` in the browser, so you can safely use this composable in your Nuxt content.
In my client's project, I used this composable to set the status code of the response to `410` when a product has expired. This way, the search engines will know that the product is no longer available and will remove it from the search results:
```vue [ProductPage.vue]
```
The important part here is to await the `useFetch` composable to be able to read evaluate the `data` value inside the `script setup` tag.
## StackBlitz
You can play with the code in this [StackBlitz](https://stackblitz.com/edit/nuxt-set-response-status?file=pages/index.vue){rel=""nofollow""}.
:stackblitz{project-id="nuxt-change-status-code-of-the-response"}
# Vue Tip: Change the Interpolation Delimiter
It is possible to adjust the delimiters used for text interpolation within the template.
This is typically used to avoid conflicting with server-side frameworks that also use mustache syntax.
The default delimiters are the double curly braces:
```vue [HelloWorld.vue] {6}
{{ title }}
```
We can change the delimiter in the `config` object of the application instance:
```html [index.html] {20-21,29}
Home
${ message }
```
::warning
The `compilerOptions` config option is only respected when using the full build (i.e. the standalone `vue.js` that can compile templates in the browser).
If you are using the runtime-only build with a build setup, checkout [the official docs](https://vuejs.org/api/application.html#app-config-compileroptions){rel=""nofollow""}.
::
Try it yourself:
:stackblitz{project-id="vue-custom-delimiter"}
# Vue Tip: Check if Component Has Event Listener Attached
Sometimes, you want to apply specific styles to a component only if it has an event listener attached. For example, you might want to add a `cursor: pointer` style to a component only if it has a `click` event listener attached.
## Vue 3
In Vue 3, you can check the props on the current component instance for that purpose:
::code-group
```vue [Child.vue] {4,6-11}
```
```vue [Parent.vue] {10}
```
::
## Vue 2
In Vue 2, you can use the `vm.$listeners` property:
```vue [Component.vue] {4}
```
## StackBlitz
Try it yourself in this StackBlitz:
:stackblitz{project-id="vue-tip-check-event-listener-attached"}
# Vue Tip: Check if Slot Is Empty
You can check if a slot is empty, for example, only to render it if it is available or has content.
## Check if a slot is empty
To check if a slot is empty, you can use `$slots`, an object representing the slots passed by the parent component.
Each slot is exposed on `$slots` as a function that returns an array of `vnodes` under the key corresponding to that slot's name. The default slot is exposed as `$slots.default`.
If a slot is a scoped slot, arguments passed to the slot functions are available to the slot as its slot props.
In the following example, the footer is only rendered if the slot with the name `footer` is present:
```vue {2}
```
::note{title="Vue 2 Code"}
Of course, you can use that functionality in Vue 2 as well:
```vue
```
::
### useSlots composable
Usage of slots inside `
```
::note
`useSlots` is a runtime function that returns the equivalent of `setupContext.slots`. You can use it in normal Composition API functions as well.
::
## Check if a slot has content
In some cases, you probably want to check if the slot is not empty **and has content** inside.
You can do that by checking the array of `vnodes`, if they are empty or not:
```vue {4,6-8,10-12,30-31}
```
## StackBlitz
The code for this tip is interactively available in the following StackBlitz project:
:stackblitz{project-id="check-if-slot-is-empty"}
# Vue Tip: Check Version at Runtime
It's possible to check Vue's version at runtime by importing `version` from the Vue npm package and splitting the string at the `.` character:
```vue
Vue Version: {{ vueVersion }}
```
[Demo](https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHRlbXBsYXRlPlxuICA8aDE+VnVlIFZlcnNpb246IHt7IHZ1ZVZlcnNpb24gfX08L2gxPlxuPC90ZW1wbGF0ZT5cblxuPHNjcmlwdCBzZXR1cD5cbmltcG9ydCB7IHZlcnNpb24gfSBmcm9tICd2dWUnO1xuICBcbmNvbnN0IHZ1ZVZlcnNpb24gPSB2ZXJzaW9uLnNwbGl0KCcuJylbMF07XG48L3NjcmlwdD4iLCJpbXBvcnQtbWFwLmpzb24iOiJ7XG4gIFwiaW1wb3J0c1wiOiB7XG4gICAgXCJ2dWVcIjogXCJodHRwczovL3NmYy52dWVqcy5vcmcvdnVlLnJ1bnRpbWUuZXNtLWJyb3dzZXIuanNcIlxuICB9XG59In0=){rel=""nofollow""}
# Vue Tip: Composable to Define Keyboard Shortcuts
I want to show you a handy Vue composable that allows you to define keyboard shortcuts in your app. I discovered it in [Nuxt UI](https://ui.nuxt.com/getting-started/shortcuts#defineshortcuts){rel=""nofollow""}, and it's called `defineShortcuts`.
## How to Use It
Let me first demonstrate how to use it. You can define your shortcuts in a `setup` function like this:
```vue [App.vue] {4-10}
```
I don't want to repeat the documentation, but let me highlight a few things:
- Shortcuts can be combined with the `_` character. For example, `meta_k` is the meta key (`Command` key on MacOS, `Control` on other OS) key and the `k` key.
- `usingInput` is a flag that tells the composable to only trigger the shortcut when the user is not typing in an input field.
- `whenever` is used to add constraints so that the shortcut is only triggered when the constraints are met. For example, you can use `whenever: [isActive]` to only trigger the shortcut when `isActive` is `true`.
## Source Code
You can grab the source code from [GitHub](https://github.com/nuxt/ui/blob/1f0f6181db7fa1ab45b8f7fec8df1cedccaec688/src/runtime/composables/defineShortcuts.ts){rel=""nofollow""}
# Vue Tip: Create Custom v-model Modifier
`v-model` has some [built-in modifiers](https://vuejs.org/guide/essentials/forms.html#modifiers){rel=""nofollow""} like `.lazy`, `.number` and `.trim`. But sometimes, you might need to add your custom modifier.
In this simple demo, I want to create a custom modifier called `no-underscore` that removes all underscores `_` from an input:
```vue
```
Inside our component we can access the modifier via the `modelModifiers` prop. We manipulate the value if an `input` event is fired and the modifier is available:
```vue {4,11}
```
If your `v-model` binding includes both modifiers and argument then the generated prop name will be `arg + "Modifiers"`:
```vue
```
Demo for this code is available at [Vue SFC Playground](https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCB7IHJlZiB9IGZyb20gJ3Z1ZSdcbmltcG9ydCBDb21wIGZyb20gJy4vQ29tcC52dWUnXG5cbmNvbnN0IHRleHQgPSByZWYoJycpXG48L3NjcmlwdD5cblxuPHRlbXBsYXRlPlxuICA8aDE+e3sgdGV4dCB9fTwvaDE+XG4gIDxDb21wIHYtbW9kZWwubm8tdW5kZXJzY29yZT1cInRleHRcIi8+XG48L3RlbXBsYXRlPiIsImltcG9ydC1tYXAuanNvbiI6IntcbiAgXCJpbXBvcnRzXCI6IHtcbiAgICBcInZ1ZVwiOiBcImh0dHBzOi8vc2ZjLnZ1ZWpzLm9yZy92dWUucnVudGltZS5lc20tYnJvd3Nlci5qc1wiXG4gIH1cbn0iLCJDb21wLnZ1ZSI6IjxzY3JpcHQgc2V0dXA+XG5jb25zdCBwcm9wcyA9IGRlZmluZVByb3BzKHtcbiAgbW9kZWxWYWx1ZTogU3RyaW5nLFxuICBtb2RlbE1vZGlmaWVyczogeyBkZWZhdWx0OiAoKSA9PiAoe30pIH1cbn0pXG5cbmNvbnN0IGVtaXQgPSBkZWZpbmVFbWl0cyhbJ3VwZGF0ZTptb2RlbFZhbHVlJ10pXG5cbmZ1bmN0aW9uIGVtaXRWYWx1ZShlKSB7XG4gIGxldCB2YWx1ZSA9IGUudGFyZ2V0LnZhbHVlXG4gIGlmIChwcm9wcy5tb2RlbE1vZGlmaWVyc1snbm8tdW5kZXJzY29yZSddKSB7XG5cdFx0dmFsdWUgPSB2YWx1ZS5yZXBsYWNlKCdfJywgJycpXG4gIH1cbiAgZW1pdCgndXBkYXRlOm1vZGVsVmFsdWUnLCB2YWx1ZSlcbn1cbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG5cdDxpbnB1dCB0eXBlPVwidGV4dFwiIDp2YWx1ZT1cIm1vZGVsVmFsdWVcIiBAaW5wdXQ9XCJlbWl0VmFsdWVcIiAvPlxuPC90ZW1wbGF0ZT4ifQ==){rel=""nofollow""}.
# Vue Tip: Creating a Custom Directive
In addition to the default set of directives like `v-model` or `v-show`, you can also register your own custom directives. For this article, let's look at how you can create a `v-theme` directive that applies a specific style to an element of our template.
::note
Custom directives should be used for reusing logic that involves low-level DOM access on plain elements.
::
You can define the custom directive inside any Vue component:
```vue {2,6-10}
Test
```
[Open playground](https://sfc.vuejs.org/#eNpNUE2LwkAM/SshF3dhO3MvtbD/weNcas2ulfliklZE/O9mqgUhh+S95L0kd/zN2SwzYYudUMh+EOpdBOg4DxGWRs4UqD8QS2crpGRnPzq15LFMWYBJ5qyItUBxOHribRymCNsIuzimyALLYaX2cK92Ic1R6NTCF/lv2PcvFIC8Ybl5MmPyqWj3rtBpV7mHixq61Ore4w9OIaciTRiyuXCKetIq4t4EO2w3WYd6c60dnkUyt9by31gfcWGTyr/VzBRdaQpkiENzLOnKVFTY4eaOjycIYW3X){rel=""nofollow""}
We defined the custom directive as an object that contains the lifecycle hooks as a Vue component. The element the directive is bound to is available as the first argument of the lifecycle hooks.
In `
```
[Open playground](https://sfc.vuejs.org/#eNqdk81uqzAQhV9l5A2plMA+AnSv7iP07koX/EyIW2NbtqGqEO/esYEmNE0XlRBg+8znM+PxyP5qHQ89siNLHXZalA7zQgKkVpcShoM7Y4fHp/B5zh9RYO2wgXkaxnH+g2lKEx8xx1a9c0rCn1rw+jUrmH3jrj7/98qC5Y9hNAemyayluDS5MkBDWxuuHVh0vaYZ3mllHIxg8AQTnIzqICLnkRfXSlq3WMm8Yhdpw7vSvEcPl/UrG6TaPUCWw+gN8xPsQnA8lKKnxSyDC2DWwIxfFRBZJGrjFX55AhQWvyddlPdY616BVEh6Pj2jC4b/KaGMd41iDxWXDZftNoFlMi5NeycBFLF17wLjeoFFypSy9SXcJHBD+iaBW1ZrEOU16q6yEmX9usk1SQBlWQm0a8sBl6B6A2tPrPUY1uML9E71ktpxW5Rl303lNoqw896/e90Q+7fxZJ26PrRpzvZs7tBDV+r4xSpJVyqQimXBFowuzMwuGHWuHxfs7Jy2xySxp9pfxBcbK9Mm9Bcbyo1Tl6DtDpVRbxYNgQsWnC+MhCYHNAeDskGD5ifmF+kNdz0QNn0AEvZlRA==){rel=""nofollow""}
At this point, you have created a custom directive that you can use in your Vue.js application.
Check the [official documentation](https://vuejs.org/guide/reusability/custom-directives.html){rel=""nofollow""} for more information.
# Nuxt Tip: Custom SPA Loading Template for Your Nuxt Application
You can use Nuxt with the [client-side rendering mode](https://nuxt.com/docs/guide/concepts/rendering#client-side-rendering){rel=""nofollow""} to create a single-page application (SPA). In this mode, Nuxt will only render the application on the client-side. This means that the server will only send a minimal HTML document to the client. The client will then render the application and fetch the data from the API.
When using the client-side rendering mode, Nuxt will display a loading indicator until the application is hydrated. The loading indicator is a simple spinner. You can customize the loading indicator by creating a custom loading component.
::note
Since Nuxt 3.7 this loading indicator is disabled per default. You need to manually enable it by setting the `spaLoadingTemplate` option to `true` in your `nuxt.config.ts` file:
```ts [nuxt.config.ts] {3}
export default defineNuxtConfig({
ssr: false, // enables SPA rendering mode
spaLoadingTemplate: true, // per default disabled since Nuxt 3.7
})
```
::
You can place a custom HTML file in `~/app/spa-loading-template.html` to customize the loading indicator. The file must contain a single HTML element which will be rendered as the loading indicator. For example, the following code is referenced in the [official docs](https://nuxt.com/docs/api/configuration/nuxt-config#spaloadingtemplate){rel=""nofollow""}:
```html [app/spa-loading-template.html]
```
## StackBlitz
You can try it yourself in the following StackBlitz project:
:stackblitz{project-id="nuxt-custom-spa-loading-template"}
# Vue Tip: Debug Computed Properties
We can debug computed properties by passing `computed()` a second options object with two callbacks:
- `onTrack` will be called when a reactive property or ref is tracked as a dependency.
- `onTrigger` will be called when the watcher callback is triggered by the mutation of a dependency.
Both callbacks will receive debugger events in the same format as component debug hooks:
```vue
```
# Vue Tip: Debug Watcher
Similar to [computed()](https://mokkapps.de/vue-tips/debug-computed-properties), watchers also support the `onTrack` and `onTrigger` options to debug a watcher's behavior:
```vue [Component.vue] {2-9,11-18}
```
`onTrack` will be called when a reactive property or ref is tracked as a dependency.
`onTrigger` will be called when the watcher callback is triggered by the mutation of a dependency.
The callbacks receive a debugger event that contains information about the dependency. If you place a `debugger` statement inside the callback, you can interactively inspect the dependency.
::note
`onTrack` and `onTrigger` watcher options only work in development mode.
::
# Vue Tip: Debugging in Templates
Probably you already tried to add a JavaScript expression like `console.log(message)` in your template:
```vue [App.vue] {8}
{{ console.log(message) }}
```
Your app will throw the following error:
::caution
Cannot read properties of undefined (reading 'log')
::
It does not work because `console` is not available on the component instance. A quick fix would be to add a `log` method to the component:
```vue [App.vue] {6-8,12}
{{ log(message) }}
```
Of course, you don't want to add that function to every component you want to debug. Therefore let's add `console.log` to the global properties that can be accessed on any component instance inside your application:
```js [main.js] {7}
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
const app = createApp(App)
app.config.globalProperties.$log = console.log
app.mount('#app')
```
Finally, you can use `$log` in every one of your components:
```vue [App.vue] {8}
{{ $log(message) || message }}
```
::note
The code for this demo is interactively available at StackBlitz:
:stackblitz{project-id="vue-tip-debugging-in-templates"}
::
# Vue Tip: Declare and Mutate v-model Props as Normal Variable Using defineModel
::note
This experimental feature will be [available in Vue 3.3](https://github.com/vuejs/core/pull/8018){rel=""nofollow""}. If you want to try it out now in Vue 2 or Vue 3, you can use it with the [Vue Macros](https://vue-macros.sxzz.moe/macros/define-models.html){rel=""nofollow""} library.
::
`defineModel` is a compiler macro that allows you to declare and mutate `v-model` props as the same as a normal variable.
## Example without defineModel
Let's take a look at a simple example that uses `v-model`. We have a `Parent.vue` component that passes a counter ref to a `Child.vue` component:
```vue [Parent.vue] {5,11}
Parent state: {{ state }}
```
Let's take a look at the implementation of the child component:
```vue [Child.vue] {4,6-8,11-12}
Child state: {{ modelValue }}
```
As `Child.vue` receives the `v-model` you need to declare a prop called `modelValue` which receives the `v-model` value. Additionally, you need to declare an emit called `update:modelValue` that is used to update the parent that the `modelValue` has been updated.
As props are readonly and you should not mutate them, you cannot update `modelValue` and will receive the following warning:
::warning
Set operation on key "modelValue" failed: target is readonly.
::
I [wrote a tip](https://mokkapps.de/vue-tips/avoid-mutating-a-prop-directly) about this topic and how you can solve this warning. `defineModel` provides a nice solution to this problem, so let's take a look at it.
## Example with defineModel
::note
In the following example, I'm using [Vue Macros's defineModels](https://vue-macros.sxzz.moe/macros/define-models.html){rel=""nofollow""} which behaves identically to `defineModel` available since Vue 3.3.0-alpha.9
::
We can simplify our child component by using `defineModels`:
```vue [Child.vue] {2-4,7}
Child with defineModels state: {{ modelValue }}
```
The `defineModels` compiler macro will declare a prop with the same name and a corresponding `update:propName` event when it is compiled.
By updating the `modelValue` ref, the corresponding `update:propName` event is automatically emitted.
Try it yourself in the following StackBlitz project:
:stackblitz{project-id="vue-define-models"}
# Vue Tip: Deep Watch on Arrays
In Vue 3, when using the [watch option](https://vuejs.org/api/options-state.html#watch){rel=""nofollow""} to watch an array, the callback will **only trigger when the array is replaced**. In other words, the watch callback is **not triggered on array mutation**.
Let's take a look at an example:
```vue [App.vue] {4-8,10-12,14-16}
{{ user.name }}
```
We have an array of users and a button to add a new user. When we click the button, the user is added to the array but the watch callback is not triggered.
To trigger the watcher on mutation, the `deep` option must be specified:
```vue [App.vue] {7}
```
Now, when we click the button, the watch callback is triggered and the new user is logged to the console.
Try it yourself in the following StackBlitz project:
:stackblitz{project-id="vue-tip-array-deep-watch"}
# Vue Tip: Defining and Registering Vue Web Components
Web Components is an umbrella term for a set of web native APIs that allows developers to create reusable custom elements. Vue has excellent support for both consuming and creating custom elements.
::note
The main advantage of web components is that they can be used with any framework or even without a framework.
::
In three simple steps, let's look at how we can create a web component from a Vue SFC (single-file component).
### 1. Create the custom element
To create the custom element, we use `defineCustomElement`:
```js {7}
import { defineCustomElement } from 'vue'
import Example from './Example.ce.vue'
console.log(Example.styles) // ["/* inlined css */"]
// convert into custom element constructor
const ExampleElement = defineCustomElement(Example)
```
You may have noticed that our SFC uses the special file ending `.ce.vue`. It inlines the SFC's `
```
The following Vue component uses a slot instead of a property to show the message:
```vue
```
The following code shows how both components can be used in your Vue application:
```vue
Welcome to Your Vue.js App
```
As you can see, the slot provides more flexibility as you can pass any HTML code into the slot.
# Vue Tip: Props and Context in Setup Method
The setup function in [Vue 3](https://v3.vuejs.org/){rel=""nofollow""} Composition API will take two arguments: `props` and `context`.
```vue
```
The first argument in the `setup` function is the `props` argument. `props` are reactive and will be updated when new props are passed in.
::warning
You cannot use ES6 destructuring because it will remove `props` reactivity. If you want to destructure `props` you need to use [toRefs](https://v3.vuejs.org/guide/reactivity-fundamentals.html#destructuring-reactive-state){rel=""nofollow""}.
::
The second argument in the `setup` function is the `context` argument. It is a normal JavaScript object that exposes some useful values:
```js
export default {
setup(props, context) {
// Attributes (Non-reactive object, equivalent to $attrs)
console.log(context.attrs)
// Slots (Non-reactive object, equivalent to $slots)
console.log(context.slots)
// Emit events (Function, equivalent to $emit)
console.log(context.emit)
// Expose public properties (Function)
console.log(context.expose)
},
}
```
# Vue Tip: Provide Fallback Content for Slots
There are cases when it's useful to specify fallback (i.e. default) content for a slot, to be rendered only when no content is provided:
```vue
```
The text "Submit" is rendered inside the `