<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ferdinand Mütsch</title>
    <description>The latest articles on DEV Community by Ferdinand Mütsch (@n1try).</description>
    <link>https://dev.to/n1try</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1178%2Fff5aeaea-10eb-427d-b624-f50d55437e43.png</url>
      <title>DEV Community: Ferdinand Mütsch</title>
      <link>https://dev.to/n1try</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/n1try"/>
    <language>en</language>
    <item>
      <title>Wakapi - Open-Source Time Tracking for Devs</title>
      <dc:creator>Ferdinand Mütsch</dc:creator>
      <pubDate>Tue, 15 Aug 2023 05:49:00 +0000</pubDate>
      <link>https://dev.to/n1try/wakapi-open-source-time-tracking-for-devs-5eng</link>
      <guid>https://dev.to/n1try/wakapi-open-source-time-tracking-for-devs-5eng</guid>
      <description>&lt;p&gt;Hi all!&lt;/p&gt;

&lt;p&gt;I want to share with you an open-source project that I started back during my studies and am passionately improving ever since. Back then, I wanted statistics about my coding (what language, editor, projects, etc.), just like WakaTime.com, but as a student I wouldn't be willing to pay for it. Because I'm a developer, I simply decided to build my own instead. It has become my no. 1 hobby project and after years I still love to spend my evenings coding on it. &lt;/p&gt;

&lt;p&gt;The project has reached more than 1,700 GitHub stars now, the hosted service at &lt;a href="https://wakapi.dev"&gt;wakapi.dev&lt;/a&gt; got 1,800 registered users today and a super friendly, small community has evolved on GitHub - things, which I am super happy and appreciative about!&lt;/p&gt;

&lt;p&gt;Wakapi is entirely open-source and can be self-hosted. But there is also a paid subscription plan for the official service, that gives users unlimited data history and a few other small perks and covers hosting, domain, etc. &lt;/p&gt;

&lt;p&gt;Go check it out and, if you like Wakapi, consider opting for a paid subscription. I'd appreciate your support a lot! &lt;/p&gt;

&lt;p&gt;P.S.: &lt;a href="https://muetsch.io/consider-sponsoring-open-source.html"&gt;Consider sponsoring open-source&lt;/a&gt; 😊.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>opensource</category>
      <category>selfhosted</category>
      <category>go</category>
    </item>
    <item>
      <title>How development history keeps repeating itself</title>
      <dc:creator>Ferdinand Mütsch</dc:creator>
      <pubDate>Tue, 08 Nov 2022 07:09:00 +0000</pubDate>
      <link>https://dev.to/n1try/how-development-history-keeps-repeating-itself-5h0n</link>
      <guid>https://dev.to/n1try/how-development-history-keeps-repeating-itself-5h0n</guid>
      <description>&lt;p&gt;I want to share a couple of thoughts that repeatedly come to my mind the more experienced I become in (web-) development. If you're observing the ecosystem thoughtfully, you can kind of see history repeat itself every couple of years - and the wheel being reinvented over and over again. Here is my personal opinion about innovation in the web world, about technologies like PHP and SOAP versus modern JavaScript frameworks like Next.js and paradigms like Serverless, about developer productivity, and more. &lt;/p&gt;

&lt;h1&gt;
  
  
  Innovation on the web
&lt;/h1&gt;

&lt;p&gt;Innovation in the world of web development, especially in the JavaScript ecosystem, has already become a meme among developers. People joke about new frameworks and libraries popping up every day. The moment you decide to pick up the latest and greatest frontend framework, it is most likely already outdated again, because the next big thing has already climbed up to the front page of HackerNews. &lt;/p&gt;

&lt;p&gt;This is not necessarily a bad thing! Innovation is always great to have and many of the web frameworks and libraries out there are actually of very good quality (code, but especially also documentation) in my opinion. The hard part about this, however, is to not get distracted as a developer. It can be quite overwhelming and in the end you'll find yourself suffering from &lt;a href="https://hackernoon.com/how-it-feels-to-learn-javascript-in-2016-d3a717dd577f"&gt;JavaScript fatigue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My take here is: &lt;strong&gt;keep your eyes open for what's new and shiny, but decide for one technology&lt;/strong&gt;, commit yourself to it, become and expert with it and only switch to something else if you really see a need to.&lt;/p&gt;

&lt;h2&gt;
  
  
  New technologies against boredom
&lt;/h2&gt;

&lt;p&gt;But why is that even? Why are there such vast amounts of technologies, all of which serve very similar purposes in the end?&lt;/p&gt;

&lt;p&gt;I cannot give a definitive answer to this question. But I reckon one reason is around the fact that the &lt;strong&gt;entry barrier&lt;/strong&gt; to writing and publishing your own web framework is quite low. The web ecosystem is already so big that you'll find resources and support on virtually any topic – and platforms like Reddit, HackerNews or ProductHunt make it easy to get people aware of your project.&lt;/p&gt;

&lt;p&gt;Another reason could be this: in a &lt;a href="https://www.programmier.bar/podcast/cto-special-17-stephan-schmidt-amazing-cto"&gt;recent podcast episode&lt;/a&gt; of &lt;em&gt;programmier.bar&lt;/em&gt; I heard the statement being made that developers need &lt;strong&gt;new technologies to stay interested&lt;/strong&gt; and keep up their excitement. The interviewee claims that there is a trend of (tech) companies increasingly pushing for the development of &lt;em&gt;shallow features&lt;/em&gt;, as opposed to &lt;em&gt;deep features&lt;/em&gt;. That is, features, which are fairly straightforward to implement and where a developer doesn't have to put a lot of thoughts and brain power into. To prevent themselves from getting bored, developers jump into new technologies / frameworks / languages or start building their own ones. &lt;/p&gt;

&lt;h1&gt;
  
  
  Reinventing the wheel
&lt;/h1&gt;

&lt;p&gt;All of these novel technologies (can be languages, frameworks, libraries, or also concepts / paradigms) aim to solve some problem. But most of those problems have already been solved before. Inspired by &lt;a href="https://syntax.fm/show/393/hasty-treat-spicy-takeout-php-is-good-and-we-re-just-re-creating-it"&gt;this Syntax.fm episode&lt;/a&gt;, and by a provocative tweet about Serverless being just the same as good old PHP scripts I saw recently (can't find the link anymore :-/), I want to share some of my thoughts on that same topic. &lt;/p&gt;

&lt;h2&gt;
  
  
  PHP vs. X
&lt;/h2&gt;

&lt;p&gt;Let's compare a few allegedly "ageing" technologies with more recent approaches, starting with PHP scripts.&lt;/p&gt;

&lt;p&gt;Please note: some of this is intentionally written in a slightly provocative way. Please note also: I'm neither a PHP developer, nor do I have a lot of experience with the below frameworks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server-side rendering (SSR)
&lt;/h3&gt;

&lt;p&gt;I feel like this is one of the hottest topics right now. Recent frameworks with SSR support include &lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt;, &lt;a href="https://nuxtjs.org/"&gt;Nuxt.js&lt;/a&gt;, &lt;a href="https://kit.svelte.dev/"&gt;SvelteKit&lt;/a&gt;, &lt;a href="https://remix.run"&gt;Remix&lt;/a&gt; and others. But didn't PHP do server rendering already 20 years ago? Yes, but... PHP did &lt;em&gt;only&lt;/em&gt; server-side rendering and nothing else. The strength of tools like Nuxt is to give developers the best of both worlds. They combine the benefits of SSR with those of single-page apps (SPA), which is extremely useful for very dynamic websites. A popular concept in this context is &lt;a href="https://markus.oberlehner.net/blog/partial-hydration-concepts-lazy-and-active/"&gt;partial hydration&lt;/a&gt;. So, if this was a competition, I'd definitely give this point to the side of the "modern approaches". &lt;/p&gt;

&lt;h3&gt;
  
  
  Mixing template and code
&lt;/h3&gt;

&lt;p&gt;Some developers appreciate &lt;a href="https://reactjs.org/docs/introducing-jsx.html"&gt;JSX&lt;/a&gt;, because a component's HTML markup and its JavaScript business logic code are very close together, with one being an intrinsic part of the other. One could argue, though, that PHP did just that already since its very beginnings, so not too much new here.&lt;/p&gt;

&lt;h3&gt;
  
  
  File-based routing
&lt;/h3&gt;

&lt;p&gt;Some frameworks advertise their mechanism of &lt;a href="https://nextjs.org/docs/routing/introduction"&gt;file-based routing&lt;/a&gt;, i.e. not having to declare HTTP routes / paths in code, but have them inferred implicitly from the app's directory structure. However, this is not at all new, either. PHP used to do just that - you place a &lt;code&gt;.php&lt;/code&gt; into some folder and can call it from the browser by its file path. &lt;/p&gt;

&lt;h3&gt;
  
  
  Serverless
&lt;/h3&gt;

&lt;p&gt;The idea of Serverless is (a) to have developers not having to worry about deploying, running and scaling their app in production (or even doing server administration), (b) to reduce boilerplate code for authentication, authorization, security and the web server itself and (c) to have very small, self-contained, single-purpose functions instead of giant coupled code bases. Serverless functions are triggered through an event (usually an HTTP call), then do their job, and are shut down again afterwards - they are entirely stateless. Sounds familiar? This is exactly what PHP scripts are. They are invoked per request and only live as long as the request itself. What is the novelty here? Indeed, Serverless is very, very close to what PHP scripts have been for years already. However, to be fair, Serverless (e.g. AWS Lambda) is - due to the way it is designed - probably much more scalable than PHP scripts running inside a single web server / FPM.&lt;/p&gt;

&lt;h2&gt;
  
  
  RPC technologies
&lt;/h2&gt;

&lt;p&gt;Another field where I found a lot of parallels between new, "fancy" technologies and old, "legacy" approaches is remote-procedure calls (RPC). RPC is an alternative approach to API design and comparable with resource-based paradigms such as REST or "query"-based approaches like GraphQL. &lt;/p&gt;

&lt;h3&gt;
  
  
  gRPC, tRPC, ...
&lt;/h3&gt;

&lt;p&gt;The most popular framework today in this space is &lt;a href="https://grpc.io/"&gt;gRPC&lt;/a&gt; by Google, but there are also &lt;a href="https://capnproto.org/"&gt;Cap'n'Proto&lt;/a&gt;, &lt;a href="https://trpc.io/"&gt;tRPC&lt;/a&gt;, &lt;a href="https://go.dev/blog/json-rpc"&gt;JSON-RPC&lt;/a&gt; (less of a framework, rather a convention) and others. And then there used to be &lt;a href="https://en.wikipedia.org/wiki/SOAP"&gt;&lt;strong&gt;SOAP&lt;/strong&gt;&lt;/a&gt; for a long time. It's &lt;a href="https://www.redhat.com/architect/apis-soap-rest-graphql-grpc"&gt;considered outdated and deprectated&lt;/a&gt; today. But in essence, it was doing the exact same thing - just in a slightly different way. Of course, gRPC has a few advantages over SOAP, especially more efficient message representation, thanks to binary encoding, and communication, thanks to HTTP/2 – on the other hand, though, SOAP has discoverability, thanks to to WSDL. The point I'm trying to make is that the fundamental concepts are almost exactly the same.&lt;/p&gt;

&lt;h1&gt;
  
  
  Old ≠ bad
&lt;/h1&gt;

&lt;p&gt;The central point I want to make with this article is: technology is not necessarily bad, just because it's old. Not even PHP (especially with &lt;a href="https://www.php.net/releases/8.0/en.php"&gt;PHP 8.0&lt;/a&gt;). Many seemingly novel concepts have already been there before at some point, and not few of them do still have their reason to exist. When a new technology emerges, it is often times just an evolution, rather than a revolution - a slight improvement or the rejuvenation of a previous technology (cf. gRPC vs. SOAP, Serverless vs. PHP scripts, ...). Of course, this is important. Technology must be adapted to modern needs. But also, at the same time, developers should be careful about not ending up reinventing the wheel once again.&lt;/p&gt;

&lt;h1&gt;
  
  
  Key takeaways
&lt;/h1&gt;

&lt;p&gt;From this discussion, my key takaways are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Technology is not bad, just because it's old.&lt;/strong&gt; Don't fooled by the novelty bias.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't ever &lt;em&gt;rant&lt;/em&gt; about a technology&lt;/strong&gt; - especially not just because everyone else on Twitter does. Even &lt;a href="https://survey.stackoverflow.co/2022/#technology-most-loved-dreaded-and-wanted"&gt;dreaded&lt;/a&gt; things like PHP or VBA have their right to exist. You can anyways only judge about a technology, if you have actually worked with it extensively. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus on building features&lt;/strong&gt;, not tools - unless you really have a need to.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://muetsch.io/how-development-history-keeps-repeating-itself.html"&gt;muetsch.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>php</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Open-source, self-hosted location tracking with OwnTracks and Grafana</title>
      <dc:creator>Ferdinand Mütsch</dc:creator>
      <pubDate>Fri, 25 Jun 2021 08:20:27 +0000</pubDate>
      <link>https://dev.to/n1try/open-source-self-hosted-location-tracking-with-owntracks-and-grafana-3p11</link>
      <guid>https://dev.to/n1try/open-source-self-hosted-location-tracking-with-owntracks-and-grafana-3p11</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you are using any kinds of Google services on your smartphone, chances are high that Google continuously tracks your location and updates your &lt;a href="https://www.google.com/maps/timeline" rel="noopener noreferrer"&gt;Timeline&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapps.muetsch.io%2Fimages%2Fo%3Aauto%2Frs%2Cs%3A640%3Fimage%3Dhttps%3A%2F%2Fmuetsch.io%2Fimages%2Fowntracks4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapps.muetsch.io%2Fimages%2Fo%3Aauto%2Frs%2Cs%3A640%3Fimage%3Dhttps%3A%2F%2Fmuetsch.io%2Fimages%2Fowntracks4.jpg" alt="Google Location Timeline Screenshot"&gt;&lt;/a&gt;&lt;br&gt;
(&lt;a href="https://techwelkin.com/wp-content/uploads/2017/04/google-timeline-location-history.jpg" rel="noopener noreferrer"&gt;Source&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;This is a pretty cool feature and for me it is super interesting to see in retrospect which places I have visited in the past. However, it is also a bit concerning to see how precisely Google tracks your daily life. That is why I turned the location tracking off and decided to go for a rather privacy-focused approach instead.&lt;/p&gt;
&lt;h2&gt;
  
  
  Your Own Setup
&lt;/h2&gt;

&lt;p&gt;To build your own, self-hosted location tracking system, you are going to need a bit of technical expertise, a few different open-source software components and a small server to host them.&lt;/p&gt;
&lt;h3&gt;
  
  
  Requirements / Components
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Android- or iOS smartphone&lt;/li&gt;
&lt;li&gt;Web server (Caddy, nginx, Apache 2, ...)&lt;/li&gt;
&lt;li&gt;PHP &amp;gt;= 7.x&lt;/li&gt;
&lt;li&gt;MySQL or MariaDB&lt;/li&gt;
&lt;li&gt;Grafana&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  OwnTracks
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapps.muetsch.io%2Fimages%2Fo%3Aauto%3Fimage%3Dhttps%3A%2F%2Fmuetsch.io%2Fimages%2Fowntracks1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapps.muetsch.io%2Fimages%2Fo%3Aauto%3Fimage%3Dhttps%3A%2F%2Fmuetsch.io%2Fimages%2Fowntracks1.png" alt="OwnTracks Logo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first step to tracking your location is to record it anywhere you go. As most people almost always carry their smartphones with them and as most smartphones have GNSS sensors, the choice is quite obvious. The only thing missing is an appropriate app. This is where &lt;a href="https://owntracks.org/" rel="noopener noreferrer"&gt;OwnTracks&lt;/a&gt; comes to play. It is an open-source mobile app that does exactly what we need - record your location and send it to a custom configured MQTT broker or HTTP endpoint. It is available for both Android (written in Kotlin) and iOS (written in Objective-C) and comes with super detailed, developer-focused &lt;a href="https://owntracks.org/booklet/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fowntracks.org%2Fbooklet%2Fguide%2Fimages%2Fowntracks-iotconf-http-arch.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fowntracks.org%2Fbooklet%2Fguide%2Fimages%2Fowntracks-iotconf-http-arch.png" alt="OwnTracks Architecture Diagram"&gt;&lt;/a&gt;&lt;br&gt;
(&lt;a href="https://owntracks.org/booklet/guide/whathow/#how-owntracks-works" rel="noopener noreferrer"&gt;Source&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;It comes with different tracking &lt;a href="https://owntracks.org/booklet/features/location/" rel="noopener noreferrer"&gt;modes&lt;/a&gt;, which essentially specify how often to send your location. Weirdly enough, these modes behave differently on Android an iOS.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Tracking on iOS&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Tracking on Android&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Move mode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;After &lt;code&gt;x&lt;/code&gt; meters (default: 100) or &lt;code&gt;t&lt;/code&gt; seconds (default: 300)&lt;/td&gt;
&lt;td&gt;Every 30 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Significant changes mode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;After &amp;gt;= 500 meters and &amp;gt;= 5 minutes&lt;/td&gt;
&lt;td&gt;After &amp;gt;= &lt;code&gt;x&lt;/code&gt; meters and &amp;gt;= &lt;code&gt;t&lt;/code&gt; seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Manual mode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;On user request&lt;/td&gt;
&lt;td&gt;On user request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Quiet mode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Never&lt;/td&gt;
&lt;td&gt;Never&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In fact, things are a bit more complex than this, as you will have to distinguish between how often the app &lt;strong&gt;requests&lt;/strong&gt; your location from the device's sensors and how often it &lt;strong&gt;sends&lt;/strong&gt; it. Also, there are different options regarding the desired precision (e.g. GPS is more accurate, but uses more power). Moreover, on iOS, there are &lt;em&gt;region monitoring&lt;/em&gt; and &lt;em&gt;iBeacon&lt;/em&gt; monitoring modes in addition. &lt;/p&gt;

&lt;p&gt;I would suggest to read through the docs and decide which modes suits you best, depending on your smartphone's OS. I, however, decided to go for the &lt;em&gt;significant changes&lt;/em&gt; mode on Android with the following custom config variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"locatorDisplacement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"locatorInterval"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"locatorPriority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This basically tells the app to &lt;em&gt;request my location with block-level accuracy (100 meters) every time I move by more than 25 meters, but at most every 60 seconds&lt;/em&gt;. These settings work quite well for me and I did not recognize any significant impact on battery consumption. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapps.muetsch.io%2Fimages%2Fo%3Aauto%2Frs%2Cs%3A320%3Fimage%3Dhttps%3A%2F%2Fmuetsch.io%2Fimages%2Fowntracks2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapps.muetsch.io%2Fimages%2Fo%3Aauto%2Frs%2Cs%3A320%3Fimage%3Dhttps%3A%2F%2Fmuetsch.io%2Fimages%2Fowntracks2.png" alt="OwnTracks App Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Server-side script
&lt;/h3&gt;

&lt;p&gt;After setting up the client side, a server-side component to receive the OwnTracks app's requests is still missing. OwnTracks ships with its &lt;a href="https://github.com/owntracks/recorder" rel="noopener noreferrer"&gt;Recorder&lt;/a&gt;, which is a small and simple web application written in C. However, I did not like it a lot, as it does not look particularly beautiful and is very limited regarding its functionality. I rather wanted to visualize my data in Grafana. But to get it there, it first needs to be persisted to a database. &lt;/p&gt;




&lt;p&gt;&lt;strong&gt;UPDATE:&lt;/strong&gt; I found &lt;a href="https://github.com/owntracks/frontend" rel="noopener noreferrer"&gt;&lt;code&gt;owntracks/frontend&lt;/code&gt;&lt;/a&gt; in the meantime, which seems to be a lot more advanced than the recorder's web UI. You may want to use this as an alternative to Grafana, which will make the setup a lot easier.&lt;/p&gt;




&lt;p&gt;A heartbeat request's payload looks like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"acc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"alt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;163&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"batt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"conn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1624607444&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;48.9995682&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lon"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;8.3940805&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"t"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"u"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"l3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tst"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1624607139&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vac"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I quickly wrote &lt;a href="https://gist.github.com/muety/3dcbb22916a4812cf3ed40ff17f1d9e2" rel="noopener noreferrer"&gt;a little PHP script&lt;/a&gt; to take OwnTracks location heartbeats and write them to a MySQL database. It can live at any web server with PHP support (I am using &lt;a href="https://caddyserver.com" rel="noopener noreferrer"&gt;Caddy2&lt;/a&gt; with &lt;code&gt;php-fpm&lt;/code&gt;). Assuming it is deployed under &lt;code&gt;https://my.server.tld/track.php&lt;/code&gt; then that is the URL you need to configure as an HTTP target endpoint in the OwnTracks app. Optionally, you can configure HTTP Basic auth inside your web server's config. OwnTrack's client app has support for that built in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grafana dashboard
&lt;/h3&gt;

&lt;p&gt;The last missing part is to actually visualize your location data. I am a big fan of Grafana, as you can easily build beautiful visualizations with low effort. Grafana integrates with MySQL as a data source, so it can read the location data previously ingested by the above script. Using the &lt;a href="https://github.com/panodata/grafana-map-panel" rel="noopener noreferrer"&gt;grafana-map-panel&lt;/a&gt; plugin, you can visualize geo data in dashboard. In addition, I added another two graphs for plotting my velocity and my phone's battery level over time. This is what it looks like in the end. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapps.muetsch.io%2Fimages%2Fo%3Aauto%2Frs%2Cs%3A640%3Fimage%3Dhttps%3A%2F%2Fmuetsch.io%2Fimages%2Fowntracks3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapps.muetsch.io%2Fimages%2Fo%3Aauto%2Frs%2Cs%3A640%3Fimage%3Dhttps%3A%2F%2Fmuetsch.io%2Fimages%2Fowntracks3.png" alt="Grafana Dashboard Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The geo data panel is generated from this underlying SQL query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;tst&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;vel&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;recordings&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
  &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;__unixEpochFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tst&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt;
  &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'$user'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt;
  &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'$device'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;tst&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;This is it! You have your own, self-hosted, Google-free location timeline now. Have fun!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://muetsch.io/open-source-self-hosted-location-tracking-with-owntracks-and-grafana.html" rel="noopener noreferrer"&gt;muetsch.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>selfhosted</category>
      <category>monitoring</category>
      <category>grafana</category>
    </item>
    <item>
      <title>Aggregating and Visualizing DMARC Reports</title>
      <dc:creator>Ferdinand Mütsch</dc:creator>
      <pubDate>Fri, 07 May 2021 20:48:25 +0000</pubDate>
      <link>https://dev.to/n1try/aggregating-and-visualizing-dmarc-reports-fa5</link>
      <guid>https://dev.to/n1try/aggregating-and-visualizing-dmarc-reports-fa5</guid>
      <description>&lt;h1&gt;
  
  
  DMARC, DKIM and SPF
&lt;/h1&gt;

&lt;p&gt;If you are using a custom domain for your e-mail with providers like &lt;a href="https://mailbox.org" rel="noopener noreferrer"&gt;mailbox.org&lt;/a&gt; or even host your own mail server, it is likely that you came across these terms at some point. All three are technologies for improving e-mail security and authentication. Their whole purpose is to ensure that a mail was actually sent by the person who appears to have sent it, i.e. the person mentioned in the mail's &lt;em&gt;From&lt;/em&gt; header. I don't want to go too much into detail here and only explain each of them very briefly. &lt;/p&gt;

&lt;h2&gt;
  
  
  SPF
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://shibumi.dev/posts/spf-dkim-dmarc/" rel="noopener noreferrer"&gt;SPF&lt;/a&gt; – the &lt;em&gt;Sender Policy Framework&lt;/em&gt; – is quite easy to understand. As the owner of a domain, say &lt;em&gt;example.org&lt;/em&gt;, you precisely specify in the DNS records of your domain what mail servers, identified by their IPs, are allowed to send mail for that domain. For instance, if you set a &lt;code&gt;TXT&lt;/code&gt; record like &lt;code&gt;"v=spf1 ip4:164.68.116.134 a -all"&lt;/code&gt;, you are telling any receiving mail server in the world to only accept mail from &lt;code&gt;*.example.org&lt;/code&gt; if the sending SMTP server has that very certain IP and drop all other incoming messages. Of course, you have to rely on the receiving server to fulfill its responsibility of actually performing that DNS lookup and verification.&lt;/p&gt;

&lt;p&gt;Syntax of SPF records still goes a bit beyond the above example and allow for _include_s, which is basically a way of delegating the specification of the actual SPF record to a different domain. This is especially helpful when using external mail providers, as you, as a customer, obviously cannot know every single IP of their outgoing SMTP hosts.&lt;/p&gt;

&lt;h2&gt;
  
  
  DKIM
&lt;/h2&gt;

&lt;p&gt;The concept of &lt;a href="https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail" rel="noopener noreferrer"&gt;DKIM&lt;/a&gt; is similarly straightforward. Its idea is that a sending mail server, e.g. yours or your mail hoster's one, adds a cryptographic signature to all outgoing mail. Given the sender's public key, a receiving server can easily verify that signature then. To find out the public key belonging to the sending servers of &lt;em&gt;example.org&lt;/em&gt;, that public key is stored as another &lt;code&gt;TXT&lt;/code&gt; record in &lt;em&gt;example.org&lt;/em&gt;'s DNS zone. That's it. &lt;/p&gt;

&lt;h2&gt;
  
  
  DMARC
&lt;/h2&gt;

&lt;p&gt;In contrast to the previous two methods, DMARC is not actually used for authentication, but rather for reporting. It is a way to notify a domain owner about what is going wrong out there in the internet with regard to e-mail. DMARC essentially tells (again, via DNS) receiving mail servers, including GMail, Outlook, GMX, etc., who to send reports about failed SPF and DKIM verifications to. Depending on the actual implementation, not only failure notifications are sent, but also regular, summarizing reports, even if all goes well. Moreover, DMARC also specifies an XML-based file format for those reports. You, as a domain owner, can then read and understand those reports and take action – whatever that might be. &lt;/p&gt;

&lt;p&gt;An example for a full DMARC record can be found &lt;a href="https://muetsch.io/images/dmarc_example.xml" rel="noopener noreferrer"&gt;here&lt;/a&gt;. I only want to emphasize one part of the &lt;code&gt;&amp;lt;record&amp;gt;&lt;/code&gt; section here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;row&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;source_ip&amp;gt;&lt;/span&gt;80.241.xx.xxx&lt;span class="nt"&gt;&amp;lt;/source_ip&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;count&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/count&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;policy_evaluated&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;disposition&amp;gt;&lt;/span&gt;none&lt;span class="nt"&gt;&amp;lt;/disposition&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dkim&amp;gt;&lt;/span&gt;pass&lt;span class="nt"&gt;&amp;lt;/dkim&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;spf&amp;gt;&lt;/span&gt;pass&lt;span class="nt"&gt;&amp;lt;/spf&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/policy_evaluated&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/row&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;identifiers&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;header_from&amp;gt;&lt;/span&gt;wakapi.dev&lt;span class="nt"&gt;&amp;lt;/header_from&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/identifiers&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The report (from Google, in this case) notifies me, owner of &lt;em&gt;wakapi.dev&lt;/em&gt;, that one mail from the above IP was received within a certain time span and that both SPF and DKIM checks were alright. Seems like my DNS records are correct and nobody tried to scam in my name. Perfect.&lt;/p&gt;

&lt;h1&gt;
  
  
  Configuring DMARC
&lt;/h1&gt;

&lt;p&gt;Setting up SPF and DKIM for your domain is highly recommended, as the chances of your mails being considered spam by a receiver are significantly lower then. Afterwards, you still need to set up DMARC. It is probably best to just google how to do that, there are many great posts out there (like &lt;a href="https://www.ionos.de/digitalguide/e-mail/e-mail-sicherheit/dmarc-erklaert/" rel="noopener noreferrer"&gt;this (German-language) one&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In essence, you will end up with a DNS record similar to this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v=DMARC1; p=reject; adkim=r; aspf=r; rua=mailto:dmarc@muetsch.io; ruf=mailto:dmarc@muetsch.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the example, any mail server who actively implements DMARC reports, is instructed to send them to &lt;em&gt;&lt;a href="mailto:dmarc@muetsch.io"&gt;dmarc@muetsch.io&lt;/a&gt;&lt;/em&gt;. I would recommend to have a separate address (different from your main address) for DMARC records, to you can easily set up rules like &lt;em&gt;"move everything addresses to &lt;a href="mailto:dmarc@mydomain.tld"&gt;dmarc@mydomain.tld&lt;/a&gt; into some IMAP folder and mark it as read"&lt;/em&gt;. &lt;/p&gt;

&lt;h1&gt;
  
  
  Aggregation and visualization
&lt;/h1&gt;

&lt;p&gt;As can be seen from the example above, DMARC records are a bit unpleasant to read and you probably do not want to go through every report (couple per day) manually. Luckily, a quick GitHub search led me to a project that helps with this, specifically it does:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read DMARC reports from your inbox via IMAP&lt;/li&gt;
&lt;li&gt;Parse them&lt;/li&gt;
&lt;li&gt;Persist them into a database&lt;/li&gt;
&lt;li&gt;Visualize them on a website&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapps.muetsch.io%2Fimages%2Fo%3Aauto%2Frs%2Cs%3A640%3Fimage%3Dhttps%3A%2F%2Fmuetsch.io%2Fimages%2Fdmarc1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapps.muetsch.io%2Fimages%2Fo%3Aauto%2Frs%2Cs%3A640%3Fimage%3Dhttps%3A%2F%2Fmuetsch.io%2Fimages%2Fdmarc1.png"&gt;&lt;/a&gt;&lt;br&gt;
(Source: &lt;a href="https://www.techsneeze.com" rel="noopener noreferrer"&gt;https://www.techsneeze.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;To be precise, the tool is two separate scripts, the parser and the web dashboard.&lt;/p&gt;
&lt;h2&gt;
  
  
  Database setup
&lt;/h2&gt;

&lt;p&gt;First, you will need a MySQL or MariaDB database, which the parser can write to and the dashboard can read from. If you do not already have a running MySQL instance anyway, you can easily set one up, e.g. using Docker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 3306:3306 &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_RANDOM_ROOT_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dmarc &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dmarc &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sshhh &lt;span class="nt"&gt;--name&lt;/span&gt; mariadb-dmarc mariadb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command already creates a database and user for you. If you don't want to spawn a whole new database instance or don't want to use Docker, you will have to create the database and user manually.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="n"&gt;dmarc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'dmarc'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'%'&lt;/span&gt; &lt;span class="n"&gt;IDENTIFIED&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;mysql_native_password&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'sshhh'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="s1"&gt;'dmarc'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'dmarc'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;FLUSH&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Parser setup
&lt;/h2&gt;

&lt;p&gt;The parser can be found at &lt;a href="https://github.com/techsneeze/dmarcts-report-parser" rel="noopener noreferrer"&gt;techsneeze/dmarcts-report-parser&lt;/a&gt; and is written as a single-file Perl script, alongside a configuration file. The repo's README explains the setup process quite well. &lt;/p&gt;

&lt;p&gt;In the config file, you need to set your above database connection (usually something like &lt;code&gt;imap.yourprovider.com&lt;/code&gt;, port 993, SSL) and credentials as well as your IMAP credentials to log in to your mail account. With &lt;code&gt;$imapreadfolder&lt;/code&gt; you tell the parser which IMAP folder to search for DMARC mails. This should preferably not be your inbox' root folder to not distract the parser and not risk losing any mail. I, personally, set up a sub folder &lt;em&gt;dmarc&lt;/em&gt; in my inbox (IMAP path is &lt;code&gt;INBOX/dmarc&lt;/code&gt; then) and created a rule to automatically put all DMARC mails in there. &lt;/p&gt;

&lt;p&gt;Since the parser is a one-shot script and not a long-running process, you will probably want to invoke it on a regular basis, which you can set up a cron job for, using &lt;code&gt;crontab -e&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@hourly     cd ~/dev/dmarcts-report-parser &amp;amp;&amp;amp; ./dmarcts-report-parser.pl -i
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Viewer setup
&lt;/h2&gt;

&lt;p&gt;The viewer / web dashboard is located at &lt;a href="https://github.com/techsneeze/dmarcts-report-viewer" rel="noopener noreferrer"&gt;techsneeze/dmarcts-report-viewer&lt;/a&gt; and implemented as a simple PHP application. That means, you will need a web server like Apache2, nginx or Caddy (I'd recommend the latter) alongside a PHP installation (e.g, using PHP-FPM). Explaining how to set these things up is beyond the scope of this article, but you can just google it. After putting your database credentials once again (this time for reading), you are good to go. &lt;/p&gt;

&lt;p&gt;Hit &lt;code&gt;http://localhost/dmarcts-report-viewer.php&lt;/code&gt; (or whatever your domain name is) in your browser and you will be presented a (not that nice-looking, but) very convenient and practical web UI to get an overview over all DMARC reports, grouped by domains, recipient providers and date. Much cooler than reading XML files one by one!&lt;/p&gt;

&lt;p&gt;Thanks a lot for the great work by &lt;a href="https://github.com/techsneeze" rel="noopener noreferrer"&gt;@techsneeze&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>security</category>
    </item>
    <item>
      <title>Decentralized cloud storage (Dropbox or Google Photos clone) on IPFS?</title>
      <dc:creator>Ferdinand Mütsch</dc:creator>
      <pubDate>Fri, 16 Apr 2021 07:11:12 +0000</pubDate>
      <link>https://dev.to/n1try/decentralized-cloud-storage-dropbox-or-google-photos-clone-on-ipfs-3kn5</link>
      <guid>https://dev.to/n1try/decentralized-cloud-storage-dropbox-or-google-photos-clone-on-ipfs-3kn5</guid>
      <description>&lt;p&gt;Today I woke up with the following idea.&lt;/p&gt;

&lt;p&gt;I thought that utilizing a system like &lt;a href="https://ipfs.io"&gt;IPFS&lt;/a&gt; combined with client-side file encryption it would probably be quite straightforward to build a truly decentralized / distributed alternative to Dropbox &amp;amp; Co. No single authority would be in charge of your files as they are distributed among potentially hundreds of network participants' computer, like with Torrent back in the days. Yet, due to encryption, nobody participating in hosting parts of your files on the network would actually be able to access them.&lt;/p&gt;

&lt;p&gt;I was wondering if someone already built something like this? Do you know of some implementation?&lt;/p&gt;

&lt;p&gt;What I found so far is &lt;a href="https://www.sharpphotos.io/"&gt;SharpPhotos&lt;/a&gt;, but it seems to be in a very early stage of development and is not open source.&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>Time Tracking for Developers – WakaTime vs. Hakatime vs. Wakapi</title>
      <dc:creator>Ferdinand Mütsch</dc:creator>
      <pubDate>Sat, 10 Apr 2021 10:36:26 +0000</pubDate>
      <link>https://dev.to/n1try/time-tracking-for-developers-wakatime-vs-hakatime-vs-wakapi-1hdf</link>
      <guid>https://dev.to/n1try/time-tracking-for-developers-wakatime-vs-hakatime-vs-wakapi-1hdf</guid>
      <description>&lt;h1&gt;
  
  
  Automated time tracking
&lt;/h1&gt;

&lt;p&gt;As a developer, you might be wondering how much time you are spending coding on a specific project, in a specific programming language, etc. Gathering detailed statistics about your work day might help you gain valuable insights on how to increase your productivity. Or maybe you are just a statistics freak, like me, who likes to track and analyze things. What you will need for that purpose is an automated time tracking tool. Many of these exist, but this article focuses on such that are specifically meant for developers, more specifically, on &lt;a href="https://wakatime.com" rel="noopener noreferrer"&gt;WakaTime&lt;/a&gt; and its derivatives in particular.&lt;/p&gt;

&lt;h1&gt;
  
  
  WakaTime and friends
&lt;/h1&gt;

&lt;p&gt;WakaTime is one of the most popular and wide-spread automated time tracking softwares for developers. It was – and still is – built by &lt;a href="https://github.com/alanhamlett" rel="noopener noreferrer"&gt;Alan Hamlett&lt;/a&gt; and started out &lt;a href="https://news.ycombinator.com/item?id=9994143" rel="noopener noreferrer"&gt;back in 2013&lt;/a&gt;. Its concept is very straightforward. Developers install a plugin into their code editor or IDE, which sends so called heartbeats to a web service on a regular basis. These heartbeats include all kinds of information on the project and the specific file you are currently working on. The backend-side web service aggregates and processes those heartbeats and generates detailed statistics and graphs from them, which you can then browse in a web dashboard. Many parts of WakaTime are open-source, however, the web service itself, which contains most of the core business logic, is not.&lt;/p&gt;

&lt;p&gt;WakaTime comes with around &lt;a href="https://wakatime.com/plugins" rel="noopener noreferrer"&gt;50 different IDE plugins&lt;/a&gt; (ranging from IntelliJ over VSCode to Vim and Emacs), which are all open source. Third-party projects like &lt;a href="https://github.com/mujx/hakatime" rel="noopener noreferrer"&gt;Hakatime&lt;/a&gt; and &lt;a href="https://wakapi.dev" rel="noopener noreferrer"&gt;Wakapi&lt;/a&gt; started to build up on that basis and implement their own backends, which resemble the official WakaTime's API and therefore are (partially) compatible.&lt;/p&gt;

&lt;p&gt;In the following, I want to present a brief comparison between these three software tools. Feel free to reach out if you are missing any aspect or if there is a mistake.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; I am the author of Wakapi.&lt;/p&gt;

&lt;h1&gt;
  
  
  Comparison
&lt;/h1&gt;

&lt;h2&gt;
  
  
  General
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;WakaTime&lt;/th&gt;
&lt;th&gt;Hakatime&lt;/th&gt;
&lt;th&gt;Wakapi&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Started in&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wakatime/wakatime/commit/3da94756aa1903c1cca5035803e3f704e818c086" rel="noopener noreferrer"&gt;2013&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/mujx/hakatime/commit/7dae338ece5bb485183c8c3e97e1cd3e36d7baae" rel="noopener noreferrer"&gt;2020&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/muety/wakapi/commit/0bd71b77085da62c573b44bb3a39f420840e7157" rel="noopener noreferrer"&gt;2019&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Open source&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Partially (BSD-3)&lt;/td&gt;
&lt;td&gt;✅ Yes (Unlicense)&lt;/td&gt;
&lt;td&gt;✅ Yes (GPL-3.0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wakatime/wakatime" rel="noopener noreferrer"&gt;wakatime/wakatime&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/mujx/hakatime" rel="noopener noreferrer"&gt;mujx/hakatime&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/muety/wakapi" rel="noopener noreferrer"&gt;muety/wakapi&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Written in&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;Haskell&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Databases&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;td&gt;Postgres&lt;/td&gt;
&lt;td&gt;SQLite, MySQL, Postgres, CockroachDB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;API Docs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ &lt;a href="https://wakatime.com/developers" rel="noopener noreferrer"&gt;Yes&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;⛔️ No&lt;/td&gt;
&lt;td&gt;✅ &lt;a href="https://wakapi.dev/swagger-ui/" rel="noopener noreferrer"&gt;Yes&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SaaS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ &lt;a href="https://wakatime.com" rel="noopener noreferrer"&gt;Yes&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://hakatime-demo.mtx-dev.xyz/" rel="noopener noreferrer"&gt;Demo only&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;✅ &lt;a href="https://wakapi.dev" rel="noopener noreferrer"&gt;Yes&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Self-hosted&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⛔️ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Docker deployment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⛔️ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pricing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0 - $49 / month&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;WakaTime&lt;/th&gt;
&lt;th&gt;Hakatime&lt;/th&gt;
&lt;th&gt;Wakapi&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Teams / Orgs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;⛔️ No&lt;/td&gt;
&lt;td&gt;⛔️ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Goals&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;⛔️ No&lt;/td&gt;
&lt;td&gt;⏳ &lt;a href="https://github.com/muety/wakapi/issues/166" rel="noopener noreferrer"&gt;TBD&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Leaderboards&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;⛔️ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Badges&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data import&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3rd-party integrations&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;⛔️ No&lt;/td&gt;
&lt;td&gt;⛔️ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Prometheus export&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⛔️ No&lt;/td&gt;
&lt;td&gt;⛔️ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;E-Mail reports&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;⛔️ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Timeline charts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;⏳ &lt;a href="https://github.com/muety/wakapi/issues/101" rel="noopener noreferrer"&gt;TBD&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Activity charts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⛔️ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;⏳ &lt;a href="https://github.com/muety/wakapi/issues/12" rel="noopener noreferrer"&gt;TBD&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Statistics
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;WakaTime&lt;/th&gt;
&lt;th&gt;Hakatime&lt;/th&gt;
&lt;th&gt;Wakapi&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Projects&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Languages&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Editors / IDEs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;⛔️ No (?)&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Operating Systems&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;⛔️ No (?)&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Machines&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;⛔️ No (?)&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Files&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;⏳ &lt;a href="https://github.com/muety/wakapi/issues/80" rel="noopener noreferrer"&gt;TBD&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Branches&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;⛔️ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Commits&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;⛔️ No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  Screenshots
&lt;/h1&gt;

&lt;h2&gt;
  
  
  WakaTime
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapps.muetsch.io%2Fimages%2Fo%3Aauto%2Frs%2Cs%3A640%3Fimage%3Dhttps%3A%2F%2Fmuetsch.io%2Fimages%2Fwakatime_screenshot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapps.muetsch.io%2Fimages%2Fo%3Aauto%2Frs%2Cs%3A640%3Fimage%3Dhttps%3A%2F%2Fmuetsch.io%2Fimages%2Fwakatime_screenshot.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Hakatime
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapps.muetsch.io%2Fimages%2Fo%3Aauto%2Frs%2Cs%3A640%3Fimage%3Dhttps%3A%2F%2Fmuetsch.io%2Fimages%2Fhakatime_screenshot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapps.muetsch.io%2Fimages%2Fo%3Aauto%2Frs%2Cs%3A640%3Fimage%3Dhttps%3A%2F%2Fmuetsch.io%2Fimages%2Fhakatime_screenshot.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wakapi
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapps.muetsch.io%2Fimages%2Fo%3Aauto%2Frs%2Cs%3A640%3Fimage%3Dhttps%3A%2F%2Fmuetsch.io%2Fimages%2Fwakapi_screenshot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapps.muetsch.io%2Fimages%2Fo%3Aauto%2Frs%2Cs%3A640%3Fimage%3Dhttps%3A%2F%2Fmuetsch.io%2Fimages%2Fwakapi_screenshot.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For Wakapi, a major redesign of the user interface &lt;a href="https://github.com/muety/wakapi/issues/82#issuecomment-774993875" rel="noopener noreferrer"&gt;is planned&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;Clearly, WakaTime is the most mature and feature-rich tool, but, of course, comes at a cost.&lt;/p&gt;

&lt;p&gt;Hakatime has a nice-looking, clean user interface and beautiful visualizations. On the other hand, it felt a bit rough and sometimes buggy here and there during my tests. But of course, this is a subjective experience.&lt;/p&gt;

&lt;p&gt;Wakapi is built on a solid, technical basis and provides lots of configuration options for developers. However, its current UI is quite minimalist and less fancy, compared to the other tools.&lt;/p&gt;

&lt;p&gt;All three projects are under active development and with many new features about to come in the future. Wakapi also highly welcomes open source contributions to its code base.&lt;/p&gt;

&lt;p&gt;If you are interested in tracking your coding statistics, I would like to encourage you to give all three tools a try. Feel free to share your thoughts!&lt;/p&gt;

</description>
      <category>productivity</category>
    </item>
    <item>
      <title>My Experiences with the Oracle Java 11 Developer Certification</title>
      <dc:creator>Ferdinand Mütsch</dc:creator>
      <pubDate>Fri, 19 Mar 2021 14:58:40 +0000</pubDate>
      <link>https://dev.to/n1try/my-experiences-with-the-oracle-java-11-developer-certification-24o7</link>
      <guid>https://dev.to/n1try/my-experiences-with-the-oracle-java-11-developer-certification-24o7</guid>
      <description>&lt;p&gt;On the occasion of the Java programming language's 25th anniversary, Oracle just recently announced a pretty nice discount on the &lt;em&gt;Java 11 SE Developer Certification&lt;/em&gt;. For a limited period of time you can get the exam, which normally costs something around $ 250 and the accompanying course, which, I believe, is even more expensive, for a total of $ 25.&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://frachtwerk.de"&gt;Frachtwerk&lt;/a&gt;, we decided to take the chance and give all backend developers the opportunity to get certified. Today, I had my exam and here are my thoughts on the journey to get there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Course
&lt;/h2&gt;

&lt;p&gt;The course's target audience includes developers, who already have a decent amount of experience with programming in Java, but not necessarily are experts, yet. Motivation for taking the course could be to further improve your skills or to specifically prepare for the certification. A certification like this, in turn, can help developers set themselves apart from potential competitors on the job market.&lt;/p&gt;

&lt;p&gt;I, personally, have been developing web applications in Java for a few years now and would consider my experience and language skills fairly advanced. However, I was surprised how much I could still learn, though. I even got to know feature of Java that I haven't had heard of before.&lt;/p&gt;

&lt;p&gt;Contents of the course range from the most essential things like types, variables, loops and conditions over inheritance principles, streams, I/O and JDBC throughout to low-level multi-threading and concurrency concepts and details about the fairly new Java 9 module system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xvUlEB7a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://apps.muetsch.io/images/o:auto/rs%2Cs:640%3Fimage%3Dhttps://muetsch.io/images/java_cert1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xvUlEB7a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://apps.muetsch.io/images/o:auto/rs%2Cs:640%3Fimage%3Dhttps://muetsch.io/images/java_cert1.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Overall, my experience with the course was very good! All content is of overly high quality, explanations are clear and understandable and the topics covered are not at all only superficial, but indeed quite advanced. At the end of each section there are quizzes for reviewing your knowledge. &lt;/p&gt;

&lt;p&gt;Here are a few examples of such questions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uHYiyLmb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://apps.muetsch.io/images/o:auto/rs%2Cs:640%3Fimage%3Dhttps://muetsch.io/images/java_cert6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uHYiyLmb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://apps.muetsch.io/images/o:auto/rs%2Cs:640%3Fimage%3Dhttps://muetsch.io/images/java_cert6.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Yr-kelC5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://apps.muetsch.io/images/o:auto/rs%2Cs:640%3Fimage%3Dhttps://muetsch.io/images/java_cert4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Yr-kelC5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://apps.muetsch.io/images/o:auto/rs%2Cs:640%3Fimage%3Dhttps://muetsch.io/images/java_cert4.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_9a-nr2S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://apps.muetsch.io/images/o:auto/rs%2Cs:640%3Fimage%3Dhttps://muetsch.io/images/java_cert5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_9a-nr2S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://apps.muetsch.io/images/o:auto/rs%2Cs:640%3Fimage%3Dhttps://muetsch.io/images/java_cert5.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Experienced Java developers might easily skip the first ten chapters, however, the higher ones were actually very interesting to me.&lt;/p&gt;

&lt;p&gt;Among others, I got to learn about how &lt;a href="https://docs.oracle.com/javase/tutorial/java/generics/erasure.html"&gt;type erasure&lt;/a&gt; of generics works, what &lt;a href="https://docs.oracle.com/javase/tutorial/java/generics/nonReifiableVarargsType.html#heap_pollution"&gt;heap pollution&lt;/a&gt; is, how the &lt;code&gt;volatile&lt;/code&gt; type modifier functions and that &lt;a href="//images/java_cert3.png"&gt;interfaces can have private, non-abstract methods&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Especially since it is free, I would definitely recommend the course, even to experienced Java developers. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Exam
&lt;/h2&gt;

&lt;p&gt;After I had spent about 1.5 full work days for preparation, I eventually felt confident enough to register for the exam. It takes place online and you can choose from a variety of different dates and times. &lt;/p&gt;

&lt;p&gt;I was perfectly satisfied with the learning path so far. However, there were a few points about the exam which I did not quite like that much.&lt;/p&gt;

&lt;p&gt;While the certification program itself is offered by Oracle, the exam takes place through an external provider called Pearson VUE. Being spezialized on online exams, they take various measures to prevent cheating. In principle, this is a positive thing. I could not take a certificate seriously if I knew that you could google during the exam or do partner work. However, in my opinion, things were a bit too strict here.&lt;/p&gt;

&lt;p&gt;First of all, you have to download and install a desktop application through which the exam itself is conducted. Unfortunately, it is only available for Windows and Mac and it turned out to be non-trivial to find a non-Linux computer with webcam and microphone at our office. The program runs full-screen and you can not exit to your desktop or other programs from it, which makes sense. Also, it automatically kills processes like &lt;code&gt;teamviewer.exe&lt;/code&gt;, &lt;code&gt;anydesk.exe&lt;/code&gt; and all kinds of software you might be able to use for cheating. &lt;/p&gt;

&lt;p&gt;Before the exam starts, you are assigned an instructor, who, apparently, is an employee at Pearson VUE, sitting in a call center somewhere. He or she asks you to upload pictures of your surrounding environment, including your desk. Also, you need to provide your ID card. The guy asked me to remove nearly everything except my notebook from my desk. You are not allowed to have pen and paper on your desk, no smartphone, no smartwatch, no food or drinks, except water. &lt;/p&gt;

&lt;p&gt;So far so good. However, at some point, things started to get a little ridiculous for my taste. I have an external monitor on my desk, which I was asked to unplug and show the unplugged power supply cable on webcam. During the exam, you are constantly being monitored through cam and microphone. If your face disappears on the webcam, you failed. If another person enters the room, you failed. A point at which I started to get annoyed was when the guy told me to not speak and not move my lips. I tend to speak questions and answers to myself while I analyze and think about them. Not allowed. I wanted to take a short break for a moment and look out of the window. Immediately, the guy reached out to me to remind me that if I looked away from my screen again, he would have to cancel my exam. Being monitored in such a strict way was distracting. &lt;/p&gt;

&lt;p&gt;Eventually, I passed the exam after 90 minutes and 50 multiple-choice coding questions and got my certificate. The only disappointing thing about it is the fact that my last name, &lt;code&gt;Mütsch&lt;/code&gt;, is instead written as &lt;code&gt;M%C3%Btsch&lt;/code&gt; on it. I would not have guessed that non-ASCII characters like German umlauts would still be a problem in 2021. But anyway, I am happy that I passed the exam, which, indeed, was way harder than I would have expected.&lt;/p&gt;

&lt;p&gt;All in all, I am still happy with this experience and would recommend it to any Java developer, too. &lt;/p&gt;

</description>
      <category>java</category>
    </item>
    <item>
      <title>Consider Sponsoring Open Source</title>
      <dc:creator>Ferdinand Mütsch</dc:creator>
      <pubDate>Sat, 12 Dec 2020 20:21:43 +0000</pubDate>
      <link>https://dev.to/n1try/consider-sponsoring-open-source-ejo</link>
      <guid>https://dev.to/n1try/consider-sponsoring-open-source-ejo</guid>
      <description>&lt;h1&gt;
  
  
  Open Source powers the world
&lt;/h1&gt;

&lt;p&gt;What would the software world be without open source projects? Imagine there was no Linux, no Java or Python, no Android, no Firefox or Chromium, no Apache2 or nginx, no Kubernetes, no VSCode, no Angular, React or Vue, no Numpy, TensorFlow or PyTorch, ... The list of big open source projects, that power the vast majority of the entire internet, is almost infinitely long. Besides that, at least as important are the countless, small hobby projects driven by one or a few individuals. If you ever were looking for some script, tool, app or library, chances are high that you found exactly what you needed on GitHub.&lt;/p&gt;

&lt;p&gt;While most of the projects mentioned above are backed by large companies like Google or Microsoft or non-profit organizations like the &lt;a href="https://opensource.org/"&gt;Open Source Initiative&lt;/a&gt; or the &lt;a href="https://apache.org/"&gt;Apache Foundation&lt;/a&gt;, individual developers with projects with only a few hundred stars build excellent software as well. But they do it in their spare time and without getting paid anything. No matter if they build and maintain their software out of passion, in order to solve a problem for themselves or just for learning purposes – in my opinion, the efforts of individual contributors should be appreciated even more.&lt;/p&gt;

&lt;p&gt;Accordingly, in this article, I want to &lt;strong&gt;encourage people to consider sponsoring for free software projects&lt;/strong&gt; they like. It complements my previous article on &lt;em&gt;&lt;a href="https://muetsch.io/donating-for-a-good-cause.html"&gt;Donating for a Good Cause&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Supporting the small ones
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;While the big corporations have the necessary funding and resources, most open source projects are developed by individuals in their spare time. However, it does require one’s efforts, time and probably includes some overhead costs too. Monetary supports surely help drive the project development. &lt;a href="https://itsfoss.com/open-source-funding-platforms/"&gt;[1]&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When maintaining a GitHub project, your aim is not to make money from it – although some &lt;a href="https://www.youtube.com/watch?v=OrxmtDw4pVI"&gt;lucky people&lt;/a&gt; can actually earn their living from their open source work. However, receiving small donations here and there, which express other people's appreciation for your efforts, will definitely boost your motiviation to keep on coding. Of course, monetary donations are only one of &lt;a href="https://itsfoss.com/help-linux-grow/"&gt;many options&lt;/a&gt; to support a project, but, indeed, probably the easiest one.&lt;/p&gt;

&lt;h1&gt;
  
  
  Ways to give
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;If you really like a product, buy it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are many different options to support a project. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Star it ⭐.&lt;/strong&gt; This is obvious, doesn't cost you anything and the project's maintainer is likely going to be a bit proud for a moment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buy the &lt;em&gt;Pro&lt;/em&gt; version 🦄.&lt;/strong&gt; Apps or web services often have a free basic version and a paid version with some benefits. Buying the advanced version will not only give you more features or less ads, but is usually also quite cheap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Give a one-time donation 💶.&lt;/strong&gt; Most of the time, a small one-time donation of a few dollars is already enough to make the project's author happy and boost her motivation. And, of course, the more people do it, the better.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set up recurring payments 🔄.&lt;/strong&gt; If you really like a project and strongly want it to keep going – especially if it has explicit maintenance costs like hosting, etc. – consider donating a small amount on a regular basis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get &lt;em&gt;sponsorware&lt;/em&gt; content 🔜.&lt;/strong&gt; &lt;a href="https://github.com/sponsorware/docs"&gt;Sponsorware&lt;/a&gt; is a release strategy for open-source software that enables developers to be compensated for their open-source work with fewer downsides than traditional open-source funding models.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Sponsoring platforms
&lt;/h1&gt;

&lt;p&gt;Different sponsoring / donations platforms have established over the years, many of them with an explicit focus on software projects and some of them also being non-profit. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--I5cykv_J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://apps.muetsch.io/images/o:auto%3Fimage%3Dhttps://muetsch.io/images/gh_sponsors.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--I5cykv_J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://apps.muetsch.io/images/o:auto%3Fimage%3Dhttps://muetsch.io/images/gh_sponsors.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Sponsors:&lt;/strong&gt; First and foremost, there is GitHub's own sponsoring feature, that was &lt;a href="https://github.blog/2020-03-24-getting-started-with-github-sponsors/"&gt;introduced in early 2020&lt;/a&gt;. On one hand, it is a sponsoring platform for itself – mostly for recurring, monthly payments – where GitHub is in the role of managing the donations. Like with most other platforms, you do not directly sponsor a certain project, but a user. On the other hand, the new feature also allows project maintainers to link other, third-party donation platforms to their projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Liberapay:&lt;/strong&gt; Founded in 2016, &lt;a href="https://liberapay.com/"&gt;Liberapay&lt;/a&gt; is one of the major donation platforms in the open source world. It is &lt;a href="https://github.com/liberapay"&gt;partially open-source&lt;/a&gt; as well, has &lt;a href="https://medium.com/liberapay-blog/the-fourth-year-of-liberapay-bbb8563cfac8"&gt;around 3000 active users&lt;/a&gt; and is free of fees, except for the payment provider's (Stripe) fees. Liberapay is for recurring donations on a weekly basis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opencollective:&lt;/strong&gt; Equally as wide-spread as Liberapay is &lt;a href="https://opencollective.com/"&gt;Opencollective&lt;/a&gt;, which has the same purpose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ko-Fi:&lt;/strong&gt; &lt;a href="https://ko-fi.com/"&gt;Ko-Fi&lt;/a&gt; is another platform that you will occasionally see linked to GitHub or GitLab projects. It is &lt;a href="https://help.ko-fi.com/hc/en-us/articles/360002506494-Does-Ko-fi-take-a-fee-"&gt;100 % free&lt;/a&gt; and supports both one-time donations as well as subscriptions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buymeacoffee:&lt;/strong&gt; Very similar to Ko-Fi, &lt;a href="https://www.buymeacoffee.com/"&gt;Buy me a coffee&lt;/a&gt; is another donations platform with support for one-time donations and monthly subscriptions. You see it often used not only for software, but also on blogs or by content creators.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bountysource:&lt;/strong&gt; Lastly, there is &lt;a href="https://www.bountysource.com/"&gt;Bountysource&lt;/a&gt;, which is specifically for software, but works a bit different than the above platforms in a way that you place a bounty on a specific issue on a repo. This way, you can push features of a project that are especially important to you. When the issue is resolved, the reward does not necessarily go to the project owner, but to the person who implemented the respective feature or bug fix. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are more platforms, but these are the ones that I consider most common or relevant.&lt;/p&gt;

&lt;p&gt;The purpose of this article is to give a few good reasons why sponsoring software projects is desirable and important and provide a brief overview of different ways to sponsor software projects. I would be happy to see more people being a bit more generous when it comes to free software 😊.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>github</category>
    </item>
    <item>
      <title>Watching stock prices with Node-RED and Webhook2Telegram</title>
      <dc:creator>Ferdinand Mütsch</dc:creator>
      <pubDate>Mon, 30 Nov 2020 20:08:47 +0000</pubDate>
      <link>https://dev.to/n1try/watching-stock-prices-with-node-red-and-webhook2telegram-o47</link>
      <guid>https://dev.to/n1try/watching-stock-prices-with-node-red-and-webhook2telegram-o47</guid>
      <description>&lt;h1&gt;
  
  
  Motivation
&lt;/h1&gt;

&lt;p&gt;I hold a few stocks and I want to stay up-to-date about their quotations. However, I found it a bit tedious to actively log in to my portfolio every day to see what has changed. So I decided that I needed a notification system that &lt;strong&gt;automatically informs me about the relative price changes&lt;/strong&gt; for all of my stocks once a day. Since I get all kinds of notifications – including security alerts from my server, updates in my GitHub feed, and more – via &lt;a href="https://telegram.org" rel="noopener noreferrer"&gt;Telegram&lt;/a&gt;, the choice to use that messenger for stock price notifications as well was quite obvious. Another obvious decision what have been to write a small Python script, that gets executed once a day via CRON. However, this time, &lt;strong&gt;I didn't want to write any code&lt;/strong&gt;, but instead try the flow-based visual programming tool Node-RED.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nodered.org/" rel="noopener noreferrer"&gt;Node-RED&lt;/a&gt; is a JavaScript-based platform to compose logical workflows through combining small, elementary building blocks together. Such building block, called nodes, include functionality to ingest data (e.g. via HTTP calls, MQTT subscriptions or reading a file), process it (e.g. string replacements, logical condition checks, aggregations, etc.) and output it in some way again (again, via HTTP, MQTT, files, etc.). Without writing any code, but only through configuring these elementary operations, whole programs can be built. While Node-RED is primarily used in IoT contexts, it basically serves any purpose. An even more comprehensive and "mature" alternative is, to some extent, &lt;a href="http://nifi.apache.org/" rel="noopener noreferrer"&gt;Apache NiFi&lt;/a&gt;. However, while Node-RED is perfect for tinkering and small projects, NiFi focuses on scalability and Big-Data-like workloads.&lt;/p&gt;

&lt;h1&gt;
  
  
  Flow
&lt;/h1&gt;

&lt;p&gt;The resulting flow, that fulfills the above mentioned purpose looks like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apps.muetsch.io/images/o:auto?image=https://muetsch.io/images/nodered-flow.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapps.muetsch.io%2Fimages%2Fo%3Aauto%2Frs%2Cs%3A640%3Fimage%3Dhttps%3A%2F%2Fmuetsch.io%2Fimages%2Fnodered-flow.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The flow's entry node is an &lt;code&gt;inject&lt;/code&gt; node, that holds a JSON array of all my stocks' symbols (e.g. &lt;code&gt;QCOM&lt;/code&gt;) and is automatically executed once every afternoon. The message is then split into multiple, individual messages, namely, one for every stock symbol. A &lt;code&gt;http request&lt;/code&gt; node then calls the &lt;a href="https://www.alphavantage.co/" rel="noopener noreferrer"&gt;Alphavantage API&lt;/a&gt; once for every message to fetch the intra-day price changes. Subsequently, the response is parsed, post-processed and formatted as Markdown. Eventually all individual messages are combined into one again before my &lt;a href="https://github.com/muety/webhook2telegram" rel="noopener noreferrer"&gt;Webhook2Telegram&lt;/a&gt; bot is requested to send me the message as a last step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapps.muetsch.io%2Fimages%2Fo%3Aauto%3Fimage%3Dhttps%3A%2F%2Fmuetsch.io%2Fimages%2Fnodered-flow2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapps.muetsch.io%2Fimages%2Fo%3Aauto%3Fimage%3Dhttps%3A%2F%2Fmuetsch.io%2Fimages%2Fnodered-flow2.png"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>telegram</category>
      <category>automation</category>
      <category>node</category>
    </item>
    <item>
      <title>Mastering Software Versioning in JavaScript Projects</title>
      <dc:creator>Ferdinand Mütsch</dc:creator>
      <pubDate>Mon, 09 Nov 2020 09:52:42 +0000</pubDate>
      <link>https://dev.to/frachtwerk/mastering-software-versioning-in-javascript-projects-4o22</link>
      <guid>https://dev.to/frachtwerk/mastering-software-versioning-in-javascript-projects-4o22</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;A frequently overlooked aspect of software development is the proper versioning of code. Consistent and descriptive version numbers not only help developers keep track of their own work, but can also inform users of your software about what to expect from a new release. While versioning is especially important for libraries and frameworks which other projects depend on, benefits apply to standalone applications equally.&lt;/p&gt;

&lt;p&gt;In this article, we introduce techniques, conventions, and tooling that helped us establish a robust way of versioning our JavaScript- and/or TypeScript-based software projects. &lt;/p&gt;

&lt;h1&gt;
  
  
  Concepts
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Semantic Versioning
&lt;/h2&gt;

&lt;p&gt;One of the most important aspects to think of when it comes to versioning is the version number itself. Before caring about tooling and others, you need to come up with syntax and semantics for it. &lt;/p&gt;

&lt;p&gt;A concept that is well established among open source software projects is &lt;a href="https://semver.org/"&gt;Semantic Versioning&lt;/a&gt;, or &lt;em&gt;SemVer&lt;/em&gt;. When following this specification, a version number consists of three digits separated by dots, like &lt;code&gt;1.5.4&lt;/code&gt; or, more formally &lt;code&gt;&amp;lt;MAJOR&amp;gt;.&amp;lt;MINOR&amp;gt;.&amp;lt;PATCH&amp;gt;&lt;/code&gt;, where each individual part has a meaning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MAJOR&lt;/code&gt;: Incrementing it indicates that there have been fundamental changes to the software and the new version is most likely not backward-compatible with the previous one.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MINOR&lt;/code&gt;: Essentially indicates that new features were added, but backward-compatibiltiy is still guaranteed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PATCH&lt;/code&gt; or &lt;code&gt;BUG FIX&lt;/code&gt;: Gives a hint that minor changes or bug fixes had been introduced recently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Strictly following these semantics helps to maintain a common understanding of what a certain version means and what to expect from a new release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conventional Commits
&lt;/h2&gt;

&lt;p&gt;The second concept that we committed ourselves to follow is &lt;a href="https://www.conventionalcommits.org/en/v1.0.0/#summary"&gt;Conventional Commits&lt;/a&gt;. Similar to semantic versioning, the conventional commits specification provides common syntax and semantics for information provided by a developer. However, in this case, the convention is not about the version number itself, but about the commit messages composed by developers when checking in new code into version control. The goal is to standardize their format and make them machine-readable to a certain extent.&lt;/p&gt;

&lt;p&gt;When following conventional commits, a commit message essentially has to be prefixed with one of a few keywords.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fix:&lt;/code&gt; – A commit message with this prefix indicates a bug fix&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;feat:&lt;/code&gt; – A commit message with this prefix indicates the introduction of a new feature or functionality&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;refactor:&lt;/code&gt; – A commit with, whose message is prefixed like this, contains code refactorings, i.e. internal, technical modifications of the implementation of certain logic&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;chore:&lt;/code&gt; – This prefix indicates minor, miscellaneous changes of any type, that do not necessarily affect the user immediately&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BREAKING CHANGE!:&lt;/code&gt; A commit message with this prefix warns about comprehensive, fundamental changes and indicates that the newly released version is likely to be incompatible with the previous one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The conventional commits specification comprises a few more keywords and also allows developers to come up with custom ones. However, these are the most relevant ones.&lt;/p&gt;

&lt;h1&gt;
  
  
  Tooling
&lt;/h1&gt;

&lt;p&gt;When having paid attention, one might have recognized a few similarities in the semantics of conventional commits and semantic versioning. Commits with &lt;code&gt;fix&lt;/code&gt;-changes correspond to the &lt;code&gt;PATCH&lt;/code&gt; version, &lt;code&gt;feat&lt;/code&gt; goes well with the &lt;code&gt;MINOR&lt;/code&gt; version and &lt;code&gt;BREAKING CHANGE&lt;/code&gt;es will inevitably result in a new &lt;code&gt;MAJOR&lt;/code&gt; version. &lt;/p&gt;

&lt;p&gt;As a consequence of following the above conventions, we enabled our project for an automated versioning workflow. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.npmjs.com/package/standard-version"&gt;standard-version&lt;/a&gt; CLI
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/standard-version"&gt;standard-version&lt;/a&gt; is a JavaScript tool that utilizes conventional commits to automatically enforce semantic versioning. Moreover, it is capable of automatically generating a changelog in Markdown format, which developers can provide their users with. &lt;/p&gt;

&lt;p&gt;When running &lt;code&gt;standard-version&lt;/code&gt;, the tool scans your commit history since when it was last executed, searches for fixes, feats, or breaking changes, and adapts the project's version accordingly.&lt;/p&gt;

&lt;p&gt;To add it to an existing project, all you need to do is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install it as a dependency
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;yarn add &lt;span class="nt"&gt;-D&lt;/span&gt; standard-version  &lt;span class="c"&gt;# (or npm i --save-dev standard-version)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Optionally add it as an NPM script to your &lt;code&gt;package.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-cool-project"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version:"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"scripts:"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"release"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"standard-version"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Release Workflow
&lt;/h1&gt;

&lt;p&gt;After the development team has committed to consequently follow the conventional commits specification and all tooling is set up, a typical workflow to release new versions of your software might look like so.&lt;/p&gt;

&lt;p&gt;Once a new version is ready to be released, i.e. at the end of a sprint, a developer executes &lt;code&gt;yarn release&lt;/code&gt; (or &lt;code&gt;npm run release&lt;/code&gt;) to kick off &lt;code&gt;standard-version&lt;/code&gt;. As a result ...&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;... the project's commit history is scanned to determine which part of the version number needs to be incremented&lt;/li&gt;
&lt;li&gt;... the &lt;code&gt;version&lt;/code&gt; property of the project's top-level &lt;code&gt;package.json&lt;/code&gt; is set to the new version&lt;/li&gt;
&lt;li&gt;... a &lt;code&gt;CHANGELOG.md&lt;/code&gt; file is written, containing separate sections for features and bug fixes&lt;/li&gt;
&lt;li&gt;... the changes are committed to Git&lt;/li&gt;
&lt;li&gt;... the new commit is given a Git tag corresponding to the new version&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Depending on your setup, a push to the remote repository might kick off your CI/CD workflow, which may automatically build a new Docker image with the newly introduced tag and push it to a public or private registry. Using tools like &lt;a href="https://github.com/containrrr/watchtower"&gt;Watchtower&lt;/a&gt;, the new image might even be rolled out to production automatically.&lt;/p&gt;

&lt;p&gt;The only manual steps required in this workflow were a single &lt;code&gt;yarn release&lt;/code&gt; command and a Git push. Nothing more, nothing less.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;The above workflow has proven to be a convenient and consistent way of managing and releasing new versions of our JavaScript- and TypeScript-based frontend-, backend- and library projects and is even more beneficial with proper CI/CD pipelines and tooling like &lt;a href="https://gitlab.com"&gt;GitLab&lt;/a&gt;, &lt;a href="https://docker.io"&gt;Docker&lt;/a&gt;, &lt;a href="https://github.com/containrrr/watchtower"&gt;Watchtower&lt;/a&gt;, &lt;a href="https://portainer.io"&gt;Portainer&lt;/a&gt;, and others. It might even be adapted to projects written in other programming languages. &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>GitHub Action to turn your profile README into a guestbook</title>
      <dc:creator>Ferdinand Mütsch</dc:creator>
      <pubDate>Tue, 15 Sep 2020 20:23:31 +0000</pubDate>
      <link>https://dev.to/n1try/github-action-to-turn-your-profile-readme-into-a-guestbook-4767</link>
      <guid>https://dev.to/n1try/github-action-to-turn-your-profile-readme-into-a-guestbook-4767</guid>
      <description>&lt;p&gt;I am happy to participate in the Actions Hackathon and to share my work with my fellow dev.to readers and the open source community. Hopefully, my little project inspires you to use it or to start hacking on your own actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  My Workflow
&lt;/h3&gt;

&lt;p&gt;As an open source contributor chances are high that you want to get feedback from the community and especially the lovely people who use your code to solve their problems. You need a way for them to interact with you and enable them to share their thoughts and thankfulness about your projects. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/muety/readme-guestbook/"&gt;readme-guestbook&lt;/a&gt; turns your GitHub profile page into an interactive guestbook. People can post issues to your repo, which are then rendered to the Markdown of your README. &lt;/p&gt;

&lt;p&gt;Try it out!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Submission Category:&lt;/strong&gt; Wacky Wildcards&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vJ70wriM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/muety"&gt;
        muety
      &lt;/a&gt; / &lt;a href="https://github.com/muety/readme-guestbook"&gt;
        readme-guestbook
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A GitHub action to create a guestbook in your README from repository issues
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Additional Resources / Info
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;My own &lt;a href="https://github.com/muety"&gt;profile&lt;/a&gt; as an example of the action in action 🤓&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>actionshackathon</category>
      <category>github</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Modern, reactive web APIs with GraphQL, Go and Server-Sent Events – Part 1</title>
      <dc:creator>Ferdinand Mütsch</dc:creator>
      <pubDate>Mon, 08 Jun 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/frachtwerk/modern-reactive-web-apis-with-graphql-go-and-server-sent-events-part-1-5</link>
      <guid>https://dev.to/frachtwerk/modern-reactive-web-apis-with-graphql-go-and-server-sent-events-part-1-5</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fax3gb0v3hw65oz3pbbyd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fax3gb0v3hw65oz3pbbyd.png" alt="Cover Image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;In the course of this two-part article, the interested reader is briefly introduced to the basic of GraphQL and how it compares to traditional approaches. In the second part, an example single-page web application (SPA) is built to demonstrate the use of GraphQL in combination with further modern web technologies. The &lt;a href="https://github.com/muety/go-graphql-sse-example" rel="noopener noreferrer"&gt;final project&lt;/a&gt; provides a clean, opinionated code- and project structure for both backend and frontend and constitutes a good starting point for new apps based on the presented tech stack.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/muety" rel="noopener noreferrer"&gt;
        muety
      &lt;/a&gt; / &lt;a href="https://github.com/muety/go-graphql-sse-example" rel="noopener noreferrer"&gt;
        go-graphql-sse-example
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Basic example application using Go + GraphQL Subscriptions + Server-Sent Events + MongoDB + VueJS
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;go-graphql-sse-example&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Basic example application to demonstrate an alternative way of building web APIs compared to REST. It combines these technologies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://golang.org" rel="nofollow noopener noreferrer"&gt;Go&lt;/a&gt; as the primary backend-side language&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://graphql.org/" rel="nofollow noopener noreferrer"&gt;GraphQL&lt;/a&gt; as a "protocol" for defining web interfaces&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events" rel="nofollow noopener noreferrer"&gt;Server-Sent Events&lt;/a&gt; as a simple protocol for live updates, used as an implementation of &lt;a href="https://graphql.org/blog/subscriptions-in-graphql-and-relay/" rel="nofollow noopener noreferrer"&gt;GraphQL Subscriptions&lt;/a&gt; here&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.mongodb.com/" rel="nofollow noopener noreferrer"&gt;MongoDB&lt;/a&gt; as a flexible document database for storage&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vuejs.org/" rel="nofollow noopener noreferrer"&gt;VueJS&lt;/a&gt; as a frontend framework to build single-page web applications&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This project is intended to serve as a starting point to build modern, GraphQL-based single-page web application with a clean, opinionated code structure in backend and frontend.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://muetsch.io/modern-reactive-web-apis-with-graphql-go-and-server-sent-events-part-1.html" rel="nofollow noopener noreferrer"&gt;Original blog post&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Inspired by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/OscarYuen/go-graphql-starter" rel="noopener noreferrer"&gt;OscarYuen/go-graphql-starter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/andoshin11/clean-architecture-example-vue" rel="noopener noreferrer"&gt;andoshin11/clean-architecture-example-vue&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Requirements&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Go &amp;gt;= 1.13&lt;/li&gt;
&lt;li&gt;NodeJS &amp;gt;= 12.8.x&lt;/li&gt;
&lt;li&gt;A MongoDB database
&lt;ul&gt;
&lt;li&gt;You can get a free one at &lt;a href="https://mlab.com/" rel="nofollow noopener noreferrer"&gt;mlab.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Backend (&lt;code&gt;/&lt;/code&gt;)&lt;/h3&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Edit &lt;code&gt;config.yml&lt;/code&gt; to configure your database&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/muety/go-graphql-sse-exampledocs/database.md" rel="noopener noreferrer"&gt;Set up&lt;/a&gt; the database (i.e. add users, collections and demo data)&lt;/li&gt;
&lt;li&gt;Compile the GraphQL schema (located in &lt;a href="https://github.com/muety/go-graphql-sse-exampleschema" rel="noopener noreferrer"&gt;&lt;code&gt;schema&lt;/code&gt;&lt;/a&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/muety/go-graphql-sse-example" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h1&gt;
  
  
  What is GraphQL?
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://engineering.fb.com/core-data/graphql-a-data-query-language/" rel="noopener noreferrer"&gt;GraphQL&lt;/a&gt; is a relatively new (proposed in 2015 by Facebook engineers) approach to designing APIs for (web) backend applications and can be considered an alternative to &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/REST" rel="noopener noreferrer"&gt;REST&lt;/a&gt; or remote-procedure-call (RPC) mechanisms like &lt;a href="https://www.jsonrpc.org/" rel="noopener noreferrer"&gt;JSON-RPC&lt;/a&gt;. In other words, it's &lt;em&gt;"an open-source data query and manipulation language for APIs"&lt;/em&gt; &lt;a href="https://en.wikipedia.org/wiki/GraphQL" rel="noopener noreferrer"&gt;[1]&lt;/a&gt;. The specification is open-source and actively evolving on &lt;a href="https://github.com/graphql/graphql-spec" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. While REST APIs are currently the de-facto standard on the web (although not necessarily all of them being fully &lt;a href="https://www.martinfowler.com/articles/richardsonMaturityModel.html" rel="noopener noreferrer"&gt;mature&lt;/a&gt;) – GraphQL starts to &lt;a href="https://trends.google.com/trends/explore?date=2018-05-06%202020-06-06&amp;amp;gprop=youtube&amp;amp;q=graphql" rel="noopener noreferrer"&gt;gain traction&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison with REST and RPC
&lt;/h2&gt;

&lt;p&gt;In contrast to REST, which is primarily structured around resources or entities and RPC-based APIs, which focus on actions or methods, GraphQL is all about the underlying data itself. The consumer of an API – usually the frontend / client-side part of a SPA – only has to know the schema and structure of the data provided by the API to &lt;a href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete" rel="noopener noreferrer"&gt;CRUD&lt;/a&gt; it. Compared to REST APIs, where the consumer heavily depends on the fixed data structure delivered by the backend API, this is especially beneficial as it introduced a lot more flexibility and decoupling and can save the developer some time making the client-side application &lt;a href="https://martinfowler.com/bliki/TolerantReader.html" rel="noopener noreferrer"&gt;tolerant&lt;/a&gt;. Also, you will probably not need &lt;a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/backends-for-frontends" rel="noopener noreferrer"&gt;backends for frontends&lt;/a&gt; anymore.&lt;/p&gt;

&lt;p&gt;Essentially, with GraphQL, &lt;strong&gt;the consumer asks exactly for what it needs and how it needs it&lt;/strong&gt;, i.e. your client application tells the backend exactly what to return and in which format. &lt;strong&gt;Consuming a GraphQL API is like querying a database, but with more guidance and control.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;Let's look at an example to get a better idea of how GraphQL works, especially in comparison to the REST principles. &lt;/p&gt;

&lt;p&gt;Imagine you have an e-commerce application with products and orders. Every order consists, among others, of a set of products. As the operator of the web shop, you might want to get a list of all orders. With a more or less RESTful API (we neglect the hypermedia controls in the example though), your request-response pair could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
-------
GET /api/orders

Response Body
-------------
[
    {
        "id": 125,
        "customerId": 8977,
        "createdAt": "2020-06-06T13:40:49.038Z",
        "productIds": [ 49863176 ]
    }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So far so good, but potentially you will also want to view the actual products right away. What you got are only ids, for each of which you would have to issue another API call to retrieve it. Alternatively, the API could also return nested objects, like so:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
    {
        "id": 125,
        "customerId": 8977,
        "createdAt": "2020-06-06T13:40:49.038Z",
        "products": [
            {
                "id": 49863176,
                "name": "Slim T-Shirt navy-blue",
                "price": 17.90,
                "options": [
                    {
                        "id": "size",
                        "name": "Size",
                        "description": "T-Shirt size",
                        "values": [
                            {
                                "id": "s",
                                "name": "Size S",
                            },
                            {
                                "id": "m",
                                "name": "Size M",
                            },
                            {
                                "id": "l",
                                "name": "Size L",
                            }
                        ]
                    }
                ]
            },
        ]
    }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;However, that is — to my understanding – not truly RESTful anymore. Also, while the above example is still quite straightforward, things get ugly as nested objects include other nested objects, that include other nested objects, that... Quickly you get JSON responses of several tens or hundreds of kilobytes, although you're potentially only interested in two or three attributes. Moreover, on some pages of your shop you may be interested in all possible options (e.g. "size") of a product, but not on others. Should your API define different &lt;a href="https://www.infoq.com/articles/View-Model-Definition/" rel="noopener noreferrer"&gt;view models&lt;/a&gt; now and expose different endpoints? Or a single endpoints with query flags like &lt;code&gt;?expanded=true&lt;/code&gt;? Soon you might be catching yourself &lt;strong&gt;tailoring your API specifically to the needs of your client&lt;/strong&gt; while neglecting REST conventions and a straightforward design. &lt;/p&gt;

&lt;p&gt;With GraphQL, things are different. Your API is a bit &lt;strong&gt;dumber and less opinionated&lt;/strong&gt; now and does not deliver data in a fixed structure, according to a specified &lt;a href="https://graphql.org/learn/queries/" rel="noopener noreferrer"&gt;GQL query&lt;/a&gt;, which looks a lot like JSON. The above example might look like this, now:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
-------
POST /api/graphql/query

{
    "query": "\{
        orders {
            id
            customerId
            products {
                name
                price
                options {
                    name
                }
            }
        }
    \}"
}

Response Body
-------------
{
    "data": {
        "orders": [
            {
                "id": 125,
                "customerId": 8977,
                "products": [
                    {
                        "name": "Slim T-Shirt navy-blue",
                        "price": 17.90,
                        "options": [
                            {
                                "name": "Size",
                            }
                        ]
                    }
                ]
            }
        ]
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This way, you get only the data you want. All your API has to know is how to fetch every piece of data. All your client has to know is how the data schema itself looks like.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it out
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmuetsch.io%2Fimages%2Fgraphql_github.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmuetsch.io%2Fimages%2Fgraphql_github.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub's official API offers GraphQL query endpoints. You can try it out using their &lt;a href="https://developer.github.com/v4/explorer/" rel="noopener noreferrer"&gt;GraphQL explorer&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;
  
  
  GraphQL Basic
&lt;/h1&gt;

&lt;p&gt;Since this article does not aim to be another introduction to GraphQL, you can read most of the basics about fields, data types, etc. in the &lt;a href="https://graphql.org/learn/queries/" rel="noopener noreferrer"&gt;official docs&lt;/a&gt;. However, it is worth mentioning that GraphQL supports three types of queries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Query&lt;/code&gt;&lt;/strong&gt;: "Standard" type of queries, used for fetching data (see above). Similar to what you would do with a &lt;code&gt;GET&lt;/code&gt; in REST.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Mutation&lt;/code&gt;&lt;/strong&gt;: Query type used to modify data. Similar to what you would do with a &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PATCH&lt;/code&gt; or &lt;code&gt;DELETE&lt;/code&gt; in REST.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Subscription&lt;/code&gt;&lt;/strong&gt;: Query type to communicate your intent to subscribe to live data updates. &lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Subscriptions
&lt;/h2&gt;

&lt;p&gt;While a basic GraphQL application will at least use the former two types, the latter is especially interesting in the context of this article. Using subscriptions, you can have your web frontend be notified when new data arrives at the server or existing data changes. For instance, the operator of the above web shop could have a live-updating dashboard, that shows new orders just as they are placed. &lt;/p&gt;

&lt;p&gt;For subscriptions, the GraphQL standard does not define a lot more than their plain existence and purpose. Especially, it is not defined how and which technology to implement them. On the web, any &lt;a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/publisher-subscriber" rel="noopener noreferrer"&gt;publish/subscribe&lt;/a&gt;-like mechanism that provides bi-directional or uni-directional server-to-client communication is appropriate. For the sake of simplicity, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events" rel="noopener noreferrer"&gt;Server-Sent Events&lt;/a&gt; are used in this article.&lt;/p&gt;
&lt;h1&gt;
  
  
  What's next?
&lt;/h1&gt;

&lt;p&gt;This part gave a brief introduction to GraphQL. The next part is about actual code. We're going to build an example web app with live-updates using GraphQL, Go, MongoDB and VueJS.&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/frachtwerk" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F2057%2Fdf6e147f-7cc2-49ec-a74d-79cb81a74bfd.png" alt="Frachtwerk GmbH"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1178%2Fff5aeaea-10eb-427d-b624-f50d55437e43.png" alt=""&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/frachtwerk/modern-reactive-web-apis-with-graphql-go-and-server-sent-events-part-2-54p3" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Modern, reactive web APIs with GraphQL, Go and Server-Sent Events – Part 2&lt;/h2&gt;
      &lt;h3&gt;Ferdinand Mütsch for Frachtwerk GmbH ・ Jun 8 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#graphql&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#go&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#mongodb&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#vue&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;



</description>
      <category>graphql</category>
      <category>go</category>
      <category>mongodb</category>
      <category>vue</category>
    </item>
  </channel>
</rss>
